Completed
Pull Request — master (#244)
by Alejandro
12:02
created

src/visits/GraphCard.js   A

Complexity

Total Complexity 11
Complexity/F 0

Size

Lines of Code 121
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 85.19%

Importance

Changes 0
Metric Value
wmc 11
eloc 99
mnd 11
bc 11
fnc 0
dl 0
loc 121
ccs 23
cts 27
cp 0.8519
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import { Card, CardHeader, CardBody, CardFooter } from 'reactstrap';
2
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
3
import PropTypes from 'prop-types';
4
import React from 'react';
5
import { keys, values, zipObj } from 'ramda';
6
import './GraphCard.scss';
7
8 3
const propTypes = {
9
  title: PropTypes.oneOfType([ PropTypes.string, PropTypes.func ]),
10
  footer: PropTypes.oneOfType([ PropTypes.string, PropTypes.node ]),
11
  isBarChart: PropTypes.bool,
12
  stats: PropTypes.object,
13
  max: PropTypes.number,
14
  highlightedStats: PropTypes.object,
15
  onClick: PropTypes.func,
16
};
17
18 7
const generateGraphData = (title, isBarChart, labels, data, highlightedData) => ({
19
  labels,
20
  datasets: [
21
    {
22
      title,
23
      label: highlightedData && 'Non-selected',
24
      data,
25
      backgroundColor: isBarChart ? 'rgba(70, 150, 229, 0.4)' : [
26
        '#97BBCD',
27
        '#DCDCDC',
28
        '#F7464A',
29
        '#46BFBD',
30
        '#FDB45C',
31
        '#949FB1',
32
        '#4D5360',
33
      ],
34
      borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
35
      borderWidth: 2,
36
    },
37
    highlightedData && {
38
      title,
39
      label: 'Selected',
40
      data: highlightedData,
41
      backgroundColor: 'rgba(247, 127, 40, 0.4)',
42
      borderColor: '#F77F28',
43
      borderWidth: 2,
44
    },
45
  ].filter(Boolean),
46
});
47
48 14
const dropLabelIfHidden = (label) => label.startsWith('hidden') ? '' : label;
49
50 3
const renderGraph = (title, isBarChart, stats, max, highlightedStats, onClick) => {
51 7
  const hasHighlightedStats = highlightedStats && Object.keys(highlightedStats).length > 0;
52 7
  const Component = isBarChart ? HorizontalBar : Doughnut;
53 7
  const labels = keys(stats).map(dropLabelIfHidden);
54 7
  const data = values(!hasHighlightedStats ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => {
55 5
    if (acc[highlightedKey]) {
56 5
      acc[highlightedKey] -= highlightedStats[highlightedKey];
57
    }
58
59 5
    return acc;
60
  }, { ...stats }));
61 7
  const highlightedData = hasHighlightedStats && values(
62 8
    { ...zipObj(labels, labels.map(() => 0)), ...highlightedStats }
63
  );
64
65 7
  const options = {
66
    legend: isBarChart ? { display: false } : { position: 'right' },
67
    scales: isBarChart && {
68
      xAxes: [
69
        {
70
          ticks: { beginAtZero: true, max },
71
          stacked: true,
72
        },
73
      ],
74
      yAxes: [{ stacked: true }],
75
    },
76
    tooltips: {
77
      intersect: !isBarChart,
78
79
      // Do not show tooltip on items with empty label when in a bar chart
80 2
      filter: ({ yLabel }) => !isBarChart || yLabel !== '',
81
    },
82
    onHover: isBarChart && (({ target }, chartElement) => {
83 2
      target.style.cursor = chartElement[0] ? 'pointer' : 'default';
84
    }),
85
  };
86 7
  const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData);
87 7
  const height = isBarChart && labels.length > 20 ? labels.length * 8 : null;
88
89
  // Provide a key based on the height, so that every time the dataset changes, a new graph is rendered
90 7
  return (
91
    <Component
92
      key={height}
93
      data={graphData}
94
      options={options}
95
      height={height}
96
      getElementAtEvent={([ chart ]) => {
97 4
        if (!onClick || !chart) {
98
          return;
99
        }
100
101
        const { _index, _chart: { data } } = chart;
102
        const { labels } = data;
103
104
        onClick(labels[_index]);
105
      }}
106
    />
107
  );
108
};
109
110 3
const GraphCard = ({ title, footer, isBarChart, stats, max, highlightedStats, onClick }) => (
111 7
  <Card className="mt-4">
112
    <CardHeader className="graph-card__header">{typeof title === 'function' ? title() : title}</CardHeader>
113
    <CardBody>{renderGraph(title, isBarChart, stats, max, highlightedStats, onClick)}</CardBody>
114
    {footer && <CardFooter className="graph-card__footer--sticky">{footer}</CardFooter>}
115
  </Card>
116
);
117
118 3
GraphCard.propTypes = propTypes;
119
120
export default GraphCard;
121