Completed
Push — master ( c670d8...934051 )
by Alejandro
13s queued 11s
created

src/visits/GraphCard.js   A

Complexity

Total Complexity 13
Complexity/F 0

Size

Lines of Code 132
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 83.33%

Importance

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