Categories
Visx

Create a React Grouped Bar Chart with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add grouped bar charts into our React app.

Install Required Packages

We have to install a few modules to create the grouped bar chart.

To get started, we run:

npm i @visx/axis @visx/group @visx/mock-data @visx/responsive @visx/scale @visx/shape

to install the packages.

Create the Chart

We can create the chart by adding the items provided by the modules.

We use the data from the @visx/mock-data module.

To create the chart, we write:

import React from "react";
import { Group } from "@visx/group";
import { BarGroup } from "@visx/shape";
import { AxisBottom } from "@visx/axis";
import cityTemperature from "@visx/mock-data/lib/mocks/cityTemperature";
import { scaleBand, scaleLinear, scaleOrdinal } from "@visx/scale";
import { timeParse, timeFormat } from "d3-time-format";

const blue = "#aeeef8";
export const green = "#e5fd3d";
const purple = "#9caff6";
export const background = "#612efb";

const data = cityTemperature.slice(0, 8);
const keys = Object.keys(data[0]).filter((d) => d !== "date");
const defaultMargin = { top: 40, right: 0, bottom: 40, left: 0 };

const parseDate = timeParse("%Y-%m-%d");
const format = timeFormat("%b %d");
const formatDate = (date) => format(parseDate(date));

const getDate = (d) => d.date;

const dateScale = scaleBand({
  domain: data.map(getDate),
  padding: 0.2
});
const cityScale = scaleBand({
  domain: keys,
  padding: 0.1
});
const tempScale = scaleLinear({
  domain: [
    0,
    Math.max(...data.map((d) => Math.max(...keys.map((key) => Number(d[key])))))
  ]
});
const colorScale = scaleOrdinal({
  domain: keys,
  range: [blue, green, purple]
});

function Example({ width, height, events = false, margin = defaultMargin }) {
  const xMax = width - margin.left - margin.right;
  const yMax = height - margin.top - margin.bottom;
  dateScale.rangeRound([0, xMax]);
  cityScale.rangeRound([0, dateScale.bandwidth()]);
  tempScale.range([yMax, 0]);

  return width < 10 ? null : (
    <svg width={width} height={height}>
      <rect
        x={0}
        y={0}
        width={width}
        height={height}
        fill={background}
        rx={14}
      />
      <Group top={margin.top} left={margin.left}>
        <BarGroup
          data={data}
          keys={keys}
          height={yMax}
          x0={getDate}
          x0Scale={dateScale}
          x1Scale={cityScale}
          yScale={tempScale}
          color={colorScale}
        >
          {(barGroups) =>
            barGroups.map((barGroup) => (
              <Group
                key={`bar-group-${barGroup.index}-${barGroup.x0}`}
                left={barGroup.x0}
              >
                {barGroup.bars.map((bar) => (
                  <rect
                    key={`bar-group-bar-${barGroup.index}-${bar.index}-${bar.value}-${bar.key}`}
                    x={bar.x}
                    y={bar.y}
                    width={bar.width}
                    height={bar.height}
                    fill={bar.color}
                    rx={4}
                    onClick={() => {
                      if (!events) return;
                      const { key, value } = bar;
                      alert(JSON.stringify({ key, value }));
                    }}
                  />
                ))}
              </Group>
            ))
          }
        </BarGroup>
      </Group>
      <AxisBottom
        top={yMax + margin.top}
        tickFormat={formatDate}
        scale={dateScale}
        stroke={green}
        tickStroke={green}
        hideAxisLine
        tickLabelProps={() => ({
          fill: green,
          fontSize: 11,
          textAnchor: "middle"
        })}
      />
    </svg>
  );
}

export default function App() {
  return (
    <div className="App">
      <Example width="500" height="300" />
    </div>
  );
}

We set the color of the bars with the blue , green , and purple variables.

background is the background color.

The data variable has the mock data values we want to get and display in the bars.

keys have the x-axis values.

defaultMargin has the margin styles.

We create the parseDate and format functions from D3’s time parsing and formatting functions.

Then we create the day and city scales with the dateScale and cityScale variables.

