470 lines
12 KiB
JavaScript
470 lines
12 KiB
JavaScript
import Pie from './Pie'
|
|
import Utils from '../utils/Utils'
|
|
import Fill from '../modules/Fill'
|
|
import Graphics from '../modules/Graphics'
|
|
import Filters from '../modules/Filters'
|
|
|
|
/**
|
|
* ApexCharts Radial Class for drawing Circle / Semi Circle Charts.
|
|
* @module Radial
|
|
**/
|
|
|
|
class Radial extends Pie {
|
|
constructor(ctx) {
|
|
super(ctx)
|
|
|
|
this.ctx = ctx
|
|
this.w = ctx.w
|
|
this.animBeginArr = [0]
|
|
this.animDur = 0
|
|
|
|
const w = this.w
|
|
this.startAngle = w.config.plotOptions.radialBar.startAngle
|
|
this.endAngle = w.config.plotOptions.radialBar.endAngle
|
|
|
|
this.totalAngle = Math.abs(
|
|
w.config.plotOptions.radialBar.endAngle -
|
|
w.config.plotOptions.radialBar.startAngle
|
|
)
|
|
|
|
this.trackStartAngle = w.config.plotOptions.radialBar.track.startAngle
|
|
this.trackEndAngle = w.config.plotOptions.radialBar.track.endAngle
|
|
|
|
this.donutDataLabels = this.w.config.plotOptions.radialBar.dataLabels
|
|
this.radialDataLabels = this.donutDataLabels // make a copy for easy reference
|
|
|
|
if (!this.trackStartAngle) this.trackStartAngle = this.startAngle
|
|
if (!this.trackEndAngle) this.trackEndAngle = this.endAngle
|
|
|
|
if (this.endAngle === 360) this.endAngle = 359.99
|
|
|
|
this.margin = parseInt(w.config.plotOptions.radialBar.track.margin, 10)
|
|
}
|
|
|
|
draw(series) {
|
|
let w = this.w
|
|
const graphics = new Graphics(this.ctx)
|
|
|
|
let ret = graphics.group({
|
|
class: 'apexcharts-radialbar'
|
|
})
|
|
|
|
if (w.globals.noData) return ret
|
|
|
|
let elSeries = graphics.group()
|
|
|
|
let centerY = this.defaultSize / 2
|
|
let centerX = w.globals.gridWidth / 2
|
|
|
|
let size = this.defaultSize / 2.05
|
|
if (!w.config.chart.sparkline.enabled) {
|
|
size = size - w.config.stroke.width - w.config.chart.dropShadow.blur
|
|
}
|
|
let colorArr = w.globals.fill.colors
|
|
|
|
if (w.config.plotOptions.radialBar.track.show) {
|
|
let elTracks = this.drawTracks({
|
|
size,
|
|
centerX,
|
|
centerY,
|
|
colorArr,
|
|
series
|
|
})
|
|
elSeries.add(elTracks)
|
|
}
|
|
|
|
let elG = this.drawArcs({
|
|
size,
|
|
centerX,
|
|
centerY,
|
|
colorArr,
|
|
series
|
|
})
|
|
|
|
let totalAngle = 360
|
|
|
|
if (w.config.plotOptions.radialBar.startAngle < 0) {
|
|
totalAngle = this.totalAngle
|
|
}
|
|
|
|
let angleRatio = (360 - totalAngle) / 360
|
|
w.globals.radialSize = size - size * angleRatio
|
|
|
|
if (this.radialDataLabels.value.show) {
|
|
let offset = Math.max(
|
|
this.radialDataLabels.value.offsetY,
|
|
this.radialDataLabels.name.offsetY
|
|
)
|
|
w.globals.radialSize += offset * angleRatio
|
|
}
|
|
|
|
elSeries.add(elG.g)
|
|
|
|
if (w.config.plotOptions.radialBar.hollow.position === 'front') {
|
|
elG.g.add(elG.elHollow)
|
|
if (elG.dataLabels) {
|
|
elG.g.add(elG.dataLabels)
|
|
}
|
|
}
|
|
|
|
ret.add(elSeries)
|
|
|
|
return ret
|
|
}
|
|
|
|
drawTracks(opts) {
|
|
let w = this.w
|
|
const graphics = new Graphics(this.ctx)
|
|
|
|
let g = graphics.group({
|
|
class: 'apexcharts-tracks'
|
|
})
|
|
|
|
let filters = new Filters(this.ctx)
|
|
let fill = new Fill(this.ctx)
|
|
|
|
let strokeWidth = this.getStrokeWidth(opts)
|
|
|
|
opts.size = opts.size - strokeWidth / 2
|
|
|
|
for (let i = 0; i < opts.series.length; i++) {
|
|
let elRadialBarTrack = graphics.group({
|
|
class: 'apexcharts-radialbar-track apexcharts-track'
|
|
})
|
|
g.add(elRadialBarTrack)
|
|
|
|
elRadialBarTrack.attr({
|
|
rel: i + 1
|
|
})
|
|
|
|
opts.size = opts.size - strokeWidth - this.margin
|
|
|
|
const trackConfig = w.config.plotOptions.radialBar.track
|
|
let pathFill = fill.fillPath({
|
|
seriesNumber: 0,
|
|
size: opts.size,
|
|
fillColors: Array.isArray(trackConfig.background)
|
|
? trackConfig.background[i]
|
|
: trackConfig.background,
|
|
solid: true
|
|
})
|
|
|
|
let startAngle = this.trackStartAngle
|
|
let endAngle = this.trackEndAngle
|
|
|
|
if (Math.abs(endAngle) + Math.abs(startAngle) >= 360)
|
|
endAngle = 360 - Math.abs(this.startAngle) - 0.1
|
|
|
|
let elPath = graphics.drawPath({
|
|
d: '',
|
|
stroke: pathFill,
|
|
strokeWidth:
|
|
(strokeWidth * parseInt(trackConfig.strokeWidth, 10)) / 100,
|
|
fill: 'none',
|
|
strokeOpacity: trackConfig.opacity,
|
|
classes: 'apexcharts-radialbar-area'
|
|
})
|
|
|
|
if (trackConfig.dropShadow.enabled) {
|
|
const shadow = trackConfig.dropShadow
|
|
filters.dropShadow(elPath, shadow)
|
|
}
|
|
|
|
elRadialBarTrack.add(elPath)
|
|
|
|
elPath.attr('id', 'apexcharts-radialbarTrack-' + i)
|
|
|
|
this.animatePaths(elPath, {
|
|
centerX: opts.centerX,
|
|
centerY: opts.centerY,
|
|
endAngle,
|
|
startAngle,
|
|
size: opts.size,
|
|
i,
|
|
totalItems: 2,
|
|
animBeginArr: 0,
|
|
dur: 0,
|
|
isTrack: true,
|
|
easing: w.globals.easing
|
|
})
|
|
}
|
|
|
|
return g
|
|
}
|
|
|
|
drawArcs(opts) {
|
|
let w = this.w
|
|
// size, donutSize, centerX, centerY, colorArr, lineColorArr, sectorAngleArr, series
|
|
|
|
let graphics = new Graphics(this.ctx)
|
|
let fill = new Fill(this.ctx)
|
|
let filters = new Filters(this.ctx)
|
|
let g = graphics.group()
|
|
|
|
let strokeWidth = this.getStrokeWidth(opts)
|
|
opts.size = opts.size - strokeWidth / 2
|
|
|
|
let hollowFillID = w.config.plotOptions.radialBar.hollow.background
|
|
let hollowSize =
|
|
opts.size -
|
|
strokeWidth * opts.series.length -
|
|
this.margin * opts.series.length -
|
|
(strokeWidth *
|
|
parseInt(w.config.plotOptions.radialBar.track.strokeWidth, 10)) /
|
|
100 /
|
|
2
|
|
|
|
let hollowRadius = hollowSize - w.config.plotOptions.radialBar.hollow.margin
|
|
|
|
if (w.config.plotOptions.radialBar.hollow.image !== undefined) {
|
|
hollowFillID = this.drawHollowImage(opts, g, hollowSize, hollowFillID)
|
|
}
|
|
|
|
let elHollow = this.drawHollow({
|
|
size: hollowRadius,
|
|
centerX: opts.centerX,
|
|
centerY: opts.centerY,
|
|
fill: hollowFillID ? hollowFillID : 'transparent'
|
|
})
|
|
|
|
if (w.config.plotOptions.radialBar.hollow.dropShadow.enabled) {
|
|
const shadow = w.config.plotOptions.radialBar.hollow.dropShadow
|
|
filters.dropShadow(elHollow, shadow)
|
|
}
|
|
|
|
let shown = 1
|
|
if (!this.radialDataLabels.total.show && w.globals.series.length > 1) {
|
|
shown = 0
|
|
}
|
|
|
|
let dataLabels = null
|
|
|
|
if (this.radialDataLabels.show) {
|
|
dataLabels = this.renderInnerDataLabels(this.radialDataLabels, {
|
|
hollowSize,
|
|
centerX: opts.centerX,
|
|
centerY: opts.centerY,
|
|
opacity: shown
|
|
})
|
|
}
|
|
|
|
if (w.config.plotOptions.radialBar.hollow.position === 'back') {
|
|
g.add(elHollow)
|
|
if (dataLabels) {
|
|
g.add(dataLabels)
|
|
}
|
|
}
|
|
|
|
let reverseLoop = false
|
|
if (w.config.plotOptions.radialBar.inverseOrder) {
|
|
reverseLoop = true
|
|
}
|
|
|
|
for (
|
|
let i = reverseLoop ? opts.series.length - 1 : 0;
|
|
reverseLoop ? i >= 0 : i < opts.series.length;
|
|
reverseLoop ? i-- : i++
|
|
) {
|
|
let elRadialBarArc = graphics.group({
|
|
class: `apexcharts-series apexcharts-radial-series`,
|
|
seriesName: Utils.escapeString(w.globals.seriesNames[i])
|
|
})
|
|
g.add(elRadialBarArc)
|
|
|
|
elRadialBarArc.attr({
|
|
rel: i + 1,
|
|
'data:realIndex': i
|
|
})
|
|
|
|
this.ctx.series.addCollapsedClassToSeries(elRadialBarArc, i)
|
|
|
|
opts.size = opts.size - strokeWidth - this.margin
|
|
|
|
let pathFill = fill.fillPath({
|
|
seriesNumber: i,
|
|
size: opts.size,
|
|
value: opts.series[i]
|
|
})
|
|
|
|
let startAngle = this.startAngle
|
|
let prevStartAngle
|
|
|
|
// if data exceeds 100, make it 100
|
|
const dataValue =
|
|
Utils.negToZero(opts.series[i] > 100 ? 100 : opts.series[i]) / 100
|
|
|
|
let endAngle = Math.round(this.totalAngle * dataValue) + this.startAngle
|
|
|
|
let prevEndAngle
|
|
if (w.globals.dataChanged) {
|
|
prevStartAngle = this.startAngle
|
|
prevEndAngle =
|
|
Math.round(
|
|
(this.totalAngle * Utils.negToZero(w.globals.previousPaths[i])) /
|
|
100
|
|
) + prevStartAngle
|
|
}
|
|
|
|
const currFullAngle = Math.abs(endAngle) + Math.abs(startAngle)
|
|
if (currFullAngle >= 360) {
|
|
endAngle = endAngle - 0.01
|
|
}
|
|
|
|
const prevFullAngle = Math.abs(prevEndAngle) + Math.abs(prevStartAngle)
|
|
if (prevFullAngle >= 360) {
|
|
prevEndAngle = prevEndAngle - 0.01
|
|
}
|
|
|
|
let angle = endAngle - startAngle
|
|
|
|
const dashArray = Array.isArray(w.config.stroke.dashArray)
|
|
? w.config.stroke.dashArray[i]
|
|
: w.config.stroke.dashArray
|
|
|
|
let elPath = graphics.drawPath({
|
|
d: '',
|
|
stroke: pathFill,
|
|
strokeWidth,
|
|
fill: 'none',
|
|
fillOpacity: w.config.fill.opacity,
|
|
classes: 'apexcharts-radialbar-area apexcharts-radialbar-slice-' + i,
|
|
strokeDashArray: dashArray
|
|
})
|
|
|
|
Graphics.setAttrs(elPath.node, {
|
|
'data:angle': angle,
|
|
'data:value': opts.series[i]
|
|
})
|
|
|
|
if (w.config.chart.dropShadow.enabled) {
|
|
const shadow = w.config.chart.dropShadow
|
|
filters.dropShadow(elPath, shadow, i)
|
|
}
|
|
filters.setSelectionFilter(elPath, 0, i)
|
|
|
|
this.addListeners(elPath, this.radialDataLabels)
|
|
|
|
elRadialBarArc.add(elPath)
|
|
|
|
elPath.attr({
|
|
index: 0,
|
|
j: i
|
|
})
|
|
|
|
let dur = 0
|
|
if (this.initialAnim && !w.globals.resized && !w.globals.dataChanged) {
|
|
dur = w.config.chart.animations.speed
|
|
}
|
|
|
|
if (w.globals.dataChanged) {
|
|
dur = w.config.chart.animations.dynamicAnimation.speed
|
|
}
|
|
this.animDur = dur / (opts.series.length * 1.2) + this.animDur
|
|
this.animBeginArr.push(this.animDur)
|
|
|
|
this.animatePaths(elPath, {
|
|
centerX: opts.centerX,
|
|
centerY: opts.centerY,
|
|
endAngle,
|
|
startAngle,
|
|
prevEndAngle,
|
|
prevStartAngle,
|
|
size: opts.size,
|
|
i,
|
|
totalItems: 2,
|
|
animBeginArr: this.animBeginArr,
|
|
dur,
|
|
shouldSetPrevPaths: true,
|
|
easing: w.globals.easing
|
|
})
|
|
}
|
|
|
|
return {
|
|
g,
|
|
elHollow,
|
|
dataLabels
|
|
}
|
|
}
|
|
|
|
drawHollow(opts) {
|
|
const graphics = new Graphics(this.ctx)
|
|
|
|
let circle = graphics.drawCircle(opts.size * 2)
|
|
|
|
circle.attr({
|
|
class: 'apexcharts-radialbar-hollow',
|
|
cx: opts.centerX,
|
|
cy: opts.centerY,
|
|
r: opts.size,
|
|
fill: opts.fill
|
|
})
|
|
|
|
return circle
|
|
}
|
|
|
|
drawHollowImage(opts, g, hollowSize, hollowFillID) {
|
|
const w = this.w
|
|
let fill = new Fill(this.ctx)
|
|
|
|
let randID = Utils.randomId()
|
|
let hollowFillImg = w.config.plotOptions.radialBar.hollow.image
|
|
|
|
if (w.config.plotOptions.radialBar.hollow.imageClipped) {
|
|
fill.clippedImgArea({
|
|
width: hollowSize,
|
|
height: hollowSize,
|
|
image: hollowFillImg,
|
|
patternID: `pattern${w.globals.cuid}${randID}`
|
|
})
|
|
hollowFillID = `url(#pattern${w.globals.cuid}${randID})`
|
|
} else {
|
|
const imgWidth = w.config.plotOptions.radialBar.hollow.imageWidth
|
|
const imgHeight = w.config.plotOptions.radialBar.hollow.imageHeight
|
|
if (imgWidth === undefined && imgHeight === undefined) {
|
|
let image = w.globals.dom.Paper.image(hollowFillImg).loaded(function(
|
|
loader
|
|
) {
|
|
this.move(
|
|
opts.centerX -
|
|
loader.width / 2 +
|
|
w.config.plotOptions.radialBar.hollow.imageOffsetX,
|
|
opts.centerY -
|
|
loader.height / 2 +
|
|
w.config.plotOptions.radialBar.hollow.imageOffsetY
|
|
)
|
|
})
|
|
g.add(image)
|
|
} else {
|
|
let image = w.globals.dom.Paper.image(hollowFillImg).loaded(function(
|
|
loader
|
|
) {
|
|
this.move(
|
|
opts.centerX -
|
|
imgWidth / 2 +
|
|
w.config.plotOptions.radialBar.hollow.imageOffsetX,
|
|
opts.centerY -
|
|
imgHeight / 2 +
|
|
w.config.plotOptions.radialBar.hollow.imageOffsetY
|
|
)
|
|
this.size(imgWidth, imgHeight)
|
|
})
|
|
g.add(image)
|
|
}
|
|
}
|
|
return hollowFillID
|
|
}
|
|
|
|
getStrokeWidth(opts) {
|
|
const w = this.w
|
|
return (
|
|
(opts.size *
|
|
(100 - parseInt(w.config.plotOptions.radialBar.hollow.size, 10))) /
|
|
100 /
|
|
(opts.series.length + 1) -
|
|
this.margin
|
|
)
|
|
}
|
|
}
|
|
|
|
export default Radial
|