Limbaji Thorat
Limbaji Thorat

Reputation: 1

how to draw a line chart with multiple colors using nivo library

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

Answers (1)

Sebastian J
Sebastian J

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.

It looks like this

Upvotes: 0

Related Questions