The cityScale values are the bars.

tempScale are the bar heights.

colorScale have the color of the bars.

The Example component has the bar chart. We put everything together in there.

The xMax and yMax values are the max x and y-axis values respectively.

We use it to set the max values of the dateScale and tempScale.

In the return statement, we put everything in the svg element.

Group has the chart components.

We map the barGroups into rect elements to display the bars.

We set the width and height prop to set their width and height.

The x-axis is rendered by the AxisBottom component.

We set the top prop to set the location of the axis.

tickFormat has the ticks.

tickLabelProps have the label styles.

Conclusion

We can add grouped bar charts easily into our React app with the Visx library.

Categories
JavaScript Best Practices

Why Should We Stop Using Objects As Maps in JavaScript?

Before ES6, to make a dictionary or a map, we often used objects to store the keys and values. This has some problems that can be avoided with maps.

An object lets us map strings to values. However, with the pitfalls of JavaScript objects and the existence of the Map constructor, we can finally stop using objects as maps or dictionaries.

Inheritance and Reading Properties

Normally, objects in JavaScript inherit from the Object object if no prototype is explicitly set. This means that we have methods that are in the prototype of the object.

To check if the property is in the object or its prototype, we have to use the hasOwnProperty of the object. This is a pain and we can easily forget about this.

This means that we can accidentally get and set properties that aren’t actually in the object that we defined. For example, if we define an empty object:

let obj = {}

Then, when we write:

'toLocaleString' in obj;

We get the value of true returned. This is because the in operator designates properties in the object’s prototype as being part of the object, which we don’t really want for dictionaries or maps.

To create a pure object with no prototype, we have to write:

let obj = Object.create(null);

The create method takes a prototype object of the object it creates as an argument, so we’ll get an object that doesn’t inherit from any prototype. Built-in methods like toString or toLocaleString, they aren’t enumerable, so they won’t be included in the for...in loop.

However, if we create an object with enumerable properties as we do in the following code:

let obj = Object.create({
  a: 1
});

for (const prop in obj) {
  console.log(prop);
}

Then, we do get the a property logged in the for...in above, which loops through all the owned and inherited properties of an object.

To ignore the inherited properties, we can use the hasOwnProperty method of an object. For example, we can write:

let obj = Object.create({
  a: 1
});

for (const prop in obj) {
  if (obj.hasOwnProperty(prop)) {
    console.log(prop);
  }
}

Then, we don’t get anything logged.

As we can see, accessing values with property keys can be tricky with regular JavaScript objects.

Overriding Values of Properties

With plain objects, we can easily override and delete existing properties. Any writable properties can have their value overridden or deleted.

We can assign values to any property that’s in an object. For example, we can write:

let obj = {};
obj.toString = null;

Then, when we run:

obj.toString();

We get the error Uncaught TypeError: obj.toString is not a function.

This is a big problem since we can easily change the value of any original or inherited property of an object. As we can see, we overwrote the built-in toString method with null with just one assignment operation.

This means that using objects as dictionaries or Maps is risky since we can easily do this accidentally. There’s no way to prevent this other than checking the values that may be names of built-in methods.

The Object’s Prototype

The prototype of an object is accessible by its __proto__ property. It’s a property that we can both get and set. For example, we can write:

let obj = Object.create({
  a: 1
});

obj.__proto__ = {
  b: 1
};

Then, our object’s prototype is { b: 1 }. This means that we changed the prototype of obj, which was { a: 1 } originally, to { b: 1 }, just by setting the __proto__ property of obj.

When we loop through the obj object with the for...in loop like the following code:

for (const prop in obj) {
  console.log(prop);
}

We get b logged.

This means that we have to avoid accessing the __proto__ property when we try to access our object that we use for a dictionary or map. What we have is another trap that might get us when using objects as maps or dictionaries.

Getting Own Enumerable Properties to Avoid Traps

To avoid traps of getting properties that are inherited from other objects, we can use the Object.keys to get the object’s own property names. It returns an array with the keys of the object that we defined and omits any inherited property names.

For example, if we have:

