Completed
Push — master ( 87ffbe...d231ed )
by Alejandro
22s queued 11s
created

src/visits/GraphCard.js   A

Complexity

Total Complexity 12
Complexity/F 0

Size

Lines of Code 133
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 83.87%

Importance

Changes 0
Metric Value
wmc 12
eloc 109
mnd 12
bc 12
fnc 0
dl 0
loc 133
ccs 26
cts 31
cp 0.8387
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
        '#F7464A',
28
        '#46BFBD',
29
        '#FDB45C',
30
        '#949FB1',
31
        '#57A773',
32
        '#414066',
33
        '#08B2E3',
34
        '#B6C454',
35
        '#DCDCDC',
36
        '#463730',
37
      ],
38
      borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
39
      borderWidth: 2,
40
    },
41
    highlightedData && {
42
      title,
43
      label: 'Selected',
44
      data: highlightedData,
45
      backgroundColor: 'rgba(247, 127, 40, 0.4)',
46
      borderColor: '#F77F28',
47
      borderWidth: 2,
48
    },
49
  ].filter(Boolean),
50
});
51
52 14
const dropLabelIfHidden = (label) => label.startsWith('hidden') ? '' : label;
53
54 3
const determineHeight = (isBarChart, labels) => {
55 7
  if (!isBarChart && labels.length > 8) {
56
    return 200;
57
  }
58
59 7
  return isBarChart && labels.length > 20 ? labels.length * 8 : null;
60
};
61
62 3
const renderGraph = (title, isBarChart, stats, max, highlightedStats, onClick) => {
63 7
  const hasHighlightedStats = highlightedStats && Object.keys(highlightedStats).length > 0;
64 7
  const Component = isBarChart ? HorizontalBar : Doughnut;
65 7
  const labels = keys(stats).map(dropLabelIfHidden);
66 7
  const data = values(!hasHighlightedStats ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => {
67 5
    if (acc[highlightedKey]) {
68 5
      acc[highlightedKey] -= highlightedStats[highlightedKey];
69
    }
70
71 5
    return acc;
72
  }, { ...stats }));
73 7
  const highlightedData = hasHighlightedStats && values(
74 8
    { ...zipObj(labels, labels.map(() => 0)), ...highlightedStats }
75
  );
76
77 7
  const options = {
78
    legend: isBarChart ? { display: false } : { position: 'right' },
79
    scales: isBarChart && {
80
      xAxes: [
81
        {
82
          ticks: { beginAtZero: true, max },
83
          stacked: true,
84
        },
85
      ],
86
      yAxes: [{ stacked: true }],
87
    },
88
    tooltips: {
89
      intersect: !isBarChart,
90
91
      // Do not show tooltip on items with empty label when in a bar chart
92 2
      filter: ({ yLabel }) => !isBarChart || yLabel !== '',
93
    },
94
    onHover: isBarChart && (({ target }, chartElement) => {
95 2
      target.style.cursor = chartElement[0] ? 'pointer' : 'default';
96
    }),
97
  };
98 7
  const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData);
99 7
  const height = determineHeight(isBarChart, labels);
100
101
  // Provide a key based on the height, so that every time the dataset changes, a new graph is rendered
102 7
  return (
103
    <Component
104
      key={height}
105
      data={graphData}
106
      options={options}
107
      height={height}
108
      getElementAtEvent={([ chart ]) => {
109 4
        if (!onClick || !chart) {
110
          return;
111
        }
112
113
        const { _index, _chart: { data } } = chart;
114
        const { labels } = data;
115
116
        onClick(labels[_index]);
117
      }}
118
    />
119
  );
120
};
121
122 3
const GraphCard = ({ title, footer, isBarChart, stats, max, highlightedStats, onClick }) => (
123 7
  <Card className="mt-4">
124
    <CardHeader className="graph-card__header">{typeof title === 'function' ? title() : title}</CardHeader>
125
    <CardBody>{renderGraph(title, isBarChart, stats, max, highlightedStats, onClick)}</CardBody>
126
    {footer && <CardFooter className="graph-card__footer--sticky">{footer}</CardFooter>}
127
  </Card>
128
);
129
130 3
GraphCard.propTypes = propTypes;
131
132
export default GraphCard;
133