@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
JavaScript
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
})
}
}