Building a Line Chart in React with VisX
Tags: react, datavisualization, tutorial, webdev
Published: 2021-12-22 10:01:39

[VisX](https://github.com/airbnb/visx) is a low-level charting library by AirBnB, which uses d3 for math and calculations. It has a steep learning curve, but on the upside, it's possible to create performant and customisable charts in react. 

To build this chart, I will work with IEA [data](https://www.iea.org/data-and-statistics/data-product/energy-technology-rd-and-d-budget-database-2) on R&D energy technology spend by country. The data are very comprehensive and go back to 1974. I've preprocessed the data and filtered out those for Denmark for Total and Renewable energy spend to compare both. 

This is the resulted data structure: 

```json
export const data = 
[{"country":"DENMARK","currency":"RDDUSD","type":"RENEWABLE","year":1975,"amount":0.804},
{"country":"DENMARK","currency":"RDDUSD","type":"RENEWABLE","year":1976,"amount":1.350},
{"country":"DENMARK","currency":"RDDUSD","type":"RENEWABLE","year":1977,"amount":7.928},
{"country":"DENMARK","currency":"RDDUSD","type":"RENEWABLE","year":1978,"amount":15.357}]
```

What we're going to build:

- App layout with Chakra UI
- Line Chart with VisX
- Tooltip and crosshair for interactivity

![Visx Line Chart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/esa5pdbgqkhnxhgw3oef.gif)
## 1. Creating NextJS App 

```
npx create-next-app@latest visx-demo
cd visx-demo
```

## 2. Installing libraries

[Chakra UI](https://chakra-ui.com/) is an awesome UI components library, which has easy styling capabilities. 
```
npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4
```
[VisX](https://airbnb.io/visx) is a modular library, and allows us to use only components we need and to keep the bundle size low. 

```
npm install @visx/axis @visx/event @visx/glyph @visx/gradient @visx/grid @visx/group @visx/responsive @visx/scale @visx/shape @visx/tooltip
```

## 3. Building layout with Chakra UI 

For our app to work with Chakra UI, we need to modify `pages/_app.js` component, to include chakra styling. 

```javascript
import { ChakraProvider, CSSReset } from "@chakra-ui/react";
import Head from "next/head";

const GlobalStyle = ({ children }) => {
  return (
    <>
      <Head>
        <meta content="width=device-width, initial-scale=1" 
         name="viewport" />
      </Head>
      <CSSReset />
      {children}
    </>
  );
};

function MyApp({ Component, pageProps }) {
  return (
    <ChakraProvider>
      <GlobalStyle />
    <Component {...pageProps} />
    </ChakraProvider>
  ) 
}

export default MyApp
```

Lets build a simple header in `components/Header.js`:

```react
import { Box, Flex,  Heading } from "@chakra-ui/react";

const Header = () => {
    return (
        <Box
            pos="fixed"
            as="header"
            top="0"
            bg='#242730'
            left="0"
            right="0"
            borderBottomWidth="1px"
            width="full"
            height="4rem"
        >
            <Box width="full" mx="auto" px={6} pr={[1, 6]} height="100%" >
                <Flex size="100%" p={[0, 3]} pl={[0, 4]} align="center" justify="space-between">
                    <Box as="a" d="block" href="/" aria-label="VisX Area Chart">
                        <Heading color="gray.100" as="h4" size="md">VizX Area Chart</Heading>
                    </Box>
                </Flex>
            </Box>
        </Box>
    );
}

export default Header;
```
Now we can import header in `pages/index.js` file and build simple layout with chakra ui components. 

```react
import Header from "../components/Header"
import { Container, Heading, Box, Text, Link } from '@chakra-ui/react'
import ParentSize from '@visx/responsive/lib/components/ParentSize';
import LineChart from '../components/LineChart';
import { data } from '../data/stats_for_Denmark'

export default function Home() {
  return (
    <>
      <Header />
      <Box height='100vh' bg="#242730">
        <Container maxW='4xl' height='85vh' mt="4rem" >
          <Heading ml='40px' as='i' size='md' color={'gray.100'}>Denmark R&D Spend on Renewable Energy vs Total</Heading>
         // Chart will go here
          <Link ml='40px' fontSize='sm' color={'gray.100'} href='https://www.iea.org/data-and-statistics/data-product/energy-technology-rd-and-d-budget-database-2' isExternal>
            Data by IEA, 2021
          </Link></Container>
      </Box>
    </>
  )
}
```

## 4. Making Viz responsive

To make the chart responsive, we need to be aware of width and height of a parent container. VisX library makes it really easy with its `<ParentSize />` component, which could be imported from `@visx/responsive/lib/components/ParentSize`. We will wrap our chart component in `<ParentSize />` and  will get width and height as props. 

```react
 <ParentSize>
{({ width, height }) => 
    <LineChart data={data} width={width} height={height} />}</ParentSize>
```

Now let's build two svg rectangles to understand `<Group />` and visualise `margins`. VisX `<Group />` components is just a container for all children `<g />` elements, it allows to simplify the code and pass the margins.  

In `components/LineChart.js`:

```react
import { Group } from "@visx/group";

function LineChart({ data, width, height }) {
    // define margins from where to start drawing the chart
    const margin = { top: 40, right: 40, bottom: 50, left: 40 };
    // defining inner measurements
    const innerWidth = width - margin.left - margin.right;
    const innerHeight = height - margin.top - margin.bottom;
    return (
        <svg width={width} height={height} >
              <rect x={0} y={0} width={width} height={height} fill={'#718096'} rx={14} />
            <Group left={margin.left} top={margin.top}>
            <rect x={0} y={0} width={innerWidth} height={innerHeight} fill={'#A0AEC0'} />
            </Group>
        </svg>
    )
}

export default LineChart
```
Layout with <rect /> and margins: 

![Layout with margins](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c494b41mbpkox2dpmlzj.png)

Browser starts drawing `<svg />` elements in a [coordinate system](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Positions) from top left corner (0,0), and the margins are defined clockwise with this initial coordinate, i.e. point of origin. 

## 5. Building Axes

Let's start small and build axes. To build axes, we need to define scales first. Under the hood VisX uses d3, so we it's really useful to review d3 scales [documentation](https://github.com/d3/d3-scale). D3 will use math to help translate our numbers to pixel locations inside the <svg /> container. 

To get a range for our scales, I use `d3.extent() `function, which returns a min and max values from the array. 

Ticks on axes can be formatted, using any functions, in this example I transform date label into the string, to remove a comma from the default number formatting. 

`const formatDate = (year) => year.toString()`

In `components/Chart.js`: 

```react
 // Defining selector functions
 const getRD = (d) => d.amount;
 const getDate = (d) => d.year;

// Defining scales

// horizontal, x scale
const timeScale = scaleLinear({
    range: [0, innerWidth],
    domain: extent(data, getDate),
    nice: true
    })

// vertical, y scale
const rdScale = scaleLinear({
     range: [innerHeight, 0],
     domain: extent(data, getRD),
     nice: true,
});
```

Defining Axes inside the `<svg/>` container:

```react
<AxisLeft
    tickTextFill={'#EDF2F7'}
    stroke={'#EDF2F7'}
    tickStroke={'#EDF2F7'}
    scale={rdScale}
    tickLabelProps={() => ({
        fill: '#EDF2F7',
        fontSize: 11,
        textAnchor: 'end',
      })} 
/>
<text x="-125" y="20" transform="rotate(-90)" fontSize={12} fill='#EDF2F7'>
     R&D Spend, RDDUSD
</text>
<AxisBottom
    scale={timeScale}
    stroke={'#EDF2F7'}
    tickFormat={formatDate}
    tickStroke={'#EDF2F7'}
    tickTextFill={'#EDF2F7'}
    top={innerHeight}
    tickLabelProps={() => ({
        fill: '#EDF2F7',
        fontSize: 11,
        textAnchor: 'middle',
    })} 
/>
```

I also want to add `<GridRows />` and `GridColumns />` for our chart, they will use the same scales as the axes. 

```react
<GridRows 
    scale={rdScale} 
    width={innerWidth} 
    height={innerHeight - margin.top} 
    stroke='#EDF2F7' 
    strokeOpacity={0.2} 
/>
<GridColumns 
    scale={timeScale} 
    width={innerWidth} 
    height={innerHeight} 
    stroke='#EDF2F7' 
    strokeOpacity={0.2} 
/>
```

This is how the result looks like. I usually remove inner and outer rectangles only in the end, and allow them to guide placing the elements during the work on the chart. 

![chart with axes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6utv0w6qmfxbil5if3x.png)

## 6. Building Line Charts

For line charts, we are going to use `<LinePath />` component. I want to build two lines to compare Denmark Total R&D investments versus investment in renewable energy. For that, I will filter the data from the original array and will define a series, which will contain both outputs to build lines. 

Color scheme for lines I took from this awesome [MetBrewer](https://github.com/BlakeRMills/MetBrewer) R color palettes package. 

```react
//colours for lines
const colors = ['#43b284', '#fab255']

// data for lines
const data1 = data.filter(function (el) {
    return el.type === "RENEWABLE"
});

const data2 = data.filter(function (el) {
    return el.type === "TOTAL"
});

const series = [data1, data2]
```

Mapping over the data to place `<LinePath />` inside the` <svg />` container: 

```react
{series.map((sData, i) => (
    <LinePath
         key={i}
         stroke={colors[i]}
         strokeWidth={3}
         data={sData}
         x={(d) => timeScale(getDate(d)) ?? 0}
         y={(d) => rdScale(getRD(d)) ?? 0}
    />
))}
```

Now we can see lines for each of the data series:

![chart with line path](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpfj3nq9hcbmws0iyu3h.png)

## 7. Adding interactivity
For this chart I wanted to add crosshairs, which are just thin line that centers on a datapoint, and a tooltip with a year and R&D Spend value. 
 
To add a tooltip, a whole component must be wrapped in a <div/> with relative position, according to [docs](https://airbnb.io/visx/docs/tooltip). 

```react
<div position = 'relative'>
/// Your whole component ///
</div>
```
VisX has a handy <useTooltip /> hook, which does a tons of work for us. 

```react
// tooltip parameters
const { 
    tooltipData, 
    tooltipLeft = 0, 
    tooltipTop = 0, 
    showTooltip, 
    hideTooltip 
} = useTooltip();
```

However, positioning the tooltip is a but tricky. In a nutshell, we need to get a X and Y coordinates from the mouse hover, and translate them into the data values. 

For that, most examples I've seen use [d3.bisect()](https://observablehq.com/@d3/d3-bisect) to find the position of the value in the dataset. As I have two lines, for which I need the d values, I've also filtered the array by the date value I got from the bisect function. 

```react
 // function get data from a year
 const getD = (year) => {
     const output = data.filter(function (el) {
         return el.year === year
     })
     return output
    }
```
Now, lets define a function, that would handle the tooltip:   

```react
const handleTooltip = useCallback((event) => {
const { x } = localPoint(event) || { x: 0 };
const x0 = timeScale.invert(x - margin.left); // get Date from the scale

const index = bisectDate(data, x0, 1); // get index of this date from the array
const d0 = data[index - 1];
const d1 = data[index];
let d = d0;
// is previous data point available?
if (d1 && getDate(d1)) {
    d = x0.valueOf() - getDate(d0).valueOf() > 
         getDate(d1).valueOf() - x0.valueOf() ? d1 : d0;
     }
showTooltip({
    tooltipData: getD(d.year),
    tooltipLeft: x,
    tooltipTop: rdScale(getRD(d))
})
})
```
This is how Tooltip data looks like in my case for two lines: 

```json
[
    {
        "country": "DENMARK",
        "currency": "RDDUSD",
        "type": "RENEWABLE",
        "year": 2006,
        "amount": 41.657
    },
    {
        "country": "DENMARK",
        "currency": "RDDUSD",
        "type": "TOTAL",
        "year": 2006,
        "amount": 112.857
    }
]
```
Now let's define the tooltip, we need to place it outside of the <svg />

```react
{/* render a tooltip */}
{tooltipData ? (
     <TooltipWithBounds 
         key={Math.random()}
         top={tooltipTop}
         left={tooltipLeft}
         style={tooltipStyles}
      > 
      <p>{`Total Spend: $${getRD(tooltipData[1])}`}</p>
      <p>{`Renewable Spend: $${getRD(tooltipData[0])}`}</p>
      <p>{`Year: ${getDate(tooltipData[1])}`}</p>
      </TooltipWithBounds>
      ) 
: null}
```

Defining a crosshair, as a simple <Line />, all `<g/>` elements must go inside the` <svg />`:

```react
{tooltipData && (
    <g>
         <Line
             from={{ x: tooltipLeft - margin.left, y: 0 }}
             to={{ x: tooltipLeft - margin.left, y:innerHeight 
             }}
              stroke={'#EDF2F7'}
              strokeWidth={2}
              pointerEvents="none"
              strokeDasharray="4,2"
           />
    </g>
)}
```

Defining dots to show on hover using `@visx/glyph`:

```react
{tooltipData && tooltipData.map((d, i) => (
    <g>
        <GlyphCircle 
            left={tooltipLeft - margin.left}
            top={rdScale(d.amount) + 2}
            size={110}
            fill={colors[i]}
            stroke={'white'}
            strokeWidth={2} />
     </g>
))}
```
Now we need to call our handleTooltip function to pass positioning values to the tooltip, crosshair and glyphs. 

I wanted to show crosshair if a user will hover at any point inside the chart, for that I changed the fill of the first <rect /> to transparent and call `handleTooltip` on any user interaction:

```react
<rect 
    x={0} 
    y={0} 
    width={innerWidth} 
    height={innerHeight} 
    fill={'transparent'}
    onTouchStart={handleTooltip} 
    onTouchMove={handleTooltip}
    onMouseMove={handleTooltip}
    onMouseLeave={() => hideTooltip()}
/>
```

I've also placed this `<rect /> ` after all my <g /> elements, because they are stacked one onto the other, and making this the top element will enable interactivity for all of chart. 

This how the final result looks like:

![final result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ynp5y59r9u3znerqn58y.png)

To be completely honest, even thought I've worked with d3.js before, building the chart with VisX was a bit tedious and hard at the time. But as I was playing with the code, I've learned to love its concise API and the power and flexibility it gives to the developer. 

If you want to try it too, here is a link to GitHub: https://github.com/MariaZentsova/visx-linechart 

Examples, I've used for study and inspiration:
[How to Make Beautiful Graphs With vx and React-Motion by Dylan Mozlowski](https://devblog.classy.org/how-to-make-beautiful-graphs-with-vx-and-react-motion-6ffe7aecf6f3 )
[VisX Area Chart](https://airbnb.io/visx/areas)
[VisX lines with glyphs](https://airbnb.io/visx/glyphs)
[VisX Area Difference Chart](https://airbnb.io/visx/threshold)