Reputation: 1
I am attempting to create a line chart where the color of the line is dependant upon the value being plotted. For example if the value is above the following thresholds [0,60,80] then the color would be either ['green', 'yellow', 'red'] respectively. currently I am using @nivo/line here is the code
import { ResponsiveLine } from "@nivo/line";
const lineData = [
{
id: "linechart",
data: [
{
x: "12 AM",
y: 40,
},
{
x: "1 AM",
y: 50,
},
{
x: "2 AM",
y: 70,
},
{
x: "3 AM",
y: 75,
},
{
x: "4 AM",
y: 90,
},
{
x: "5 AM",
y: 85,
},
{
x: "6 AM",
y: 65,
},
{
x: "7 AM",
y: 60,
},
{
x: "8 AM",
y: 50,
},
{
x: "9 AM",
y: 70,
},
{
x: "10 AM",
y: 90,
},
{
x: "11 AM",
y: 50,
},
{
x: "12 PM",
y: 30,
},
],
},
];
const getColorForValue = (value: number) => {
if (value <= 60) return "green";
if (value <= 80) return "yellow";
return "red";
};
const CustomLineLayer = ({ data, xScale, yScale }: any) => {
return data.map((dataSeries: any, lineIndex: any) => {
const lineSegments = [];
for (let i = 0; i < dataSeries.data.length - 1; i++) {
const point1 = dataSeries.data[i];
const point2 = dataSeries.data[i + 1];
const x1 = xScale(point1.x);
const y1 = yScale(point1.y);
const x2 = xScale(point2.x);
const y2 = yScale(point2.y);
const color = getColorForValue(point1.y);
lineSegments.push(
<line
key={`line-segment-${lineIndex}-${i}`}
x1={x1}
y1={y1}
x2={x2}
y2={y2}
stroke={color}
strokeWidth={2}
/>
);
}
return <g key={`line-${lineIndex}`}>{lineSegments}</g>;
});
};
export const MyResponsiveLineChart = () => (
<ResponsiveLine
data={lineData}
margin={{ top: 50, right: 50, bottom: 50, left: 50 }}
xScale={{ type: "point" }}
yScale={{
type: "linear",
min: 0,
max: 100,
}}
layers={["grid", "markers", "axes", CustomLineLayer]}
gridYValues={[60, 80, 100]}
lineWidth={3}
yFormat=" >-.2f"
axisTop={null}
axisRight={null}
theme={{
fontSize: 14,
grid: {
line: {
strokeDasharray: "6 6",
},
},
}}
axisBottom={{
tickSize: 5,
tickPadding: 10,
tickRotation: 0,
}}
axisLeft={{
tickSize: 0,
tickPadding: 10,
tickRotation: 0,
}}
enableGridX={false}
enableGridY={true}
curve="natural"
# animate={true}
/>
);
And the output looks like this output is not as smooth curve and the line is changing the color but not in specified range like 0 to 60 it should be green ,61 to 80 it should be yellow and 81 to 100 red. If anybody know the solution for this please let me know.
Upvotes: 0
Views: 331
Reputation: 1
For anyone interested, i had the same problem and came up with this:
import { ResponsiveLine } from "@nivo/line";
const lineData = [
{
id: "linechart",
data: [
{
x: "12 AM",
y: 40,
},
{
x: "1 AM",
y: 50,
},
{
x: "2 AM",
y: 70,
},
{
x: "3 AM",
y: 75,
},
{
x: "4 AM",
y: 90,
},
{
x: "5 AM",
y: 85,
},
{
x: "6 AM",
y: 65,
},
{
x: "7 AM",
y: 60,
},
{
x: "8 AM",
y: 50,
},
{
x: "9 AM",
y: 70,
},
{
x: "10 AM",
y: 90,
},
{
x: "11 AM",
y: 50,
},
{
x: "12 PM",
y: 30,
},
],
},
];
const getColorForValue = (value) => {
if (value <= 60) return "green";
if (value <= 80) return "yellow";
return "red";
};
const interpolateYValueAtX = (x1, y1, x2, y2, targetY) => {
const slope = (y2 - y1) / (x2 - x1);
const intercept = y1 - slope * x1;
const targetX = (targetY - intercept) / slope;
return targetX;
};
const CustomLineLayer = ({ data, xScale, yScale }) => {
return data.map((dataSeries, lineIndex) => {
const lineSegments = [];
for (let i = 0; i < dataSeries.data.length - 1; i++) {
const point1 = dataSeries.data[i];
const point2 = dataSeries.data[i + 1];
const x1 = xScale(point1.x);
const y1 = yScale(point1.y);
const x2 = xScale(point2.x);
const y2 = yScale(point2.y);
// Thresholds where the color changes
const thresholds = [60, 80]; // Example thresholds
thresholds.forEach(threshold => {
const yThreshold = yScale(threshold);
if ((y1 < yThreshold && y2 > yThreshold) || (y1 > yThreshold && y2 < yThreshold)) {
// The line crosses the threshold, calculate the crossing point
const crossingX = interpolateYValueAtX(x1, y1, x2, y2, yThreshold);
// Segment 1: From point1 to crossing point
const color1 = getColorForValue(point1.y);
lineSegments.push(
<line
key={`line-segment-${lineIndex}-${i}-part1`}
x1={x1}
y1={y1}
x2={crossingX}
y2={yThreshold}
stroke={color1}
strokeWidth={2}
/>
);
// Segment 2: From crossing point to point2
const color2 = getColorForValue(point2.y);
lineSegments.push(
<line
key={`line-segment-${lineIndex}-${i}-part2`}
x1={crossingX}
y1={yThreshold}
x2={x2}
y2={y2}
stroke={color2}
strokeWidth={2}
/>
);
} else {
// The segment does not cross the threshold, draw it normally
const color = getColorForValue(point1.y);
lineSegments.push(
<line
key={`line-segment-${lineIndex}-${i}`}
x1={x1}
y1={y1}
x2={x2}
y2={y2}
stroke={color}
strokeWidth={2}
/>
);
}
});
}
return <g key={`line-${lineIndex}`}>{lineSegments}</g>;
});
};
const HighlightLayer = ({ xScale, yScale, innerWidth, range, color }) => {
const [yStart, yEnd] = range; // Destructure the range array into start and end values
const yTop = yScale(yEnd); // Top of the range
const yBottom = yScale(yStart); // Bottom of the range
return (
<rect
x={0}
y={yTop}
width={innerWidth}
height={yBottom - yTop} // Height based on the top and bottom Y values
fill={color} // Color passed as a prop
/>
);
};
export const MyResponsiveLineChart = () => (
<ResponsiveLine
data={lineData}
margin={{ top: 50, right: 50, bottom: 50, left: 50 }}
xScale={{ type: "point" }}
yScale={{
type: "linear",
min: 0,
max: 100,
}}
layers={[
"grid",
"markers",
"areas",
// "lines",
"axes",
// "points",
"legends",
(props) => <CustomLineLayer {...props} />,
(props) => (
<HighlightLayer
{...props}
range={[0, 60]}
color="rgba(144, 238, 144, 0.05)"
/>
), // Green layer for 0 to 60
(props) => (
<HighlightLayer
{...props}
range={[60, 80]}
color="rgba(255, 255, 0, 0.05)"
/>
), // Yellow layer for 60 to 80
(props) => (
<HighlightLayer
{...props}
range={[80, 100]}
color="rgba(255, 99, 71, 0.05)"
/>
), // Red layer for 80 to 100
]}
// layers={["grid", "markers", "axes", CustomLineLayer]}
gridYValues={[60, 80, 100]}
lineWidth={3}
yFormat=" >-.2f"
axisTop={null}
axisRight={null}
theme={{
fontSize: 14,
grid: {
line: {
strokeDasharray: "6 6",
},
},
}}
axisBottom={{
tickSize: 5,
tickPadding: 10,
tickRotation: 0,
}}
axisLeft={{
tickSize: 0,
tickPadding: 10,
tickRotation: 0,
}}
enableGridX={false}
enableGridY={true}
curve="natural"
/>
);
export default MyResponsiveLineChart;
It's using the same data, hope it helps.
Upvotes: 0