UNPKG

apexcharts

Version:

A JavaScript Chart Library

435 lines (343 loc) 11.6 kB
import Fill from '../modules/Fill' import Graphics from '../modules/Graphics' import Markers from '../modules/Markers' import DataLabels from '../modules/DataLabels' import Filters from '../modules/Filters' /** * ApexCharts Radar Class for Spider/Radar Charts. * @module Radar **/ class Radar { constructor (ctx) { this.ctx = ctx this.w = ctx.w this.chartType = this.w.config.chart.type this.initialAnim = this.w.config.chart.animations.enabled this.dynamicAnim = this.initialAnim && this.w.config.chart.animations.dynamicAnimation.enabled this.animDur = 0 const w = this.w this.graphics = new Graphics(this.ctx) this.lineColorArr = w.globals.stroke.colors !== undefined ? w.globals.stroke.colors : w.globals.colors this.defaultSize = w.globals.svgHeight < w.globals.svgWidth ? w.globals.svgHeight - 35 : w.globals.gridWidth this.maxValue = this.w.globals.maxY this.maxLabelWidth = 20 const longestLabel = w.globals.labels.slice().sort(function (a, b) { return b.length - a.length })[0] const labelWidth = this.graphics.getTextRects(longestLabel, w.config.dataLabels.style.fontSize) this.size = this.defaultSize / 2.1 - w.config.stroke.width - w.config.chart.dropShadow.blur - labelWidth.width / 1.75 if (w.config.plotOptions.radar.size !== undefined) { this.size = w.config.plotOptions.radar.size } this.dataRadiusOfPercent = [] this.dataRadius = [] this.angleArr = [] this.yaxisLabelsTextsPos = [] } draw (series) { let w = this.w const fill = new Fill(this.ctx) const allSeries = [] this.dataPointsLen = series[w.globals.maxValsInArrayIndex].length this.disAngle = Math.PI * 2 / this.dataPointsLen let halfW = w.globals.gridWidth / 2 let halfH = w.globals.gridHeight / 2 let translateX = halfW let translateY = halfH let ret = this.graphics.group({ class: 'apexcharts-radar-series', 'data:innerTranslateX': translateX, 'data:innerTranslateY': translateY - 25, transform: `translate(${translateX || 0}, ${translateY || 0})` }) let dataPointsPos = [] let elPointsMain = null this.yaxisLabels = this.graphics.group({ class: 'apexcharts-yaxis' }) series.forEach((s, i) => { // el to which series will be drawn let elSeries = this.graphics.group().attr({ class: `apexcharts-series ${w.globals.seriesNames[i].toString().replace(/ /g, '-')}`, 'rel': i + 1, 'data:realIndex': i }) this.dataRadiusOfPercent[i] = [] this.dataRadius[i] = [] this.angleArr[i] = [] s.forEach((dv, j) => { this.dataRadiusOfPercent[i][j] = dv / this.maxValue this.dataRadius[i][j] = this.dataRadiusOfPercent[i][j] * this.size this.angleArr[i][j] = j * this.disAngle }) dataPointsPos = this.getDataPointsPos(this.dataRadius[i], this.angleArr[i]) const paths = this.createPaths(dataPointsPos, { x: 0, y: 0 }) // points elPointsMain = this.graphics.group({ class: 'apexcharts-series-markers-wrap hidden' }) w.globals.delayedElements.push({ el: elPointsMain.node, index: i }) const defaultRenderedPathOptions = { i, realIndex: i, animationDelay: i, initialSpeed: w.config.chart.animations.speed, dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed, className: `apexcharts-radar`, id: `apexcharts-radar`, shouldClipToGrid: false, bindEventsOnPaths: false, stroke: w.globals.stroke.colors[i], strokeLineCap: w.config.stroke.lineCap } let pathFrom = null if (w.globals.previousPaths.length > 0) { pathFrom = this.getPathFrom(i) } for (let p = 0; p < paths.linePathsTo.length; p++) { let renderedLinePath = this.graphics.renderPaths({ ...defaultRenderedPathOptions, pathFrom: pathFrom === null ? paths.linePathsFrom[p] : pathFrom, pathTo: paths.linePathsTo[p], strokeWidth: Array.isArray(w.config.stroke.width) ? w.config.stroke.width[i] : w.config.stroke.width, fill: 'none' }) elSeries.add(renderedLinePath) let pathFill = fill.fillPath(elSeries, { seriesNumber: i }) let renderedAreaPath = this.graphics.renderPaths({ ...defaultRenderedPathOptions, pathFrom: pathFrom === null ? paths.areaPathsFrom[p] : pathFrom, pathTo: paths.areaPathsTo[p], strokeWidth: 0, fill: pathFill }) if (w.config.chart.dropShadow.enabled) { const filters = new Filters(this.ctx) const shadow = w.config.chart.dropShadow filters.dropShadow(renderedAreaPath, {...shadow, noUserSpaceOnUse: true}) } elSeries.add(renderedAreaPath) } s.forEach((sj, j) => { let markers = new Markers(this.ctx) let opts = markers.getMarkerConfig('apexcharts-marker', i) let point = this.graphics.drawMarker( dataPointsPos[j].x, dataPointsPos[j].y, opts ) point.attr('rel', j) point.attr('j', j) point.attr('index', i) point.node.setAttribute('default-marker-size', opts.pSize) let elPointsWrap = this.graphics.group({ class: 'apexcharts-series-markers' }) if (elPointsWrap) { elPointsWrap.add(point) } elPointsMain.add(elPointsWrap) elSeries.add(elPointsMain) }) allSeries.push(elSeries) }) this.drawPolygons({ parent: ret }) if (w.config.dataLabels.enabled) { const dataLabels = this.drawLabels() ret.add(dataLabels) } ret.add(this.yaxisLabels) allSeries.forEach((elS) => { ret.add(elS) }) return ret } drawPolygons (opts) { const w = this.w const { parent } = opts const yaxisTexts = w.globals.yAxisScale[0].result.reverse() const layers = yaxisTexts.length let radiusSizes = [] let layerDis = this.size / (layers - 1) for (var i = 0; i < layers; i++) { radiusSizes[i] = layerDis * (i) } radiusSizes.reverse() let polygonStrings = [] let lines = [] radiusSizes.forEach((radiusSize, r) => { const polygon = this.getPolygonPos(radiusSize) let string = '' polygon.forEach((p, i) => { if (r === 0) { const line = this.graphics.drawLine(p.x, p.y, 0, 0, w.config.plotOptions.radar.polygons.strokeColor) lines.push(line) } if (i === 0) { this.yaxisLabelsTextsPos.push({ x: p.x, y: p.y }) } string += p.x + ',' + p.y + ' ' }) polygonStrings.push(string) }) polygonStrings.forEach((p, i) => { const polygon = this.graphics.drawPolygon(p, w.config.plotOptions.radar.polygons.strokeColor, w.globals.radarPolygons.fill.colors[i]) parent.add(polygon) }) lines.forEach((l) => { parent.add(l) }) if (w.config.yaxis[0].show) { this.yaxisLabelsTextsPos.forEach((p, i) => { const yText = this.drawYAxisText(p.x, p.y, i, yaxisTexts[i]) this.yaxisLabels.add(yText) }) } } drawYAxisText (x, y, i, text) { const w = this.w const yaxisConfig = w.config.yaxis[0] const formatter = w.globals.yLabelFormatters[0] const yaxisLabel = this.graphics.drawText({ x: x + yaxisConfig.labels.offsetX, y: y + yaxisConfig.labels.offsetY, text: formatter(text, i), textAnchor: 'middle', fontSize: yaxisConfig.labels.style.fontSize, fontFamily: yaxisConfig.labels.style.fontFamily, foreColor: yaxisConfig.labels.style.color }) return yaxisLabel } drawLabels () { const w = this.w let limit = 10 let textAnchor = 'middle' const dataLabelsConfig = w.config.dataLabels let elDataLabelsWrap = this.graphics.group({ class: 'apexcharts-datalabels' }) let polygonPos = this.getPolygonPos(this.size) let currPosX = 0 let currPosY = 0 w.globals.labels.forEach((label, i) => { let formatter = dataLabelsConfig.formatter let dataLabels = new DataLabels(this.ctx) if (polygonPos[i]) { currPosX = polygonPos[i].x currPosY = polygonPos[i].y if (Math.abs(polygonPos[i].x) >= limit) { if (polygonPos[i].x > 0) { textAnchor = 'start' currPosX += 10 } else if (polygonPos[i].x < 0) { textAnchor = 'end' currPosX -= 10 } } else { textAnchor = 'middle' } if (Math.abs(polygonPos[i].y) >= this.size - limit) { if (polygonPos[i].y < 0) { currPosY -= 10 } else if (polygonPos[i].y > 0) { currPosY += 10 } } let text = formatter(label, { seriesIndex: -1, dataPointIndex: i, w }) dataLabels.plotDataLabelsText({ x: currPosX, y: currPosY, text, textAnchor, i: i, j: i, parent: elDataLabelsWrap, dataLabelsConfig, offsetCorrection: false }) } }) return elDataLabelsWrap } createPaths (pos, origin) { let linePathsTo = [] let linePathsFrom = [] let areaPathsTo = [] let areaPathsFrom = [] if (pos.length) { linePathsFrom = [this.graphics.move(origin.x, origin.y)] areaPathsFrom = [this.graphics.move(origin.x, origin.y)] let linePathTo = this.graphics.move(pos[0].x, pos[0].y) let areaPathTo = this.graphics.move(pos[0].x, pos[0].y) pos.forEach((p, i) => { linePathTo += this.graphics.line(p.x, p.y) areaPathTo += this.graphics.line(p.x, p.y) if (i === pos.length - 1) { linePathTo += 'Z' areaPathTo += 'Z' } }) linePathsTo.push(linePathTo) areaPathsTo.push(areaPathTo) } return { linePathsFrom, linePathsTo, areaPathsFrom, areaPathsTo } } getPathFrom (realIndex) { let w = this.w let pathFrom = null for (let pp = 0; pp < w.globals.previousPaths.length; pp++) { let gpp = w.globals.previousPaths[pp] if ( gpp.paths.length > 0 && parseInt(gpp.realIndex) === parseInt(realIndex) ) { if (typeof w.globals.previousPaths[pp].paths[0] !== 'undefined') { pathFrom = w.globals.previousPaths[pp].paths[0].d } } } return pathFrom } getDataPointsPos (dataRadiusArr, angleArr, dataPointsLen = this.dataPointsLen) { dataRadiusArr = dataRadiusArr || [] angleArr = angleArr || [] var dataPointsPosArray = [] for (var j = 0; j < dataPointsLen; j++) { var curPointPos = {} curPointPos.x = dataRadiusArr[j] * Math.sin(angleArr[j]) curPointPos.y = -dataRadiusArr[j] * Math.cos(angleArr[j]) dataPointsPosArray.push(curPointPos) } return dataPointsPosArray } getPolygonPos (size) { var dotsArray = [] var angle = Math.PI * 2 / this.dataPointsLen for (let i = 0; i < this.dataPointsLen; i++) { var curPos = {} curPos.x = size * Math.sin(i * angle) curPos.y = -size * Math.cos(i * angle) dotsArray.push(curPos) } return dotsArray } } export default Radar