如何在Recharts库中以与图表相同的插值方式显示最后一个区间作为虚线?

huangapple go评论67阅读模式
英文:

How to display the last interval as a dotted line in Recharts library with same interpolation as the chart?

问题

在Recharts库中,是否有可能将最后一个区间显示为虚线?现在我正在使用Recharts库在React中进行开发,并且遇到了一个挑战。我有一个包含多个区间的折线图。我发现我可以将最后一个区间显示为一个独立的线条组件。然而,它目前显示为一条直线。我希望这条线能够以与图表其余部分相同的插值方式继续。我该如何实现这个?

英文:

Is it possible to display the last interval as a dotted line in the Recharts library?

I'm working with the Recharts library in React and facing a challenge. I have a line chart with multiple intervals. What I've found is that I can display the last interval as a separate Line component. However, it's currently appearing as a straight line. I want this line to continue with the same interpolation as the rest of the chart. How can I accomplish this?

如何在Recharts库中以与图表相同的插值方式显示最后一个区间作为虚线?

Here's a basic structure for a Recharts component.

<ResponsiveContainer>
   <ComposedChart data={data}>
      <CartesianGrid />
      <XAxis />
      <YAxis />
      <Tooltip />
      <Line type="monotone" dataKey="score" />
      <Line type="monotone" dataKey="median" />
      <Area type="monotone" dataKey="deviation" >
      <Line type="monotone" dataKey="prediction" strokeDasharray="6 4" />
    </ComposedChart>
 </ResponsiveContainer>

And the data example

data = [
    {
        "intervalName": "Interval 1",
        "prediction": null,
        "score": 16991.578,
        "median": 558934.61825,
        "deviation": [
            390456.78457310924,
            727412.4519268909
        ]
    },
    {
        "intervalName": "Interval 2",
        "prediction": null,
        "score": 582619.453,
        "median": 558934.61825,
        "deviation": [
            320355.6152603979,
            797513.6212396022
        ]
    },
    {
        "intervalName": "Interval 3",
        "prediction": null,
        "score": 5539.119,
        "median": 559293.0755,
        "deviation": [
            320626.4142448605,
            797959.7367551395
        ]
    },
    {
        "intervalName": "Interval 4",
        "prediction": null,
        "score": 125833.72200000001,
        "median": 409077.43374999997,
        "deviation": [
            132343.3306994175,
            685811.5368005824
        ]
    },
    {
        "intervalName": "Interval 5",
        "prediction": 76713.714,
        "score": 76713.714,
        "median": 275481.02749999997,
        "deviation": [
            16272.15931816565,
            534689.8956818343
        ]
    },
    {
        "intervalName": "Interval 6",
        "prediction": 368890.673174,
        "score": null,
        "median": 191209.251,
        "deviation": [
            -7836.152843409567,
            390254.65484340955
        ]
    }
];

答案1

得分: 1

以下是您要翻译的内容:

为了使预测和分数完美适应平滑的分段插值曲线,不仅需要将所有点的预测值设置为分数值(并使前n-1个区间的曲线不可见),还需要将预测点添加到分数值中(并使其曲线在最后一个区间不可见)。

您只需为倒数第二个点之前的3-4个点设置虚拟预测值的值(因为在那之前的点的影响通常减小到无限小的值),但设置所有点似乎更简单。

一个可能的想法是创建一个单一的曲线,用于显示前n-1个区间的分数和最后一个区间的预测。可以使用渐变颜色来为最后一个区间更改颜色,如此处所述。

由于您不想更改颜色,而是要更改虚线数组,我建议创建两个相同的曲线,一个用于分数,将最后一个区间设置为完全透明的颜色,另一个用于预测,将前n-1个区间设置为完全透明,最后一个区间设置为虚线。

标记将如下所示:

<div style={styles}>
<ResponsiveContainer>
<ComposedChart data={data}>
<defs>
{gradientTwoColors(
"hideAllButLastInterval",
"rgba(0,0,0,0)",
defaultColor,
lastIntervalPercent
)}
{gradientTwoColors(
"hideJustLastInterval",
defaultColor,
"rgba(0,0,0,0)",
lastIntervalPercent
)}
</defs>
<CartesianGrid />
<XAxis />
<YAxis />
<Tooltip formatter={tooltipFormatter1} />
<Line type="monotone" dataKey="score" strokeDasharray="0 100" />
<Line type="monotone" dataKey="prediction" strokeDasharray="0 100" />
<Line type="monotone" dataKey="median" />
<Area type="monotone" dataKey="deviation" />
<Line
name="line1_noTooltip"
type="monotone"
stroke="url('#hideJustLastInterval')"
dataKey={scoreOrPrediction}
/>
<Line
name="line2_noTooltip"
type="monotone"
stroke="url('#hideAllButLastInterval')"
strokeDasharray="5 5"
dataKey={scoreOrPrediction}
/>
</ComposedChart>
</ResponsiveContainer>
</div>

