We've recently released VictoryHistogram
, a new charting component we've added to our Victory
library that allows you to create histogram charts in React. Histograms, while looking similar to bar charts, are different. Rather than displaying categorical data, histograms are used to visualize the distribution of data, by "binning" or "bucketing" data points into ranges.
Previously there wasn't an automatic way to do this in Victory
; you'd have to do much of the data processing yourself and plot the results with VictoryBar
. Now with VictoryHistogram
, you can easily create histograms.
In Action
Let's take a look and see how you might go about using VictoryHistogram for visualizing your data.
Here's a basic example. We have our array of data, and we pass that into VictoryHistogram
which automatically bins these values into four bins. You can customize this behavior via the bins
prop, with which you can specify specific bin ranges or an approximate number of bins.
const App = () => { const data = [ { x: 0 }, { x: 1 }, { x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 4 } ]; return ( <VictoryChart> <VictoryHistogram data={data} /> </VictoryChart> ); };
Let's add some styling to these bins using the style
and cornerRadius
props.
const App = () => { const data = [ { x: 0 }, { x: 1 }, { x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 4 } ]; return ( <VictoryChart> <VictoryHistogram style={{ data: { fill: "hsl(355, 88%, 67%)", stroke: "hsl(355, 10%, 25%)" } }} cornerRadius={5} data={data} /> </VictoryChart> ); };
Let's try using some more interesting data. In this case the data represents songs listened to on a particular date for a particular person. So each object represents one song, and contains the date it was listened to and the genre of the song.
So we pass this data to VictoryHistogram
and we specify that the x
value is located at the day
property of each object by passing the prop, x="day"
import data from "./data.js"; // [{ day: new Date(2020, 3, 1), genre: 'hip-hop' }, ...] const App = () => { return ( <VictoryChart> <VictoryHistogram style={{ data: { fill: "hsl(355, 88%, 67%)", stroke: "hsl(355, 10%, 25%)" } }} cornerRadius={5} data={data} x="day" /> </VictoryChart> ); };
Let's add a title using VictoryLabel
. Let's also update our axes by rendering two <VictoryAxis />
components, and using tickFormat
and tickCount
to display all of the month tick labels on the x-axis, and add a label to the y-axis via the label
prop.
import data from "./data.js"; // [{ day: new Date(2020, 3, 1), genre: 'hip-hop' }, ...] const sharedAxisStyles = { tickLabels: { fontSize: 13 }, axisLabel: { padding: 39, fontSize: 13, fontStyle: "italic" } }; const App = () => { return ( <VictoryChart scale={{ x: "time" }} style={{ parent: { padding: 36 } }}> <VictoryLabel x={225} y={30} textAnchor="middle" text="Songs listened to in 2020" /> <VictoryHistogram style={{ data: { fill: "hsl(355, 88%, 67%)", stroke: "hsl(355, 10%, 25%)" } }} cornerRadius={5} data={data} x="day" /> <VictoryAxis tickCount={12} tickFormat={date => date.toLocaleString("default", { month: "short" })} style={sharedAxisStyles} /> <VictoryAxis dependentAxis label="Total # of Songs" style={sharedAxisStyles} /> </VictoryChart> ); };
Right now each bar represents the total songs listened to in a month, but we don't really see the breakdown of the songs that are included in that month. Luckily we have the genre of each song. So we can split up our data by genre using lodash groupBy
(or whatever method you prefer). Then render a VictoryStack
component and inside of that, we render a VictoryHistogram
for each genre we have.
import data from "./data.js"; // [{ day: new Date(2020, 3, 1), genre: 'hip-hop' }, ...] const groupedData = _.groupBy(data, ({ genre }) => genre); /* { rock: [{ day: new Date(2020, 3, 1), genre: 'rock' }, ...], hip-hop: [{ day: new Date(2020, 3, 1), genre: 'hip-hop' }, ...] ... } */ const sharedAxisStyles = { tickLabels: { fontSize: 13 }, axisLabel: { padding: 39, fontSize: 13, fontStyle: "italic" } }; const App = () => { return ( <VictoryChart scale={{ x: "time" }} style={{ parent: { padding: 36 } }}> <VictoryLabel x={225} y={25} textAnchor="middle" text="Songs listened to in 2020" /> <VictoryStack colorScale={[ "#003f5c", "#2f4b7c", "#665191", "#a05195", "#d45087", "#f95d6a", "#ff7c43", "#ffa600" ]} > {Object.entries(groupedData).map(([key, dataGroup]) => { return ( <VictoryHistogram data={dataGroup} x="day" style={{ data: { strokeWidth: 1, stroke: "hsl(324, 10%, 20%)" } }} /> ); })} </VictoryStack> <VictoryAxis tickCount={12} tickFormat={date => date.toLocaleString("default", { month: "short" })} style={sharedAxisStyles} /> <VictoryAxis dependentAxis label="Total # of Songs" style={sharedAxisStyles} /> </VictoryChart> ); };
We can tweak the styling again a little more, remove the borders via the style
prop, and space the bins out a bit using the binSpacing
prop.
import data from "./data.js"; // [{ day: new Date(2020, 3, 1), genre: 'hip-hop' }, ...] const groupedData = _.groupBy(data, ({ genre }) => genre); /* { rock: [{ day: new Date(2020, 3, 1), genre: 'rock' }, ...], hip-hop: [{ day: new Date(2020, 3, 1), genre: 'hip-hop' }, ...] ... } */ const sharedAxisStyles = { tickLabels: { fontSize: 13 }, axisLabel: { padding: 39, fontSize: 13, fontStyle: "italic" } }; const App = () => { return ( <VictoryChart scale={{ x: "time" }} style={{ parent: { padding: 36 } }}> <VictoryLabel x={225} y={25} textAnchor="middle" text="Songs listened to in 2020" /> <VictoryStack colorScale={[ "#003f5c", "#2f4b7c", "#665191", "#a05195", "#d45087", "#f95d6a", "#ff7c43", "#ffa600" ]} > {Object.entries(groupedData).map(([key, dataGroup]) => { return ( <VictoryHistogram data={dataGroup} x="day" binSpacing={8} style={{ data: { strokeWidth: 0 } }} /> ); })} </VictoryStack> <VictoryAxis tickCount={12} tickFormat={date => date.toLocaleString("default", { month: "short" })} style={sharedAxisStyles} /> <VictoryAxis dependentAxis label="Total # of Songs" style={sharedAxisStyles} /> </VictoryChart> ); };
Lastly, we may want to add some tooltips so we can see the values of the data we are rendering. To do that we provide a containerComponent
prop to VictoryChart
. In this case we'll want to pass a VictoryVoronoiContainer
component, which is used to associate a user's mouse position with the nearest data point. Then we add a labels
prop to determine the text inside of the tooltip.
import data from "./data.js"; const groupedData = _.groupBy(data, ({ genre }) => genre); const sharedAxisStyles = { tickLabels: { fontSize: 13 }, axisLabel: { padding: 39, fontSize: 13, fontStyle: "italic" } }; const App = () => { return ( <VictoryChart scale={{ x: "time" }} containerComponent={ <VictoryVoronoiContainer labels={({ datum }) => datum.y > 0 ? `${datum.y} ${datum.binnedData[0].genre} songs` : null } /> } > <VictoryLabel x={225} y={25} textAnchor="middle" text="Songs listened to in 2020" /> <VictoryStack colorScale={[ "#003f5c", "#2f4b7c", "#665191", "#a05195", "#d45087", "#f95d6a", "#ff7c43", "#ffa600" ]} > {Object.entries(groupedData).map(([key, dataGroup]) => { return ( <VictoryHistogram data={dataGroup} x="day" binSpacing={8} style={{ data: { strokeWidth: 0 } }} /> ); })} </VictoryStack> <VictoryAxis tickCount={12} tickFormat={date => date.toLocaleString("default", { month: "short" })} style={sharedAxisStyles} /> <VictoryAxis dependentAxis label="Total # of Songs" style={sharedAxisStyles} /> </VictoryChart> ); };
To see this example, check out the link here:
Coming Up
In our next Victory
blog post, we'll be kicking off a new blog post series around building advanced sports visualizations with Victory
, like the one shown below.