| @@ -105,8 +105,11 @@ | ||
| 105 | 105 | switch (_this.chartType) { |
| 106 | 106 | case 'pie_chart': |
| 107 | 107 | _this.pieChartView(); |
| 108 | 108 | break; |
| 109 | + case 'donut_chart': | |
| 110 | + _this.donutChartView(); | |
| 111 | + break; | |
| 109 | 112 | case 'bar_chart': |
| 110 | 113 | _this.barChartView(); |
| 111 | 114 | break; |
| 112 | 115 | case 'line_chart': |
| @@ -128,8 +131,9 @@ | ||
| 128 | 131 | var dataTypes = _this.chartConvertData( getChartSource ); |
| 129 | 132 | |
| 130 | 133 | var settings = _this.dbData.options; |
| 131 | 134 | var nSettings = _this.configOptionsForCharts(settings); |
| 135 | + _this.applyPieDataGrouping(dataTypes, nSettings); | |
| 132 | 136 | |
| 133 | 137 | // var ctx = document.getElementById(_this.htmlClassPrefix + _this.uniqueId + '-canvas'); |
| 134 | 138 | var parentId = _this.htmlClassPrefix + _this.chartType + '-' + _this.uniqueId; |
| 135 | 139 | var canvasId = parentId + '-canvas'; |
| @@ -163,9 +167,9 @@ | ||
| 163 | 167 | dataTypes.dataSets[0].backgroundColor = []; |
| 164 | 168 | dataTypes.dataSets[0].borderColor = []; |
| 165 | 169 | |
| 166 | 170 | for (var i = 0; i < sliceCount; i++) { |
| 167 | - dataTypes.dataSets[0].backgroundColor[i] = nSettings.sliceColor?.[i] || nSettings.sliceColorDefault?.[i % nSettings.sliceColorDefault.length]; | |
| 171 | + dataTypes.dataSets[0].backgroundColor[i] = i === dataTypes.dataSets[0].groupedSliceIndex ? nSettings.dataGroupingColor : (nSettings.sliceColor?.[i] || nSettings.sliceColorDefault?.[i % nSettings.sliceColorDefault.length]); | |
| 168 | 172 | dataTypes.dataSets[0].borderColor[i] = nSettings.slicesBorderColor?.[i] || 'transparent'; |
| 169 | 173 | } |
| 170 | 174 | dataTypes.dataSets[0].borderWidth = nSettings.sliceBorderWidth; |
| 171 | 175 | _this.chartObject = new Chart(ctx, { |
| @@ -177,9 +181,9 @@ | ||
| 177 | 181 | options: { |
| 178 | 182 | radius: nSettings.outerRadius, |
| 179 | 183 | spacing: nSettings.sliceSpacing, |
| 180 | 184 | circumference: nSettings.circumference, |
| 181 | - rotation: nSettings.startAngle, | |
| 185 | + rotation: nSettings.rotationDegree, | |
| 182 | 186 | borderColor: nSettings.sliceBorderColor, |
| 183 | 187 | hover: nSettings.enableInteractivity ? { |
| 184 | 188 | mode: 'nearest', |
| 185 | 189 | intersect: true |
| @@ -209,22 +213,122 @@ | ||
| 209 | 213 | weight: nSettings.tooltipBoldText === 'true' ? 'bold' : (nSettings.tooltipBoldText === 'false' ? 'normal' : undefined) |
| 210 | 214 | }, |
| 211 | 215 | position: 'nearest', |
| 212 | 216 | events: ['click'], |
| 213 | - callbacks: nSettings.showColorCode ? { | |
| 214 | - label: function(context) { | |
| 215 | - var label = context.label || ''; | |
| 216 | - var value = context.parsed || 0; | |
| 217 | - var color = context.dataset.backgroundColor[context.dataIndex]; | |
| 218 | - return label + ': ' + value + ' (Color: ' + color + ')'; | |
| 217 | + callbacks: _this.getTooltipCallbacks(nSettings.tooltipText, nSettings.showColorCode) | |
| 218 | + }, | |
| 219 | + legend: { | |
| 220 | + position: nSettings.legendPosition, | |
| 221 | + align: nSettings.legendAlignment, | |
| 222 | + reverse: nSettings.legendReverse, | |
| 223 | + labels: { | |
| 224 | + color: nSettings.legendColor, | |
| 225 | + font: { | |
| 226 | + size: nSettings.legendFontSize, | |
| 227 | + weight: nSettings.legendBoldText ? 'bold' : 'normal', | |
| 228 | + style: nSettings.legendItalicText ? 'italic' : 'normal' | |
| 219 | 229 | } |
| 220 | - } : { | |
| 221 | - label: function(context) { | |
| 222 | - var label = context.label || ''; | |
| 223 | - var value = context.parsed || 0; | |
| 224 | - return label + ': ' + value; | |
| 225 | - } | |
| 226 | 230 | } |
| 231 | + } | |
| 232 | + } | |
| 233 | + } | |
| 234 | + }); | |
| 235 | + | |
| 236 | + _this.resizeChart(); | |
| 237 | + } | |
| 238 | + | |
| 239 | + // Load chart by pie chart | |
| 240 | + ChartBuilderChartJs.prototype.donutChartView = function(){ | |
| 241 | + var _this = this; | |
| 242 | + var getChartSource = _this.dbData.source; | |
| 243 | + | |
| 244 | + var dataTypes = _this.chartConvertData( getChartSource ); | |
| 245 | + | |
| 246 | + var settings = _this.dbData.options; | |
| 247 | + var nSettings = _this.configOptionsForCharts(settings); | |
| 248 | + _this.applyPieDataGrouping(dataTypes, nSettings); | |
| 249 | + | |
| 250 | + // var ctx = document.getElementById(_this.htmlClassPrefix + _this.uniqueId + '-canvas'); | |
| 251 | + var parentId = _this.htmlClassPrefix + _this.chartType + '-' + _this.uniqueId; | |
| 252 | + var canvasId = parentId + '-canvas'; | |
| 253 | + | |
| 254 | + var parentElement = document.getElementById(parentId); | |
| 255 | + var canvasElement = document.getElementById(canvasId); | |
| 256 | + | |
| 257 | + if (!canvasElement) { | |
| 258 | + canvasElement = document.createElement('canvas'); | |
| 259 | + canvasElement.id = canvasId; | |
| 260 | + parentElement.appendChild(canvasElement); | |
| 261 | + } | |
| 262 | + | |
| 263 | + var ctx = canvasElement; | |
| 264 | + ctx.width = nSettings.chartWidth; | |
| 265 | + ctx.height = nSettings.chartHeight; | |
| 266 | + | |
| 267 | + if (nSettings.chartWidthFormat === 'px') { | |
| 268 | + ctx.width = parseInt(nSettings.chartWidth); | |
| 269 | + } else if (nSettings.chartWidthFormat === '%') { | |
| 270 | + ctx.style.width = nSettings.chartWidth + '%'; | |
| 271 | + } | |
| 272 | + | |
| 273 | + if (nSettings.chartHeightFormat === 'px') { | |
| 274 | + ctx.height = parseInt(nSettings.chartHeight); | |
| 275 | + } else if (nSettings.chartHeightFormat === '%') { | |
| 276 | + ctx.style.height = nSettings.chartHeight + '%'; | |
| 277 | + } | |
| 278 | + | |
| 279 | + var sliceCount = dataTypes?.dataSets[0]?.data?.length || 0; | |
| 280 | + dataTypes.dataSets[0].backgroundColor = []; | |
| 281 | + dataTypes.dataSets[0].borderColor = []; | |
| 282 | + | |
| 283 | + for (var i = 0; i < sliceCount; i++) { | |
| 284 | + dataTypes.dataSets[0].backgroundColor[i] = nSettings.sliceColor?.[i] || nSettings.sliceColorDefault?.[i % nSettings.sliceColorDefault.length]; | |
| 285 | + dataTypes.dataSets[0].borderColor[i] = nSettings.slicesBorderColor?.[i] || 'transparent'; | |
| 286 | + } | |
| 287 | + dataTypes.dataSets[0].borderWidth = nSettings.sliceBorderWidth; | |
| 288 | + _this.chartObject = new Chart(ctx, { | |
| 289 | + type: 'doughnut', | |
| 290 | + data: { | |
| 291 | + labels: dataTypes?.labels, | |
| 292 | + datasets: dataTypes?.dataSets, | |
| 293 | + }, | |
| 294 | + options: { | |
| 295 | + radius: nSettings.outerRadius, | |
| 296 | + spacing: nSettings.sliceSpacing, | |
| 297 | + circumference: nSettings.circumference, | |
| 298 | + rotation: nSettings.rotationDegree, | |
| 299 | + borderColor: nSettings.sliceBorderColor, | |
| 300 | + hover: nSettings.enableInteractivity ? { | |
| 301 | + mode: 'nearest', | |
| 302 | + intersect: true | |
| 303 | + } : { | |
| 304 | + mode: null, | |
| 305 | + intersect: false | |
| 306 | + }, | |
| 307 | + plugins: { | |
| 308 | + tooltip:{ | |
| 309 | + enabled: nSettings.enableInteractivity, | |
| 310 | + titleColor: nSettings.tooltipColor, | |
| 311 | + bodyColor: nSettings.tooltipColor, | |
| 312 | + footerColor: nSettings.tooltipColor, | |
| 313 | + titleFont: { | |
| 314 | + size: nSettings.tooltipFontSize || 12, | |
| 315 | + style: nSettings.tooltipItalicText ? 'italic' : 'normal', | |
| 316 | + weight: nSettings.tooltipBoldText === 'true' ? 'bold' : (nSettings.tooltipBoldText === 'false' ? 'normal' : undefined) | |
| 317 | + }, | |
| 318 | + bodyFont: { | |
| 319 | + size: nSettings.tooltipFontSize || 12, | |
| 320 | + style: nSettings.tooltipItalicText ? 'italic' : 'normal', | |
| 321 | + weight: nSettings.tooltipBoldText === 'true' ? 'bold' : (nSettings.tooltipBoldText === 'false' ? 'normal' : undefined) | |
| 322 | + }, | |
| 323 | + footerFont: { | |
| 324 | + size: nSettings.tooltipFontSize || 12, | |
| 325 | + style: nSettings.tooltipItalicText ? 'italic' : 'normal', | |
| 326 | + weight: nSettings.tooltipBoldText === 'true' ? 'bold' : (nSettings.tooltipBoldText === 'false' ? 'normal' : undefined) | |
| 327 | + }, | |
| 328 | + position: 'nearest', | |
| 329 | + events: ['click'], | |
| 330 | + callbacks: _this.getTooltipCallbacks(nSettings.tooltipText, nSettings.showColorCode) | |
| 227 | 331 | }, |
| 228 | 332 | legend: { |
| 229 | 333 | position: nSettings.legendPosition, |
| 230 | 334 | align: nSettings.legendAlignment, |
| @@ -449,13 +553,17 @@ | ||
| 449 | 553 | newSettings.outerRadius = settings['outer_radius']; |
| 450 | 554 | newSettings.sliceSpacing = settings['slice_spacing']; |
| 451 | 555 | newSettings.circumference = settings['circumference']; |
| 452 | 556 | newSettings.startAngle = settings['start_angle']; |
| 557 | + newSettings.rotationDegree = settings['rotation_degree']; | |
| 453 | 558 | newSettings.sliceColor = settings['slice_color']; |
| 454 | 559 | newSettings.slicesBorderColor = settings['slices_border_color']; |
| 455 | 560 | newSettings.sliceColorDefault = settings['slice_colors_default']; |
| 456 | 561 | newSettings.sliceBorderWidth = settings['slice_border_width']; |
| 457 | 562 | newSettings.sliceBorderColor = settings['slice_border_color']; |
| 563 | + newSettings.dataGroupingLimit = parseFloat(settings['data_grouping_limit']) / 100; | |
| 564 | + newSettings.dataGroupingLabel = settings['data_grouping_label']; | |
| 565 | + newSettings.dataGroupingColor = settings['data_grouping_color']; | |
| 458 | 566 | newSettings.legendPosition = settings['legend_position']; |
| 459 | 567 | newSettings.legendAlignment = settings['legend_alignment']; |
| 460 | 568 | newSettings.legendColor = settings['legend_color']; |
| 461 | 569 | newSettings.legendFontSize = settings['legend_font_size']; |
| @@ -466,12 +574,47 @@ | ||
| 466 | 574 | newSettings.tooltipItalicText = (settings['tooltip_italic'] == 'italic') ? true : false; |
| 467 | 575 | newSettings.tooltipFontSize = settings['tooltip_font_size']; |
| 468 | 576 | newSettings.tooltipBoldText = settings['tooltip_bold']; |
| 469 | 577 | newSettings.showColorCode = (settings['show_color_code'] == 'checked') ? true : false; |
| 578 | + newSettings.tooltipText = settings['tooltip_text']; | |
| 470 | 579 | newSettings.enableInteractivity = (settings['enable_interactivity'] == 'off') ? false : true; |
| 471 | 580 | return newSettings; |
| 472 | 581 | } |
| 473 | 582 | |
| 583 | + ChartBuilderChartJs.prototype.applyPieDataGrouping = function (dataTypes, settings) { | |
| 584 | + var groupingLimit = parseFloat(settings.dataGroupingLimit); | |
| 585 | + var dataSet = dataTypes && dataTypes.dataSets && dataTypes.dataSets[0]; | |
| 586 | + | |
| 587 | + if (!(groupingLimit > 0) || !dataSet || !Array.isArray(dataSet.data)) { | |
| 588 | + return; | |
| 589 | + } | |
| 590 | + | |
| 591 | + var total = dataSet.data.reduce(function (sum, value) { | |
| 592 | + return sum + (Number(value) || 0); | |
| 593 | + }, 0); | |
| 594 | + var groupedValue = 0; | |
| 595 | + var groupedSlices = 0; | |
| 596 | + var labels = []; | |
| 597 | + var values = []; | |
| 598 | + | |
| 599 | + dataSet.data.forEach(function (value, index) { | |
| 600 | + var numericValue = Number(value) || 0; | |
| 601 | + if (total > 0 && numericValue / total < groupingLimit) { | |
| 602 | + groupedValue += numericValue; | |
| 603 | + groupedSlices++; | |
| 604 | + } else { | |
| 605 | + labels.push(dataTypes.labels[index]); | |
| 606 | + values.push(numericValue); | |
| 607 | + } | |
| 608 | + }); | |
| 609 | + | |
| 610 | + if (groupedSlices > 0) { | |
| 611 | + dataTypes.labels = labels.concat([settings.dataGroupingLabel]); | |
| 612 | + dataSet.data = values.concat([groupedValue]); | |
| 613 | + dataSet.groupedSliceIndex = values.length; | |
| 614 | + } | |
| 615 | + } | |
| 616 | + | |
| 474 | 617 | // Detect window resize moment to draw charts responsively |
| 475 | 618 | ChartBuilderChartJs.prototype.resizeChart = function(){ |
| 476 | 619 | var _this = this; |
| 477 | 620 | |
| @@ -512,8 +655,54 @@ | ||
| 512 | 655 | data: item.slice(1).map(Number), |
| 513 | 656 | })); |
| 514 | 657 | |
| 515 | 658 | return {labels, dataSets}; |
| 659 | + } | |
| 660 | + | |
| 661 | + ChartBuilderChartJs.prototype.getTooltipCallbacks = function(tooltipText, showColorCode) { | |
| 662 | + var _this = this; | |
| 663 | + if (showColorCode === true) { | |
| 664 | + return { | |
| 665 | + label: function(context) { | |
| 666 | + var label = context.label || ''; | |
| 667 | + var value = context.parsed || 0; | |
| 668 | + var color = context.dataset.backgroundColor[context.dataIndex]; | |
| 669 | + return ' ' + label + ': ' + value + ' (Color: ' + color + ')'; | |
| 670 | + } | |
| 671 | + }; | |
| 672 | + } else { | |
| 673 | + switch(tooltipText) { | |
| 674 | + case 'percentage': | |
| 675 | + return { | |
| 676 | + label: function(context) { | |
| 677 | + var label = context.label || ''; | |
| 678 | + var value = context.parsed || 0; | |
| 679 | + var total = context.chart._metasets[context.datasetIndex].total; | |
| 680 | + var percentage = ((value / total) * 100).toFixed(1) + '%'; | |
| 681 | + return ' ' + label + ': ' + percentage; | |
| 682 | + } | |
| 683 | + }; | |
| 684 | + case 'both': | |
| 685 | + return { | |
| 686 | + label: function(context) { | |
| 687 | + var label = context.label || ''; | |
| 688 | + var value = context.parsed || 0; | |
| 689 | + var total = context.chart._metasets[context.datasetIndex].total; | |
| 690 | + var percentage = ((value / total) * 100).toFixed(1) + '%'; | |
| 691 | + return ' ' + label + ': ' + value + ' (' + percentage + ')'; | |
| 692 | + } | |
| 693 | + }; | |
| 694 | + case 'value': | |
| 695 | + default: | |
| 696 | + return { | |
| 697 | + label: function(context) { | |
| 698 | + var label = context.label || ''; | |
| 699 | + var value = context.parsed || 0; | |
| 700 | + return ' ' + label + ': ' + value; | |
| 701 | + } | |
| 702 | + }; | |
| 703 | + } | |
| 704 | + } | |
| 516 | 705 | } |
| 517 | 706 | |
| 518 | 707 | // Update chart data and display immediately |
| 519 | 708 | ChartBuilderChartJs.prototype.updateChartData = function( newData ){ |