javascriptreactjsd3.jschartsnivo-react

trying to create line down a graph in nivo at set time intervals


I'm using nivo charts to visualize some sick datasets.

The example is like this,

import { ResponsiveLine } from '@nivo/line'

const MyResponsiveLine = ({ data /* see data tab */ }) => (
    <ResponsiveLine
        data={data}
        margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
        xScale={{ type: 'point' }}
        yScale={{ type: 'linear', min: 'auto', max: 'auto', stacked: true, reverse: false }}
        yFormat=" >-.2f"
        axisTop={null}
        axisRight={null}
        axisBottom={{
            orient: 'bottom',
            tickSize: 5,
            tickPadding: 5,
            tickRotation: 0,
            legend: 'transportation',
            legendOffset: 36,
            legendPosition: 'middle'
        }}
        axisLeft={{
            orient: 'left',
            tickSize: 5,
            tickPadding: 5,
            tickRotation: 0,
            legend: 'count',
            legendOffset: -40,
            legendPosition: 'middle'
        }}
        pointSize={10}
        pointColor={{ theme: 'background' }}
        pointBorderWidth={2}
        pointBorderColor={{ from: 'serieColor' }}
        pointLabelYOffset={-12}
        useMesh={true}
        legends={[
            {
                anchor: 'bottom-right',
                direction: 'column',
                justify: false,
                translateX: 100,
                translateY: 0,
                itemsSpacing: 0,
                itemDirection: 'left-to-right',
                itemWidth: 80,
                itemHeight: 20,
                itemOpacity: 0.75,
                symbolSize: 12,
                symbolShape: 'circle',
                symbolBorderColor: 'rgba(0, 0, 0, .5)',
                effects: [
                    {
                        on: 'hover',
                        style: {
                            itemBackground: 'rgba(0, 0, 0, .03)',
                            itemOpacity: 1
                        }
                    }
                ]
            }
        ]}
    />
)

With the data simply like this,

0: 0
1: 0
2: 0
3: 0
4: 0
5: 0
6: 0
7: 0
8: 0
9: 0
10: 0
11: 0
12: 0
13: 0
14: 0
15: -4.1524
16: -2.1525
17: -3.12351
18: 5.123123
19: 3.123123
20: 0.6547929999999998
21: 0.414856
22: -1.1863169999999998
23: 0.7934469999999998

I want to really simply add in a at time 10, 14, 18 a line for when i exercise. Ideally, I'd like to be able to shade the area beneath that line for 4 hours afterwards, in a sort of parabola (or a specific shape really), having the shading finish at 4 hours after.

I'm quite lost on how to achieve this with Nivo Charts. I suppose this isn't a normal functionality but was wondering if there was something I was missing that I could work in?

A good example of what I'm trying to achieve is like this sandbox,

https://codesandbox.io/s/simple-composed-chart-forked-b0bfi

I'd be happy to use this sandboxes code if it could be a bit more visually appealing, ideally like to stick with nivo though!

Thanks!


