import Animations from '../modules/Animations' import Fill from '../modules/Fill' import Filters from '../modules/Filters' import Graphics from '../modules/Graphics' import Markers from '../modules/Markers' /** * ApexCharts Scatter Class. * This Class also handles bubbles chart as currently there is no major difference in drawing them, * @module Scatter **/ export default class Scatter { constructor(ctx) { this.ctx = ctx this.w = ctx.w this.initialAnim = this.w.config.chart.animations.enabled this.dynamicAnim = this.initialAnim && this.w.config.chart.animations.dynamicAnimation.enabled } draw(elSeries, j, opts) { let w = this.w let graphics = new Graphics(this.ctx) let realIndex = opts.realIndex let pointsPos = opts.pointsPos let zRatio = opts.zRatio let elPointsMain = opts.elParent let elPointsWrap = graphics.group({ class: `apexcharts-series-markers apexcharts-series-${w.config.chart.type}` }) elPointsWrap.attr('clip-path', `url(#gridRectMarkerMask${w.globals.cuid})`) if (Array.isArray(pointsPos.x)) { for (let q = 0; q < pointsPos.x.length; q++) { let dataPointIndex = j + 1 let shouldDraw = true // a small hack as we have 2 points for the first val to connect it if (j === 0 && q === 0) dataPointIndex = 0 if (j === 0 && q === 1) dataPointIndex = 1 let radius = 0 let finishRadius = w.globals.markers.size[realIndex] if (zRatio !== Infinity) { // means we have a bubble finishRadius = w.globals.seriesZ[realIndex][dataPointIndex] / zRatio const bubble = w.config.plotOptions.bubble if (bubble.minBubbleRadius && finishRadius < bubble.minBubbleRadius) { finishRadius = bubble.minBubbleRadius } if (bubble.maxBubbleRadius && finishRadius > bubble.maxBubbleRadius) { finishRadius = bubble.maxBubbleRadius } } if (!w.config.chart.animations.enabled) { radius = finishRadius } let x = pointsPos.x[q] let y = pointsPos.y[q] radius = radius || 0 if ( y === null || typeof w.globals.series[realIndex][dataPointIndex] === 'undefined' ) { shouldDraw = false } if (shouldDraw) { const point = this.drawPoint( x, y, radius, finishRadius, realIndex, dataPointIndex, j ) elPointsWrap.add(point) } elPointsMain.add(elPointsWrap) } } } drawPoint(x, y, radius, finishRadius, realIndex, dataPointIndex, j) { const w = this.w let i = realIndex let anim = new Animations(this.ctx) let filters = new Filters(this.ctx) let fill = new Fill(this.ctx) let markers = new Markers(this.ctx) const graphics = new Graphics(this.ctx) const markerConfig = markers.getMarkerConfig({ cssClass: 'apexcharts-marker', seriesIndex: i, dataPointIndex, finishRadius: w.config.chart.type === 'bubble' || (w.globals.comboCharts && w.config.series[realIndex] && w.config.series[realIndex].type === 'bubble') ? finishRadius : null }) finishRadius = markerConfig.pSize let pathFillCircle = fill.fillPath({ seriesNumber: realIndex, dataPointIndex, color: markerConfig.pointFillColor, patternUnits: 'objectBoundingBox', value: w.globals.series[realIndex][j] }) let el if (markerConfig.shape === 'circle') { el = graphics.drawCircle(radius) } else if ( markerConfig.shape === 'square' || markerConfig.shape === 'rect' ) { el = graphics.drawRect( 0, 0, markerConfig.width - markerConfig.pointStrokeWidth / 2, markerConfig.height - markerConfig.pointStrokeWidth / 2, markerConfig.pRadius ) } if (w.config.series[i].data[dataPointIndex]) { if (w.config.series[i].data[dataPointIndex].fillColor) { pathFillCircle = w.config.series[i].data[dataPointIndex].fillColor } } el.attr({ x: x - markerConfig.width / 2 - markerConfig.pointStrokeWidth / 2, y: y - markerConfig.height / 2 - markerConfig.pointStrokeWidth / 2, cx: x, cy: y, fill: pathFillCircle, 'fill-opacity': markerConfig.pointFillOpacity, stroke: markerConfig.pointStrokeColor, r: finishRadius, 'stroke-width': markerConfig.pointStrokeWidth, 'stroke-dasharray': markerConfig.pointStrokeDashArray, 'stroke-opacity': markerConfig.pointStrokeOpacity }) if (w.config.chart.dropShadow.enabled) { const dropShadow = w.config.chart.dropShadow filters.dropShadow(el, dropShadow, realIndex) } if (this.initialAnim && !w.globals.dataChanged && !w.globals.resized) { let speed = w.config.chart.animations.speed anim.animateMarker( el, 0, markerConfig.shape === 'circle' ? finishRadius : { width: markerConfig.width, height: markerConfig.height }, speed, w.globals.easing, () => { window.setTimeout(() => { anim.animationCompleted(el) }, 100) } ) } else { w.globals.animationEnded = true } if (w.globals.dataChanged && markerConfig.shape === 'circle') { if (this.dynamicAnim) { let speed = w.config.chart.animations.dynamicAnimation.speed let prevX, prevY, prevR let prevPathJ = null prevPathJ = w.globals.previousPaths[realIndex] && w.globals.previousPaths[realIndex][j] if (typeof prevPathJ !== 'undefined' && prevPathJ !== null) { // series containing less elements will ignore these values and revert to 0 prevX = prevPathJ.x prevY = prevPathJ.y prevR = typeof prevPathJ.r !== 'undefined' ? prevPathJ.r : finishRadius } for (let cs = 0; cs < w.globals.collapsedSeries.length; cs++) { if (w.globals.collapsedSeries[cs].index === realIndex) { speed = 1 finishRadius = 0 } } if (x === 0 && y === 0) finishRadius = 0 anim.animateCircle( el, { cx: prevX, cy: prevY, r: prevR }, { cx: x, cy: y, r: finishRadius }, speed, w.globals.easing ) } else { el.attr({ r: finishRadius }) } } el.attr({ rel: dataPointIndex, j: dataPointIndex, index: realIndex, 'default-marker-size': finishRadius }) filters.setSelectionFilter(el, realIndex, dataPointIndex) markers.addEvents(el) el.node.classList.add('apexcharts-marker') return el } centerTextInBubble(y) { let w = this.w y = y + parseInt(w.config.dataLabels.style.fontSize, 10) / 4 return { y } } }