Added custom responsive legend to doughnut charts

This commit is contained in:
Alejandro Celaya 2020-06-06 11:58:25 +02:00
parent 770cc59448
commit 949e0da105
3 changed files with 94 additions and 21 deletions
src/visits/helpers
test/visits/helpers

View file

@ -1,8 +1,10 @@
import React from 'react';
import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
import { keys, values } from 'ramda';
import classNames from 'classnames';
import { fillTheGaps } from '../../utils/helpers/visits';
import './DefaultChart.scss';
const propTypes = {
title: PropTypes.oneOfType([ PropTypes.string, PropTypes.func ]),
@ -58,6 +60,39 @@ const determineHeight = (isBarChart, labels) => {
return isBarChart && labels.length > 20 ? labels.length * 8 : null;
};
/* eslint-disable react/prop-types */
const renderPieChartLegend = ({ config }) => {
const { labels, datasets } = config.data;
const { defaultColor } = config.options;
const [{ backgroundColor: colors }] = datasets;
return (
<ul className="default-chart__pie-chart-legend">
{labels.map((label, index) => (
<li key={label} className="default-chart__pie-chart-legend-item d-flex">
<div
className="default-chart__pie-chart-legend-item-color"
style={{ backgroundColor: colors[index] || defaultColor }}
/>
<small className="default-chart__pie-chart-legend-item-text flex-fill">{label}</small>
</li>
))}
</ul>
);
};
/* eslint-enable react/prop-types */
const chartElementAtEvent = (onClick) => ([ chart ]) => {
if (!onClick || !chart) {
return;
}
const { _index, _chart: { data } } = chart;
const { labels } = data;
onClick(labels[_index]);
};
const DefaultChart = ({ title, isBarChart, stats, max, highlightedStats, highlightedLabel, onClick }) => {
const hasHighlightedStats = highlightedStats && Object.keys(highlightedStats).length > 0;
const Component = isBarChart ? HorizontalBar : Doughnut;
@ -70,9 +105,11 @@ const DefaultChart = ({ title, isBarChart, stats, max, highlightedStats, highlig
return acc;
}, { ...stats }));
const highlightedData = hasHighlightedStats && fillTheGaps(highlightedStats, labels);
const chartRef = useRef();
const options = {
legend: isBarChart ? { display: false } : { position: 'right' },
legend: { display: false },
legendCallback: !isBarChart && renderPieChartLegend,
scales: isBarChart && {
xAxes: [
{
@ -97,22 +134,23 @@ const DefaultChart = ({ title, isBarChart, stats, max, highlightedStats, highlig
// Provide a key based on the height, so that every time the dataset changes, a new graph is rendered
return (
<Component
key={height}
data={graphData}
options={options}
height={height}
getElementAtEvent={([ chart ]) => {
if (!onClick || !chart) {
return;
}
const { _index, _chart: { data } } = chart;
const { labels } = data;
onClick(labels[_index]);
}}
/>
<div className="row">
<div className={classNames('col-sm-12', { 'col-md-7': !isBarChart })}>
<Component
ref={chartRef}
key={height}
data={graphData}
options={options}
height={height}
getElementAtEvent={chartElementAtEvent(onClick)}
/>
</div>
{!isBarChart && (
<div className="col-sm-12 col-md-5">
{chartRef.current && chartRef.current.chartInstance.generateLegend()}
</div>
)}
</div>
);
};

View file

@ -0,0 +1,29 @@
@import '../../utils/base';
.default-chart__pie-chart-legend {
list-style-type: none;
padding: 0;
margin: 0;
@media (max-width: $smMax) {
margin-top: 1rem;
}
}
.default-chart__pie-chart-legend-item:not(:first-child) {
margin-top: .3rem;
}
.default-chart__pie-chart-legend-item-color {
width: 20px;
min-width: 20px;
height: 20px;
margin-right: 5px;
border-radius: 10px;
}
.default-chart__pie-chart-legend-item-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View file

@ -17,13 +17,14 @@ describe('<DefaultChart />', () => {
wrapper = shallow(<DefaultChart title="The chart" stats={stats} />);
const doughnut = wrapper.find(Doughnut);
const horizontal = wrapper.find(HorizontalBar);
const cols = wrapper.find('.col-sm-12');
expect(doughnut).toHaveLength(1);
expect(horizontal).toHaveLength(0);
const { labels, datasets } = doughnut.prop('data');
const [{ title, data, backgroundColor, borderColor }] = datasets;
const { legend, scales } = doughnut.prop('options');
const { legend, legendCallback, scales } = doughnut.prop('options');
expect(title).toEqual('The chart');
expect(labels).toEqual(keys(stats));
@ -43,24 +44,28 @@ describe('<DefaultChart />', () => {
'#463730',
]);
expect(borderColor).toEqual('white');
expect(legend).toEqual({ position: 'right' });
expect(legend).toEqual({ display: false });
expect(typeof legendCallback).toEqual('function');
expect(scales).toBeUndefined();
expect(cols).toHaveLength(2);
});
it('renders HorizontalBar when is not a bar chart', () => {
wrapper = shallow(<DefaultChart isBarChart title="The chart" stats={stats} />);
const doughnut = wrapper.find(Doughnut);
const horizontal = wrapper.find(HorizontalBar);
const cols = wrapper.find('.col-sm-12');
expect(doughnut).toHaveLength(0);
expect(horizontal).toHaveLength(1);
const { datasets: [{ backgroundColor, borderColor }] } = horizontal.prop('data');
const { legend, scales } = horizontal.prop('options');
const { legend, legendCallback, scales } = horizontal.prop('options');
expect(backgroundColor).toEqual('rgba(70, 150, 229, 0.4)');
expect(borderColor).toEqual('rgba(70, 150, 229, 1)');
expect(legend).toEqual({ display: false });
expect(legendCallback).toEqual(false);
expect(scales).toEqual({
xAxes: [
{
@ -70,6 +75,7 @@ describe('<DefaultChart />', () => {
],
yAxes: [{ stacked: true }],
});
expect(cols).toHaveLength(1);
});
it.each([