Completed
Push — master ( 72dd2b...e26cdc )
by Alejandro
14s queued 10s
created

src/visits/helpers/DefaultChart.js   A

Complexity

Total Complexity 11
Complexity/F 0

Size

Lines of Code 160
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 75%

Importance

Changes 0
Metric Value
wmc 11
eloc 131
mnd 11
bc 11
fnc 0
dl 0
loc 160
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
ccs 27
cts 36
cp 0.75
1
import React, { useRef } from 'react';
2
import PropTypes from 'prop-types';
3
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
4
import { keys, values } from 'ramda';
5
import classNames from 'classnames';
6
import { fillTheGaps } from '../../utils/helpers/visits';
7
import './DefaultChart.scss';
8
9 4
const propTypes = {
10
  title: PropTypes.oneOfType([ PropTypes.string, PropTypes.func ]),
11
  isBarChart: PropTypes.bool,
12
  stats: PropTypes.object,
13
  max: PropTypes.number,
14
  highlightedStats: PropTypes.object,
15
  highlightedLabel: PropTypes.string,
16
  onClick: PropTypes.func,
17
};
18
19 7
const generateGraphData = (title, isBarChart, labels, data, highlightedData, highlightedLabel) => ({
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: highlightedLabel || '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 4
const determineHeight = (isBarChart, labels) => {
56 7
  if (!isBarChart) {
57 1
    return 300;
58
  }
59
60 6
  return isBarChart && labels.length > 20 ? labels.length * 8 : null;
61
};
62
63
/* eslint-disable react/prop-types */
64 4
const renderPieChartLegend = ({ config }) => {
65
  const { labels, datasets } = config.data;
66
  const { defaultColor } = config.options;
67
  const [{ backgroundColor: colors }] = datasets;
68
69
  return (
70
    <ul className="default-chart__pie-chart-legend">
71
      {labels.map((label, index) => (
72
        <li key={label} className="default-chart__pie-chart-legend-item d-flex">
73
          <div
74
            className="default-chart__pie-chart-legend-item-color"
75
            style={{ backgroundColor: colors[index] || defaultColor }}
76
          />
77
          <small className="default-chart__pie-chart-legend-item-text flex-fill">{label}</small>
78
        </li>
79
      ))}
80
    </ul>
81
  );
82
};
83
/* eslint-enable react/prop-types */
84
85 7
const chartElementAtEvent = (onClick) => ([ chart ]) => {
86 4
  if (!onClick || !chart) {
87
    return;
88
  }
89
90
  const { _index, _chart: { data } } = chart;
91
  const { labels } = data;
92
93
  onClick(labels[_index]);
94
};
95
96 4
const DefaultChart = ({ title, isBarChart, stats, max, highlightedStats, highlightedLabel, onClick }) => {
97 7
  const hasHighlightedStats = highlightedStats && Object.keys(highlightedStats).length > 0;
98 7
  const Component = isBarChart ? HorizontalBar : Doughnut;
99 7
  const labels = keys(stats).map(dropLabelIfHidden);
100 7
  const data = values(!hasHighlightedStats ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => {
101 5
    if (acc[highlightedKey]) {
102 5
      acc[highlightedKey] -= highlightedStats[highlightedKey];
103
    }
104
105 5
    return acc;
106
  }, { ...stats }));
107 7
  const highlightedData = hasHighlightedStats && fillTheGaps(highlightedStats, labels);
108 7
  const chartRef = useRef();
109
110 7
  const options = {
111
    legend: { display: false },
112
    legendCallback: !isBarChart && renderPieChartLegend,
113
    scales: isBarChart && {
114
      xAxes: [
115
        {
116
          ticks: { beginAtZero: true, precision: 0, max },
117
          stacked: true,
118
        },
119
      ],
120
      yAxes: [{ stacked: true }],
121
    },
122
    tooltips: {
123
      intersect: !isBarChart,
124
125
      // Do not show tooltip on items with empty label when in a bar chart
126 2
      filter: ({ yLabel }) => !isBarChart || yLabel !== '',
127
    },
128
    onHover: isBarChart && (({ target }, chartElement) => {
129 2
      target.style.cursor = chartElement[0] ? 'pointer' : 'default';
130
    }),
131
  };
132 7
  const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData, highlightedLabel);
133 7
  const height = determineHeight(isBarChart, labels);
134
135
  // Provide a key based on the height, so that every time the dataset changes, a new graph is rendered
136 7
  return (
137
    <div className="row">
138
      <div className={classNames('col-sm-12', { 'col-md-7': !isBarChart })}>
139
        <Component
140
          ref={chartRef}
141
          key={height}
142
          data={graphData}
143
          options={options}
144
          height={height}
145
          getElementAtEvent={chartElementAtEvent(onClick)}
146
        />
147
      </div>
148
      {!isBarChart && (
149
        <div className="col-sm-12 col-md-5">
150
          {chartRef.current && chartRef.current.chartInstance.generateLegend()}
151
        </div>
152
      )}
153
    </div>
154
  );
155
};
156
157 4
DefaultChart.propTypes = propTypes;
158
159
export default DefaultChart;
160