Completed
Push — master ( 984c1e...16ffbc )
by Alejandro
19s queued 10s
created

src/visits/helpers/GraphCard.js   A

Complexity

Total Complexity 13
Complexity/F 0

Size

Lines of Code 133
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 83.33%

Importance

Changes 0
Metric Value
wmc 13
eloc 110
mnd 13
bc 13
fnc 0
dl 0
loc 133
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
ccs 25
cts 30
cp 0.8333
rs 10
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 } from 'ramda';
6
import { fillTheGaps } from '../../utils/helpers/visits';
7
import './GraphCard.scss';
8
9 3
const propTypes = {
10
  title: PropTypes.oneOfType([ PropTypes.string, PropTypes.func ]),
11
  footer: PropTypes.oneOfType([ PropTypes.string, PropTypes.node ]),
12
  isBarChart: PropTypes.bool,
13
  stats: PropTypes.object,
14
  max: PropTypes.number,
15
  highlightedStats: PropTypes.object,
16
  highlightedLabel: PropTypes.string,
17
  onClick: PropTypes.func,
18
};
19
20 7
const generateGraphData = (title, isBarChart, labels, data, highlightedData, highlightedLabel) => ({
21
  labels,
22
  datasets: [
23
    {
24
      title,
25
      label: highlightedData ? 'Non-selected' : 'Visits',
26
      data,
27
      backgroundColor: isBarChart ? 'rgba(70, 150, 229, 0.4)' : [
28
        '#97BBCD',
29
        '#F7464A',
30
        '#46BFBD',
31
        '#FDB45C',
32
        '#949FB1',
33
        '#57A773',
34
        '#414066',
35
        '#08B2E3',
36
        '#B6C454',
37
        '#DCDCDC',
38
        '#463730',
39
      ],
40
      borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
41
      borderWidth: 2,
42
    },
43
    highlightedData && {
44
      title,
45
      label: highlightedLabel || 'Selected',
46
      data: highlightedData,
47
      backgroundColor: 'rgba(247, 127, 40, 0.4)',
48
      borderColor: '#F77F28',
49
      borderWidth: 2,
50
    },
51
  ].filter(Boolean),
52
});
53
54 14
const dropLabelIfHidden = (label) => label.startsWith('hidden') ? '' : label;
55
56 3
const determineHeight = (isBarChart, labels) => {
57 7
  if (!isBarChart && labels.length > 8) {
58
    return 200;
59
  }
60
61 7
  return isBarChart && labels.length > 20 ? labels.length * 8 : null;
62
};
63
64 3
const renderGraph = (title, isBarChart, stats, max, highlightedStats, highlightedLabel, onClick) => {
65 7
  const hasHighlightedStats = highlightedStats && Object.keys(highlightedStats).length > 0;
66 7
  const Component = isBarChart ? HorizontalBar : Doughnut;
67 7
  const labels = keys(stats).map(dropLabelIfHidden);
68 7
  const data = values(!hasHighlightedStats ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => {
69 5
    if (acc[highlightedKey]) {
70 5
      acc[highlightedKey] -= highlightedStats[highlightedKey];
71
    }
72
73 5
    return acc;
74
  }, { ...stats }));
75 7
  const highlightedData = hasHighlightedStats && fillTheGaps(highlightedStats, labels);
76
77 7
  const options = {
78
    legend: isBarChart ? { display: false } : { position: 'right' },
79
    scales: isBarChart && {
80
      xAxes: [
81
        {
82
          ticks: { beginAtZero: true, precision: 0, 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, highlightedLabel);
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, highlightedLabel, onClick }) => (
123 7
  <Card>
124
    <CardHeader className="graph-card__header">{typeof title === 'function' ? title() : title}</CardHeader>
125
    <CardBody>{renderGraph(title, isBarChart, stats, max, highlightedStats, highlightedLabel, 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