From 770cc59448dba615587fbf715de40a059aad3364 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 6 Jun 2020 10:35:13 +0200 Subject: [PATCH] Extracted logic to render graph from GraphCard to DefatlChart component --- src/visits/helpers/DefaultChart.js | 121 ++++++++++++++++++ src/visits/helpers/GraphCard.js | 112 +--------------- .../{GraphCard.test.js => DefaultChart.js} | 10 +- 3 files changed, 131 insertions(+), 112 deletions(-) create mode 100644 src/visits/helpers/DefaultChart.js rename test/visits/helpers/{GraphCard.test.js => DefaultChart.js} (87%) diff --git a/src/visits/helpers/DefaultChart.js b/src/visits/helpers/DefaultChart.js new file mode 100644 index 00000000..40fbc0da --- /dev/null +++ b/src/visits/helpers/DefaultChart.js @@ -0,0 +1,121 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Doughnut, HorizontalBar } from 'react-chartjs-2'; +import { keys, values } from 'ramda'; +import { fillTheGaps } from '../../utils/helpers/visits'; + +const propTypes = { + title: PropTypes.oneOfType([ PropTypes.string, PropTypes.func ]), + isBarChart: PropTypes.bool, + stats: PropTypes.object, + max: PropTypes.number, + highlightedStats: PropTypes.object, + highlightedLabel: PropTypes.string, + onClick: PropTypes.func, +}; + +const generateGraphData = (title, isBarChart, labels, data, highlightedData, highlightedLabel) => ({ + labels, + datasets: [ + { + title, + label: highlightedData ? 'Non-selected' : 'Visits', + data, + backgroundColor: isBarChart ? 'rgba(70, 150, 229, 0.4)' : [ + '#97BBCD', + '#F7464A', + '#46BFBD', + '#FDB45C', + '#949FB1', + '#57A773', + '#414066', + '#08B2E3', + '#B6C454', + '#DCDCDC', + '#463730', + ], + borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white', + borderWidth: 2, + }, + highlightedData && { + title, + label: highlightedLabel || 'Selected', + data: highlightedData, + backgroundColor: 'rgba(247, 127, 40, 0.4)', + borderColor: '#F77F28', + borderWidth: 2, + }, + ].filter(Boolean), +}); + +const dropLabelIfHidden = (label) => label.startsWith('hidden') ? '' : label; + +const determineHeight = (isBarChart, labels) => { + if (!isBarChart && labels.length > 8) { + return 200; + } + + return isBarChart && labels.length > 20 ? labels.length * 8 : null; +}; + +const DefaultChart = ({ title, isBarChart, stats, max, highlightedStats, highlightedLabel, onClick }) => { + const hasHighlightedStats = highlightedStats && Object.keys(highlightedStats).length > 0; + const Component = isBarChart ? HorizontalBar : Doughnut; + const labels = keys(stats).map(dropLabelIfHidden); + const data = values(!hasHighlightedStats ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => { + if (acc[highlightedKey]) { + acc[highlightedKey] -= highlightedStats[highlightedKey]; + } + + return acc; + }, { ...stats })); + const highlightedData = hasHighlightedStats && fillTheGaps(highlightedStats, labels); + + const options = { + legend: isBarChart ? { display: false } : { position: 'right' }, + scales: isBarChart && { + xAxes: [ + { + ticks: { beginAtZero: true, precision: 0, max }, + stacked: true, + }, + ], + yAxes: [{ stacked: true }], + }, + tooltips: { + intersect: !isBarChart, + + // Do not show tooltip on items with empty label when in a bar chart + filter: ({ yLabel }) => !isBarChart || yLabel !== '', + }, + onHover: isBarChart && (({ target }, chartElement) => { + target.style.cursor = chartElement[0] ? 'pointer' : 'default'; + }), + }; + const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData, highlightedLabel); + const height = determineHeight(isBarChart, labels); + + // Provide a key based on the height, so that every time the dataset changes, a new graph is rendered + return ( + { + if (!onClick || !chart) { + return; + } + + const { _index, _chart: { data } } = chart; + const { labels } = data; + + onClick(labels[_index]); + }} + /> + ); +}; + +DefaultChart.propTypes = propTypes; + +export default DefaultChart; diff --git a/src/visits/helpers/GraphCard.js b/src/visits/helpers/GraphCard.js index 155a3d9d..5fc23d3f 100644 --- a/src/visits/helpers/GraphCard.js +++ b/src/visits/helpers/GraphCard.js @@ -1,9 +1,7 @@ import { Card, CardHeader, CardBody, CardFooter } from 'reactstrap'; -import { Doughnut, HorizontalBar } from 'react-chartjs-2'; import PropTypes from 'prop-types'; import React from 'react'; -import { keys, values } from 'ramda'; -import { fillTheGaps } from '../../utils/helpers/visits'; +import DefaultChart from './DefaultChart'; import './GraphCard.scss'; const propTypes = { @@ -17,112 +15,12 @@ const propTypes = { onClick: PropTypes.func, }; -const generateGraphData = (title, isBarChart, labels, data, highlightedData, highlightedLabel) => ({ - labels, - datasets: [ - { - title, - label: highlightedData ? 'Non-selected' : 'Visits', - data, - backgroundColor: isBarChart ? 'rgba(70, 150, 229, 0.4)' : [ - '#97BBCD', - '#F7464A', - '#46BFBD', - '#FDB45C', - '#949FB1', - '#57A773', - '#414066', - '#08B2E3', - '#B6C454', - '#DCDCDC', - '#463730', - ], - borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white', - borderWidth: 2, - }, - highlightedData && { - title, - label: highlightedLabel || 'Selected', - data: highlightedData, - backgroundColor: 'rgba(247, 127, 40, 0.4)', - borderColor: '#F77F28', - borderWidth: 2, - }, - ].filter(Boolean), -}); - -const dropLabelIfHidden = (label) => label.startsWith('hidden') ? '' : label; - -const determineHeight = (isBarChart, labels) => { - if (!isBarChart && labels.length > 8) { - return 200; - } - - return isBarChart && labels.length > 20 ? labels.length * 8 : null; -}; - -const renderGraph = (title, isBarChart, stats, max, highlightedStats, highlightedLabel, onClick) => { - const hasHighlightedStats = highlightedStats && Object.keys(highlightedStats).length > 0; - const Component = isBarChart ? HorizontalBar : Doughnut; - const labels = keys(stats).map(dropLabelIfHidden); - const data = values(!hasHighlightedStats ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => { - if (acc[highlightedKey]) { - acc[highlightedKey] -= highlightedStats[highlightedKey]; - } - - return acc; - }, { ...stats })); - const highlightedData = hasHighlightedStats && fillTheGaps(highlightedStats, labels); - - const options = { - legend: isBarChart ? { display: false } : { position: 'right' }, - scales: isBarChart && { - xAxes: [ - { - ticks: { beginAtZero: true, precision: 0, max }, - stacked: true, - }, - ], - yAxes: [{ stacked: true }], - }, - tooltips: { - intersect: !isBarChart, - - // Do not show tooltip on items with empty label when in a bar chart - filter: ({ yLabel }) => !isBarChart || yLabel !== '', - }, - onHover: isBarChart && (({ target }, chartElement) => { - target.style.cursor = chartElement[0] ? 'pointer' : 'default'; - }), - }; - const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData, highlightedLabel); - const height = determineHeight(isBarChart, labels); - - // Provide a key based on the height, so that every time the dataset changes, a new graph is rendered - return ( - { - if (!onClick || !chart) { - return; - } - - const { _index, _chart: { data } } = chart; - const { labels } = data; - - onClick(labels[_index]); - }} - /> - ); -}; - -const GraphCard = ({ title, footer, isBarChart, stats, max, highlightedStats, highlightedLabel, onClick }) => ( +const GraphCard = ({ title, footer, ...rest }) => ( {typeof title === 'function' ? title() : title} - {renderGraph(title, isBarChart, stats, max, highlightedStats, highlightedLabel, onClick)} + + + {footer && {footer}} ); diff --git a/test/visits/helpers/GraphCard.test.js b/test/visits/helpers/DefaultChart.js similarity index 87% rename from test/visits/helpers/GraphCard.test.js rename to test/visits/helpers/DefaultChart.js index b75ce941..7bff35e9 100644 --- a/test/visits/helpers/GraphCard.test.js +++ b/test/visits/helpers/DefaultChart.js @@ -2,9 +2,9 @@ import React from 'react'; import { shallow } from 'enzyme'; import { Doughnut, HorizontalBar } from 'react-chartjs-2'; import { keys, values } from 'ramda'; -import GraphCard from '../../../src/visits/helpers/GraphCard'; +import DefaultChart from '../../../src/visits/helpers/DefaultChart'; -describe('', () => { +describe('', () => { let wrapper; const stats = { foo: 123, @@ -14,7 +14,7 @@ describe('', () => { afterEach(() => wrapper && wrapper.unmount()); it('renders Doughnut when is not a bar chart', () => { - wrapper = shallow(); + wrapper = shallow(); const doughnut = wrapper.find(Doughnut); const horizontal = wrapper.find(HorizontalBar); @@ -48,7 +48,7 @@ describe('', () => { }); it('renders HorizontalBar when is not a bar chart', () => { - wrapper = shallow(); + wrapper = shallow(); const doughnut = wrapper.find(Doughnut); const horizontal = wrapper.find(HorizontalBar); @@ -79,7 +79,7 @@ describe('', () => { [{ bar: 20, foo: 13 }, [ 110, 436 ], [ 13, 20 ]], [ undefined, [ 123, 456 ], undefined ], ])('splits highlighted data from regular data', (highlightedStats, expectedData, expectedHighlightedData) => { - wrapper = shallow(); + wrapper = shallow(); const horizontal = wrapper.find(HorizontalBar); const { datasets: [{ data, label }, highlightedData ] } = horizontal.prop('data');