apexcharts
Version:
A JavaScript Chart Library
267 lines (212 loc) • 7.36 kB
JavaScript
import DataLabels from '../modules/DataLabels'
import Animations from '../modules/Animations'
import Graphics from '../modules/Graphics'
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.dynamicAnim = this.w.config.chart.animations.dynamicAnimation
this.rectRadius = this.w.config.plotOptions.heatmap.radius
this.strokeWidth = this.w.config.stroke.width
}
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
for (let i = series.length - 1; i >= 0; i--) {
// el to which series will be drawn
let elSeries = graphics.group({
class: `apexcharts-series apexcharts-heatmap-series ${w.globals.seriesNames[i].toString().replace(/ /g, '-')}`,
'rel': i + 1,
'data:realIndex': i
})
if (w.config.chart.dropShadow.enabled) {
const shadow = w.config.chart.dropShadow
const filters = new Filters(this.ctx)
filters.dropShadow(elSeries, shadow)
}
let x1 = 0
for (let j = 0; j < series[i].length; j++) {
let colorShadePercent = 1
const heatColorProps = this.determineHeatColor(i, j)
if (w.globals.hasNegs) {
let shadeIntensity = w.config.plotOptions.heatmap.shadeIntensity
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
if (w.config.plotOptions.heatmap.enableShades) {
let utils = new Utils()
color = Utils.hexToRgba(
utils.shadeColor(colorShadePercent, heatColorProps.color),
w.config.fill.opacity
)
}
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: series[i][j],
'stroke-width': this.strokeWidth,
stroke: w.globals.stroke.colors[0],
color: 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, 1)'
this.animateHeatColor(rect, Utils.rgb2hex(colorFrom), Utils.rgb2hex(color), speed)
}
}
let dataLabels = this.calculateHeatmapDataLabels({
x: x1,
y: y1,
i,
j,
series,
rectHeight: yDivision,
rectWidth: xDivision
})
if (dataLabels !== null) {
elSeries.add(dataLabels)
}
x1 = x1 + xDivision
}
y1 = y1 + yDivision
ret.add(elSeries)
}
// adjust yaxis labels for heatmap
w.globals.yAxisScale[0].result.push('')
let divisor = w.globals.gridHeight / w.globals.series.length
w.config.yaxis[0].labels.offsetY = -(divisor / 2)
return ret
}
determineHeatColor (i, j) {
const w = this.w
const 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 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
min = range.from
max = range.to
total = Math.abs(max) + Math.abs(min)
percent = (100 * val) / total
}
})
}
return {
color,
percent
}
}
calculateHeatmapDataLabels ({
x,
y,
i,
j,
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 + parseInt(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,
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)
}
animateHeatColor (el, colorFrom, colorTo, speed) {
el.attr({ fill: colorFrom }).animate(speed).attr({ fill: colorTo })
}
}