apexcharts
Version:
A JavaScript Chart Library
236 lines (202 loc) • 7.42 kB
JavaScript
// @ts-check
import Utils from '../../../utils/Utils'
import Graphics from '../../../modules/Graphics'
import DataLabels from '../../../modules/DataLabels'
import { resolveDataLabelOffset } from '../../../modules/helpers/DataLabelOffset'
export default class TreemapHelpers {
/**
* @param {import('../../../types/internal').ChartStateW} w
* @param {import('../../../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.ctx = ctx
this.w = w
}
checkColorRange() {
const w = this.w
let negRange = false
const chartOpts = w.config.plotOptions[w.config.chart.type]
if (chartOpts.colorScale.ranges.length > 0) {
/**
* @param {number} range
*/
chartOpts.colorScale.ranges.map((/** @type {any} */ range) => {
if (range.from <= 0) {
negRange = true
}
})
}
return negRange
}
/**
* @param {string} chartType
* @param {number} i
* @param {number} j
* @param {any} negRange
*/
getShadeColor(chartType, i, j, negRange) {
const w = this.w
let colorShadePercent = 1
const shadeIntensity = w.config.plotOptions[chartType].shadeIntensity
const colorProps = this.determineColor(chartType, i, j)
if (/** @type {any} */ (w.globals).hasNegs || negRange) {
if (w.config.plotOptions[chartType].reverseNegativeShade) {
if (colorProps.percent < 0) {
colorShadePercent =
(colorProps.percent / 100) * (shadeIntensity * 1.25)
} else {
colorShadePercent =
(1 - colorProps.percent / 100) * (shadeIntensity * 1.25)
}
} else {
if (colorProps.percent <= 0) {
colorShadePercent =
1 - (1 + colorProps.percent / 100) * shadeIntensity
} else {
colorShadePercent = (1 - colorProps.percent / 100) * shadeIntensity
}
}
} else {
colorShadePercent = 1 - colorProps.percent / 100
if (chartType === 'treemap') {
colorShadePercent =
(1 - colorProps.percent / 100) * (shadeIntensity * 1.25)
}
}
let color = colorProps.color
const utils = new Utils()
if (w.config.plotOptions[chartType].enableShades) {
// The shadeColor function may return either an RGB or a hex color value
// However, hexToRgba requires the input to be in hex format
// The ternary operator checks if the color is in RGB format, and if so, converts it to hex
if (this.w.config.theme.mode === 'dark') {
const shadeColor = utils.shadeColor(
colorShadePercent * -1,
colorProps.color,
)
color = Utils.hexToRgba(
Utils.isColorHex(shadeColor) ? shadeColor : Utils.rgb2hex(shadeColor),
w.config.fill.opacity,
)
} else {
const shadeColor = utils.shadeColor(colorShadePercent, colorProps.color)
color = Utils.hexToRgba(
Utils.isColorHex(shadeColor) ? shadeColor : Utils.rgb2hex(shadeColor),
w.config.fill.opacity,
)
}
}
return { color, colorProps }
}
/**
* @param {string} chartType
* @param {number} i
* @param {number} j
*/
determineColor(chartType, i, j) {
const w = this.w
const val = w.seriesData.series[i][j]
const chartOpts = w.config.plotOptions[chartType]
let seriesNumber = chartOpts.colorScale.inverse ? j : i
if (chartOpts.distributed && w.config.chart.type === 'treemap') {
seriesNumber = j
}
let color = w.globals.colors[seriesNumber]
let foreColor = null
let min
let max
if (!chartOpts.distributed && chartType === 'heatmap') {
// Non-distributed heatmap uses the global y-extent; the per-row min/max
// below would only be computed and immediately discarded.
min = w.globals.minY
max = w.globals.maxY
} else {
// Per-row extent (distributed heatmap / treemap). Use a loop, not
// Math.min(...row): a wide continuous-X heatmap row can exceed the call
// argument limit and throw "RangeError: Maximum call stack size exceeded".
const row = w.seriesData.series[i]
min = row.length ? row[0] : 0
max = min
for (let k = 1; k < row.length; k++) {
if (row[k] < min) min = row[k]
if (row[k] > max) max = row[k]
}
}
const csMin = chartOpts.colorScale.min
const csMax = chartOpts.colorScale.max
if (typeof csMin !== 'undefined' && typeof csMax !== 'undefined' && csMax > csMin) {
// BOTH ends set: an explicit, ABSOLUTE window. This is what lets
// several charts share one color scale (a trellis pushes the union
// extent): comparing against each chart's own niced globals would
// shift the mapping per chart by a nice-step. Values outside the
// window saturate at its ends.
min = csMin
max = csMax
} else if (typeof csMin !== 'undefined') {
// One-sided: the historical expand-not-clamp semantics.
min = csMin < w.globals.minY ? csMin : w.globals.minY
max = csMax > w.globals.maxY ? csMax : w.globals.maxY
}
const total = Math.abs(max) + Math.abs(min)
// Saturate into the scale window (only an explicit window can place a
// value outside min..max).
const clamped = Math.min(Math.max(val, min), max)
// When min === max (total === 0), every cell sits at the midpoint of the
// scale. Use a tiny positive epsilon so percent → 0 instead of exploding
// to a large negative number (the previous `total - 0.000001` fallback
// produced a negative divisor).
let percent = total === 0 ? 0 : (100 * clamped) / total
if (chartOpts.colorScale.ranges.length > 0) {
const colorRange = chartOpts.colorScale.ranges
/**
* @param {number} range
*/
colorRange.map((/** @type {any} */ range) => {
if (val >= range.from && val <= range.to) {
color = range.color
foreColor = range.foreColor ? range.foreColor : null
min = range.from
max = range.to
const rTotal = Math.abs(max) + Math.abs(min)
percent = rTotal === 0 ? 0 : (100 * val) / rTotal
}
})
}
return {
color,
foreColor,
percent,
}
}
/** @param {{ text?: any, x?: any, y?: any, i?: any, j?: any, colorProps?: any, fontSize?: any, series?: any }} opts */
calculateDataLabels({ text, x, y, i, j, colorProps, fontSize }) {
const w = this.w
const dataLabelsConfig = w.config.dataLabels
const graphics = new Graphics(this.w)
const dataLabels = new DataLabels(this.w, this.ctx)
let elDataLabelsWrap = null
if (dataLabelsConfig.enabled) {
elDataLabelsWrap = graphics.group({
class: 'apexcharts-data-labels',
})
// offsets may be a function evaluated per data point
const offX = resolveDataLabelOffset(dataLabelsConfig.offsetX, w, i, j)
const offY = resolveDataLabelOffset(dataLabelsConfig.offsetY, w, i, j)
const dataLabelsX = x + offX
const dataLabelsY =
y + parseFloat(dataLabelsConfig.style.fontSize) / 3 + offY
dataLabels.plotDataLabelsText({
x: dataLabelsX,
y: dataLabelsY,
text,
i,
j,
color: colorProps.foreColor,
parent: elDataLabelsWrap,
fontSize,
dataLabelsConfig,
})
}
return elDataLabelsWrap
}
}