evui 3.3.12 → 3.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evui",
3
- "version": "3.3.12",
3
+ "version": "3.3.13",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -282,7 +282,9 @@ class EvChart {
282
282
  */
283
283
  createAxes(dir, axes = []) {
284
284
  const ctx = this.bufferCtx;
285
- const labels = this.data.labels;
285
+ const labels = this.options.type === 'heatMap'
286
+ ? this.data.labels[dir]
287
+ : this.data.labels;
286
288
  const options = this.options;
287
289
  return axes.map((axis) => {
288
290
  switch (axis.type) {
@@ -1,36 +1,24 @@
1
1
  import { merge } from 'lodash-es';
2
- import Canvas from '../helpers/helpers.canvas';
3
2
  import Util from '../helpers/helpers.util';
4
- import { COLOR, HEAT_MAP_OPTION } from '../helpers/helpers.constant';
3
+ import { HEAT_MAP_OPTION } from '../helpers/helpers.constant';
5
4
 
6
5
  class HeatMap {
7
- constructor(sId, opt, sIdx) {
6
+ constructor(sId, opt, colorOpt) {
8
7
  const merged = merge({}, HEAT_MAP_OPTION, opt);
9
8
  Object.keys(merged).forEach((key) => {
10
9
  this[key] = merged[key];
11
10
  });
12
11
 
13
- ['color', 'pointFill', 'fillColor'].forEach((colorProp) => {
14
- if (this[colorProp] === undefined) {
15
- this[colorProp] = COLOR[sIdx];
16
- }
17
- });
18
-
19
- this.colorAxis = this.createColorAxis(opt.colorOpt);
20
- this.errorColor = opt.colorOpt.error;
21
- this.borderColor = opt.colorOpt.border;
12
+ this.createColorAxis(colorOpt);
22
13
 
23
14
  this.sId = sId;
24
15
  this.data = [];
25
- this.spaces = opt.spaces || { x: null, y: null };
16
+ this.labels = {};
17
+ this.valueOpt = {};
26
18
  this.size = {
27
19
  w: 0,
28
20
  h: 0,
29
21
  };
30
- this.valueOpt = {
31
- max: 0,
32
- interval: 0,
33
- };
34
22
  this.type = 'heatMap';
35
23
  }
36
24
 
@@ -41,7 +29,7 @@ class HeatMap {
41
29
  */
42
30
  createColorAxis(colorOpt) {
43
31
  const colorAxis = [];
44
- const { min, max, categoryCnt } = colorOpt;
32
+ const { min, max, categoryCnt, error, stroke } = colorOpt;
45
33
 
46
34
  const minColor = min.includes('#') ? Util.hexToRgb(min) : min;
47
35
  const maxColor = max.includes('#') ? Util.hexToRgb(max) : max;
@@ -66,30 +54,66 @@ class HeatMap {
66
54
  });
67
55
  }
68
56
 
69
- return colorAxis;
57
+ this.colorAxis = colorAxis;
58
+ this.errorColor = error;
59
+ this.stroke = stroke;
70
60
  }
71
61
 
72
62
  getColorIndex(value) {
73
63
  const existError = this.valueOpt.existError;
74
- const maxIndex = this.colorAxis.length;
75
- if (value < 0) {
76
- return maxIndex - 1;
64
+ const maxIndex = this.colorAxis.length - 1;
65
+ if (existError && value < 0) {
66
+ return maxIndex;
77
67
  }
78
68
 
79
69
  const colorIndex = Math.floor(value / this.valueOpt.interval);
80
70
  if (colorIndex >= maxIndex) {
81
- return existError ? maxIndex - 2 : maxIndex - 1;
71
+ return existError ? maxIndex - 1 : maxIndex;
82
72
  }
83
73
 
84
74
  return colorIndex;
85
75
  }
86
76
 
87
- drawItem(ctx, xp, yp) {
77
+ drawItem(ctx, x, y, w, h) {
88
78
  ctx.beginPath();
89
- ctx.strokeRect(xp - this.size.w, yp + Math.SQRT2, this.size.w, this.size.h);
90
- ctx.fillRect(xp - this.size.w, yp + Math.SQRT2, this.size.w, this.size.h);
79
+ if (this.stroke.show) {
80
+ ctx.fillRect(x, y, w, h);
81
+ ctx.strokeRect(x, y, w, h);
82
+ } else {
83
+ const aliasPixel = Util.aliasPixel(1);
84
+ ctx.fillRect(
85
+ x,
86
+ y - aliasPixel,
87
+ w + aliasPixel,
88
+ h + aliasPixel,
89
+ );
90
+ }
91
91
  ctx.closePath();
92
- ctx.stroke();
92
+ }
93
+
94
+ calculateXY(dir, value, startPoint) {
95
+ let point = null;
96
+
97
+ if (this.labels[dir] && this.labels[dir].length) {
98
+ const index = this.labels[dir].findIndex(label => label === value);
99
+
100
+ if (index > -1) {
101
+ point = dir === 'x'
102
+ ? startPoint + (this.size.w * index)
103
+ : startPoint - (this.size.h * (index + 1));
104
+ } else {
105
+ const timeIndex = this.labels[dir].findIndex(label =>
106
+ new Date(label).getTime() === new Date(value).getTime(),
107
+ );
108
+ if (timeIndex > -1) {
109
+ point = dir === 'x'
110
+ ? startPoint + (this.size.w * timeIndex)
111
+ : startPoint - (this.size.h * (timeIndex + 1));
112
+ }
113
+ }
114
+ }
115
+
116
+ return point;
93
117
  }
94
118
 
95
119
  draw(param) {
@@ -97,10 +121,7 @@ class HeatMap {
97
121
  return;
98
122
  }
99
123
 
100
- const { ctx, chartRect, labelOffset, axesSteps } = param;
101
-
102
- const minmaxX = axesSteps.x[this.xAxisIndex];
103
- const minmaxY = axesSteps.y[this.yAxisIndex];
124
+ const { ctx, chartRect, labelOffset } = param;
104
125
 
105
126
  const xArea = chartRect.chartWidth - (labelOffset.left + labelOffset.right);
106
127
  const yArea = chartRect.chartHeight - (labelOffset.top + labelOffset.bottom);
@@ -108,29 +129,128 @@ class HeatMap {
108
129
  const xsp = chartRect.x1 + labelOffset.left;
109
130
  const ysp = chartRect.y2 - labelOffset.bottom;
110
131
 
111
- this.size.w = Math.floor(xArea / (this.spaces.x || (minmaxX.graphMax - minmaxX.graphMin)));
112
- this.size.h = Math.floor(yArea / (this.spaces.y || (minmaxY.graphMax - minmaxY.graphMin)));
132
+ this.size.w = xArea / this.labels.x.length;
133
+ this.size.h = yArea / this.labels.y.length;
113
134
 
114
135
  this.data.forEach((item) => {
115
- item.xp = Canvas.calculateX(item.x, minmaxX.graphMin, minmaxX.graphMax, xArea, xsp);
116
- item.yp = Canvas.calculateY(item.y, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp);
117
-
118
- const { xp, yp, o: value } = item;
119
-
120
- if (xp !== null && yp !== null) {
136
+ let xp = this.calculateXY('x', item.x, xsp);
137
+ let yp = this.calculateXY('y', item.y, ysp);
138
+ let w = this.size.w;
139
+ let h = this.size.h;
140
+ const value = item.o;
141
+
142
+ if (xp !== null && yp !== null
143
+ && (value !== null && value !== undefined)) {
121
144
  const colorIndex = this.getColorIndex(value);
122
145
  const opacity = this.colorAxis[colorIndex].state === 'downplay' ? 0.1 : 1;
123
146
  item.dataColor = value < 0 ? this.errorColor : this.colorAxis[colorIndex].value;
124
147
  item.cId = this.colorAxis[colorIndex].id;
125
148
  if (this.colorAxis[colorIndex].show) {
126
- ctx.strokeStyle = Util.colorStringToRgba(this.borderColor, opacity);
127
149
  ctx.fillStyle = Util.colorStringToRgba(item.dataColor, opacity);
128
- this.drawItem(ctx, xp, yp);
150
+ if (this.stroke.show) {
151
+ const { color, lineWidth } = this.stroke;
152
+ ctx.strokeStyle = Util.colorStringToRgba(color, opacity);
153
+ ctx.lineWidth = lineWidth;
154
+ xp += (lineWidth * 1.5);
155
+ yp += (lineWidth * 1.5);
156
+ w -= (lineWidth * 2);
157
+ h -= (lineWidth * 2);
158
+ }
159
+ this.drawItem(ctx, xp, yp, w, h);
160
+
161
+ if (this.showValue.use) {
162
+ this.drawValueLabels({
163
+ context: ctx,
164
+ data: item,
165
+ positions: {
166
+ x: xp,
167
+ y: yp,
168
+ w,
169
+ h,
170
+ },
171
+ });
172
+ }
129
173
  }
174
+
175
+ item.xp = xp;
176
+ item.yp = yp;
177
+ item.w = w;
178
+ item.h = h;
130
179
  }
131
180
  });
132
181
  }
133
182
 
183
+ /**
184
+ * Draw value label if series 'use' of showValue option is true
185
+ *
186
+ * @param context canvas context
187
+ * @param data series value data (model.store.js addData return value)
188
+ */
189
+ drawValueLabels({ context, data, positions }) {
190
+ const { fontSize, textColor, align, formatter, decimalPoint } = this.showValue;
191
+ const { x, y, w, h } = positions;
192
+ const ctx = context;
193
+
194
+ ctx.save();
195
+ ctx.beginPath();
196
+
197
+ ctx.font = `normal normal normal ${fontSize}px Roboto`;
198
+ ctx.fillStyle = textColor;
199
+ ctx.lineWidth = 1;
200
+ ctx.textBaseline = 'middle';
201
+ ctx.textAlign = align !== 'center' ? 'left' : 'center';
202
+
203
+ const value = data.o;
204
+
205
+ let formattedTxt;
206
+ if (formatter) {
207
+ formattedTxt = formatter(value);
208
+ }
209
+
210
+ if (!formatter || typeof formattedTxt !== 'string') {
211
+ formattedTxt = Util.labelSignFormat(value, decimalPoint);
212
+ }
213
+
214
+ const vw = Math.round(ctx.measureText(formattedTxt).width);
215
+ const vh = fontSize;
216
+ const centerX = x + (w / 2);
217
+ const centerY = y + (h / 2);
218
+
219
+ if (vw >= w || formattedTxt < 0) {
220
+ return;
221
+ }
222
+
223
+ switch (align) {
224
+ case 'top': {
225
+ const xPos = centerX - (vw / 2);
226
+ const yPos = centerY - (vh / 2);
227
+ ctx.fillText(formattedTxt, xPos, yPos);
228
+ break;
229
+ }
230
+ case 'right': {
231
+ const xPos = x + w - vw;
232
+ ctx.fillText(formattedTxt, xPos, centerY);
233
+ break;
234
+ }
235
+ case 'bottom': {
236
+ const xPos = centerX - (vw / 2);
237
+ const yPos = centerY + (vh / 2);
238
+ ctx.fillText(formattedTxt, xPos, yPos);
239
+ break;
240
+ }
241
+ case 'left':
242
+ ctx.fillText(formattedTxt, x, centerY);
243
+ break;
244
+ default: {
245
+ const xPos = centerX - (vw / 2);
246
+ ctx.fillText(formattedTxt, xPos, centerY);
247
+ break;
248
+ }
249
+ }
250
+
251
+ ctx.restore();
252
+ }
253
+
134
254
  /**
135
255
  *Returns items in range
136
256
  * @param {object} params range values
@@ -141,9 +261,20 @@ class HeatMap {
141
261
  const gdata = this.data;
142
262
  const xep = xsp + width;
143
263
  const yep = ysp + height;
144
- return gdata.filter(seriesData =>
145
- (xsp - 1 <= seriesData.xp && seriesData.xp <= xep + 1
146
- && ysp - 1 <= seriesData.yp && seriesData.yp <= yep + 1));
264
+ return gdata.filter(({ xp, yp, w, h }) => {
265
+ const x1 = xp;
266
+ const x2 = xp + w;
267
+ const y1 = yp;
268
+ const y2 = yp + h;
269
+
270
+ return ((x1 <= xsp && x2 >= xsp) && (y1 <= ysp && y2 >= ysp))
271
+ || ((x1 <= xep && x2 >= xep) && (y1 <= ysp && y2 >= ysp))
272
+ || ((x1 <= xsp && x2 >= xsp) && (y1 <= yep && y2 >= yep))
273
+ || ((x1 <= xep && x2 >= xep) && (y1 <= yep && y2 >= yep))
274
+ || ((x1 >= xsp && x1 <= xep) && (y1 >= ysp && y1 <= yep))
275
+ || ((x1 >= xsp && x1 <= xep) && (y2 >= ysp && y2 <= yep))
276
+ || ((x2 >= xsp && x2 <= xep) && (y1 >= ysp && y1 <= yep));
277
+ });
147
278
  }
148
279
 
149
280
  /**
@@ -159,13 +290,29 @@ class HeatMap {
159
290
 
160
291
  const x = gdata.xp;
161
292
  const y = gdata.yp;
293
+ const w = gdata.w;
294
+ const h = gdata.h;
162
295
 
163
296
  ctx.save();
164
297
  if (x !== null && y !== null) {
165
298
  const color = gdata.dataColor;
166
299
  ctx.strokeStyle = Util.colorStringToRgba(color, 1);
167
300
  ctx.fillStyle = Util.colorStringToRgba(color, this.highlight.maxShadowOpacity);
168
- this.drawItem(ctx, x, y);
301
+ ctx.shadowColor = color;
302
+ this.drawItem(ctx, x, y, w, h);
303
+
304
+ if (this.showValue.use) {
305
+ this.drawValueLabels({
306
+ context: ctx,
307
+ data: gdata,
308
+ positions: {
309
+ x,
310
+ y,
311
+ w,
312
+ h,
313
+ },
314
+ });
315
+ }
169
316
  }
170
317
 
171
318
  ctx.restore();
@@ -186,16 +333,13 @@ class HeatMap {
186
333
  color: null,
187
334
  name: null,
188
335
  };
189
- const wSize = this.size.w;
190
- const hSize = this.size.h;
191
336
  const gdata = this.data;
192
337
 
193
338
  const foundItem = gdata.find((data) => {
194
- const x = data.xp;
195
- const y = data.yp;
339
+ const { xp: x, yp: y, w: wSize, h: hSize } = data;
196
340
 
197
- return (x - wSize <= xp)
198
- && (xp <= x)
341
+ return (x <= xp)
342
+ && (xp <= x + wSize)
199
343
  && (y <= yp)
200
344
  && (yp <= y + hSize);
201
345
  });
@@ -147,6 +147,8 @@ class Line {
147
147
  if (this.stackIndex) {
148
148
  const reversedDataList = this.data.slice().reverse();
149
149
  reversedDataList.forEach((curr, ix) => {
150
+ ctx.beginPath();
151
+
150
152
  x = getXPos(curr.x);
151
153
  y = getYPos(curr.b);
152
154
 
@@ -129,17 +129,19 @@ export const PLOT_BAND_OPTION = {
129
129
  };
130
130
 
131
131
  export const HEAT_MAP_OPTION = {
132
- ...LINE_OPTION,
133
- colorOpt: {
134
- min: '#FFFFFF',
135
- max: '#0052FF',
136
- categoryCnt: 5,
137
- border: '#FFFFFF',
138
- error: '#FF0000',
139
- },
140
- spaces: {
141
- x: 0,
142
- y: 0,
132
+ show: true,
133
+ highlight: {
134
+ maxShadowOpacity: 0.4,
135
+ },
136
+ xAxisIndex: 0,
137
+ yAxisIndex: 0,
138
+ showLegend: true,
139
+ showValue: {
140
+ use: false,
141
+ fontSize: 12,
142
+ textColor: '#000000',
143
+ formatter: null,
144
+ decimalPoint: 0,
143
145
  },
144
146
  };
145
147
 
@@ -54,7 +54,7 @@ const modules = {
54
54
  return new Pie(id, opt, index);
55
55
  } else if (type === 'heatMap') {
56
56
  this.seriesInfo.charts.heatMap.push(id);
57
- return new HeatMap(id, opt, index);
57
+ return new HeatMap(id, opt, this.options.heatMapColor);
58
58
  }
59
59
 
60
60
  return false;
@@ -1,6 +1,5 @@
1
1
  import { reverse } from 'lodash-es';
2
2
  import Util from '../helpers/helpers.util';
3
- import { TIME_INTERVALS } from '../helpers/helpers.constant';
4
3
 
5
4
  const modules = {
6
5
  /**
@@ -37,8 +36,9 @@ const modules = {
37
36
  const sData = data[seriesID];
38
37
 
39
38
  if (series && sData) {
39
+ series.labels = label;
40
40
  series.data = this.addSeriesDSForHeatMap(sData);
41
- series.minMax = this.getSeriesMinMaxForHeatMap(series.data, series.spaces);
41
+ series.minMax = this.getSeriesMinMax(series.data);
42
42
  series.valueOpt = this.getSeriesValueOptForHeatMap(series);
43
43
  }
44
44
  });
@@ -301,17 +301,20 @@ const modules = {
301
301
  /**
302
302
  * Take data to create data for each series
303
303
  * @param {array} data data array for each series
304
+ * @param {object} label chart label
305
+ *
304
306
  * @returns {array} data info added position and etc
305
307
  */
306
308
  addSeriesDSForHeatMap(data) {
307
- return data.map(item => ({
308
- x: item.x,
309
- y: item.y,
310
- o: item.value,
309
+ return data.map(({ x, y, value }) => ({
310
+ x,
311
+ y,
312
+ o: value,
311
313
  xp: null,
312
314
  yp: null,
315
+ w: null,
316
+ h: null,
313
317
  dataColor: null,
314
- value: item.value,
315
318
  cId: null,
316
319
  }));
317
320
  },
@@ -414,73 +417,14 @@ const modules = {
414
417
  return def;
415
418
  },
416
419
 
417
- adjustMinMax(max, min, opt, space) {
418
- if ((opt.type === 'time' && opt.categoryMode) || opt.type === 'step') {
419
- return {
420
- max,
421
- min,
422
- };
423
- }
424
-
425
- let targetMax = max;
426
- let targetMin = min;
427
- if (targetMax > 0 && opt.interval && space) {
428
- if (targetMax < (opt.interval * space)) {
429
- targetMax += opt.interval;
430
- }
431
- }
432
-
433
- let targetInterval = opt.interval;
434
- if (opt.type === 'time') {
435
- if (typeof targetInterval === 'string') {
436
- targetInterval = TIME_INTERVALS[targetInterval].size;
437
- } else if (typeof targetInterval === 'object') {
438
- targetInterval = targetInterval.time * TIME_INTERVALS[targetInterval.unit].size;
439
- }
440
- }
441
-
442
- if (!opt.startToZero || targetMin > 0) {
443
- const targetSpace = space ? (space - 1) : (targetMax - targetMin);
444
- const targetStep = Math.ceil((max - targetMin) / targetSpace);
445
- targetMin = targetMin < targetStep ? 0 : targetMin - targetStep;
446
- }
447
-
448
- return {
449
- max: targetMax,
450
- min: targetMin,
451
- };
452
- },
453
- /**
454
- * Take series data to create min/max info for each series
455
- * @param data
456
- * @param spaces
457
- * @returns {*|{maxDomain: null, minY: null, minX: null, maxY: null, maxX: null}}
458
- */
459
- getSeriesMinMaxForHeatMap(data, spaces) {
460
- const axesXOption = this.options.axesX[0];
461
- const axesYOption = this.options.axesY[0];
462
- const seriesMinMax = this.getSeriesMinMax(data);
463
-
464
- const adjustX = this.adjustMinMax(seriesMinMax.maxX, seriesMinMax.minX, axesXOption, spaces.x);
465
- seriesMinMax.maxX = adjustX.max;
466
- seriesMinMax.minX = adjustX.min;
467
-
468
- const adjustY = this.adjustMinMax(seriesMinMax.maxY, seriesMinMax.minY, axesYOption, spaces.y);
469
- seriesMinMax.maxY = adjustY.max;
470
- seriesMinMax.minY = adjustY.min;
471
-
472
- return seriesMinMax;
473
- },
474
-
475
420
  getSeriesValueOptForHeatMap(series) {
476
421
  const data = series.data;
477
- const colorOpt = series.colorOpt;
478
- const colorAxis = series.colorAxis;
422
+ const colorOpt = this.options.heatMapColor;
479
423
  const categoryCnt = colorOpt.categoryCnt;
480
424
 
481
425
  let maxValue = 0;
482
426
  let isExistError = false;
483
- data.forEach(({ value }) => {
427
+ data.forEach(({ o: value }) => {
484
428
  if (maxValue < value) {
485
429
  maxValue = value;
486
430
  }
@@ -488,18 +432,19 @@ const modules = {
488
432
  isExistError = true;
489
433
  }
490
434
  });
491
- const valueInterval = Math.ceil(maxValue / categoryCnt);
492
- if (isExistError && colorAxis.length === categoryCnt) {
493
- colorAxis.push({
435
+
436
+ if (isExistError && series.colorAxis.length === categoryCnt) {
437
+ series.colorAxis.push({
494
438
  id: `color#${categoryCnt}`,
495
439
  value: colorOpt.error,
496
440
  state: 'normal',
497
441
  show: true,
498
442
  });
499
443
  }
444
+
500
445
  return {
501
446
  max: maxValue,
502
- interval: valueInterval,
447
+ interval: Math.ceil(maxValue / categoryCnt),
503
448
  existError: isExistError,
504
449
  };
505
450
  },
@@ -56,6 +56,8 @@ const modules = {
56
56
  this.setTooltipLayoutPosition(hitInfo, e);
57
57
  if (type === 'scatter') {
58
58
  this.drawTooltipForScatter(hitInfo, this.tooltipCtx);
59
+ } else if (type === 'heatMap') {
60
+ this.drawToolTipForHeatMap(hitInfo, this.tooltipCtx);
59
61
  } else {
60
62
  this.drawTooltip(hitInfo, this.tooltipCtx);
61
63
  }
@@ -112,7 +114,7 @@ const modules = {
112
114
  this.render(hitInfo);
113
115
  }
114
116
 
115
- ({ label: args.label, value: args.value, sId: args.seriesId } = hitInfo);
117
+ ({ label: args.label, value: args.value, sId: args.seriesId, acc: args.acc } = hitInfo);
116
118
  }
117
119
 
118
120
  if (typeof this.listeners['dbl-click'] === 'function') {
@@ -140,6 +142,7 @@ const modules = {
140
142
  value: args.value,
141
143
  sId: args.seriesId,
142
144
  maxIndex: args.dataIndex,
145
+ acc: args.acc,
143
146
  } = hitInfo);
144
147
  }
145
148
 
@@ -80,10 +80,12 @@ const modules = {
80
80
  Object.values(seriesList).forEach((series) => {
81
81
  if (!series.isExistGrp && series.showLegend) {
82
82
  const { colorAxis, valueOpt } = series;
83
+ const { max, interval, existError } = valueOpt;
83
84
  colorAxis.forEach((colorItem, index) => {
84
- const maxValue = valueOpt.interval * (index + 1);
85
- const minValue = maxValue - valueOpt.interval;
86
- const name = valueOpt.existError && index === colorAxis.length - 1
85
+ const minValue = interval * index;
86
+ const maxValue = index === colorAxis.length - 1
87
+ ? max : minValue + interval - 1;
88
+ const name = existError && index === colorAxis.length - 1
87
89
  ? 'error' : `${minValue} - ${maxValue}`;
88
90
  this.addLegend({
89
91
  cId: colorItem.id,
@@ -27,6 +27,11 @@ const modules = {
27
27
 
28
28
  const chartWidth = centerX - (padding.left + padding.right);
29
29
  const chartHeight = centerY - (padding.bottom + padding.top);
30
+ if ((typeof chartWidth === 'number' && chartWidth < 0)
31
+ || (typeof chartHeight === 'number' && chartHeight < 0)) {
32
+ return;
33
+ }
34
+
30
35
  const innerRadius = Math.min(chartWidth, chartHeight) * pieOption.doughnutHoleSize;
31
36
  const outerRadius = Math.min(chartWidth, chartHeight);
32
37
 
@@ -109,8 +114,14 @@ const modules = {
109
114
 
110
115
  const centerX = width / 2;
111
116
  const centerY = height / 2;
117
+
112
118
  const chartWidth = centerX - (padding.left + padding.right);
113
119
  const chartHeight = centerY - (padding.bottom + padding.top);
120
+ if ((typeof chartWidth === 'number' && chartWidth < 0)
121
+ || (typeof chartHeight === 'number' && chartHeight < 0)) {
122
+ return;
123
+ }
124
+
114
125
  const innerRadius = Math.min(chartWidth, chartHeight) * pieOption.doughnutHoleSize;
115
126
  const outerRadius = Math.min(chartWidth, chartHeight);
116
127
 
@@ -190,8 +201,14 @@ const modules = {
190
201
 
191
202
  const centerX = width / 2;
192
203
  const centerY = height / 2;
204
+
193
205
  const chartWidth = centerX - (padding.left + padding.right);
194
206
  const chartHeight = centerY - (padding.bottom + padding.top);
207
+ if ((typeof chartWidth === 'number' && chartWidth < 0)
208
+ || (typeof chartHeight === 'number' && chartHeight < 0)) {
209
+ return;
210
+ }
211
+
195
212
  const radius = Math.min(chartWidth, chartHeight) * pieOption.doughnutHoleSize;
196
213
 
197
214
  ctx.save();