Solution

  • You can do this using a custom area layer. https://nivo.rocks/storybook/?path=/story/line--custom-layers

    Here is a working example based on your initial example. The added area layer parts are commented:

    import { ResponsiveLine } from '@nivo/line'
    
    /* Added these two imports */
    import { Defs } from '@nivo/core'
    import { area, curveMonotoneX } from 'd3-shape'
    
    
    function App() {
      let data = [{
        id:"data",
        data: [
          { x:0, y:0 },
          { x:1, y:0 },
          { x:2, y:0 },
          { x:3, y:0 },
          { x:4, y:0 },
          { x:5, y:0 },
          { x:6, y:0 },
          { x:7, y:0 },
          { x:8, y:0 },
          { x:9, y:0 },
          { x:10, y: 0 },
          { x:11, y: 0 },
          { x:12, y: 0 },
          { x:13, y: 0 },
          { x:14, y: 0 },
          { x:15, y: -4.1524 },
          { x:16, y: -2.1525 },
          { x:17, y: -3.12351 },
          { x:18, y: 5.123123 },
          { x:19, y: 3.123123 },
          { x:20, y: 0.6547929999999998 },
          { x:21, y: 0.414856 },
          { x:22, y: -1.1863169999999998 },
          { x:23, y: 0.7934469999999998 }]
      }];
    
      /* Added this AreaLayer function */
      function createAreaLayer(startingXCoordinate) {
        let areaData = [
          {data: {x: startingXCoordinate, y: 5}},
          {data: {x: startingXCoordinate + 0.5, y: repeatRoot(10, 1) - 1}},
          {data: {x: startingXCoordinate + 1, y: repeatRoot(10, 2) - 1}},
          {data: {x: startingXCoordinate + 1.5, y: repeatRoot(10, 3) - 1}},
          {data: {x: startingXCoordinate + 2, y: repeatRoot(10, 4) - 1}},
          {data: {x: startingXCoordinate + 2.5, y: repeatRoot(10, 5) - 1}},
          {data: {x: startingXCoordinate + 3, y: repeatRoot(10, 6) - 1}},
          {data: {x: startingXCoordinate + 3.5, y: repeatRoot(10, 7) - 1}},
          {data: {x: startingXCoordinate + 4, y: 0}},
        ];
    
        function repeatRoot(number, times) {
          if (times === 1) {
            return Math.sqrt(number);
          } else {
            return Math.sqrt(repeatRoot(number, times - 1));
          }
        }
    
        function interpolatedXScale(xScale, x) {
          const floorX = Math.floor(x);
          const decimalPart = x - floorX;
          return xScale(floorX) + (xScale(floorX + 1) - xScale(floorX)) * decimalPart;
        }
    
        return function({series, xScale, yScale, innerHeight}) {
          const areaGenerator = area()
              .x(d => interpolatedXScale(xScale, d.data.x))
              .y0(yScale(0))
              .y1(d => yScale(d.data.y))
              .curve(curveMonotoneX);
    
          return (
              <>
                <Defs
                    defs={[
                      {
                        id: 'pattern',
                        type: 'patternLines',
                        background: 'transparent',
                        color: '#3daff7',
                        lineWidth: 1,
                        spacing: 6,
                        rotation: -45,
                      },
                    ]}
                />
                <path
                    d={areaGenerator(areaData)}
                    fill="url(#pattern)"
                    fillOpacity={0.6}
                    stroke="#3daff7"
                    strokeWidth={2}
                />
              </>
          )
        };
      }
    
      return (
        <div  style={{height:"500px"}}>
          <ResponsiveLine
              data={data}
              margin={{
                top: 0,
                right: 50,
                bottom: 50,
                left: 50
              }}
              yScale={{
                type: "linear",
                stacked: false
              }}
              xScale={{ type: 'point' }}
              yFormat=" >-.2f"
              axisTop={null}
              axisRight={null}
              axisBottom={{
                orient: 'bottom',
                tickSize: 5,
                tickPadding: 5,
                tickRotation: 0,
                legend: 'transportation',
                legendOffset: 36,
                legendPosition: 'middle'
              }}
              axisLeft={{
                orient: 'left',
                tickSize: 5,
                tickPadding: 5,
                tickRotation: 0,
                legend: 'count',
                legendOffset: -40,
                legendPosition: 'middle'
              }}
              pointSize={10}
              pointColor={{ theme: 'background' }}
              pointBorderWidth={2}
              pointBorderColor={{ from: 'serieColor' }}
              pointLabelYOffset={-12}
              useMesh={true}
              legends={[
                {
                  anchor: 'bottom-right',
                  direction: 'column',
                  justify: false,
                  translateX: 100,
                  translateY: 0,
                  itemsSpacing: 0,
                  itemDirection: 'left-to-right',
                  itemWidth: 80,
                  itemHeight: 20,
                  itemOpacity: 0.75,
                  symbolSize: 12,
                  symbolShape: 'circle',
                  symbolBorderColor: 'rgba(0, 0, 0, .5)',
                  effects: [
                    {
                      on: 'hover',
                      style: {
                        itemBackground: 'rgba(0, 0, 0, .03)',
                        itemOpacity: 1
                      }
                    }
                  ]
                },
              ]}
    
              /* Added this layers attribute */
              layers={[
                'grid',
                'markers',
                'areas',
                createAreaLayer(10),
                createAreaLayer(14),
                createAreaLayer(18),
                'lines',
                'slices',
                'axes',
                'points',
                'legends',
              ]}
          />
    
    
        </div>
      );
    }
    
    export default App;