apexcharts
Version:
A JavaScript Chart Library
513 lines (439 loc) • 13.1 kB
JavaScript
import Graphics from './Graphics'
import Utils from '../utils/Utils'
/**
* ApexCharts Fill Class for setting fill options of the paths.
*
* @module Fill
**/
class Fill {
constructor(ctx) {
this.ctx = ctx
this.w = ctx.w
this.opts = null
this.seriesIndex = 0
this.patternIDs = []
}
clippedImgArea(params) {
let w = this.w
let cnf = w.config
let svgW = parseInt(w.globals.gridWidth, 10)
let svgH = parseInt(w.globals.gridHeight, 10)
let size = svgW > svgH ? svgW : svgH
let fillImg = params.image
let imgWidth = 0
let imgHeight = 0
if (
typeof params.width === 'undefined' &&
typeof params.height === 'undefined'
) {
if (
cnf.fill.image.width !== undefined &&
cnf.fill.image.height !== undefined
) {
imgWidth = cnf.fill.image.width + 1
imgHeight = cnf.fill.image.height
} else {
imgWidth = size + 1
imgHeight = size
}
} else {
imgWidth = params.width
imgHeight = params.height
}
let elPattern = document.createElementNS(w.globals.SVGNS, 'pattern')
Graphics.setAttrs(elPattern, {
id: params.patternID,
patternUnits: params.patternUnits
? params.patternUnits
: 'userSpaceOnUse',
width: imgWidth + 'px',
height: imgHeight + 'px',
})
let elImage = document.createElementNS(w.globals.SVGNS, 'image')
elPattern.appendChild(elImage)
elImage.setAttributeNS(window.SVG.xlink, 'href', fillImg)
Graphics.setAttrs(elImage, {
x: 0,
y: 0,
preserveAspectRatio: 'none',
width: imgWidth + 'px',
height: imgHeight + 'px',
})
elImage.style.opacity = params.opacity
w.globals.dom.elDefs.node.appendChild(elPattern)
}
getSeriesIndex(opts) {
const w = this.w
const cType = w.config.chart.type
if (
((cType === 'bar' || cType === 'rangeBar') &&
w.config.plotOptions.bar.distributed) ||
cType === 'heatmap' ||
cType === 'treemap'
) {
this.seriesIndex = opts.seriesNumber
} else {
this.seriesIndex = opts.seriesNumber % w.globals.series.length
}
return this.seriesIndex
}
computeColorStops(data, multiColorConfig) {
const w = this.w
let maxPositive = null
let minNegative = null
for (let value of data) {
if (value >= multiColorConfig.threshold) {
if (maxPositive === null || value > maxPositive) {
maxPositive = value
}
} else {
if (minNegative === null || value < minNegative) {
minNegative = value
}
}
}
if (maxPositive === null) {
maxPositive = multiColorConfig.threshold
}
if (minNegative === null) {
minNegative = multiColorConfig.threshold
}
let totalRange =
maxPositive -
multiColorConfig.threshold +
(multiColorConfig.threshold - minNegative)
if (totalRange === 0) {
totalRange = 1
}
let negativePercentage =
((multiColorConfig.threshold - minNegative) / totalRange) * 100
let offset = 100 - negativePercentage
offset = Math.max(0, Math.min(offset, 100))
return [
{
offset: offset,
color: multiColorConfig.colorAboveThreshold,
opacity: w.config.fill.opacity,
},
{
offset: 0,
color: multiColorConfig.colorBelowThreshold,
opacity: w.config.fill.opacity,
},
]
}
fillPath(opts) {
let w = this.w
this.opts = opts
let cnf = this.w.config
let pathFill
let patternFill, gradientFill
this.seriesIndex = this.getSeriesIndex(opts)
const drawMultiColorLine =
cnf.plotOptions.line.colors.colorAboveThreshold &&
cnf.plotOptions.line.colors.colorBelowThreshold
let fillColors = this.getFillColors()
let fillColor = fillColors[this.seriesIndex]
//override fillcolor if user inputted color with data
if (w.globals.seriesColors[this.seriesIndex] !== undefined) {
fillColor = w.globals.seriesColors[this.seriesIndex]
}
if (typeof fillColor === 'function') {
fillColor = fillColor({
seriesIndex: this.seriesIndex,
dataPointIndex: opts.dataPointIndex,
value: opts.value,
w,
})
}
let fillType = opts.fillType
? opts.fillType
: this.getFillType(this.seriesIndex)
let fillOpacity = Array.isArray(cnf.fill.opacity)
? cnf.fill.opacity[this.seriesIndex]
: cnf.fill.opacity
// when line colors needs to be different based on values, we use gradient config to achieve this
const useGradient = fillType === 'gradient' || drawMultiColorLine
if (opts.color) {
fillColor = opts.color
}
if (
w.config.series[this.seriesIndex]?.data?.[opts.dataPointIndex]?.fillColor
) {
fillColor =
w.config.series[this.seriesIndex]?.data?.[opts.dataPointIndex]
?.fillColor
}
// in case a color is undefined, fallback to white color to prevent runtime error
if (!fillColor) {
fillColor = '#fff'
console.warn('undefined color - ApexCharts')
}
let defaultColor = fillColor
if (fillColor.indexOf('rgb') === -1) {
if (fillColor.indexOf('#') === -1) {
defaultColor = fillColor
} else if (fillColor.length < 9) {
// if the hex contains alpha and is of 9 digit, skip the opacity
defaultColor = Utils.hexToRgba(fillColor, fillOpacity)
}
} else {
if (fillColor.indexOf('rgba') > -1) {
fillOpacity = Utils.getOpacityFromRGBA(fillColor)
} else {
defaultColor = Utils.hexToRgba(Utils.rgb2hex(fillColor), fillOpacity)
}
}
if (opts.opacity) fillOpacity = opts.opacity
if (fillType === 'pattern') {
patternFill = this.handlePatternFill({
fillConfig: opts.fillConfig,
patternFill,
fillColor,
fillOpacity,
defaultColor,
})
}
if (useGradient) {
let colorStops = [...cnf.fill.gradient.colorStops] || []
let type = cnf.fill.gradient.type
if (drawMultiColorLine) {
colorStops[this.seriesIndex] = this.computeColorStops(
w.globals.series[this.seriesIndex],
cnf.plotOptions.line.colors
)
type = 'vertical'
}
gradientFill = this.handleGradientFill({
type,
fillConfig: opts.fillConfig,
fillColor,
fillOpacity,
colorStops,
i: this.seriesIndex,
})
}
if (fillType === 'image') {
let imgSrc = cnf.fill.image.src
let patternID = opts.patternID ? opts.patternID : ''
const patternKey = `pattern${w.globals.cuid}${
opts.seriesNumber + 1
}${patternID}`
if (this.patternIDs.indexOf(patternKey) === -1) {
this.clippedImgArea({
opacity: fillOpacity,
image: Array.isArray(imgSrc)
? opts.seriesNumber < imgSrc.length
? imgSrc[opts.seriesNumber]
: imgSrc[0]
: imgSrc,
width: opts.width ? opts.width : undefined,
height: opts.height ? opts.height : undefined,
patternUnits: opts.patternUnits,
patternID: patternKey,
})
this.patternIDs.push(patternKey)
}
pathFill = `url(#${patternKey})`
} else if (useGradient) {
pathFill = gradientFill
} else if (fillType === 'pattern') {
pathFill = patternFill
} else {
pathFill = defaultColor
}
// override pattern/gradient if opts.solid is true
if (opts.solid) {
pathFill = defaultColor
}
return pathFill
}
getFillType(seriesIndex) {
const w = this.w
if (Array.isArray(w.config.fill.type)) {
return w.config.fill.type[seriesIndex]
} else {
return w.config.fill.type
}
}
getFillColors() {
const w = this.w
const cnf = w.config
const opts = this.opts
let fillColors = []
if (w.globals.comboCharts) {
if (w.config.series[this.seriesIndex].type === 'line') {
if (Array.isArray(w.globals.stroke.colors)) {
fillColors = w.globals.stroke.colors
} else {
fillColors.push(w.globals.stroke.colors)
}
} else {
if (Array.isArray(w.globals.fill.colors)) {
fillColors = w.globals.fill.colors
} else {
fillColors.push(w.globals.fill.colors)
}
}
} else {
if (cnf.chart.type === 'line') {
if (Array.isArray(w.globals.stroke.colors)) {
fillColors = w.globals.stroke.colors
} else {
fillColors.push(w.globals.stroke.colors)
}
} else {
if (Array.isArray(w.globals.fill.colors)) {
fillColors = w.globals.fill.colors
} else {
fillColors.push(w.globals.fill.colors)
}
}
}
// colors passed in arguments
if (typeof opts.fillColors !== 'undefined') {
fillColors = []
if (Array.isArray(opts.fillColors)) {
fillColors = opts.fillColors.slice()
} else {
fillColors.push(opts.fillColors)
}
}
return fillColors
}
handlePatternFill({
fillConfig,
patternFill,
fillColor,
fillOpacity,
defaultColor,
}) {
let fillCnf = this.w.config.fill
if (fillConfig) {
fillCnf = fillConfig
}
const opts = this.opts
let graphics = new Graphics(this.ctx)
let patternStrokeWidth = Array.isArray(fillCnf.pattern.strokeWidth)
? fillCnf.pattern.strokeWidth[this.seriesIndex]
: fillCnf.pattern.strokeWidth
let patternLineColor = fillColor
if (Array.isArray(fillCnf.pattern.style)) {
if (typeof fillCnf.pattern.style[opts.seriesNumber] !== 'undefined') {
let pf = graphics.drawPattern(
fillCnf.pattern.style[opts.seriesNumber],
fillCnf.pattern.width,
fillCnf.pattern.height,
patternLineColor,
patternStrokeWidth,
fillOpacity
)
patternFill = pf
} else {
patternFill = defaultColor
}
} else {
patternFill = graphics.drawPattern(
fillCnf.pattern.style,
fillCnf.pattern.width,
fillCnf.pattern.height,
patternLineColor,
patternStrokeWidth,
fillOpacity
)
}
return patternFill
}
handleGradientFill({
type,
fillColor,
fillOpacity,
fillConfig,
colorStops,
i,
}) {
let fillCnf = this.w.config.fill
if (fillConfig) {
fillCnf = {
...fillCnf,
...fillConfig,
}
}
const opts = this.opts
let graphics = new Graphics(this.ctx)
let utils = new Utils()
type = type || fillCnf.gradient.type
let gradientFrom = fillColor
let gradientTo
let opacityFrom =
fillCnf.gradient.opacityFrom === undefined
? fillOpacity
: Array.isArray(fillCnf.gradient.opacityFrom)
? fillCnf.gradient.opacityFrom[i]
: fillCnf.gradient.opacityFrom
if (gradientFrom.indexOf('rgba') > -1) {
opacityFrom = Utils.getOpacityFromRGBA(gradientFrom)
}
let opacityTo =
fillCnf.gradient.opacityTo === undefined
? fillOpacity
: Array.isArray(fillCnf.gradient.opacityTo)
? fillCnf.gradient.opacityTo[i]
: fillCnf.gradient.opacityTo
if (
fillCnf.gradient.gradientToColors === undefined ||
fillCnf.gradient.gradientToColors.length === 0
) {
if (fillCnf.gradient.shade === 'dark') {
gradientTo = utils.shadeColor(
parseFloat(fillCnf.gradient.shadeIntensity) * -1,
fillColor.indexOf('rgb') > -1 ? Utils.rgb2hex(fillColor) : fillColor
)
} else {
gradientTo = utils.shadeColor(
parseFloat(fillCnf.gradient.shadeIntensity),
fillColor.indexOf('rgb') > -1 ? Utils.rgb2hex(fillColor) : fillColor
)
}
} else {
if (fillCnf.gradient.gradientToColors[opts.seriesNumber]) {
const gToColor = fillCnf.gradient.gradientToColors[opts.seriesNumber]
gradientTo = gToColor
if (gToColor.indexOf('rgba') > -1) {
opacityTo = Utils.getOpacityFromRGBA(gToColor)
}
} else {
gradientTo = fillColor
}
}
if (fillCnf.gradient.gradientFrom) {
gradientFrom = fillCnf.gradient.gradientFrom
}
if (fillCnf.gradient.gradientTo) {
gradientTo = fillCnf.gradient.gradientTo
}
if (fillCnf.gradient.inverseColors) {
let t = gradientFrom
gradientFrom = gradientTo
gradientTo = t
}
if (gradientFrom.indexOf('rgb') > -1) {
gradientFrom = Utils.rgb2hex(gradientFrom)
}
if (gradientTo.indexOf('rgb') > -1) {
gradientTo = Utils.rgb2hex(gradientTo)
}
return graphics.drawGradient(
type,
gradientFrom,
gradientTo,
opacityFrom,
opacityTo,
opts.size,
fillCnf.gradient.stops,
colorStops,
i
)
}
}
export default Fill