Passed
Push — master ( 8cc70a...503e6a )
by Guangyu
10:03 queued 13s
created

MultipleLineChart.js ➔ maxValue   A

Complexity

Conditions 4

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 11
rs 9.95
c 0
b 0
f 0
cc 4
1
import React, { useState, useContext, useEffect, useRef } from 'react';
2
import { Row, Col, Card, CardBody } from 'reactstrap';
3
import { CheckPicker } from 'rsuite';
4
import { Chart } from 'react-chartjs-2';
5
import annotationPlugin from 'chartjs-plugin-annotation';
6
import {
7
  Chart as ChartJS,
8
  CategoryScale,
9
  LinearScale,
10
  PointElement,
11
  LineElement,
12
  Tooltip,
13
  Legend,
14
} from 'chart.js';
15
import { rgbaColor, themeColors, isIterableArray } from '../../../helpers/utils';
16
import AppContext from '../../../context/Context';
17
import moment from 'moment';
18
import { node } from 'prop-types';
19
20
ChartJS.register(
21
  annotationPlugin,
22
  CategoryScale,
23
  LinearScale,
24
  PointElement,
25
  LineElement,
26
  Tooltip,
27
  Legend
28
);
29
30
const maxAnnotation1 = {
31
  type: 'label',
32
  backgroundColor: 'rgba(245, 245, 245, 0.5)',
33
  content: (ctx) => 'MAX ' + maxValue(ctx).toFixed(2),
34
  font: {
35
    size: 16
36
  },
37
  padding: {
38
    top: 6,
39
    left: 6,
40
    right: 6,
41
    bottom: 6
42
  },
43
  position: {
44
    x: (ctx) => maxIndex(ctx) <= 3 ? 'start' : maxIndex(ctx) >= 10 ? 'end' : 'center',
45
    y: 'end'
46
  },
47
  xValue: (ctx) => maxLabel(ctx),
48
  yAdjust: 24,
49
  yValue: (ctx) => maxValue(ctx)
50
};
51
52
function maxValue(ctx) {
53
  if (ctx.chart.data.datasets.length < 1) {
54
    return 0.00;
55
  }
56
  let max = -9999999999999;
57
  const dataset = ctx.chart.data.datasets[0];
58
  dataset.data.forEach(function(el) {
59
    max = Math.max(max, el);
60
  });
61
  return max;
62
}
63
64
function maxIndex(ctx) {
65
  if (ctx.chart.data.datasets.length < 1) {
66
    return 0;
67
  }
68
  const max = maxValue(ctx);
69
  const dataset = ctx.chart.data.datasets[0];
70
  return dataset.data.indexOf(max);
71
}
72
73
function maxLabel(ctx) {
74
  if (ctx.chart.data.datasets.length < 1) {
75
    return 0;
76
  }
77
  return ctx.chart.data.labels[maxIndex(ctx)];
78
}
79
80
const maxAnnotation2 = {
81
  type: 'point',
82
  backgroundColor: 'transparent',
83
  pointStyle: 'rectRounded',
84
  radius: 10,
85
  xValue: (ctx) => maxLabel(ctx),
86
  yValue: (ctx) => maxValue(ctx)
87
};
88
89
function minValue(ctx) {
90
  if (ctx.chart.data.datasets.length < 1) {
91
    return 0.00;
92
  }
93
  let min = 999999999999999;
94
  const dataset = ctx.chart.data.datasets[0];
95
  dataset.data.forEach(function(el) {
96
    min = Math.min(min, el);
97
  });
98
  return min;
99
}
100
101
function minIndex(ctx) {
102
  if (ctx.chart.data.datasets.length < 1) {
103
    return 0;
104
  }
105
  const min = minValue(ctx);
106
  const dataset = ctx.chart.data.datasets[0];
107
  return dataset.data.indexOf(min);
108
}
109
110
function minLabel(ctx) {
111
  if (ctx.chart.data.datasets.length < 1) {
112
    return 0;
113
  }
114
  return ctx.chart.data.labels[minIndex(ctx)];
115
}
116
117
const minAnnotation1 = {
118
  type: 'label',
119
  backgroundColor: 'rgba(245, 245, 245, 0.5)',
120
  content: (ctx) => 'MIN ' + minValue(ctx).toFixed(2),
121
  font: {
122
    size: 16
123
  },
124
  padding: {
125
    top: 6,
126
    left: 6,
127
    right: 6,
128
    bottom: 6
129
  },
130
  position: {
131
    x: 'start',
132
    y: 'end'
133
  },
134
  xValue: (ctx) => minLabel(ctx),
135
  yAdjust: -12,
136
  yValue: (ctx) => minValue(ctx)
137
};
138
139
const minAnnotation2 = {
140
  type: 'point',
141
  backgroundColor: 'transparent',
142
  pointStyle: 'rectRounded',
143
  radius: 10,
144
  xValue: (ctx) => minLabel(ctx),
145
  yValue: (ctx) => minValue(ctx)
146
};
147
148
function average(ctx) {
149
  if (ctx.chart.data.datasets.length < 1) {
150
    return 0.00;
151
  }
152
  const values = ctx.chart.data.datasets[0].data;
153
  return values.reduce((a, b) => a + b, 0) / values.length;
154
}
155
156
const annotation = {
157
  type: 'line',
158
  borderColor: 'black',
159
  borderDash: [6, 6],
160
  borderDashOffset: 0,
161
  borderWidth: 3,
162
  label: {
163
    display: true,
164
    content: (ctx) => 'Average: ' + average(ctx).toFixed(2),
165
    position: 'end'
166
  },
167
  scaleID: 'y',
168
  value: (ctx) => average(ctx)
169
};
170
171
const MultipleLineChart = ({
172
  reportingTitle,
173
  baseTitle,
174
  labels,
175
  data,
176
  options
177
}) => {
178
  const [values, setValues] = useState(['a0']);
179
  const [oldValues, setOldValues] = useState(['a0']);
180
  const { isDark } = useContext(AppContext);
181
  const [nodes, setNodes] = useState([{
182
    label: options.label,
183
    borderWidth: 2,
184
    data: data['a0'],
185
    borderColor: rgbaColor("#"+((1<<24)*Math.random()|0).toString(16), 0.8),
186
  }]);
187
  const [lastMoment, setLastMoment] = useState(moment());
188
  const chartRef = useRef(null);
189
  const [lineData, setLineData] = useState({
190
    datasets: nodes,
191
  });  
192
193
  let handleChange = (arr) => {
194
    let currentMoment = moment();
195
    if (currentMoment.diff(lastMoment) <= 750) {
196
      return;
197
    }
198
    setOldValues(values);
199
    setValues(arr);
200
    setLastMoment(currentMoment);
201
  }
202
203
  useEffect(() => {
204
    const chart = chartRef.current;
205
    if (chart) {
206
      const ctx = chart.ctx;
207
      const gradientFill = isDark
208
        ? ctx.createLinearGradient(0, 0, 0, ctx.canvas.height)
209
        : ctx.createLinearGradient(0, 0, 0, 250);
210
      gradientFill.addColorStop(0, isDark ? 'rgba(44,123,229, 0.5)' : 'rgba(255, 255, 255, 0.3)');
211
      gradientFill.addColorStop(1, isDark ? 'transparent' : 'rgba(255, 255, 255, 0)');
212
      let tempNodes = [...nodes];
213
      if (options[0] && data['a0'] && tempNodes.length > 0 && tempNodes[0].label === undefined) {
214
        let index = values[0];
215
        tempNodes[0] = {
216
          label: options[index.slice(1)].label,
217
          borderWidth: 2,
218
          data: data[index],
219
          borderColor: rgbaColor("#"+((1<<24)*Math.random()|0).toString(16), 0.8),
220
          tension: 0.4,
221
        }
222
      }
223
      if (oldValues.length < values.length) {
224
        let index = values[values.length - 1];
225
        tempNodes.push({
226
          label: options[index.slice(1)].label,
227
          borderWidth: 2,
228
          data: data[index],
229
          borderColor: rgbaColor("#"+((1<<24)*Math.random()|0).toString(16), 0.8),
230
          tension: 0.4,
231
        })
232
      } else {
233
        let i = 0
234
        for (; i <= oldValues.length; i++ ) {
235
          if (i === values.length || oldValues[i] !== values[i]){
236
            break;
237
          }
238
        }
239
        tempNodes.splice(i, 1);
240
      }
241
      let chartData = {
242
        datasets: tempNodes,
243
        labels: labels[values[0]]
244
      };
245
      setNodes(tempNodes);
246
      setLineData(chartData);
247
    }
248
  }, [data, labels, values]);
249
250
  const config = {
251
    options: {
252
      plugins: {
253
        legend: {
254
          display: false,
255
        },
256
        annotation: {
257
          annotations: {
258
            annotation,
259
            maxAnnotation1,
260
            maxAnnotation2,
261
            minAnnotation1,
262
            minAnnotation2
263
          }
264
        }
265
      },
266
      hover: { mode: 'label' },
267
      scales: {
268
        x: {
269
            ticks: {
270
              fontColor: rgbaColor('#789', 0.8),
271
              fontStyle: 600
272
            },
273
            gridLines: {
274
              color: rgbaColor('#000', 0.1),
275
              zeroLineColor: rgbaColor('#000', 0.1),
276
              lineWidth: 1
277
            }
278
          },
279
        y:  {
280
          display: true,
281
          gridLines: {
282
            color: rgbaColor('#000', 0.1)
283
          }
284
        }
285
      }
286
    }
287
  };
288
  return (
289
    <Card className="mb-3">
290
      <CardBody className="rounded-soft">
291
        <Row className="text-white align-items-center no-gutters">
292
          <Col>
293
            <h4 className="text-lightSlateGray mb-0">{reportingTitle}</h4>
294
            <p className="fs--1 font-weight-semi-bold">
295
              {baseTitle}
296
            </p>
297
          </Col>
298
          {options[0] && isIterableArray(options) &&
299
            <Col xs="auto" className="d-none d-sm-block">
300
              <CheckPicker
301
                data={options}
302
                value={values}
303
                appearance="default"
304
                placeholder="select"
305
                searchable={false}
306
                countable={false}
307
                onSelect={handleChange}
308
                style={{ width: 224, borderRadius: '.25rem'}}
309
                />
310
            </Col>
311
          }
312
        </Row>
313
        <Chart type="line" ref={chartRef} data={lineData} options={config.options} width={1618} height={375} />
314
      </CardBody>
315
    </Card>
316
  );
317
};
318
319
export default MultipleLineChart;