let obj = Object.create({
  a: 1
});
console.log(Object.keys(obj));

Then we get an empty array logged.

Similarly, Object.entries accepts an object as an argument and returns an array with arrays that have the key as the first element and the value of the key as the second element.

For example, if we write:

let obj = Object.create({
  a: 1
});
console.log(Object.entries(obj));

Then we also get an empty array logged.

ES6 Maps

Better yet, we should be using ES6 Map objects, which are an actual implementation of a map or dictionary.

Map objects have the set method that lets us add keys and values, which are the first and second arguments of what the method accepts respectively.

We can define Maps as we do in the following code:

let objMap = new Map();
objMap.set('foo', 'bar');
objMap.set('a', 1);
objMap.set('b', 2);

Instead of using the set method to add our keys and values, we can also pass a nested array where each entry of the array has the key as the first element and the value as the second element.

One good thing about Map objects is that we can use non-string keys. For example, we can write:

let objMap = new Map();
objMap.set(1, 'a');

We can also use nested arrays to define a Map. For example, instead of using the set method multiple times, we can write the following:

const arrayOfKeysAndValues = [
  ['foo', 'bar'],
  ['a', 1],
  ['b', 2]
]
let objMap = new Map(arrayOfKeysAndValues);
console.log(objMap)

There are also specialized methods to get entries by key, get all entries, loop through each entry, and remove entries. We can use the get method to get an entry by its key:

objMap.get('foo'); // 'bar'

We can also get a value from a non-string key, unlike objects. For instance, if we have:

let objMap = new Map();
objMap.set(true, 'a');

Then console.log(objMap.get(true)); will get us 'a'.

And we can clear all entries of the Map object with the clear method. For example, we can write:

objMap.clear();

We can get all entries with the objMap.entries method and we can use the for...of loop to loop through the items as well since it has an iterator.

Conclusion

We should stop using objects as dictionaries now. There are too many pitfalls since objects inherit from the Object object by default and other objects as we set them.

It also lets us override the value of methods like toString which isn’t a result we want most of the time.

To avoid these issues, we should use the Map object which was introduced in ES6. It has special methods to get and set entries and we can loop through them with the for...of loop or convert the object to an array.

Categories
Visx

Create a React Area Difference Chart with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add area difference charts into our React app.

Install Required Libraries

We’ve to install multiple modules provided by the Visx library to create the Area Difference Chart.

To do this, we run:

npm i @visx/axis @visx/curve @visx/grid @visx/group @visx/mock-data @visx/responsive @visx/scale @visx/shape @visx/threshold

Create the Chart

Once we installed all the required libraries, we add the chart by writing:

import React from "react";
import { Group } from "@visx/group";
import { curveBasis } from "@visx/curve";
import { LinePath } from "@visx/shape";
import { Threshold } from "@visx/threshold";
import { scaleTime, scaleLinear } from "@visx/scale";
import { AxisLeft, AxisBottom } from "@visx/axis";
import { GridRows, GridColumns } from "@visx/grid";
import cityTemperature from "@visx/mock-data/lib/mocks/cityTemperature";

export const background = "#f3f3f3";

const date = (d) => new Date(d.date).valueOf();
const ny = (d) => Number(d["New York"]);
const sf = (d) => Number(d["San Francisco"]);

const timeScale = scaleTime({
  domain: [
    Math.min(...cityTemperature.map(date)),
    Math.max(...cityTemperature.map(date))
  ]
});
const temperatureScale = scaleLinear({
  domain: [
    Math.min(...cityTemperature.map((d) => Math.min(ny(d), sf(d)))),
    Math.max(...cityTemperature.map((d) => Math.max(ny(d), sf(d))))
  ],
  nice: true
});

const defaultMargin = { top: 40, right: 30, bottom: 50, left: 40 };

