UNPKG

@29aries/apexcharts

Version:

Interactive JavaScript Charts built on SVG - forked by 29aries to allow custom labels - forked from 3.20.0

407 lines (342 loc) 10.7 kB
import DataLabels from '../modules/DataLabels' import Animations from '../modules/Animations' import Graphics from '../modules/Graphics' import Fill from '../modules/Fill' import Utils from '../utils/Utils' import Filters from '../modules/Filters' /** * ApexCharts HeatMap Class. * @module HeatMap **/ export default class HeatMap { constructor(ctx, xyRatios) { this.ctx = ctx this.w = ctx.w this.xRatio = xyRatios.xRatio this.yRatio = xyRatios.yRatio this.negRange = false this.dynamicAnim = this.w.config.chart.animations.dynamicAnimation this.rectRadius = this.w.config.plotOptions.heatmap.radius this.strokeWidth = this.w.config.stroke.show ? this.w.config.stroke.width : 0 } draw(series) { let w = this.w const graphics = new Graphics(this.ctx) let ret = graphics.group({ class: 'apexcharts-heatmap' }) ret.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`) // width divided into equal parts let xDivision = w.globals.gridWidth / w.globals.dataPoints let yDivision = w.globals.gridHeight / w.globals.series.length let y1 = 0 let rev = false this.checkColorRange() let heatSeries = series.slice() if (w.config.yaxis[0].reversed) { rev = true heatSeries.reverse() } for ( let i = rev ? 0 : heatSeries.length - 1; rev ? i < heatSeries.length : i >= 0; rev ? i++ : i-- ) { // el to which series will be drawn let elSeries = graphics.group({ class: `apexcharts-series apexcharts-heatmap-series`, seriesName: Utils.escapeString(w.globals.seriesNames[i]), rel: i + 1, 'data:realIndex': i }) this.ctx.series.addCollapsedClassToSeries(elSeries, i) if (w.config.chart.dropShadow.enabled) { const shadow = w.config.chart.dropShadow const filters = new Filters(this.ctx) filters.dropShadow(elSeries, shadow, i) } let x1 = 0 for (let j = 0; j < heatSeries[i].length; j++) { let colorShadePercent = 1 let shadeIntensity = w.config.plotOptions.heatmap.shadeIntensity const heatColorProps = this.determineHeatColor(i, j) if (w.globals.hasNegs || this.negRange) { if (w.config.plotOptions.heatmap.reverseNegativeShade) { if (heatColorProps.percent < 0) { colorShadePercent = (heatColorProps.percent / 100) * (shadeIntensity * 1.25) } else { colorShadePercent = (1 - heatColorProps.percent / 100) * (shadeIntensity * 1.25) } } else { if (heatColorProps.percent <= 0) { colorShadePercent = 1 - (1 + heatColorProps.percent / 100) * shadeIntensity } else { colorShadePercent = (1 - heatColorProps.percent / 100) * shadeIntensity } } } else { colorShadePercent = 1 - heatColorProps.percent / 100 } let color = heatColorProps.color let utils = new Utils() if (w.config.plotOptions.heatmap.enableShades) { if (colorShadePercent < 0) colorShadePercent = 0 if (this.w.config.theme.mode === 'dark') { color = Utils.hexToRgba( utils.shadeColor(colorShadePercent * -1, heatColorProps.color), w.config.fill.opacity ) } else { color = Utils.hexToRgba( utils.shadeColor(colorShadePercent, heatColorProps.color), w.config.fill.opacity ) } } if (w.config.fill.type === 'image') { const fill = new Fill(this.ctx) color = fill.fillPath({ seriesNumber: i, dataPointIndex: j, opacity: w.globals.hasNegs ? heatColorProps.percent < 0 ? 1 - (1 + heatColorProps.percent / 100) : shadeIntensity + heatColorProps.percent / 100 : heatColorProps.percent / 100, patternID: Utils.randomId(), width: w.config.fill.image.width ? w.config.fill.image.width : xDivision, height: w.config.fill.image.height ? w.config.fill.image.height : yDivision }) } let radius = this.rectRadius let rect = graphics.drawRect(x1, y1, xDivision, yDivision, radius) rect.attr({ cx: x1, cy: y1 }) rect.node.classList.add('apexcharts-heatmap-rect') elSeries.add(rect) rect.attr({ fill: color, i, index: i, j, val: heatSeries[i][j], 'stroke-width': this.strokeWidth, stroke: w.config.plotOptions.heatmap.useFillColorAsStroke ? color : w.globals.stroke.colors[0], color }) rect.node.addEventListener( 'mouseenter', graphics.pathMouseEnter.bind(this, rect) ) rect.node.addEventListener( 'mouseleave', graphics.pathMouseLeave.bind(this, rect) ) rect.node.addEventListener( 'mousedown', graphics.pathMouseDown.bind(this, rect) ) if (w.config.chart.animations.enabled && !w.globals.dataChanged) { let speed = 1 if (!w.globals.resized) { speed = w.config.chart.animations.speed } this.animateHeatMap(rect, x1, y1, xDivision, yDivision, speed) } if (w.globals.dataChanged) { let speed = 1 if (this.dynamicAnim.enabled && w.globals.shouldAnimate) { speed = this.dynamicAnim.speed let colorFrom = w.globals.previousPaths[i] && w.globals.previousPaths[i][j] && w.globals.previousPaths[i][j].color if (!colorFrom) colorFrom = 'rgba(255, 255, 255, 0)' this.animateHeatColor( rect, Utils.isColorHex(colorFrom) ? colorFrom : Utils.rgb2hex(colorFrom), Utils.isColorHex(color) ? color : Utils.rgb2hex(color), speed ) } } let dataLabels = this.calculateHeatmapDataLabels({ x: x1, y: y1, i, j, heatColorProps, series: heatSeries, rectHeight: yDivision, rectWidth: xDivision }) if (dataLabels !== null) { elSeries.add(dataLabels) } x1 = x1 + xDivision } y1 = y1 + yDivision ret.add(elSeries) } // adjust yaxis labels for heatmap let yAxisScale = w.globals.yAxisScale[0].result.slice() if (w.config.yaxis[0].reversed) { yAxisScale.unshift('') } else { yAxisScale.push('') } w.globals.yAxisScale[0].result = yAxisScale let divisor = w.globals.gridHeight / w.globals.series.length w.config.yaxis[0].labels.offsetY = -(divisor / 2) return ret } checkColorRange() { const w = this.w let heatmap = w.config.plotOptions.heatmap if (heatmap.colorScale.ranges.length > 0) { heatmap.colorScale.ranges.map((range, index) => { if (range.from <= 0) { this.negRange = true } }) } } determineHeatColor(i, j) { const w = this.w let val = w.globals.series[i][j] let heatmap = w.config.plotOptions.heatmap let seriesNumber = heatmap.colorScale.inverse ? j : i let color = w.globals.colors[seriesNumber] let foreColor = null let min = Math.min(...w.globals.series[i]) let max = Math.max(...w.globals.series[i]) if (!heatmap.distributed) { min = w.globals.minY max = w.globals.maxY } if (typeof heatmap.colorScale.min !== 'undefined') { min = heatmap.colorScale.min < w.globals.minY ? heatmap.colorScale.min : w.globals.minY max = heatmap.colorScale.max > w.globals.maxY ? heatmap.colorScale.max : w.globals.maxY } let total = Math.abs(max) + Math.abs(min) let percent = (100 * val) / (total === 0 ? total - 0.000001 : total) if (heatmap.colorScale.ranges.length > 0) { const colorRange = heatmap.colorScale.ranges colorRange.map((range, index) => { if (val >= range.from && val <= range.to) { color = range.color foreColor = range.foreColor ? range.foreColor : null min = range.from max = range.to let rTotal = Math.abs(max) + Math.abs(min) percent = (100 * val) / (rTotal === 0 ? rTotal - 0.000001 : rTotal) } }) } return { color, foreColor, percent } } calculateHeatmapDataLabels({ x, y, i, j, heatColorProps, series, rectHeight, rectWidth }) { let w = this.w // let graphics = new Graphics(this.ctx) let dataLabelsConfig = w.config.dataLabels const graphics = new Graphics(this.ctx) let dataLabels = new DataLabels(this.ctx) let formatter = dataLabelsConfig.formatter let elDataLabelsWrap = null if (dataLabelsConfig.enabled) { elDataLabelsWrap = graphics.group({ class: 'apexcharts-data-labels' }) const offX = dataLabelsConfig.offsetX const offY = dataLabelsConfig.offsetY let dataLabelsX = x + rectWidth / 2 + offX let dataLabelsY = y + rectHeight / 2 + parseFloat(dataLabelsConfig.style.fontSize) / 3 + offY let text = formatter(w.globals.series[i][j], { seriesIndex: i, dataPointIndex: j, w }) dataLabels.plotDataLabelsText({ x: dataLabelsX, y: dataLabelsY, text, i, j, color: heatColorProps.foreColor, parent: elDataLabelsWrap, dataLabelsConfig }) } return elDataLabelsWrap } animateHeatMap(el, x, y, width, height, speed) { const animations = new Animations(this.ctx) animations.animateRect( el, { x: x + width / 2, y: y + height / 2, width: 0, height: 0 }, { x, y, width, height }, speed, () => { animations.animationCompleted(el) } ) } animateHeatColor(el, colorFrom, colorTo, speed) { el.attr({ fill: colorFrom }) .animate(speed) .attr({ fill: colorTo }) } }