请注意,前两个 Line 组件是完全不可见的,它们只是为了提供提示的值,而最后两个 Line 组件创建了两个相同的曲线,但不提供提示的输入。辅助函数和变量如下所示:

const scoreOrPrediction = function (data) {
// 为两个相同的曲线提供数据
return data.score !== null ? data.score : data.prediction;
};
const defaultColor = Line.defaultProps.stroke,
// 在计算 lastIntervalPercent 时应格外小心
// 下面的表达式适用于数据长度减1个相等间隔的情况
// 但如果在“线性”轴上有数值 x 值,则应更新公式以使用这些值
lastIntervalPercent = ((data.length - 2) * 100) / (data.length - 1);
const gradientTwoColors = (id, col1, col2, percentChange) => (
<linearGradient id={id} x1="0" y1="0" x2="100%" y2="0">
<stop offset="0%" stopColor={col1} />
<stop offset={`${percentChange}%`} stopColor={col1} />
<stop offset={`${percentChange}%`} stopColor={`${col2}`} />
<stop offset="100%" stopColor={col2} />
</linearGradient>
);
const formatIfNumeric = (x) => (typeof x === typeof 1 ? x.toFixed() : x);
const tooltipFormatter1 = (value, name) => {
if (name.includes("noTooltip")) {
return [];
}
if (Array.isArray(value)) {
return value.map(formatIfNumeric).join(" - ");
}
return [formatIfNumeric(value), name];
};

您可以在这个 codesandbox 上找到此解决方案。

英文:

In order to have the prediction and score fit perfectly in the smooth piecewise interpolating
curve, not only do you have to set prediction values equal to score values for all the points
(and make the curve for the first n-1 intervals invisible), but you also have to add the
predicted point to the score values (and make its curve invisible for the last interval).

You could only set the values of virtual prediction for only 3-4 points prior to the
penultimate point (as the influence of the points before that typically vanes to infinitesimal
values), but setting all points seems simpler.

A possible idea is to create a single curve that will display score for the first
n-1 intervals and prediction for the last interval. Changing the color for the
last interval can be done using gradients for colors, as described
here.

Since you don't want to change the color, but the dash array, I'd go with creating
two identical curves, one for the score, that has the last interval set to fully transparent color
and one for the prediction, having the first n-1 interval fully transparent and the last
one dashed.

The markup would look like:

<div style={styles}>
<ResponsiveContainer>
<ComposedChart data={data}>
<defs>
{gradientTwoColors(
"hideAllButLastInterval",
"rgba(0,0,0,0)",
defaultColor,
lastIntervalPercent
)}
{gradientTwoColors(
"hideJustLastInterval",
defaultColor,
"rgba(0,0,0,0)",
lastIntervalPercent
)}
</defs>
<CartesianGrid />
<XAxis />
<YAxis />
<Tooltip formatter={tooltipFormatter1} />
<Line type="monotone" dataKey="score" strokeDasharray="0 100" />
<Line type="monotone" dataKey="prediction" strokeDasharray="0 100" />
<Line type="monotone" dataKey="median" />
<Area type="monotone" dataKey="deviation" />
<Line
name="line1_noTooltip"
type="monotone"
stroke="url('#hideJustLastInterval')"
dataKey={scoreOrPrediction}
/>
<Line
name="line2_noTooltip"
type="monotone"
stroke="url('#hideAllButLastInterval')"
strokeDasharray="5 5"
dataKey={scoreOrPrediction}
/>
</ComposedChart>
</ResponsiveContainer>
</div>

Note that the first two Line components are completely invisible, they are there just to
provide the values for the tooltip, while the last two Line components make the two
identical curve described above, but don't give any input to the tooltip. The auxiliary functions
and variables are

const scoreOrPrediction = function (data) {
// provides the data for the two identical curves
return data.score !== null ? data.score : data.prediction;
};
const defaultColor = Line.defaultProps.stroke,
// great care should be taken when computing lastIntervalPercent
// the expression below works for data.length - 1 equal intervals
// but if there are numeric x values in a "linear" axis, the formula
// should be updated to use those values
lastIntervalPercent = ((data.length - 2) * 100) / (data.length - 1);
const gradientTwoColors = (id, col1, col2, percentChange) => (
<linearGradient id={id} x1="0" y1="0" x2="100%" y2="0">
<stop offset="0%" stopColor={col1} />
<stop offset={`${percentChange}%`} stopColor={col1} />
<stop offset={`${percentChange}%`} stopColor={`${col2}`} />
<stop offset="100%" stopColor={col2} />
</linearGradient>
);
const formatIfNumeric = (x) => (typeof x === typeof 1 ? x.toFixed() : x);
const tooltipFormatter1 = (value, name) => {
if (name.includes("noTooltip")) {
return [];
}
if (Array.isArray(value)) {
return value.map(formatIfNumeric).join(" - ");
}
return [formatIfNumeric(value), name];
};

Here's a codesandbox
with this solution.

huangapple
  • 本文由 发表于 2023年5月22日 23:25:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76307718.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定