function Theshold({ width, height, margin = defaultMargin }) {
  if (width < 10) return null;

const xMax = width - margin.left - margin.right;
  const yMax = height - margin.top - margin.bottom;

timeScale.range([0, xMax]);
  temperatureScale.range([yMax, 0]);

return (
    <div>
      <svg width={width} height={height}>
        <rect
          x={0}
          y={0}
          width={width}
          height={height}
          fill={background}
          rx={14}
        />
        <Group left={margin.left} top={margin.top}>
          <GridRows
            scale={temperatureScale}
            width={xMax}
            height={yMax}
            stroke="#e0e0e0"
          />
          <GridColumns
            scale={timeScale}
            width={xMax}
            height={yMax}
            stroke="#e0e0e0"
          />
          <line x1={xMax} x2={xMax} y1={0} y2={yMax} stroke="#e0e0e0" />
          <AxisBottom
            top={yMax}
            scale={timeScale}
            numTicks={width > 520 ? 10 : 5}
          />
          <AxisLeft scale={temperatureScale} />
          <text x="-70" y="15" transform="rotate(-90)" fontSize={10}>
            Temperature (°F)
          </text>
          <Threshold
            id={`${Math.random()}`}
            data={cityTemperature}
            x={(d) => timeScale(date(d)) ?? 0}
            y0={(d) => temperatureScale(ny(d)) ?? 0}
            y1={(d) => temperatureScale(sf(d)) ?? 0}
            clipAboveTo={0}
            clipBelowTo={yMax}
            curve={curveBasis}
            belowAreaProps={{
              fill: "violet",
              fillOpacity: 0.4
            }}
            aboveAreaProps={{
              fill: "green",
              fillOpacity: 0.4
            }}
          />
          <LinePath
            data={cityTemperature}
            curve={curveBasis}
            x={(d) => timeScale(date(d)) ?? 0}
            y={(d) => temperatureScale(sf(d)) ?? 0}
            stroke="#222"
            strokeWidth={1.5}
            strokeOpacity={0.8}
            strokeDasharray="1,2"
          />
          <LinePath
            data={cityTemperature}
            curve={curveBasis}
            x={(d) => timeScale(date(d)) ?? 0}
            y={(d) => temperatureScale(ny(d)) ?? 0}
            stroke="#222"
            strokeWidth={1.5}
          />
        </Group>
      </svg>
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <Theshold width="500" height="300" />
    </div>
  );
}

We use the mock data provided by the Visx library to create the chart.

We create the timeScale scale for the x-axis.

And we create the temperatureScale for the y-axis.

We call scaleTime and scaleLinear respectively to scale the data to display the chart.

Next, we set the margins with the defaultMargin object.

Then we create the Threshold component which has the area difference chart.

In it, we computed the xMax and yMax values to create the max value for the x and y axes respectively.

Then we call the range method to create the time and temperature scales for the chart.

Next, we add the svg element to add the container for the chart.

rect has the rectangle that surrounds the chart.

Group has items we need to form the chart.

GridRows has the grid rows.

We set the scale prop to the temperatureScale to display the temperatures.

Likewise, we do the same for the GridColumns component but scale is set to timeScale .

line has the perimeter lines for the chart.

AxisBottom has the x-axis.

AxisLeft is the y-axis.

We add the Threshold component to bad the fill between the lines.

And we add the lines with the LinePath component.

We set the data by setting the data prop.

The x and y props return the x and y values.

Finally, in App , we render the Threshold component we created and set the width and height to display the chart.

Conclusion

We can create area difference charts easily in our React app with the Visx library.

Categories
Visx

Create a React Filled Line Graph with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add filled line graphs into our React app.

Getting Started

To get started, we’ve to install several modules provided by Visx.,

To install the ones needed by the bar graph, we run:

$ npm install --save @visx/axis @visx/curve @visx/gradient @visx/grid @visx/group @visx/mock-data @visx/react-spring @visx/responsive @visx/shape @visx/scale

The @visx/mock-data library has mock data we can use in our bar graphs.

Add the Filled Line Graph

To add the filled line graph, we write:

import React, { useState, useMemo } from "react";
import AreaClosed from "@visx/shape/lib/shapes/AreaClosed";
import { curveMonotoneX } from "@visx/curve";
import {
  scaleUtc,
  scaleLinear,
  scaleLog,
  scaleBand,
  coerceNumber
} from "@visx/scale";
import { Orientation } from "@visx/axis";
import {
  AnimatedAxis,
  AnimatedGridRows,
  AnimatedGridColumns
} from "@visx/react-spring";
import { LinearGradient } from "@visx/gradient";
import { timeFormat } from "d3-time-format";

