diff --git a/samples/vanilla-js/column/border-radius-test.html b/samples/vanilla-js/column/border-radius-test.html new file mode 100644 index 000000000..e17ed7263 --- /dev/null +++ b/samples/vanilla-js/column/border-radius-test.html @@ -0,0 +1,149 @@ + + + + + + + Border Radius Test - Stacked Column + + + + + + + + + + + + + +
+ + + + diff --git a/src/assets/apexcharts.css b/src/assets/apexcharts.css index a558fc854..52b837a9e 100644 --- a/src/assets/apexcharts.css +++ b/src/assets/apexcharts.css @@ -681,3 +681,14 @@ rect.legend-mouseover-inactive, .apexcharts-rangebar-goals-markers { pointer-events: none } + +.apexcharts-flip-y { + transform: scaleY(-1) translateY(-100%); + transform-origin: top; + transform-box: fill-box; +} +.apexcharts-flip-x { + transform: scaleX(-1); + transform-origin: center; + transform-box: fill-box; +} \ No newline at end of file diff --git a/src/charts/Bar.js b/src/charts/Bar.js index 976170156..f2911fb89 100644 --- a/src/charts/Bar.js +++ b/src/charts/Bar.js @@ -318,6 +318,7 @@ class Bar { elBarShadows, visibleSeries, type, + classes, }) { const w = this.w const graphics = new Graphics(this.ctx) @@ -379,7 +380,7 @@ class Bar { animationDelay: delay, initialSpeed: w.config.chart.animations.speed, dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed, - className: `apexcharts-${type}-area`, + className: `apexcharts-${type}-area ${classes}`, chartType: type, }) diff --git a/src/charts/BarStacked.js b/src/charts/BarStacked.js index b6ba8c55f..4cd1dae6b 100644 --- a/src/charts/BarStacked.js +++ b/src/charts/BarStacked.js @@ -119,10 +119,8 @@ class BarStacked extends Bar { } for (let j = 0; j < w.globals.dataPoints; j++) { - const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex) const commonPathOpts = { indexes: { i, j, realIndex, translationsIndex, bc }, - strokeWidth, x, y, elSeries, @@ -169,6 +167,23 @@ class BarStacked extends Bar { let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex) + let classes = '' + + if (w.globals.isBarHorizontal) { + if ( + this.barHelpers.arrBorderRadius[realIndex][j] === 'bottom' && + w.globals.series[realIndex][j] > 0 + ) { + classes = 'apexcharts-flip-x' + } + } else { + if ( + this.barHelpers.arrBorderRadius[realIndex][j] === 'bottom' && + w.globals.series[realIndex][j] > 0 + ) { + classes = 'apexcharts-flip-y' + } + } elSeries = this.renderSeries({ realIndex, pathFill, @@ -177,7 +192,7 @@ class BarStacked extends Bar { columnGroupIndex, pathFrom: paths.pathFrom, pathTo: paths.pathTo, - strokeWidth, + strokeWidth: this.barHelpers.getStrokeWidth(i, j, realIndex), elSeries, x, y, @@ -188,6 +203,7 @@ class BarStacked extends Bar { elGoalsMarkers, type: 'bar', visibleSeries: columnGroupIndex, + classes, }) } @@ -291,7 +307,6 @@ class BarStacked extends Bar { drawStackedBarPaths({ indexes, barHeight, - strokeWidth, zeroW, x, y, @@ -355,7 +370,6 @@ class BarStacked extends Bar { barHeight, x1: barXPosition, x2: x, - strokeWidth, series: this.series, realIndex: indexes.realIndex, seriesGroup, @@ -518,7 +532,6 @@ class BarStacked extends Bar { y1: barYPosition, y2: y, yRatio: this.yRatio[translationsIndex], - strokeWidth: this.strokeWidth, series: this.series, seriesGroup, realIndex: indexes.realIndex, diff --git a/src/charts/Line.js b/src/charts/Line.js index b56c5dc16..9e1172477 100644 --- a/src/charts/Line.js +++ b/src/charts/Line.js @@ -953,8 +953,8 @@ class Line { // Check for single isolated point if (series[i][j + 1] === null) { - linePaths.push(linePath); - areaPaths.push(areaPath); + linePaths.push(linePath) + areaPaths.push(areaPath) // Stay in pathState = 0; break } @@ -1042,8 +1042,8 @@ class Line { // Check for single isolated point if (series[i][j + 1] === null) { - linePaths.push(linePath); - areaPaths.push(areaPath); + linePaths.push(linePath) + areaPaths.push(areaPath) // Stay in pathState = 0 break } diff --git a/src/charts/common/bar/Helpers.js b/src/charts/common/bar/Helpers.js index e098fea44..242667ac1 100644 --- a/src/charts/common/bar/Helpers.js +++ b/src/charts/common/bar/Helpers.js @@ -37,7 +37,7 @@ export default class Helpers { } } - this.arrBorderRadius = this.createBorderRadiusArr(w.globals.seriesPercent) + this.arrBorderRadius = this.createBorderRadiusArr(w.globals.series) if (this.barCtx.seriesLen === 0) { // A small adjustment when combo charts are used @@ -227,10 +227,7 @@ export default class Helpers { let strokeWidth = 0 const w = this.w - if ( - typeof this.barCtx.series[i][j] === 'undefined' || - this.barCtx.series[i][j] === null - ) { + if (!this.barCtx.series[i][j]) { this.barCtx.isNullValue = true } else { this.barCtx.isNullValue = false @@ -245,7 +242,7 @@ export default class Helpers { return strokeWidth } - createBorderRadiusArr(seriesPercent) { + createBorderRadiusArr(series) { const w = this.w const alwaysApplyRadius = @@ -253,29 +250,97 @@ export default class Helpers { w.config.plotOptions.bar.borderRadiusWhenStacked !== 'last' || w.config.plotOptions.bar.borderRadius <= 0 - const numRows = seriesPercent.length - const numCols = seriesPercent[0].length - - const resultArr = Array.from({ length: numRows }, () => - Array.from({ length: numCols }, () => alwaysApplyRadius) + const numSeries = series.length + const numColumns = series[0].length + const output = Array.from({ length: numSeries }, () => + Array(numColumns).fill(alwaysApplyRadius ? 'top' : 'none') ) - if (alwaysApplyRadius) return resultArr + if (alwaysApplyRadius) return output + + for (let j = 0; j < numColumns; j++) { + let positiveIndices = [] + let negativeIndices = [] + let nonZeroCount = 0 + + // Collect positive and negative indices + for (let i = 0; i < numSeries; i++) { + const value = series[i][j] + if (value > 0) { + positiveIndices.push(i) + nonZeroCount++ + } else if (value < 0) { + negativeIndices.push(i) + nonZeroCount++ + } + } - // For each column - for (let j = 0; j < numCols; j++) { - // Iterate from the last row upwards - for (let i = numRows - 1, found = false; i >= 0; i--) { - if (!found && seriesPercent[i][j] !== 0) { - resultArr[i][j] = true - found = true + if (positiveIndices.length > 0 && negativeIndices.length === 0) { + // Only positive values in this column + if (positiveIndices.length === 1) { + // Single positive value + output[positiveIndices[0]][j] = 'both' } else { - resultArr[i][j] = false + // Multiple positive values + const firstPositiveIndex = positiveIndices[0] + const lastPositiveIndex = positiveIndices[positiveIndices.length - 1] + for (let i of positiveIndices) { + if (i === firstPositiveIndex) { + output[i][j] = 'bottom' + } else if (i === lastPositiveIndex) { + output[i][j] = 'top' + } else { + output[i][j] = 'none' + } + } + } + } else if (negativeIndices.length > 0 && positiveIndices.length === 0) { + // Only negative values in this column + if (negativeIndices.length === 1) { + // Single negative value + output[negativeIndices[0]][j] = 'both' + } else { + // Multiple negative values + const firstNegativeIndex = negativeIndices[0] + const lastNegativeIndex = negativeIndices[negativeIndices.length - 1] + for (let i of negativeIndices) { + if (i === firstNegativeIndex) { + output[i][j] = 'bottom' + } else if (i === lastNegativeIndex) { + output[i][j] = 'top' + } else { + output[i][j] = 'none' + } + } } + } else if (positiveIndices.length > 0 && negativeIndices.length > 0) { + // Mixed positive and negative values + // Assign 'top' to the last positive bar + const lastPositiveIndex = positiveIndices[positiveIndices.length - 1] + for (let i of positiveIndices) { + if (i === lastPositiveIndex) { + output[i][j] = 'top' + } else { + output[i][j] = 'none' + } + } + // Assign 'bottom' to the last negative bar (closest to axis) + const lastNegativeIndex = negativeIndices[negativeIndices.length - 1] + for (let i of negativeIndices) { + if (i === lastNegativeIndex) { + output[i][j] = 'bottom' + } else { + output[i][j] = 'none' + } + } + } else if (nonZeroCount === 1) { + // Only one non-zero value (either positive or negative) + const index = positiveIndices[0] || negativeIndices[0] + output[index][j] = 'both' } } - return resultArr + return output } barBackground({ j, i, x1, x2, y1, y2, elSeries }) { @@ -313,7 +378,6 @@ export default class Helpers { barXPosition, y1, y2, - strokeWidth, seriesGroup, realIndex, i, @@ -321,10 +385,6 @@ export default class Helpers { w, }) { const graphics = new Graphics(this.barCtx.ctx) - strokeWidth = Array.isArray(strokeWidth) - ? strokeWidth[realIndex] - : strokeWidth - if (!strokeWidth) strokeWidth = 0 let bW = barWidth let bXP = barXPosition @@ -335,15 +395,12 @@ export default class Helpers { bW = barWidth + w.config.series[realIndex].data[j].columnWidthOffset } - // Center the stroke on the coordinates - let strokeCenter = strokeWidth / 2 - - const x1 = bXP + strokeCenter - const x2 = bXP + bW - strokeCenter + const x1 = bXP + const x2 = bXP + bW // append tiny pixels to avoid exponentials (which cause issues in border-radius) - y1 += 0.001 - strokeCenter - y2 += 0.001 + strokeCenter + y1 += 0.001 + y2 += 0.001 let pathTo = graphics.move(x1, y1) let pathFrom = graphics.move(x1, y1) @@ -357,8 +414,9 @@ export default class Helpers { pathTo + graphics.line(x1, y2) + graphics.line(x2, y2) + - graphics.line(x2, y1) + - (w.config.plotOptions.bar.borderRadiusApplication === 'around' + sl + + (w.config.plotOptions.bar.borderRadiusApplication === 'around' || + this.arrBorderRadius[realIndex][j] === 'both' ? ' Z' : ' z') @@ -373,11 +431,12 @@ export default class Helpers { sl + sl + graphics.line(x1, y1) + - (w.config.plotOptions.bar.borderRadiusApplication === 'around' + (w.config.plotOptions.bar.borderRadiusApplication === 'around' || + this.arrBorderRadius[realIndex][j] === 'both' ? ' Z' : ' z') - if (this.arrBorderRadius[realIndex][j]) { + if (this.arrBorderRadius[realIndex][j] !== 'none') { pathTo = graphics.roundPathCorners( pathTo, w.config.plotOptions.bar.borderRadius @@ -387,8 +446,8 @@ export default class Helpers { if (w.config.chart.stacked) { let _ctx = this.barCtx _ctx = this.barCtx[seriesGroup] - _ctx.yArrj.push(y2 - strokeCenter) - _ctx.yArrjF.push(Math.abs(y1 - y2 + strokeWidth)) + _ctx.yArrj.push(y2) + _ctx.yArrjF.push(Math.abs(y1 - y2)) _ctx.yArrjVal.push(this.barCtx.series[i][j]) } @@ -403,7 +462,6 @@ export default class Helpers { barHeight, x1, x2, - strokeWidth, seriesGroup, realIndex, i, @@ -411,10 +469,6 @@ export default class Helpers { w, }) { const graphics = new Graphics(this.barCtx.ctx) - strokeWidth = Array.isArray(strokeWidth) - ? strokeWidth[realIndex] - : strokeWidth - if (!strokeWidth) strokeWidth = 0 let bYP = barYPosition let bH = barHeight @@ -425,15 +479,12 @@ export default class Helpers { bH = barHeight + w.config.series[realIndex].data[j].barHeightOffset } - // Center the stroke on the coordinates - let strokeCenter = strokeWidth / 2 - - const y1 = bYP + strokeCenter - const y2 = bYP + bH - strokeCenter + const y1 = bYP + const y2 = bYP + bH // append tiny pixels to avoid exponentials (which cause issues in border-radius) - x1 += 0.001 - strokeCenter - x2 += 0.001 + strokeCenter + x1 += 0.001 + x2 += 0.001 let pathTo = graphics.move(x1, y1) let pathFrom = graphics.move(x1, y1) @@ -448,7 +499,8 @@ export default class Helpers { graphics.line(x2, y1) + graphics.line(x2, y2) + sl + - (w.config.plotOptions.bar.borderRadiusApplication === 'around' + (w.config.plotOptions.bar.borderRadiusApplication === 'around' || + this.arrBorderRadius[realIndex][j] === 'both' ? ' Z' : ' z') @@ -461,11 +513,12 @@ export default class Helpers { sl + sl + graphics.line(x1, y1) + - (w.config.plotOptions.bar.borderRadiusApplication === 'around' + (w.config.plotOptions.bar.borderRadiusApplication === 'around' || + this.arrBorderRadius[realIndex][j] === 'both' ? ' Z' : ' z') - if (this.arrBorderRadius[realIndex][j]) { + if (this.arrBorderRadius[realIndex][j] !== 'none') { pathTo = graphics.roundPathCorners( pathTo, w.config.plotOptions.bar.borderRadius @@ -475,7 +528,7 @@ export default class Helpers { if (w.config.chart.stacked) { let _ctx = this.barCtx _ctx = this.barCtx[seriesGroup] - _ctx.xArrj.push(x2 + strokeCenter) + _ctx.xArrj.push(x2) _ctx.xArrjF.push(Math.abs(x1 - x2)) _ctx.xArrjVal.push(this.barCtx.series[i][j]) } @@ -666,7 +719,8 @@ export default class Helpers { graphics.line(currX2, currY1) + graphics.line(currX1, currY1) + graphics.line(prevX1, prevY2) + - (w.config.plotOptions.bar.borderRadiusApplication === 'around' + (w.config.plotOptions.bar.borderRadiusApplication === 'around' || + this.arrBorderRadius[realIndex][j] === 'both' ? ' Z' : ' z') diff --git a/src/modules/Range.js b/src/modules/Range.js index f744967ce..2c2966383 100644 --- a/src/modules/Range.js +++ b/src/modules/Range.js @@ -279,7 +279,10 @@ class Range { gl.minY = lowestYInAllSeries } } else { - gl.minY = minYMaxY.minY + gl.minY = + gl.minY !== Number.MIN_VALUE + ? Math.min(minYMaxY.minY, gl.minY) + : minYMaxY.minY } cnf.yaxis.forEach((yaxe, index) => {