From b31f7e13b13bf3f35e2b77df12a7445aa7071c71 Mon Sep 17 00:00:00 2001 From: Juned Chhipa Date: Thu, 18 Jul 2024 21:42:32 +0530 Subject: [PATCH 1/8] fixes #4569; more marker shapes --- src/assets/apexcharts.css | 4 +- src/charts/Line.js | 128 +++++++++++---------- src/charts/Scatter.js | 7 +- src/modules/Animations.js | 6 +- src/modules/Graphics.js | 190 ++++++++++++++++--------------- src/modules/Markers.js | 24 ++-- src/modules/legend/Helpers.js | 4 +- src/modules/legend/Legend.js | 100 ++++++---------- src/modules/settings/Defaults.js | 5 +- src/modules/settings/Options.js | 12 +- src/modules/tooltip/Marker.js | 19 +++- src/modules/tooltip/Position.js | 33 +++--- src/modules/tooltip/Tooltip.js | 4 + src/modules/tooltip/Utils.js | 8 ++ types/apexcharts.d.ts | 7 +- 15 files changed, 272 insertions(+), 279 deletions(-) diff --git a/src/assets/apexcharts.css b/src/assets/apexcharts.css index d9eadd09e..f25a5b1ca 100644 --- a/src/assets/apexcharts.css +++ b/src/assets/apexcharts.css @@ -630,7 +630,7 @@ rect.legend-mouseover-inactive, .apexcharts-line, .apexcharts-line-series .apexcharts-series-markers .apexcharts-marker.no-pointer-events, .apexcharts-point-annotation-label, -.apexcharts-radar-series path, +.apexcharts-radar-series path:not(.apexcharts-marker), .apexcharts-radar-series polygon, .apexcharts-toolbar svg, .apexcharts-tooltip .apexcharts-marker, @@ -640,7 +640,7 @@ rect.legend-mouseover-inactive, pointer-events: none } -.apexcharts-marker { +.apexcharts-tooltip-active .apexcharts-marker { transition: .15s ease all } diff --git a/src/charts/Line.js b/src/charts/Line.js index 67cd3bd75..319233b82 100644 --- a/src/charts/Line.js +++ b/src/charts/Line.js @@ -67,7 +67,6 @@ class Line { let realIndex = w.globals.comboCharts ? seriesIndex[i] : i let translationsIndex = this.yRatio.length > 1 ? realIndex : 0 - this._initSerieVariables(series, i, realIndex) let yArrj = [] // hold y values of current iterating series @@ -103,7 +102,7 @@ class Line { series, prevY, lineYPosition, - translationsIndex + translationsIndex, }) prevY = firstPrevY.prevY if (w.config.stroke.curve === 'monotoneCubic' && series[i][0] === null) { @@ -125,7 +124,7 @@ class Line { series: seriesRangeEnd, prevY: prevY2, lineYPosition, - translationsIndex + translationsIndex, }) prevY2 = firstPrevY2.prevY pY2 = prevY2 @@ -204,7 +203,8 @@ class Line { // unsegmented paths conditional branch. let segments = paths.linePaths.length / 2 for (let s = 0; s < segments; s++) { - paths.linePaths[s] = rangePaths.linePaths[s + segments] + paths.linePaths[s] + paths.linePaths[s] = + rangePaths.linePaths[s + segments] + paths.linePaths[s] } paths.linePaths.splice(segments) paths.pathFromLine = rangePaths.pathFromLine + paths.pathFromLine @@ -313,7 +313,16 @@ class Line { this.appendPathFrom = true } - _calculatePathsFrom({ type, series, i, realIndex, translationsIndex, prevX, prevY, prevY2 }) { + _calculatePathsFrom({ + type, + series, + i, + realIndex, + translationsIndex, + prevX, + prevY, + prevY2, + }) { const w = this.w const graphics = new Graphics(this.ctx) let linePath, areaPath, pathFromLine, pathFromArea @@ -378,7 +387,7 @@ class Line { if (forecast.count > 0 && type !== 'rangeArea') { const forecastCutoff = w.globals.seriesXvalues[realIndex][ - w.globals.seriesXvalues[realIndex].length - forecast.count - 1 + w.globals.seriesXvalues[realIndex].length - forecast.count - 1 ] const elForecastMask = graphics.drawRect( forecastCutoff, @@ -561,8 +570,8 @@ class Line { (w.config.chart.stacked && w.globals.comboCharts && (!this.w.config.chart.stackOnlyBar || - this.w.config.series[realIndex]?.type === 'bar' - || this.w.config.series[realIndex]?.type === 'column')) + this.w.config.series[realIndex]?.type === 'bar' || + this.w.config.series[realIndex]?.type === 'column')) let curve = w.config.stroke.curve if (Array.isArray(curve)) { @@ -601,9 +610,11 @@ class Line { // which is not collapsed - fixes apexcharts.js#1372 const prevIndex = (pi) => { for (let pii = pi; pii > 0; pii--) { - if (w.globals.collapsedSeriesIndices.indexOf( - seriesIndex?.[pii] || pii - ) > -1) { + if ( + w.globals.collapsedSeriesIndices.indexOf( + seriesIndex?.[pii] || pii + ) > -1 + ) { pii-- } else { return pii @@ -634,9 +645,10 @@ class Line { xArrj.push(x) // push current Y that will be used as next series's bottom position - if (isNull - && (w.config.stroke.curve === 'smooth' - || w.config.stroke.curve === 'monotoneCubic') + if ( + isNull && + (w.config.stroke.curve === 'smooth' || + w.config.stroke.curve === 'monotoneCubic') ) { yArrj.push(null) y2Arrj.push(null) @@ -786,15 +798,16 @@ class Line { let graphics = new Graphics(this.ctx) const areaBottomY = this.areaBottomY let rangeArea = type === 'rangeArea' - let isLowerRangeAreaPath = (type === 'rangeArea' && isRangeStart) + let isLowerRangeAreaPath = type === 'rangeArea' && isRangeStart switch (curve) { case 'monotoneCubic': let yAj = isRangeStart ? yArrj : y2Arrj let getSmoothInputs = (xArr, yArr) => { - return xArr.map((_, i) => { - return [_, yArr[i]] - }) + return xArr + .map((_, i) => { + return [_, yArr[i]] + }) .filter((_) => _[1] !== null) } let getSegmentLengths = (yArr) => { @@ -833,9 +846,10 @@ class Line { pathState = 1 // continue through to pathState 1 case 1: - if (!(rangeArea - ? xArrj.length === series[i].length - : (j === series[i].length - 2)) + if ( + !(rangeArea + ? xArrj.length === series[i].length + : j === series[i].length - 2) ) { break } @@ -847,9 +861,10 @@ class Line { const _yAj = isRangeStart ? yAj : yAj.slice().reverse() const smoothInputs = getSmoothInputs(_xAj, _yAj) - const points = smoothInputs.length > 1 - ? spline.points(smoothInputs) - : smoothInputs + const points = + smoothInputs.length > 1 + ? spline.points(smoothInputs) + : smoothInputs let smoothInputsLower = [] if (rangeArea) { @@ -878,20 +893,19 @@ class Line { graphics.move( smoothInputs[_start][0], smoothInputs[_start][1] - ) - + svgPoints + ) + svgPoints } else if (rangeArea) { linePath = graphics.move( smoothInputsLower[_start][0], smoothInputsLower[_start][1] - ) - + graphics.line( + ) + + graphics.line( smoothInputs[_start][0], smoothInputs[_start][1] - ) - + svgPoints - + graphics.line( + ) + + svgPoints + + graphics.line( smoothInputsLower[_end][0], smoothInputsLower[_end][1] ) @@ -900,13 +914,12 @@ class Line { graphics.move( smoothInputs[_start][0], smoothInputs[_start][1] - ) - + svgPoints + ) + svgPoints areaPath = - linePath - + graphics.line(smoothInputs[_end][0], areaBottomY) - + graphics.line(smoothInputs[_start][0], areaBottomY) - + 'z' + linePath + + graphics.line(smoothInputs[_end][0], areaBottomY) + + graphics.line(smoothInputs[_start][0], areaBottomY) + + 'z' areaPaths.push(areaPath) } linePaths.push(linePath) @@ -933,9 +946,7 @@ class Line { segmentStartX = pX if (isLowerRangeAreaPath) { // Need to add path portion that will join to the upper path - linePath = - graphics.move(pX, y2Arrj[j]) - + graphics.line(pX, pY) + linePath = graphics.move(pX, y2Arrj[j]) + graphics.line(pX, pY) } else { linePath = graphics.move(pX, pY) } @@ -959,9 +970,9 @@ class Line { linePath += graphics.move(pX, pY) } areaPath += - graphics.line(pX, areaBottomY) - + graphics.line(segmentStartX, areaBottomY) - + 'z' + graphics.line(pX, areaBottomY) + + graphics.line(segmentStartX, areaBottomY) + + 'z' linePaths.push(linePath) areaPaths.push(areaPath) pathState = -1 @@ -973,13 +984,12 @@ class Line { if (isLowerRangeAreaPath) { // Need to add path portion that will join to the upper path linePath += - graphics.curve(x, y, x, y, x, y2) - + graphics.move(x, y2) + graphics.curve(x, y, x, y, x, y2) + graphics.move(x, y2) } areaPath += - graphics.curve(x, y, x, y, x, areaBottomY) - + graphics.line(segmentStartX, areaBottomY) - + 'z' + graphics.curve(x, y, x, y, x, areaBottomY) + + graphics.line(segmentStartX, areaBottomY) + + 'z' linePaths.push(linePath) areaPaths.push(areaPath) pathState = -1 @@ -1018,9 +1028,7 @@ class Line { segmentStartX = pX if (isLowerRangeAreaPath) { // Need to add path portion that will join to the upper path - linePath = - graphics.move(pX, y2Arrj[j]) - + graphics.line(pX, pY) + linePath = graphics.move(pX, y2Arrj[j]) + graphics.line(pX, pY) } else { linePath = graphics.move(pX, pY) } @@ -1044,9 +1052,9 @@ class Line { linePath += graphics.move(pX, pY) } areaPath += - graphics.line(pX, areaBottomY) - + graphics.line(segmentStartX, areaBottomY) - + 'z' + graphics.line(pX, areaBottomY) + + graphics.line(segmentStartX, areaBottomY) + + 'z' linePaths.push(linePath) areaPaths.push(areaPath) pathState = -1 @@ -1060,9 +1068,9 @@ class Line { linePath += graphics.line(x, y2) } areaPath += - graphics.line(x, areaBottomY) - + graphics.line(segmentStartX, areaBottomY) - + 'z' + graphics.line(x, areaBottomY) + + graphics.line(segmentStartX, areaBottomY) + + 'z' linePaths.push(linePath) areaPaths.push(areaPath) pathState = -1 @@ -1097,7 +1105,9 @@ class Line { series[i].length === 1 ) { let pSize = this.strokeWidth - w.config.markers.strokeWidth / 2 - if (!(pSize > 0)) { pSize = 0 } + if (!(pSize > 0)) { + pSize = 0 + } // fixes apexcharts.js#1282, #1252 let elPointsWrap = this.markers.plotChartMarkers( pointsPos, @@ -1113,4 +1123,4 @@ class Line { } } -export default Line \ No newline at end of file +export default Line diff --git a/src/charts/Scatter.js b/src/charts/Scatter.js index 7b0f101c2..074b0cd2b 100644 --- a/src/charts/Scatter.js +++ b/src/charts/Scatter.js @@ -114,8 +114,6 @@ export default class Scatter { : null, }) - radius = markerConfig.pSize - let pathFillCircle = fill.fillPath({ seriesNumber: realIndex, dataPointIndex, @@ -134,7 +132,6 @@ export default class Scatter { el.attr({ fill: pathFillCircle, - r: radius, }) if (w.config.chart.dropShadow.enabled) { @@ -145,7 +142,7 @@ export default class Scatter { if (this.initialAnim && !w.globals.dataChanged && !w.globals.resized) { let speed = w.config.chart.animations.speed - anim.animateMarker(el, 0, radius, speed, w.globals.easing, () => { + anim.animateMarker(el, speed, w.globals.easing, () => { window.setTimeout(() => { anim.animationCompleted(el) }, 100) @@ -158,7 +155,7 @@ export default class Scatter { rel: dataPointIndex, j: dataPointIndex, index: realIndex, - 'default-marker-size': radius, + 'default-marker-size': markerConfig.pSize, }) filters.setSelectionFilter(el, realIndex, dataPointIndex) diff --git a/src/modules/Animations.js b/src/modules/Animations.js index 92499c346..bee9d7131 100644 --- a/src/modules/Animations.js +++ b/src/modules/Animations.js @@ -89,16 +89,12 @@ export default class Animations { /* ** Animate radius of a circle element */ - animateMarker(el, from, to, speed, easing, cb) { - if (!from) from = 0 - + animateMarker(el, speed, easing, cb) { el.attr({ - r: from, opacity: 0, }) .animate(speed, easing) .attr({ - r: to, opacity: 1, }) .afterAll(() => { diff --git a/src/modules/Graphics.js b/src/modules/Graphics.js index 04fd89e4b..2c66ef800 100644 --- a/src/modules/Graphics.js +++ b/src/modules/Graphics.js @@ -725,38 +725,103 @@ class Graphics { return elText } + getMarkerPath(x, y, type, size) { + let d = '' + switch (type) { + case 'cross': + size = size / 1.4 + d = `M ${x - size} ${y - size} L ${x + size} ${y + size} M ${ + x - size + } ${y + size} L ${x + size} ${y - size}` + break + case 'plus': + size = size / 1.12 + d = `M ${x - size} ${y} L ${x + size} ${y} M ${x} ${y - size} L ${x} ${ + y + size + }` + break + case 'star': + case 'sparkle': + let points = 5 + size = size * 1.15 + if (type === 'sparkle') { + size = size / 1.1 + points = 4 + } + const step = Math.PI / points + + for (let i = 0; i <= 2 * points; i++) { + const angle = i * step + const radius = i % 2 === 0 ? size : size / 2 + const xPos = x + radius * Math.sin(angle) + const yPos = y - radius * Math.cos(angle) + + d += (i === 0 ? 'M' : 'L') + xPos + ',' + yPos + } + d += 'Z' + break + case 'triangle': + d = `M ${x} ${y - size} + L ${x + size} ${y + size} + L ${x - size} ${y + size} + Z` + break + case 'square': + case 'rect': + size = size / 1.125 + d = `M ${x - size} ${y - size} + L ${x + size} ${y - size} + L ${x + size} ${y + size} + L ${x - size} ${y + size} + Z` + break + case 'diamond': + size = size * 1.05 + d = `M ${x} ${y - size} + L ${x + size} ${y} + L ${x} ${y + size} + L ${x - size} ${y} + Z` + break + case 'line': + size = size / 1.1 + d = `M ${x - size} ${y} + L ${x + size} ${y}` + break + case 'circle': + default: + size = size * 2 + d = `M ${x}, ${y} + m -${size / 2}, 0 + a ${size / 2},${size / 2} 0 1,0 ${size},0 + a ${size / 2},${size / 2} 0 1,0 -${size},0` + break + } + return d + } + /** - * Draws an 'X' at the given coordinates. - * @param {number} x - The x-coordinate of the 'X'. - * @param {number} y - The y-coordinate of the 'X'. - * @param {number} size - The size of the 'X'. - * @param {Object} opts - The options for the 'X'. - * @returns {Object} The created 'X'. + * @param {number} x - The x-coordinate of the marker + * @param {number} y - The y-coordinate of the marker. + * @param {number} size - The size of the marker + * @param {Object} opts - The options for the marker. + * @returns {Object} The created marker. */ - drawXMarker(x, y, type, size, opts) { - let halfSize = size / 2 - - const d = - type === 'cross' - ? `M ${x - halfSize} ${y - halfSize} L ${x + halfSize} ${ - y + halfSize - } M ${x - halfSize} ${y + halfSize} L ${x + halfSize} ${ - y - halfSize - }` - : `M ${x - halfSize} ${y} L ${x + halfSize} ${y} M ${x} ${ - y - halfSize - } L ${x} ${y + halfSize}` + drawMarkerShape(x, y, type, size, opts) { const path = this.drawPath({ - d, + d: this.getMarkerPath(x, y, type, size, opts), stroke: opts.pointStrokeColor, strokeDashArray: opts.pointStrokeDashArray, strokeWidth: opts.pointStrokeWidth, fill: opts.pointFillColor, + fillOpacity: opts.pointFillOpacity, + strokeOpacity: opts.pointStrokeOpacity, }) path.attr({ cx: x, cy: y, + shape: opts.shape, class: opts.class ? opts.class : '', }) @@ -767,79 +832,22 @@ class Graphics { x = x || 0 let size = opts.pSize || 0 - let elPoint = null - if (opts?.shape === 'cross') { - elPoint = this.drawXMarker(x, y, opts?.shape, size * 1.8, { - ...opts, - pointStrokeColor: opts.pointFillColor, - }) - } else if (opts?.shape === 'plus') { - elPoint = this.drawXMarker(x, y, opts?.shape, size * 2, { - ...opts, - pointStrokeColor: opts.pointFillColor, - }) - } else if (opts?.shape === 'line') { - elPoint = this.drawLine( - x - size, - y, - x + size, - y, - opts.pointFillColor, - opts.pointStrokeDashArray, - opts.pointStrokeWidth, - opts.pointStrokeLineCap - ) - - elPoint.attr({ - cx: x, - cy: y, - class: opts.class ? opts.class : '', - }) - } else if (opts.shape === 'square' || opts.shape === 'rect') { - let radius = opts.pRadius === undefined ? size : opts.pRadius - - if (y === null || !size) { - size = 0 - radius = 0 - } - - let nSize = size * 2 - - let p = this.drawRect(nSize, nSize, nSize, nSize, radius) - - p.attr({ - x: x - nSize / 2, - y: y - nSize / 2, - cx: x, - cy: y, - class: opts.class ? opts.class : '', - fill: opts.pointFillColor, - 'fill-opacity': opts.pointFillOpacity ? opts.pointFillOpacity : 1, - stroke: opts.pointStrokeColor, - 'stroke-width': opts.pointStrokeWidth ? opts.pointStrokeWidth : 0, - 'stroke-opacity': opts.pointStrokeOpacity ? opts.pointStrokeOpacity : 1, - }) - - elPoint = p - } else if (opts.shape === 'circle' || !opts.shape) { - if (!Utils.isNumber(y)) { - size = 0 - y = 0 - } - - elPoint = this.drawCircle(size, { - cx: x, - cy: y, - class: opts.class ? opts.class : '', - stroke: opts.pointStrokeColor, - fill: opts.pointFillColor, - 'fill-opacity': opts.pointFillOpacity ? opts.pointFillOpacity : 1, - 'stroke-width': opts.pointStrokeWidth ? opts.pointStrokeWidth : 0, - 'stroke-opacity': opts.pointStrokeOpacity ? opts.pointStrokeOpacity : 1, - }) + if (!Utils.isNumber(y)) { + size = 0 + y = 0 } - return elPoint + return this.drawMarkerShape(x, y, opts?.shape, size, { + ...opts, + ...(opts.shape === 'line' || + opts.shape === 'plus' || + opts.shape === 'cross' + ? { + pointStrokeColor: opts.pointFillColor, + pointStrokeOpacity: opts.pointFillOpacity, + } + : {}), + }) } pathMouseEnter(path, e) { diff --git a/src/modules/Markers.js b/src/modules/Markers.js index 13439d8c9..544f214c2 100644 --- a/src/modules/Markers.js +++ b/src/modules/Markers.js @@ -112,7 +112,7 @@ export default class Markers { } } - if (pSize) { + if (typeof pSize !== 'undefined') { opts.pSize = pSize } @@ -210,30 +210,30 @@ export default class Markers { } } - addEvents(circle) { + addEvents(marker) { const w = this.w const graphics = new Graphics(this.ctx) - circle.node.addEventListener( + marker.node.addEventListener( 'mouseenter', - graphics.pathMouseEnter.bind(this.ctx, circle) + graphics.pathMouseEnter.bind(this.ctx, marker) ) - circle.node.addEventListener( + marker.node.addEventListener( 'mouseleave', - graphics.pathMouseLeave.bind(this.ctx, circle) + graphics.pathMouseLeave.bind(this.ctx, marker) ) - circle.node.addEventListener( + marker.node.addEventListener( 'mousedown', - graphics.pathMouseDown.bind(this.ctx, circle) + graphics.pathMouseDown.bind(this.ctx, marker) ) - circle.node.addEventListener('click', w.config.markers.onClick) - circle.node.addEventListener('dblclick', w.config.markers.onDblClick) + marker.node.addEventListener('click', w.config.markers.onClick) + marker.node.addEventListener('dblclick', w.config.markers.onDblClick) - circle.node.addEventListener( + marker.node.addEventListener( 'touchstart', - graphics.pathMouseDown.bind(this.ctx, circle), + graphics.pathMouseDown.bind(this.ctx, marker), { passive: true } ) } diff --git a/src/modules/legend/Helpers.js b/src/modules/legend/Helpers.js index fffbc8823..85480ca8e 100644 --- a/src/modules/legend/Helpers.js +++ b/src/modules/legend/Helpers.js @@ -42,8 +42,6 @@ export default class Helpers { cursor: pointer; line-height: normal; display: flex; - } - .apexcharts-legend.apx-legend-position-bottom .apexcharts-legend-series, .apexcharts-legend.apx-legend-position-top .apexcharts-legend-series{ align-items: center; } .apexcharts-legend-text { @@ -59,7 +57,7 @@ export default class Helpers { align-items: center; justify-content: center; cursor: pointer; - margin-right: 3px; + margin-right: 1px; } .apexcharts-legend-series.apexcharts-no-click { diff --git a/src/modules/legend/Legend.js b/src/modules/legend/Legend.js index 1eef631c9..ef4d95944 100644 --- a/src/modules/legend/Legend.js +++ b/src/modules/legend/Legend.js @@ -72,43 +72,34 @@ class Legend { elMarker.classList.add('apexcharts-legend-marker') let mShape = w.config.legend.markers.shape || w.config.markers.shape - let mSize = w.config.legend.markers.size - let mOffsetX = w.config.legend.markers.offsetX - let mOffsetY = w.config.legend.markers.offsetY - let mBorderWidth = w.config.legend.markers.strokeWidth - let mBorderColor = w.config.legend.markers.strokeColor - let mBorderRadius = w.config.legend.markers.radius + let shape = mShape + if (Array.isArray(mShape)) { + shape = mShape[i] + } + let mSize = Array.isArray(w.config.legend.markers.size) + ? parseFloat(w.config.legend.markers.size[i]) + : parseFloat(w.config.legend.markers.size) + let mOffsetX = Array.isArray(w.config.legend.markers.offsetX) + ? parseFloat(w.config.legend.markers.offsetX[i]) + : parseFloat(w.config.legend.markers.offsetX) + let mOffsetY = Array.isArray(w.config.legend.markers.offsetY) + ? parseFloat(w.config.legend.markers.offsetY[i]) + : parseFloat(w.config.legend.markers.offsetY) + let mBorderWidth = Array.isArray(w.config.legend.markers.strokeWidth) + ? parseFloat(w.config.legend.markers.strokeWidth[i]) + : parseFloat(w.config.legend.markers.strokeWidth) let mStyle = elMarker.style - mStyle.height = - (Array.isArray(mSize) - ? parseFloat(mSize[i]) * 2 - : parseFloat(mSize) * 2) + 'px' - mStyle.width = - (Array.isArray(mSize) - ? parseFloat(mSize[i]) * 2 - : parseFloat(mSize) * 2) + 'px' - mStyle.left = - (Array.isArray(mOffsetX) - ? parseFloat(mOffsetX[i]) - : parseFloat(mOffsetX)) + 'px' - mStyle.top = - (Array.isArray(mOffsetY) - ? parseFloat(mOffsetY[i]) - : parseFloat(mOffsetY)) + 'px' - mStyle.borderWidth = Array.isArray(mBorderWidth) - ? mBorderWidth[i] - : mBorderWidth - mStyle.borderColor = Array.isArray(mBorderColor) - ? mBorderColor[i] - : mBorderColor - mStyle.borderRadius = Array.isArray(mBorderRadius) - ? parseFloat(mBorderRadius[i]) + 'px' - : parseFloat(mBorderRadius) + 'px' + mStyle.height = (mSize + mBorderWidth) * 2 + 'px' + mStyle.width = (mSize + mBorderWidth) * 2 + 'px' + mStyle.left = mOffsetX + 'px' + mStyle.top = mOffsetY + 'px' if (w.config.legend.markers.customHTML) { mStyle.background = 'transparent' + mStyle.color = fillcolor[i] + if (Array.isArray(w.config.legend.markers.customHTML)) { if (w.config.legend.markers.customHTML[i]) { elMarker.innerHTML = w.config.legend.markers.customHTML[i]() @@ -116,29 +107,14 @@ class Legend { } else { elMarker.innerHTML = w.config.legend.markers.customHTML() } - } - - let shape = mShape - if (Array.isArray(mShape)) { - shape = mShape[i] - } - - if (shape !== 'circle') { + } else { let markers = new Markers(this.ctx) const markerConfig = markers.getMarkerConfig({ - cssClass: 'apexcharts-legend-marker apexcharts-marker', + cssClass: `apexcharts-legend-marker apexcharts-marker apexcharts-marker-${shape}`, seriesIndex: i, + strokeWidth: mBorderWidth, size: mSize, - pRadius: Array.isArray(mBorderRadius) - ? mBorderRadius[i] - : mBorderRadius, - strokeWidth: - shape === 'plus' || shape === 'cross' || shape === 'line' - ? Array.isArray(mBorderWidth) - ? mBorderWidth[i] - : mBorderWidth - : 0, }) const SVGMarker = SVG(elMarker).size('100%', '100%') @@ -147,32 +123,22 @@ class Legend { pointFillColor: Array.isArray(w.config.legend.markers.fillColors) ? fillcolor[i] : markerConfig.pointFillColor, + shape, }) - const shapes = SVG.select( + const shapesEls = SVG.select( '.apexcharts-legend-marker.apexcharts-marker' ).members - shapes.forEach((shape) => { - shape.node.style.transform = 'translate(50%, 50%)' + shapesEls.forEach((shapeEl) => { + if (shapeEl.node.classList.contains('apexcharts-marker-triangle')) { + shapeEl.node.style.transform = 'translate(50%, 45%)' + } else { + shapeEl.node.style.transform = 'translate(50%, 50%)' + } }) SVGMarker.add(marker) - } else { - mStyle.color = fillcolor[i] - mStyle.borderRadius = '100%' - - if (!w.config.legend.markers.customHTML) { - mStyle.background = fillcolor[i] - mStyle.setProperty('background', fillcolor[i], 'important') - - // override with data color - if (w.globals.seriesColors[i] !== undefined) { - mStyle.background = w.globals.seriesColors[i] - mStyle.color = w.globals.seriesColors[i] - } - } } - return elMarker } diff --git a/src/modules/settings/Defaults.js b/src/modules/settings/Defaults.js index 17c99dc16..a49d038b4 100644 --- a/src/modules/settings/Defaults.js +++ b/src/modules/settings/Defaults.js @@ -323,7 +323,6 @@ export default class Defaults { legend: { markers: { shape: 'square', - radius: 2, }, }, tooltip: { @@ -468,7 +467,7 @@ export default class Defaults { }, }, markers: { - size: 5, + size: 7, strokeWidth: 1, strokeColors: '#111', }, @@ -1072,7 +1071,7 @@ export default class Defaults { width: 2, }, markers: { - size: 3, + size: 5, strokeWidth: 1, strokeOpacity: 1, }, diff --git a/src/modules/settings/Options.js b/src/modules/settings/Options.js index eb5e61d77..5507e7ff6 100644 --- a/src/modules/settings/Options.js +++ b/src/modules/settings/Options.js @@ -98,7 +98,7 @@ export default class Options { shape: 'circle', offsetX: 0, offsetY: 0, - radius: 2, + // radius: 2, // DEPRECATED cssClass: '', }, label: { @@ -128,7 +128,7 @@ export default class Options { }, }, customSVG: { - // this will be deprecated in the next major version as it is going to be replaced with a better alternative below + // this will be deprecated in the next major version as it is going to be replaced with a better alternative below (image) SVG: undefined, cssClass: undefined, offsetX: 0, @@ -807,13 +807,13 @@ export default class Options { useSeriesColors: false, }, markers: { - size: 6, + size: 7, fillColors: undefined, // width: 12, // [DEPRECATED] // height: 12, // [DEPRECATED] - strokeWidth: 2, + strokeWidth: 1, shape: undefined, // circle, square, line, plus, cross, star - radius: 2, + // radius: 2, // DEPRECATED offsetX: 0, offsetY: 0, customHTML: undefined, @@ -843,7 +843,7 @@ export default class Options { // width: 8, // only applicable when shape is rect/square [DEPRECATED] // height: 8, // only applicable when shape is rect/square [DEPRECATED] shape: 'circle', // circle, square, line, plus, cross - radius: 2, + // radius: 2, // DEPRECATED offsetX: 0, offsetY: 0, showNullDataPoints: true, diff --git a/src/modules/tooltip/Marker.js b/src/modules/tooltip/Marker.js index 9a6d6bd2b..b6b397a55 100644 --- a/src/modules/tooltip/Marker.js +++ b/src/modules/tooltip/Marker.js @@ -61,7 +61,7 @@ export default class Marker { let elPointOptions = marker.getMarkerConfig({ cssClass: PointClasses, - seriesIndex: Number(pointsMain.getAttribute('data:realIndex')) // fixes apexcharts/apexcharts.js #1427 + seriesIndex: Number(pointsMain.getAttribute('data:realIndex')), // fixes apexcharts/apexcharts.js #1427 }) point = graphics.drawMarker(0, 0, elPointOptions) @@ -159,14 +159,19 @@ export default class Marker { w.globals.markers.size[index] + w.config.markers.hover.sizeOffset } - if (newSize < 0) newSize = 0 - elPoint.setAttribute('r', newSize) + if (newSize < 0) { + newSize = 0 + } + + const path = this.ttCtx.tooltipUtil.getPathFromPoint(point, newSize) + point.setAttribute('d', path) } } oldPointSize(point) { const size = parseFloat(point.getAttribute('default-marker-size')) - point.setAttribute('r', size) + const path = this.ttCtx.tooltipUtil.getPathFromPoint(point, size) + point.setAttribute('d', path) } resetPointsSize() { @@ -178,10 +183,12 @@ export default class Marker { for (let p = 0; p < points.length; p++) { const size = parseFloat(points[p].getAttribute('default-marker-size')) + if (Utils.isNumber(size) && size >= 0) { - points[p].setAttribute('r', size) + const path = this.ttCtx.tooltipUtil.getPathFromPoint(points[p], size) + points[p].setAttribute('d', path) } else { - points[p].setAttribute('r', 0) + points[p].setAttribute('d', 'M0,0') } } } diff --git a/src/modules/tooltip/Position.js b/src/modules/tooltip/Position.js index 56681fa7e..e5cc25ee7 100644 --- a/src/modules/tooltip/Position.js +++ b/src/modules/tooltip/Position.js @@ -163,22 +163,22 @@ export default class Position { * @memberof Position * @param {int} - cx = point's x position, wherever point's x is, you need to move tooltip * @param {int} - cy = point's y position, wherever point's y is, you need to move tooltip - * @param {int} - r = point's radius + * @param {int} - markerSize = point's size */ - moveTooltip(cx, cy, r = null) { + moveTooltip(cx, cy, markerSize = null) { let w = this.w let ttCtx = this.ttCtx const tooltipEl = ttCtx.getElTooltip() let tooltipRect = ttCtx.tooltipRect - let pointR = r !== null ? parseFloat(r) : 1 + let pointSize = markerSize !== null ? parseFloat(markerSize) : 1 - let x = parseFloat(cx) + pointR + 5 - let y = parseFloat(cy) + pointR / 2 // - tooltipRect.ttHeight / 2 + let x = parseFloat(cx) + pointSize + 5 + let y = parseFloat(cy) + pointSize / 2 // - tooltipRect.ttHeight / 2 if (x > w.globals.gridWidth / 2) { - x = x - tooltipRect.ttWidth - pointR - 10 + x = x - tooltipRect.ttWidth - pointSize - 10 } if (x > w.globals.gridWidth - tooltipRect.ttWidth - 10) { @@ -244,6 +244,7 @@ export default class Position { let ttCtx = this.ttCtx let cx = 0 let cy = 0 + const graphics = new Graphics(this.ctx) let pointsArr = w.globals.pointsArray @@ -264,18 +265,16 @@ export default class Position { cy = pointsArr[capturedSeries][j]?.[1] || 0 let point = w.globals.dom.baseEl.querySelector( - `.apexcharts-series[data\\:realIndex='${capturedSeries}'] .apexcharts-series-markers circle` + `.apexcharts-series[data\\:realIndex='${capturedSeries}'] .apexcharts-series-markers path` ) if (point && cy < w.globals.gridHeight && cy > 0) { - point.setAttribute('r', hoverSize) + const shape = point.getAttribute('shape') - point.setAttribute('cx', cx) - point.setAttribute('cy', cy) + const path = graphics.getMarkerPath(cx, cy, shape, hoverSize * 1.5) + point.setAttribute('d', path) } - // point.style.opacity = w.config.markers.hover.opacity - this.moveXCrosshairs(cx) if (!ttCtx.fixedTooltip) { @@ -295,6 +294,8 @@ export default class Position { let pointsArr = w.globals.pointsArray let series = new Series(this.ctx) + const graphics = new Graphics(this.ctx) + activeSeries = series.getActiveConfigSeriesIndex('asc', [ 'line', 'area', @@ -327,6 +328,8 @@ export default class Position { let pcy2 points[p].setAttribute('cx', cx) + const shape = points[p].getAttribute('shape') + if (w.config.chart.type === 'rangeArea' && !w.globals.comboCharts) { const rangeStartIndex = j + w.globals.series[p].length pcy2 = pointsArr[p][rangeStartIndex][1] @@ -340,10 +343,10 @@ export default class Position { pcy < w.globals.gridHeight + hoverSize && pcy + hoverSize > 0 ) { - points[p] && points[p].setAttribute('r', hoverSize) - points[p] && points[p].setAttribute('cy', pcy) + const path = graphics.getMarkerPath(cx, pcy, shape, hoverSize) + points[p].setAttribute('d', path) } else { - points[p] && points[p].setAttribute('r', 0) + points[p].setAttribute('d', '') } } } diff --git a/src/modules/tooltip/Tooltip.js b/src/modules/tooltip/Tooltip.js index 78dcbf009..9650d95d9 100644 --- a/src/modules/tooltip/Tooltip.js +++ b/src/modules/tooltip/Tooltip.js @@ -598,6 +598,7 @@ export default class Tooltip { } } + w.globals.dom.baseEl.classList.add('apexcharts-tooltip-active') opt.tooltipEl.classList.add('apexcharts-active') } else if (e.type === 'mouseout' || e.type === 'touchend') { this.handleMouseOut(opt) @@ -614,6 +615,7 @@ export default class Tooltip { let seriesBound = w.globals.dom.elWrap.getBoundingClientRect() if (e.type === 'mousemove' || e.type === 'touchmove') { + w.globals.dom.baseEl.classList.add('apexcharts-tooltip-active') tooltipEl.classList.add('apexcharts-active') this.tooltipLabels.drawSeriesTexts({ @@ -645,6 +647,7 @@ export default class Tooltip { } } else if (e.type === 'mouseout' || e.type === 'touchend') { tooltipEl.classList.remove('apexcharts-active') + w.globals.dom.baseEl.classList.remove('apexcharts-tooltip-active') if (w.config.legend.tooltipHoverFormatter) { this.legendLabels.forEach((l) => { const defaultText = l.getAttribute('data:default-text') @@ -735,6 +738,7 @@ export default class Tooltip { const w = this.w const xcrosshairs = this.getElXCrosshairs() + w.globals.dom.baseEl.classList.remove('apexcharts-tooltip-active') opt.tooltipEl.classList.remove('apexcharts-active') this.deactivateHoverFilter() diff --git a/src/modules/tooltip/Utils.js b/src/modules/tooltip/Utils.js index 9bb16d889..441625478 100644 --- a/src/modules/tooltip/Utils.js +++ b/src/modules/tooltip/Utils.js @@ -1,4 +1,5 @@ import Utilities from '../../utils/Utils' +import Graphics from '../Graphics' /** * ApexCharts Tooltip.Utils Class to support Tooltip functionality. @@ -310,6 +311,13 @@ export default class Utils { return markers.length > 0 } + getPathFromPoint(point, size) { + let cx = Number(point.getAttribute('cx')) + let cy = Number(point.getAttribute('cy')) + let shape = point.getAttribute('shape') + return new Graphics(this.ctx).getMarkerPath(cx, cy, shape, size) + } + getElBars() { return this.w.globals.dom.baseEl.querySelectorAll( '.apexcharts-bar-series, .apexcharts-candlestick-series, .apexcharts-boxPlot-series, .apexcharts-rangebar-series' diff --git a/types/apexcharts.d.ts b/types/apexcharts.d.ts index df8d640c3..22bc9f8b1 100644 --- a/types/apexcharts.d.ts +++ b/types/apexcharts.d.ts @@ -437,7 +437,6 @@ type PointAnnotations = { shape?: string offsetX?: number offsetY?: number - radius?: number cssClass?: string } label?: AnnotationLabel @@ -831,7 +830,6 @@ type ApexLegend = { shape?: ApexMarkerShape offsetX?: number offsetY?: number - radius?: number customHTML?(): any onClick?(): void } @@ -851,7 +849,7 @@ type ApexLegend = { } } -type MarkerShapeOptions = "circle" | "square" | "rect" | "line" | 'cross' | 'plus' +type MarkerShapeOptions = "circle" | "square" | "rect" | "line" | 'cross' | 'plus' | 'star' | 'sparkle' | 'diamond' | 'triangle' type ApexMarkerShape = MarkerShapeOptions | MarkerShapeOptions[] @@ -873,8 +871,7 @@ type ApexMarkers = { strokeDashArray?: number | number[] fillOpacity?: number | number[] discrete?: ApexDiscretePoint[] - shape?: ApexMarkerShape - radius?: number + shape?: ApexMarkerShape offsetX?: number offsetY?: number showNullDataPoints?: boolean From 260567fd6499cd423283a92ce42af5e58725f42a Mon Sep 17 00:00:00 2001 From: Juned Chhipa Date: Thu, 18 Jul 2024 22:24:39 +0530 Subject: [PATCH 2/8] update scatter demo to show different marker shapes --- samples/react/scatter/scatter-datetime.html | 136 +++++++++++++---- samples/source/scatter/scatter-datetime.xml | 143 ++++++++++++++---- .../vanilla-js/scatter/scatter-datetime.html | 136 +++++++++++++---- samples/vue/scatter/scatter-datetime.html | 136 +++++++++++++---- 4 files changed, 423 insertions(+), 128 deletions(-) diff --git a/samples/react/scatter/scatter-datetime.html b/samples/react/scatter/scatter-datetime.html index 84f50547b..425684385 100644 --- a/samples/react/scatter/scatter-datetime.html +++ b/samples/react/scatter/scatter-datetime.html @@ -4,14 +4,14 @@ - Timeline Scatter Chart + Scatter chart with different marker shapes