export const backgroundColor = "#da7cff";
const axisColor = "#fff";
const tickLabelColor = "#fff";
export const labelColor = "#340098";
const gridColor = "#6e0fca";
const margin = {
  top: 40,
  right: 150,
  bottom: 20,
  left: 50
};

const tickLabelProps = () => ({
  fill: tickLabelColor,
  fontSize: 12,
  fontFamily: "sans-serif",
  textAnchor: "middle"
});

const getMinMax = (vals) => {
  const numericVals = vals.map(coerceNumber);
  return [Math.min(...numericVals), Math.max(...numericVals)];
};

function Example({
  width: outerWidth = 800,
  height: outerHeight = 800,
  showControls = true
}) {
  const width = outerWidth - margin.left - margin.right;
  const height = outerHeight - margin.top - margin.bottom;
  const [dataToggle] = useState(true);
  const [animationTrajectory] = useState("center");

  const axes = useMemo(() => {
    const linearValues = dataToggle ? [0, 2, 4, 6, 8, 10] : [6, 8, 10, 12];

  return [
      {
        scale: scaleLinear({
          domain: getMinMax(linearValues),
          range: [0, width]
        }),
        values: linearValues,
        tickFormat: (v, index, ticks) =>
          index === 0
            ? "first"
            : index === ticks[ticks.length - 1].index
            ? "last"
            : `${v}`,
        label: "linear"
      }
    ];
  }, [dataToggle, width]);

  if (width < 10) return null;

  const scalePadding = 40;
  const scaleHeight = height / axes.length - scalePadding;

  const yScale = scaleLinear({
    domain: [100, 0],
    range: [scaleHeight, 0]
  });

  return (
    <>
      <svg width={outerWidth} height={outerHeight}>
        <LinearGradient
          id="visx-axis-gradient"
          from={backgroundColor}
          to={backgroundColor}
          toOpacity={0.5}
        />
        <rect
          x={0}
          y={0}
          width={outerWidth}
          height={outerHeight}
          fill={"url(#visx-axis-gradient)"}
          rx={14}
        />
        <g transform={`translate(${margin.left},${margin.top})`}>
          {axes.map(({ scale, values, label, tickFormat }, i) => (
            <g
              key={`scale-${i}`}
              transform={`translate(0, ${i * (scaleHeight + scalePadding)})`}
            >
              <AnimatedGridRows
                key={`gridrows-${animationTrajectory}`}
                scale={yScale}
                stroke={gridColor}
                width={width}
                numTicks={dataToggle ? 1 : 3}
                animationTrajectory={animationTrajectory}
              />
              <AnimatedGridColumns
                key={`gridcolumns-${animationTrajectory}`}
                scale={scale}
                stroke={gridColor}
                height={scaleHeight}
                numTicks={dataToggle ? 5 : 2}
                animationTrajectory={animationTrajectory}
              />
              <AreaClosed
                data={values.map((x) => [
                  (scale(x) ?? 0) +
                    ("bandwidth" in scale &&
                    typeof scale.bandwidth !== "undefined"
                      ? scale.bandwidth() / 2
                      : 0),
                  yScale(10 + Math.random() * 90)
                ])}
                yScale={yScale}
                curve={curveMonotoneX}
                fill={gridColor}
                fillOpacity={0.2}
              />
              <AnimatedAxis
                key={`axis-${animationTrajectory}`}
                orientation={Orientation.bottom}
                top={scaleHeight}
                scale={scale}
                tickFormat={tickFormat}
                stroke={axisColor}
                tickStroke={axisColor}
                tickLabelProps={tickLabelProps}
                tickValues={
                  label === "log" || label === "time" ? undefined : values
                }
                numTicks={label === "time" ? 6 : undefined}
                label={label}
                labelProps={{
                  x: width + 30,
                  y: -10,
                  fill: labelColor,
                  fontSize: 18,
                  strokeWidth: 0,
                  stroke: "#fff",
                  paintOrder: "stroke",
                  fontFamily: "sans-serif",
                  textAnchor: "start"
                }}
                animationTrajectory={animationTrajectory}
              />
            </g>
          ))}
        </g>
      </svg>
    </>
  );
}

export default function App() {
  return (
    <div>
      <Example width="500" height="300" />
    </div>
  );
}

We add the backgroundColor to set the background color.

axisColor has the axis color.

tickLabelColor has the x-axis tick label’s color.

labelColor has the y-axis label color.

gridColor has the grid color.

We change the text styles with the tickLabelProps function.

To add the graph content, we add the axes array.

We compute the values for the graph from the dataToggle object.

linearValues has the linear scale value.

We have the yScale object to add the y-axis values.

In the JSX we return, we add the SVG, rect rectangle element, and then in the axes.map callback, we return the graph.

The graph has the g element with the SVG group.

AnimatedGridRows has the animated grid rows.

We pass in the yScale value to it to display the y values.

stroke sets the stroke.

width sets the width.

AnimatedGridColumns has the grid columns.

numTicks lets us set the number of ticks on the x-axis.

We have similar code with the AnimatedGridColumns component to add the columns.

AreaClosed has the filled line graph.

AnimatedAxis has the animated x-axis.

We set the label styles with the labelProps .

tickValues has the tick values.

Conclusion

We can add a filled line graph into our React app with Visx.

Categories
Visx

Create a React Bar Graph with the Visx Library

Visx is a library that lets us add graphics to our React app easily.

In this article, we’ll look at how to use it to add bar graphs into our React app.

Getting Started

To get started, we’ve to install several modules provided by Visx.,

To install the ones needed by the bar graph, we run:

$ npm install --save @visx/mock-data @visx/group @visx/shape @visx/scale

The @visx/mock-data library has mock data we can use in our bar graphs.

Create the Bar Graph

To create the bar graph, we add the following code:

import React from "react";
import { letterFrequency } from "@visx/mock-data";
import { Group } from "@visx/group";
import { Bar } from "@visx/shape";
import { scaleLinear, scaleBand } from "@visx/scale";

const data = letterFrequency;
const width = 500;
const height = 300;
const margin = { top: 20, bottom: 20, left: 20, right: 20 };
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;
const x = (d) => d.letter;
const y = (d) => +d.frequency * 100;

const xScale = scaleBand({
  range: [0, xMax],
  round: true,
  domain: data.map(x),
  padding: 0.4
});
const yScale = scaleLinear({
  range: [yMax, 0],
  round: true,
  domain: [0, Math.max(...data.map(y))]
});

const compose = (scale, accessor) => (data) => scale(accessor(data));
const xPoint = compose(xScale, x);
const yPoint = compose(yScale, y);

function BarGraph(props) {
  return (
    <svg width={width} height={height}>
      {data.map((d, i) => {
        const barHeight = yMax - yPoint(d);
        return (
          <Group key={`bar-${i}`}>
            <Bar
              x={xPoint(d)}
              y={yMax - barHeight}
              height={barHeight}
              width={xScale.bandwidth()}
              fill="#fc2e1c"
            />
          </Group>
        );
      })}
    </svg>
  );
}

export default function App() {
  return (
    <div>
      <BarGraph />
    </div>
  );
}

data has the data for our graph.

width and height are the width and height of the graph.

margin has the margins.

xMax has the max x-axis value.

yMax has the max y-axis value.

x is a function to return the data to display for the x-axis.

And y is the function to return the data to display on the y-axis.

xScale lets us create the x-axis values to add to the graph.

yScale has the y-axis values for the graph.

Then we create the compose function to scale the x and y axis values to fit in the graph.

Once we did that, we create the BarGraph component with the Group and Bar components.

Bar has the bars.

Group has the container for the bars.

We set the fill color for the bar.

And the x and y values

y is set to the height of the bar.

The width is scaled according to the screen size.

Conclusion

We can add bar graphs easily into our React app with the Visx library.