From 34e756fde61d567dc924f916ba0c6ba0272ac975 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Thu, 27 Mar 2025 20:32:35 -0400 Subject: [PATCH 01/18] add label config Co-authored-by: pranavm2109 --- packages/mermaid/src/config.type.ts | 4 ++++ packages/mermaid/src/schemas/config.schema.yaml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 638847547..b5d1f03d4 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -935,6 +935,10 @@ export interface XYChartConfig extends BaseDiagramConfig { * Top and bottom space from the chart title */ titlePadding?: number; + /** + * Should show the data label on the chart + */ + showDataLabel?: boolean; /** * Should show the chart title */ diff --git a/packages/mermaid/src/schemas/config.schema.yaml b/packages/mermaid/src/schemas/config.schema.yaml index e36b00bf9..9c7ffeb5a 100644 --- a/packages/mermaid/src/schemas/config.schema.yaml +++ b/packages/mermaid/src/schemas/config.schema.yaml @@ -1228,6 +1228,10 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file) type: number default: 10 minimum: 0 + showDataLabel: + description: Should show the data label on the chart + type: boolean + default: true showTitle: description: Should show the chart title type: boolean From 99a2dc7c1f1e926d24ed87c93c4e104c68d8c854 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Thu, 27 Mar 2025 21:51:21 -0400 Subject: [PATCH 02/18] adds label data to bar chart Co-authored-by: pranavm2109 --- .../diagrams/xychart/chartBuilder/interfaces.ts | 1 + packages/mermaid/src/diagrams/xychart/xychartDb.ts | 5 +++++ .../src/diagrams/xychart/xychartRenderer.ts | 14 ++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts index 3d188895f..2a98c80ed 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts @@ -93,6 +93,7 @@ export interface XYChartConfig { titleFontSize: number; titlePadding: number; showTitle: boolean; + showLabelData?: boolean; xAxis: XYChartAxisConfig; yAxis: XYChartAxisConfig; chartOrientation: 'vertical' | 'horizontal'; diff --git a/packages/mermaid/src/diagrams/xychart/xychartDb.ts b/packages/mermaid/src/diagrams/xychart/xychartDb.ts index fb2435df2..9ad7cd420 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartDb.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartDb.ts @@ -195,6 +195,10 @@ function getChartConfig() { return xyChartConfig; } +function getXYChartData() { + return xyChartData; +} + const clear = function () { commonClear(); plotIndex = 0; @@ -226,4 +230,5 @@ export default { setTmpSVGG, getChartThemeConfig, getChartConfig, + getXYChartData, }; diff --git a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts index 1f4d36e8a..cebdce9f8 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts @@ -14,6 +14,7 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram const db = diagObj.db as typeof XYChartDB; const themeConfig = db.getChartThemeConfig(); const chartConfig = db.getChartConfig(); + const labelData = db.getXYChartData().plots[0].data.map((data) => data[1]); function getDominantBaseLine(horizontalPos: TextVerticalPos) { return horizontalPos === 'top' ? 'text-before-edge' : 'middle'; } @@ -87,6 +88,19 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram .attr('fill', (data) => data.fill) .attr('stroke', (data) => data.strokeFill) .attr('stroke-width', (data) => data.strokeWidth); + + if (chartConfig.showLabelData) { + shapeGroup + .selectAll('text') + .data(shape.data) + .enter() + .append('text') + .attr('x', (data) => data.x + data.width / 2) // Center text horizontally + .attr('y', (data) => data.y + 25) // Position text 5 pixels above the rect + .attr('text-anchor', 'middle') + .attr('fill', 'black') + .text((data, index) => labelData[index]); + } break; case 'text': shapeGroup From 17fcf43cdb5029717327bd9fb201bd1677c672ff Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Thu, 27 Mar 2025 22:14:07 -0400 Subject: [PATCH 03/18] fix typo Co-authored-by: pranavm2109 --- .../mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts | 2 +- packages/mermaid/src/diagrams/xychart/xychartRenderer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts b/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts index 2a98c80ed..5f6f862ee 100644 --- a/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts +++ b/packages/mermaid/src/diagrams/xychart/chartBuilder/interfaces.ts @@ -93,7 +93,7 @@ export interface XYChartConfig { titleFontSize: number; titlePadding: number; showTitle: boolean; - showLabelData?: boolean; + showDataLabel: boolean; xAxis: XYChartAxisConfig; yAxis: XYChartAxisConfig; chartOrientation: 'vertical' | 'horizontal'; diff --git a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts index cebdce9f8..0f9670900 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts @@ -89,7 +89,7 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram .attr('stroke', (data) => data.strokeFill) .attr('stroke-width', (data) => data.strokeWidth); - if (chartConfig.showLabelData) { + if (chartConfig.showDataLabel) { shapeGroup .selectAll('text') .data(shape.data) From f2f2a1d27554588f816e9e3396d0bf9545a40099 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Thu, 27 Mar 2025 23:09:27 -0400 Subject: [PATCH 04/18] adds test and horizontal support Co-authored-by: pranavm2109 --- cypress/integration/rendering/xyChart.spec.js | 110 ++++++++++++++++++ .../src/diagrams/xychart/xychartRenderer.ts | 34 ++++-- 2 files changed, 134 insertions(+), 10 deletions(-) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index 1245760e8..dc3ba2235 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -179,6 +179,7 @@ describe('XY Chart', () => { axisLineWidth: 5 chartOrientation: horizontal plotReservedSpacePercent: 60 + showDataLabel: true --- xychart-beta title "Sales Revenue" @@ -315,4 +316,113 @@ describe('XY Chart', () => { ); cy.get('svg'); }); + it('should render bar labels by default', () => { + imgSnapshotTest( + ` + xychart-beta + title "Default Label Chart" + x-axis Categories [A, B, C] + y-axis "Values" 0 --> 100 + bar [10, 50, 90] + `, + {} + ); + }); + + it('should not render bar labels when showLabelData is false', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showLabelData: false + --- + xychart-beta + title "No Label Chart" + x-axis Categories [A, B, C] + y-axis "Values" 0 --> 100 + bar [10, 50, 90] + `, + {} + ); + }); + + it('should render horizontal bar chart with labels', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + chartOrientation: horizontal + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec] + y-axis "Revenue (in $)" 4000 --> 11000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] + `, + {} + ); + }); + + it('should render horizontal bar chart without labels', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: false + chartOrientation: horizontal + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec] + y-axis "Revenue (in $)" 4000 --> 11000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] + `, + {} + ); + }); + + it('should render multiple bar plots with labels correctly', () => { + // In this test, we create two bar plots. The default config should render labels for each. + imgSnapshotTest( + ` + xychart-beta + title "Multiple Bar Plots" + x-axis Categories [A, B, C] + y-axis "Values" 0 --> 100 + bar [10, 50, 90] + bar [20, 60, 80] + `, + {} + ); + }); + + it('should render a single bar with label', () => { + imgSnapshotTest( + ` + xychart-beta + title "Single Bar Chart" + x-axis Categories [A] + y-axis "Value" 0 --> 100 + bar [75] + `, + {} + ); + }); + + it('should render negative and decimal values with correct labels', () => { + imgSnapshotTest( + ` + xychart-beta + title "Decimal and Negative Values" + x-axis Categories [A, B, C] + y-axis -10 --> 10 + bar [ -2.5, 0.75, 5.1 ] + `, + {} + ); + }); }); diff --git a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts index 0f9670900..c01fbe9f7 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts @@ -90,16 +90,30 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram .attr('stroke-width', (data) => data.strokeWidth); if (chartConfig.showDataLabel) { - shapeGroup - .selectAll('text') - .data(shape.data) - .enter() - .append('text') - .attr('x', (data) => data.x + data.width / 2) // Center text horizontally - .attr('y', (data) => data.y + 25) // Position text 5 pixels above the rect - .attr('text-anchor', 'middle') - .attr('fill', 'black') - .text((data, index) => labelData[index]); + if (chartConfig.chartOrientation === 'horizontal') { + shapeGroup + .selectAll('text') + .data(shape.data) + .enter() + .append('text') + .attr('x', (data) => data.x + data.width - 50) + .attr('y', (data) => data.y + data.height / 2 + 1) + .attr('text-anchor', 'start') + .attr('dominant-baseline', 'middle') + .attr('fill', 'black') + .text((data, index) => labelData[index]); + } else { + shapeGroup + .selectAll('text') + .data(shape.data) + .enter() + .append('text') + .attr('x', (data) => data.x + data.width / 2) + .attr('y', (data) => data.y + 25) + .attr('text-anchor', 'middle') + .attr('fill', 'black') + .text((data, index) => labelData[index]); + } } break; case 'text': From ad6f855f5ee5e4b0cca9cb0e1df41bb23a845242 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Fri, 28 Mar 2025 16:04:51 -0400 Subject: [PATCH 05/18] adds dynamic text adjustment Co-authored-by: pranavm2109 --- .../src/diagrams/xychart/xychartRenderer.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts index c01fbe9f7..9690319a6 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts @@ -91,18 +91,52 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram if (chartConfig.showDataLabel) { if (chartConfig.chartOrientation === 'horizontal') { + // Append a temporary group to measure the widths of the texts + const tempGroup = svg.append('g').attr('class', 'temp-label-group'); + // Append texts temporarily to measure their widths + const tempTexts = tempGroup + .selectAll('text') + .data(labelData) + .enter() + .append('text') + .attr('font-size', (data, i) => shape.data[i].height * 0.7) + .text((d) => d); + // Measure widths and determine the font size & actual widths + const measured = tempTexts.nodes().map((node, i) => { + const bbox = node.getBBox(); + return { + width: bbox.width, + height: bbox.height, + fontSize: shape.data[i].height * 0.7, + }; + }); + const uniformFontSize = Math.floor(Math.min(...measured.map((m) => m.fontSize))); + const longestTextWidth = Math.max(...measured.map((m) => m.width)); + // Clean up temp texts + tempGroup.remove(); + shapeGroup .selectAll('text') .data(shape.data) .enter() .append('text') - .attr('x', (data) => data.x + data.width - 50) + .attr('x', (data) => data.x + data.width - longestTextWidth - 5) .attr('y', (data) => data.y + data.height / 2 + 1) .attr('text-anchor', 'start') .attr('dominant-baseline', 'middle') .attr('fill', 'black') + .attr('font-size', `${uniformFontSize}px`) .text((data, index) => labelData[index]); } else { + // Compute candidate font sizes for each bar using width only. + const candidateFontSizes = shape.data.map((data, index) => { + const label = labelData[index].toString(); + return data.width / (label.length * 0.6); + }); + + // Use the smallest font size for uniformity. + const uniformFontSize = Math.floor(Math.min(...candidateFontSizes)); + shapeGroup .selectAll('text') .data(shape.data) @@ -112,6 +146,7 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram .attr('y', (data) => data.y + 25) .attr('text-anchor', 'middle') .attr('fill', 'black') + .attr('font-size', `${uniformFontSize}px`) .text((data, index) => labelData[index]); } } From f4c08a0c6f23ed7eabdd8788ea27e8b44f270879 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Wed, 2 Apr 2025 22:25:34 -0400 Subject: [PATCH 06/18] dynamically resizes vertical xychart bar Co-authored-by: pranavm2109 --- .../src/diagrams/xychart/xychartRenderer.ts | 67 ++++++++++++++++--- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts index 9690319a6..f17b0c1a4 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts @@ -50,6 +50,16 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram const groups: Record = {}; + interface BarItem { + data: { + x: number; + y: number; + width: number; + height: number; + }; + label: string; + } + function getGroup(gList: string[]) { let elem = group; let prefix = ''; @@ -121,33 +131,70 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram .enter() .append('text') .attr('x', (data) => data.x + data.width - longestTextWidth - 5) - .attr('y', (data) => data.y + data.height / 2 + 1) + .attr('y', (data) => data.y + data.height / 2 + 0.2 * data.height) .attr('text-anchor', 'start') .attr('dominant-baseline', 'middle') .attr('fill', 'black') .attr('font-size', `${uniformFontSize}px`) .text((data, index) => labelData[index]); } else { - // Compute candidate font sizes for each bar using width only. - const candidateFontSizes = shape.data.map((data, index) => { - const label = labelData[index].toString(); - return data.width / (label.length * 0.6); + const yOffset = 10; + + // filter out bars that have zero width or height. + const validItems = shape.data + .map((d, i) => ({ data: d, label: labelData[i].toString() })) + .filter((item) => item.data.width > 0 && item.data.height > 0); + + // Helper function that checks if the text with a given fontSize fits within the bar boundaries. + function fitsInBar(item: BarItem, fontSize: number, yOffset: number): boolean { + const { data, label } = item; + const charWidthFactor = 0.7; + const textWidth = fontSize * label.length * charWidthFactor; + + // Compute horizontal boundaries using the center. + const centerX = data.x + data.width / 2; + const leftEdge = centerX - textWidth / 2; + const rightEdge = centerX + textWidth / 2; + + // Check that text doesn't overflow horizontally. + const horizontalFits = leftEdge >= data.x && rightEdge <= data.x + data.width; + + // For vertical placement, we use 'dominant-baseline: hanging' so that y marks the top of the text. + // Thus, the bottom edge is y + yOffset + fontSize. + const verticalFits = data.y + yOffset + fontSize <= data.y + data.height; + + return horizontalFits && verticalFits; + } + + // For each valid item, start with a candidate font size based on the width, + // then reduce it until the text fits within both the horizontal and vertical boundaries. + const candidateFontSizes = validItems.map((item) => { + const { data, label } = item; + let fontSize = data.width / (label.length * 0.7); + + // Decrease the font size until the text fits or fontSize reaches 0. + while (!fitsInBar(item, fontSize, yOffset) && fontSize > 0) { + fontSize -= 1; + } + return fontSize; }); - // Use the smallest font size for uniformity. + // Choose the smallest candidate across all valid bars for uniformity. const uniformFontSize = Math.floor(Math.min(...candidateFontSizes)); + // Render text only for valid items. shapeGroup .selectAll('text') - .data(shape.data) + .data(validItems) .enter() .append('text') - .attr('x', (data) => data.x + data.width / 2) - .attr('y', (data) => data.y + 25) + .attr('x', (item) => item.data.x + item.data.width / 2) + .attr('y', (item) => item.data.y + yOffset) .attr('text-anchor', 'middle') + .attr('dominant-baseline', 'hanging') .attr('fill', 'black') .attr('font-size', `${uniformFontSize}px`) - .text((data, index) => labelData[index]); + .text((item) => item.label); } } break; From 2798e27b1e09a971b85382f99a7e60e1f684c326 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Wed, 2 Apr 2025 23:00:12 -0400 Subject: [PATCH 07/18] dynamically resizes horizontal xychart bar Co-authored-by: pranavm2109 --- .../src/diagrams/xychart/xychartRenderer.ts | 62 +++++++++++-------- .../mermaid/src/schemas/config.schema.yaml | 2 +- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts index f17b0c1a4..b86c8556e 100644 --- a/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts +++ b/packages/mermaid/src/diagrams/xychart/xychartRenderer.ts @@ -101,42 +101,50 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram if (chartConfig.showDataLabel) { if (chartConfig.chartOrientation === 'horizontal') { - // Append a temporary group to measure the widths of the texts - const tempGroup = svg.append('g').attr('class', 'temp-label-group'); - // Append texts temporarily to measure their widths - const tempTexts = tempGroup - .selectAll('text') - .data(labelData) - .enter() - .append('text') - .attr('font-size', (data, i) => shape.data[i].height * 0.7) - .text((d) => d); - // Measure widths and determine the font size & actual widths - const measured = tempTexts.nodes().map((node, i) => { - const bbox = node.getBBox(); - return { - width: bbox.width, - height: bbox.height, - fontSize: shape.data[i].height * 0.7, - }; + // Factor to approximate each character's width. + const charWidthFactor = 0.7; + + // Filter out bars that have zero width or height. + const validItems = shape.data + .map((d, i) => ({ data: d, label: labelData[i].toString() })) + .filter((item) => item.data.width > 0 && item.data.height > 0); + + // Helper function to check if the text fits horizontally with a 10px right margin. + function fitsHorizontally(item: BarItem, fontSize: number): boolean { + const { data, label } = item; + // Approximate the text width. + const textWidth: number = fontSize * label.length * charWidthFactor; + // The available width is the bar's width minus a 10px right margin. + return textWidth <= data.width - 10; + } + + // For each valid bar, start with an initial candidate font size (70% of the bar's height), + // then reduce it until the text fits horizontally. + const candidateFontSizes = validItems.map((item) => { + const { data } = item; + let fontSize = data.height * 0.7; + // Decrease fontSize until the text fits horizontally. + while (!fitsHorizontally(item, fontSize) && fontSize > 0) { + fontSize -= 1; + } + return fontSize; }); - const uniformFontSize = Math.floor(Math.min(...measured.map((m) => m.fontSize))); - const longestTextWidth = Math.max(...measured.map((m) => m.width)); - // Clean up temp texts - tempGroup.remove(); + + // Choose the smallest candidate font size across all valid bars for uniformity. + const uniformFontSize = Math.floor(Math.min(...candidateFontSizes)); shapeGroup .selectAll('text') - .data(shape.data) + .data(validItems) .enter() .append('text') - .attr('x', (data) => data.x + data.width - longestTextWidth - 5) - .attr('y', (data) => data.y + data.height / 2 + 0.2 * data.height) - .attr('text-anchor', 'start') + .attr('x', (item) => item.data.x + item.data.width - 10) + .attr('y', (item) => item.data.y + item.data.height / 2) + .attr('text-anchor', 'end') .attr('dominant-baseline', 'middle') .attr('fill', 'black') .attr('font-size', `${uniformFontSize}px`) - .text((data, index) => labelData[index]); + .text((item) => item.label); } else { const yOffset = 10; diff --git a/packages/mermaid/src/schemas/config.schema.yaml b/packages/mermaid/src/schemas/config.schema.yaml index 9c7ffeb5a..da95313d1 100644 --- a/packages/mermaid/src/schemas/config.schema.yaml +++ b/packages/mermaid/src/schemas/config.schema.yaml @@ -1231,7 +1231,7 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file) showDataLabel: description: Should show the data label on the chart type: boolean - default: true + default: false showTitle: description: Should show the chart title type: boolean From 8bdd7ec719e42407f1b3b51e2ee66e023c64d6a5 Mon Sep 17 00:00:00 2001 From: pranavm2109 Date: Fri, 4 Apr 2025 15:20:51 -0400 Subject: [PATCH 08/18] updated tests to account for showDataLabel set to false as default Co-authored-by: Shahir Ahmed --- cypress/integration/rendering/xyChart.spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index dc3ba2235..3ab42fa34 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -316,9 +316,15 @@ describe('XY Chart', () => { ); cy.get('svg'); }); - it('should render bar labels by default', () => { + it('should render bar labels when showDataLabel is set to true', () => { imgSnapshotTest( ` + --- + config: + themeVariables: + xyChart: + showDataLabel: true + --- xychart-beta title "Default Label Chart" x-axis Categories [A, B, C] @@ -329,14 +335,9 @@ describe('XY Chart', () => { ); }); - it('should not render bar labels when showLabelData is false', () => { + it('should not render bar labels by default', () => { imgSnapshotTest( ` - --- - config: - xyChart: - showLabelData: false - --- xychart-beta title "No Label Chart" x-axis Categories [A, B, C] @@ -372,7 +373,6 @@ describe('XY Chart', () => { --- config: xyChart: - showDataLabel: false chartOrientation: horizontal --- xychart-beta From 73f8dee643ac1a8069f30374406436c95d88f898 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Fri, 4 Apr 2025 15:22:40 -0400 Subject: [PATCH 09/18] add config to test files Co-authored-by: Pranav Mishra --- cypress/integration/rendering/xyChart.spec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index 3ab42fa34..de0e3b65e 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -389,6 +389,11 @@ describe('XY Chart', () => { // In this test, we create two bar plots. The default config should render labels for each. imgSnapshotTest( ` + --- + config: + xyChart: + showDataLabel: true + --- xychart-beta title "Multiple Bar Plots" x-axis Categories [A, B, C] @@ -403,6 +408,11 @@ describe('XY Chart', () => { it('should render a single bar with label', () => { imgSnapshotTest( ` + --- + config: + xyChart: + showDataLabel: true + --- xychart-beta title "Single Bar Chart" x-axis Categories [A] @@ -416,6 +426,11 @@ describe('XY Chart', () => { it('should render negative and decimal values with correct labels', () => { imgSnapshotTest( ` + --- + config: + xyChart: + showDataLabel: true + --- xychart-beta title "Decimal and Negative Values" x-axis Categories [A, B, C] From 8b7a4db2ef552c55e4c54a230ff28a20d60400e9 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Fri, 4 Apr 2025 21:34:46 -0400 Subject: [PATCH 10/18] test for horzontal xy-axis Co-authored-by: Pranav Mishra --- cypress/integration/rendering/xyChart.spec.js | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index de0e3b65e..e325cb2c2 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -440,4 +440,62 @@ describe('XY Chart', () => { {} ); }); + + it('should render data labels within the bar in the horizontal xy-chart', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + chartOrientation: horizontal + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan,b,c] + y-axis "Revenue (in $)" 4000 --> 12000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000,11000 ,5000,6000] + `, + {} + ); + + let textProps, barProps; + + cy.get('text') + .contains('5000') + .then(($el) => { + const bbox = $el[0].getBBox(); + textProps = { + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + }; + console.log('textProps', textProps); + }); + + cy.get('g.bar-plot-0') + .find('rect') + .first() + .then((rect) => { + barProps = { + x: parseFloat(rect.attr('x')), + y: parseFloat(rect.attr('y')), + width: parseFloat(rect.attr('width')), + height: parseFloat(rect.attr('height')), + }; + console.log('barProps', barProps); + }) + .then(() => { + // Ensure that both textProps and barProps are defined before the assertion + expect(textProps.x).to.be.greaterThan(barProps.x); + expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + expect(textProps.y).to.be.greaterThan(barProps.y); + expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + expect(textProps.y + textProps.height / 2).to.be.closeTo( + barProps.y + barProps.height / 2, + 1 + ); + }); + }); }); From a2bf5103ce82d62ced1fed014b57247f43a08489 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Fri, 4 Apr 2025 21:44:00 -0400 Subject: [PATCH 11/18] test for vertical xy-axis Co-authored-by: Pranav Mishra --- cypress/integration/rendering/xyChart.spec.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index e325cb2c2..2ba1924b0 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -490,6 +490,7 @@ describe('XY Chart', () => { // Ensure that both textProps and barProps are defined before the assertion expect(textProps.x).to.be.greaterThan(barProps.x); expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + expect(textProps.y).to.be.greaterThan(barProps.y); expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); expect(textProps.y + textProps.height / 2).to.be.closeTo( @@ -498,4 +499,56 @@ describe('XY Chart', () => { ); }); }); + + it('should render data labels within the bar in the horizontal xy-chart', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan,b,c] + y-axis "Revenue (in $)" 4000 --> 12000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000,11000 ,5000,6000] + `, + {} + ); + let textProps, barProps; + cy.get('text') + .contains('5000') + .then(($el) => { + const bbox = $el[0].getBBox(); + textProps = { + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + }; + console.log('textProps', textProps); + }); + cy.get('g.bar-plot-0') + .find('rect') + .first() + .then((rect) => { + barProps = { + x: parseFloat(rect.attr('x')), + y: parseFloat(rect.attr('y')), + width: parseFloat(rect.attr('width')), + height: parseFloat(rect.attr('height')), + }; + console.log('barProps', barProps); + }) + .then(() => { + // Ensure that both textProps and barProps are defined before the assertion + expect(textProps.x).to.be.greaterThan(barProps.x); + expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + expect(textProps.x + textProps.width / 2).to.be.closeTo(barProps.x + barProps.width / 2, 1); + + expect(textProps.y).to.be.greaterThan(barProps.y); + expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + }); + }); }); From dff00f2c4ffb084b8bfbdb76f1e4896ef3294b4d Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Thu, 10 Apr 2025 21:04:55 -0400 Subject: [PATCH 12/18] added new tests Co-authored-by: Pranav Mishra --- cypress/integration/rendering/xyChart.spec.js | 184 +++++++++--------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index 2ba1924b0..344527319 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -441,114 +441,114 @@ describe('XY Chart', () => { ); }); - it('should render data labels within the bar in the horizontal xy-chart', () => { + it('should render data labels within each bar in the horizontal xy-chart', () => { imgSnapshotTest( ` - --- - config: - xyChart: - showDataLabel: true - chartOrientation: horizontal - --- - xychart-beta - title "Sales Revenue" - x-axis Months [jan,b,c] - y-axis "Revenue (in $)" 4000 --> 12000 - bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000,11000 ,5000,6000] - `, + --- + config: + xyChart: + showDataLabel: true + chartOrientation: horizontal + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan,b,c] + y-axis "Revenue (in $)" 4000 --> 12000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000, 11000, 5000, 6000] + `, {} ); - let textProps, barProps; - - cy.get('text') - .contains('5000') - .then(($el) => { - const bbox = $el[0].getBBox(); - textProps = { - x: bbox.x, - y: bbox.y, - width: bbox.width, - height: bbox.height, + cy.get('g.bar-plot-0').within(() => { + cy.get('rect').each(($rect, index) => { + // Extract bar properties + const barProps = { + x: parseFloat($rect.attr('x')), + y: parseFloat($rect.attr('y')), + width: parseFloat($rect.attr('width')), + height: parseFloat($rect.attr('height')), }; - console.log('textProps', textProps); - }); - cy.get('g.bar-plot-0') - .find('rect') - .first() - .then((rect) => { - barProps = { - x: parseFloat(rect.attr('x')), - y: parseFloat(rect.attr('y')), - width: parseFloat(rect.attr('width')), - height: parseFloat(rect.attr('height')), - }; - console.log('barProps', barProps); - }) - .then(() => { - // Ensure that both textProps and barProps are defined before the assertion - expect(textProps.x).to.be.greaterThan(barProps.x); - expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + // Get the text element corresponding to this bar by index. + cy.get('text') + .eq(index) + .then(($text) => { + const bbox = $text[0].getBBox(); + const textProps = { + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + }; - expect(textProps.y).to.be.greaterThan(barProps.y); - expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); - expect(textProps.y + textProps.height / 2).to.be.closeTo( - barProps.y + barProps.height / 2, - 1 - ); + // Verify that the text label is positioned within the boundaries of the bar. + expect(textProps.x).to.be.greaterThan(barProps.x); + expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + + expect(textProps.y).to.be.greaterThan(barProps.y); + expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + expect(textProps.y + textProps.height / 2).to.be.closeTo( + barProps.y + barProps.height / 2, + 1 + ); + }); }); + }); }); - it('should render data labels within the bar in the horizontal xy-chart', () => { + it('should render data labels within each bar in the vertical xy-chart', () => { imgSnapshotTest( ` - --- - config: - xyChart: - showDataLabel: true - --- - xychart-beta - title "Sales Revenue" - x-axis Months [jan,b,c] - y-axis "Revenue (in $)" 4000 --> 12000 - bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000,11000 ,5000,6000] - `, + --- + config: + xyChart: + showDataLabel: true + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan,b,c] + y-axis "Revenue (in $)" 4000 --> 12000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000, 11000, 5000, 6000] + `, {} ); - let textProps, barProps; - cy.get('text') - .contains('5000') - .then(($el) => { - const bbox = $el[0].getBBox(); - textProps = { - x: bbox.x, - y: bbox.y, - width: bbox.width, - height: bbox.height, - }; - console.log('textProps', textProps); - }); - cy.get('g.bar-plot-0') - .find('rect') - .first() - .then((rect) => { - barProps = { - x: parseFloat(rect.attr('x')), - y: parseFloat(rect.attr('y')), - width: parseFloat(rect.attr('width')), - height: parseFloat(rect.attr('height')), - }; - console.log('barProps', barProps); - }) - .then(() => { - // Ensure that both textProps and barProps are defined before the assertion - expect(textProps.x).to.be.greaterThan(barProps.x); - expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); - expect(textProps.x + textProps.width / 2).to.be.closeTo(barProps.x + barProps.width / 2, 1); - expect(textProps.y).to.be.greaterThan(barProps.y); - expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + cy.get('g.bar-plot-0').within(() => { + cy.get('rect').each(($rect, index) => { + // Extract bar properties + const barProps = { + x: parseFloat($rect.attr('x')), + y: parseFloat($rect.attr('y')), + width: parseFloat($rect.attr('width')), + height: parseFloat($rect.attr('height')), + }; + + // Get the text element corresponding to this bar by index. + cy.get('text') + .eq(index) + .then(($text) => { + const bbox = $text[0].getBBox(); + const textProps = { + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + }; + + // Verify that the text label is positioned within the boundaries of the bar. + expect(textProps.x).to.be.greaterThan(barProps.x); + expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + + // Check horizontal alignment (within tolerance) + expect(textProps.x + textProps.width / 2).to.be.closeTo( + barProps.x + barProps.width / 2, + 1 + ); + + expect(textProps.y).to.be.greaterThan(barProps.y); + expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + }); }); + }); }); }); From b00be59ea8dc1eaac054e32b2010faf4927f96a4 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Thu, 10 Apr 2025 21:22:36 -0400 Subject: [PATCH 13/18] added new tests Co-authored-by: Pranav Mishra --- cypress/integration/rendering/xyChart.spec.js | 222 ++++++++++++++++++ 1 file changed, 222 insertions(+) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index 344527319..418420e4f 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -551,4 +551,226 @@ describe('XY Chart', () => { }); }); }); + + it('should render data labels within each bar in the horizontal xy-chart with multiple bars of different sizes', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + chartOrientation: horizontal + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s] + y-axis "Revenue (in $)" 4000 --> 12000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 8000, 10000, 5000, 7600, 4999,11000 ,5000,6000] + `, + {} + ); + + cy.get('g.bar-plot-0').within(() => { + cy.get('rect').each(($rect, index) => { + // Extract bar properties + const barProps = { + x: parseFloat($rect.attr('x')), + y: parseFloat($rect.attr('y')), + width: parseFloat($rect.attr('width')), + height: parseFloat($rect.attr('height')), + }; + + // Get the text element corresponding to this bar by index. + cy.get('text') + .eq(index) + .then(($text) => { + const bbox = $text[0].getBBox(); + const textProps = { + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + }; + + // Verify that the text label is positioned within the boundaries of the bar. + expect(textProps.x).to.be.greaterThan(barProps.x); + expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + + expect(textProps.y).to.be.greaterThan(barProps.y); + expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + expect(textProps.y + textProps.height / 2).to.be.closeTo( + barProps.y + barProps.height / 2, + 1 + ); + }); + }); + }); + }); + + it('should render data labels within each bar in the vertical xy-chart', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s] + y-axis "Revenue (in $)" 4000 --> 12000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 8000, 10000, 5000, 7600, 4999,11000 ,5000,6000] + `, + {} + ); + + cy.get('g.bar-plot-0').within(() => { + cy.get('rect').each(($rect, index) => { + // Extract bar properties + const barProps = { + x: parseFloat($rect.attr('x')), + y: parseFloat($rect.attr('y')), + width: parseFloat($rect.attr('width')), + height: parseFloat($rect.attr('height')), + }; + + // Get the text element corresponding to this bar by index. + cy.get('text') + .eq(index) + .then(($text) => { + const bbox = $text[0].getBBox(); + const textProps = { + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + }; + + // Verify that the text label is positioned within the boundaries of the bar. + expect(textProps.x).to.be.greaterThan(barProps.x); + expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + + // Check horizontal alignment (within tolerance) + expect(textProps.x + textProps.width / 2).to.be.closeTo( + barProps.x + barProps.width / 2, + 1 + ); + + expect(textProps.y).to.be.greaterThan(barProps.y); + expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + }); + }); + }); + }); + + it('should render data labels correctly for a bar in the horizontal xy-chart', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + chartOrientation: horizontal + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan] + y-axis "Revenue (in $)" 3000 --> 12000 + bar [4000] + `, + {} + ); + + cy.get('g.bar-plot-0').within(() => { + cy.get('rect').each(($rect, index) => { + // Extract bar properties + const barProps = { + x: parseFloat($rect.attr('x')), + y: parseFloat($rect.attr('y')), + width: parseFloat($rect.attr('width')), + height: parseFloat($rect.attr('height')), + }; + + // Get the text element corresponding to this bar by index. + cy.get('text') + .eq(index) + .then(($text) => { + const bbox = $text[0].getBBox(); + const textProps = { + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + }; + + // Verify that the text label is positioned within the boundaries of the bar. + expect(textProps.x).to.be.greaterThan(barProps.x); + expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + + expect(textProps.y).to.be.greaterThan(barProps.y); + expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + expect(textProps.y + textProps.height / 2).to.be.closeTo( + barProps.y + barProps.height / 2, + 1 + ); + }); + }); + }); + }); + + it('should render data labels correctly for a bar in the vertical xy-chart', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan] + y-axis "Revenue (in $)" 3000 --> 12000 + bar [4000] + `, + {} + ); + + cy.get('g.bar-plot-0').within(() => { + cy.get('rect').each(($rect, index) => { + // Extract bar properties + const barProps = { + x: parseFloat($rect.attr('x')), + y: parseFloat($rect.attr('y')), + width: parseFloat($rect.attr('width')), + height: parseFloat($rect.attr('height')), + }; + + // Get the text element corresponding to this bar by index. + cy.get('text') + .eq(index) + .then(($text) => { + const bbox = $text[0].getBBox(); + const textProps = { + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + }; + + // Verify that the text label is positioned within the boundaries of the bar. + expect(textProps.x).to.be.greaterThan(barProps.x); + expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + + // Check horizontal alignment (within tolerance) + expect(textProps.x + textProps.width / 2).to.be.closeTo( + barProps.x + barProps.width / 2, + 1 + ); + + expect(textProps.y).to.be.greaterThan(barProps.y); + expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + }); + }); + }); + }); }); From f69cc17795d8c309cf1620e2e54caff8aca7b7c8 Mon Sep 17 00:00:00 2001 From: Shahir Ahmed Date: Fri, 11 Apr 2025 16:24:10 -0400 Subject: [PATCH 14/18] refactor xy-chart tests Co-authored-by: Pranav Mishra --- cypress/integration/rendering/xyChart.spec.js | 339 ++++++++++-------- 1 file changed, 197 insertions(+), 142 deletions(-) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index 418420e4f..ebe9c7906 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -316,34 +316,21 @@ describe('XY Chart', () => { ); cy.get('svg'); }); - it('should render bar labels when showDataLabel is set to true', () => { - imgSnapshotTest( - ` - --- - config: - themeVariables: - xyChart: - showDataLabel: true - --- - xychart-beta - title "Default Label Chart" - x-axis Categories [A, B, C] - y-axis "Values" 0 --> 100 - bar [10, 50, 90] - `, - {} - ); - }); - it('should not render bar labels by default', () => { + it('should render vertical bar chart with labels', () => { imgSnapshotTest( ` - xychart-beta - title "No Label Chart" - x-axis Categories [A, B, C] - y-axis "Values" 0 --> 100 - bar [10, 50, 90] - `, + --- + config: + xyChart: + showDataLabel: true + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec] + y-axis "Revenue (in $)" 4000 --> 11000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] + `, {} ); }); @@ -367,7 +354,20 @@ describe('XY Chart', () => { ); }); - it('should render horizontal bar chart without labels', () => { + it('should render vertical bar chart without labels by default', () => { + imgSnapshotTest( + ` + xychart-beta + title "Sales Revenue" + x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec] + y-axis "Revenue (in $)" 4000 --> 11000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000] + `, + {} + ); + }); + + it('should render horizontal bar chart without labels by default', () => { imgSnapshotTest( ` --- @@ -385,8 +385,7 @@ describe('XY Chart', () => { ); }); - it('should render multiple bar plots with labels correctly', () => { - // In this test, we create two bar plots. The default config should render labels for each. + it('should render multiple bar plots vertically with labels correctly', () => { imgSnapshotTest( ` --- @@ -399,13 +398,31 @@ describe('XY Chart', () => { x-axis Categories [A, B, C] y-axis "Values" 0 --> 100 bar [10, 50, 90] - bar [20, 60, 80] `, {} ); }); - it('should render a single bar with label', () => { + it('should render multiple bar plots horizontally with labels correctly', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + chartOrientation: horizontal + --- + xychart-beta + title "Multiple Bar Plots" + x-axis Categories [A, B, C] + y-axis "Values" 0 --> 100 + bar [10, 50, 90] + `, + {} + ); + }); + + it('should render a single bar with label for a vertical xy-chart', () => { imgSnapshotTest( ` --- @@ -423,7 +440,26 @@ describe('XY Chart', () => { ); }); - it('should render negative and decimal values with correct labels', () => { + it('should render a single bar with label for a horizontal xy-chart', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + chartOrientation: horizontal + --- + xychart-beta + title "Single Bar Chart" + x-axis Categories [A] + y-axis "Value" 0 --> 100 + bar [75] + `, + {} + ); + }); + + it('should render negative and decimal values with correct labels for vertical xy-chart', () => { imgSnapshotTest( ` --- @@ -441,7 +477,7 @@ describe('XY Chart', () => { ); }); - it('should render data labels within each bar in the horizontal xy-chart', () => { + it('should render negative and decimal values with correct labels for horizontal xy-chart', () => { imgSnapshotTest( ` --- @@ -450,50 +486,14 @@ describe('XY Chart', () => { showDataLabel: true chartOrientation: horizontal --- - xychart-beta - title "Sales Revenue" - x-axis Months [jan,b,c] - y-axis "Revenue (in $)" 4000 --> 12000 - bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000, 11000, 5000, 6000] - `, + xychart-beta + title "Decimal and Negative Values" + x-axis Categories [A, B, C] + y-axis -10 --> 10 + bar [ -2.5, 0.75, 5.1 ] + `, {} ); - - cy.get('g.bar-plot-0').within(() => { - cy.get('rect').each(($rect, index) => { - // Extract bar properties - const barProps = { - x: parseFloat($rect.attr('x')), - y: parseFloat($rect.attr('y')), - width: parseFloat($rect.attr('width')), - height: parseFloat($rect.attr('height')), - }; - - // Get the text element corresponding to this bar by index. - cy.get('text') - .eq(index) - .then(($text) => { - const bbox = $text[0].getBBox(); - const textProps = { - x: bbox.x, - y: bbox.y, - width: bbox.width, - height: bbox.height, - }; - - // Verify that the text label is positioned within the boundaries of the bar. - expect(textProps.x).to.be.greaterThan(barProps.x); - expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); - - expect(textProps.y).to.be.greaterThan(barProps.y); - expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); - expect(textProps.y + textProps.height / 2).to.be.closeTo( - barProps.y + barProps.height / 2, - 1 - ); - }); - }); - }); }); it('should render data labels within each bar in the vertical xy-chart', () => { @@ -552,7 +552,118 @@ describe('XY Chart', () => { }); }); - it('should render data labels within each bar in the horizontal xy-chart with multiple bars of different sizes', () => { + it('should render data labels within each bar in the horizontal xy-chart', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + chartOrientation: horizontal + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan,b,c] + y-axis "Revenue (in $)" 4000 --> 12000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000, 11000, 5000, 6000] + `, + {} + ); + + cy.get('g.bar-plot-0').within(() => { + cy.get('rect').each(($rect, index) => { + // Extract bar properties + const barProps = { + x: parseFloat($rect.attr('x')), + y: parseFloat($rect.attr('y')), + width: parseFloat($rect.attr('width')), + height: parseFloat($rect.attr('height')), + }; + + // Get the text element corresponding to this bar by index. + cy.get('text') + .eq(index) + .then(($text) => { + const bbox = $text[0].getBBox(); + const textProps = { + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + }; + + // Verify that the text label is positioned within the boundaries of the bar. + expect(textProps.x).to.be.greaterThan(barProps.x); + expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + + expect(textProps.y).to.be.greaterThan(barProps.y); + expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + expect(textProps.y + textProps.height / 2).to.be.closeTo( + barProps.y + barProps.height / 2, + 1 + ); + }); + }); + }); + }); + + it('should render data labels within each bar in the vertical xy-chart with a lot of bars of different sizes', () => { + imgSnapshotTest( + ` + --- + config: + xyChart: + showDataLabel: true + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s] + y-axis "Revenue (in $)" 4000 --> 12000 + bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 8000, 10000, 5000, 7600, 4999,11000 ,5000,6000] + `, + {} + ); + + cy.get('g.bar-plot-0').within(() => { + cy.get('rect').each(($rect, index) => { + // Extract bar properties + const barProps = { + x: parseFloat($rect.attr('x')), + y: parseFloat($rect.attr('y')), + width: parseFloat($rect.attr('width')), + height: parseFloat($rect.attr('height')), + }; + + // Get the text element corresponding to this bar by index. + cy.get('text') + .eq(index) + .then(($text) => { + const bbox = $text[0].getBBox(); + const textProps = { + x: bbox.x, + y: bbox.y, + width: bbox.width, + height: bbox.height, + }; + + // Verify that the text label is positioned within the boundaries of the bar. + expect(textProps.x).to.be.greaterThan(barProps.x); + expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); + + // Check horizontal alignment (within tolerance) + expect(textProps.x + textProps.width / 2).to.be.closeTo( + barProps.x + barProps.width / 2, + 1 + ); + + expect(textProps.y).to.be.greaterThan(barProps.y); + expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); + }); + }); + }); + }); + + it('should render data labels within each bar in the horizontal xy-chart with a lot of bars of different sizes', () => { imgSnapshotTest( ` --- @@ -607,19 +718,19 @@ describe('XY Chart', () => { }); }); - it('should render data labels within each bar in the vertical xy-chart', () => { + it('should render data labels correctly for a bar in the vertical xy-chart', () => { imgSnapshotTest( ` - --- - config: - xyChart: - showDataLabel: true - --- - xychart-beta - title "Sales Revenue" - x-axis Months [jan,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s] - y-axis "Revenue (in $)" 4000 --> 12000 - bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 8000, 10000, 5000, 7600, 4999,11000 ,5000,6000] + --- + config: + xyChart: + showDataLabel: true + --- + xychart-beta + title "Sales Revenue" + x-axis Months [jan] + y-axis "Revenue (in $)" 3000 --> 12000 + bar [4000] `, {} ); @@ -717,60 +828,4 @@ describe('XY Chart', () => { }); }); }); - - it('should render data labels correctly for a bar in the vertical xy-chart', () => { - imgSnapshotTest( - ` - --- - config: - xyChart: - showDataLabel: true - --- - xychart-beta - title "Sales Revenue" - x-axis Months [jan] - y-axis "Revenue (in $)" 3000 --> 12000 - bar [4000] - `, - {} - ); - - cy.get('g.bar-plot-0').within(() => { - cy.get('rect').each(($rect, index) => { - // Extract bar properties - const barProps = { - x: parseFloat($rect.attr('x')), - y: parseFloat($rect.attr('y')), - width: parseFloat($rect.attr('width')), - height: parseFloat($rect.attr('height')), - }; - - // Get the text element corresponding to this bar by index. - cy.get('text') - .eq(index) - .then(($text) => { - const bbox = $text[0].getBBox(); - const textProps = { - x: bbox.x, - y: bbox.y, - width: bbox.width, - height: bbox.height, - }; - - // Verify that the text label is positioned within the boundaries of the bar. - expect(textProps.x).to.be.greaterThan(barProps.x); - expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width); - - // Check horizontal alignment (within tolerance) - expect(textProps.x + textProps.width / 2).to.be.closeTo( - barProps.x + barProps.width / 2, - 1 - ); - - expect(textProps.y).to.be.greaterThan(barProps.y); - expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); - }); - }); - }); - }); }); From 4bb635148916977d58155ea760f7a9d57d0414a0 Mon Sep 17 00:00:00 2001 From: pranavm2109 Date: Mon, 14 Apr 2025 13:53:18 -0400 Subject: [PATCH 15/18] modified description of showDataLabel in config schema and added it to docs Co-authored-by: Shahir Ahmed --- docs/syntax/xyChart.md | 25 +++++++++++-------- packages/mermaid/src/config.type.ts | 2 +- packages/mermaid/src/docs/syntax/xyChart.md | 24 ++++++++++-------- .../mermaid/src/schemas/config.schema.yaml | 2 +- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/docs/syntax/xyChart.md b/docs/syntax/xyChart.md index 7197b984d..235b4e337 100644 --- a/docs/syntax/xyChart.md +++ b/docs/syntax/xyChart.md @@ -107,17 +107,18 @@ xychart-beta ## Chart Configurations -| Parameter | Description | Default value | -| ------------------------ | ---------------------------------------------- | :-----------: | -| width | Width of the chart | 700 | -| height | Height of the chart | 500 | -| titlePadding | Top and Bottom padding of the title | 10 | -| titleFontSize | Title font size | 20 | -| showTitle | Title to be shown or not | true | -| xAxis | xAxis configuration | AxisConfig | -| yAxis | yAxis configuration | AxisConfig | -| chartOrientation | 'vertical' or 'horizontal' | 'vertical' | -| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 | +| Parameter | Description | Default value | +| ------------------------ | ------------------------------------------------------------- | :-----------: | +| width | Width of the chart | 700 | +| height | Height of the chart | 500 | +| titlePadding | Top and Bottom padding of the title | 10 | +| titleFontSize | Title font size | 20 | +| showTitle | Title to be shown or not | true | +| xAxis | xAxis configuration | AxisConfig | +| yAxis | yAxis configuration | AxisConfig | +| chartOrientation | 'vertical' or 'horizontal' | 'vertical' | +| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 | +| showDataLabel | Should show the value corresponding to the bar within the bar | false | ### AxisConfig @@ -163,6 +164,7 @@ config: xyChart: width: 900 height: 600 + showDataLabel: true themeVariables: xyChart: titleColor: "#ff0000" @@ -181,6 +183,7 @@ config: xyChart: width: 900 height: 600 + showDataLabel: true themeVariables: xyChart: titleColor: "#ff0000" diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 538629b27..30f195bc8 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -940,7 +940,7 @@ export interface XYChartConfig extends BaseDiagramConfig { */ titlePadding?: number; /** - * Should show the data label on the chart + * Should show the value corresponding to the bar within the bar */ showDataLabel?: boolean; /** diff --git a/packages/mermaid/src/docs/syntax/xyChart.md b/packages/mermaid/src/docs/syntax/xyChart.md index e6e969462..96a56e2a7 100644 --- a/packages/mermaid/src/docs/syntax/xyChart.md +++ b/packages/mermaid/src/docs/syntax/xyChart.md @@ -95,17 +95,18 @@ xychart-beta ## Chart Configurations -| Parameter | Description | Default value | -| ------------------------ | ---------------------------------------------- | :-----------: | -| width | Width of the chart | 700 | -| height | Height of the chart | 500 | -| titlePadding | Top and Bottom padding of the title | 10 | -| titleFontSize | Title font size | 20 | -| showTitle | Title to be shown or not | true | -| xAxis | xAxis configuration | AxisConfig | -| yAxis | yAxis configuration | AxisConfig | -| chartOrientation | 'vertical' or 'horizontal' | 'vertical' | -| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 | +| Parameter | Description | Default value | +| ------------------------ | ------------------------------------------------------------- | :-----------: | +| width | Width of the chart | 700 | +| height | Height of the chart | 500 | +| titlePadding | Top and Bottom padding of the title | 10 | +| titleFontSize | Title font size | 20 | +| showTitle | Title to be shown or not | true | +| xAxis | xAxis configuration | AxisConfig | +| yAxis | yAxis configuration | AxisConfig | +| chartOrientation | 'vertical' or 'horizontal' | 'vertical' | +| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 | +| showDataLabel | Should show the value corresponding to the bar within the bar | false | ### AxisConfig @@ -152,6 +153,7 @@ config: xyChart: width: 900 height: 600 + showDataLabel: true themeVariables: xyChart: titleColor: "#ff0000" diff --git a/packages/mermaid/src/schemas/config.schema.yaml b/packages/mermaid/src/schemas/config.schema.yaml index 4ae792d28..f668fd96b 100644 --- a/packages/mermaid/src/schemas/config.schema.yaml +++ b/packages/mermaid/src/schemas/config.schema.yaml @@ -1229,7 +1229,7 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file) default: 10 minimum: 0 showDataLabel: - description: Should show the data label on the chart + description: Should show the value corresponding to the bar within the bar type: boolean default: false showTitle: From 946452c7b46ab9b8faf679824131744bfb8c6605 Mon Sep 17 00:00:00 2001 From: pranavm2109 Date: Mon, 14 Apr 2025 14:38:23 -0400 Subject: [PATCH 16/18] updated tests Co-authored-by: Shahir Ahmed --- cypress/integration/rendering/xyChart.spec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js index ebe9c7906..a582355e8 100644 --- a/cypress/integration/rendering/xyChart.spec.js +++ b/cypress/integration/rendering/xyChart.spec.js @@ -542,7 +542,7 @@ describe('XY Chart', () => { // Check horizontal alignment (within tolerance) expect(textProps.x + textProps.width / 2).to.be.closeTo( barProps.x + barProps.width / 2, - 1 + 5 ); expect(textProps.y).to.be.greaterThan(barProps.y); @@ -600,7 +600,7 @@ describe('XY Chart', () => { expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); expect(textProps.y + textProps.height / 2).to.be.closeTo( barProps.y + barProps.height / 2, - 1 + 5 ); }); }); @@ -653,7 +653,7 @@ describe('XY Chart', () => { // Check horizontal alignment (within tolerance) expect(textProps.x + textProps.width / 2).to.be.closeTo( barProps.x + barProps.width / 2, - 1 + 5 ); expect(textProps.y).to.be.greaterThan(barProps.y); @@ -711,7 +711,7 @@ describe('XY Chart', () => { expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); expect(textProps.y + textProps.height / 2).to.be.closeTo( barProps.y + barProps.height / 2, - 1 + 5 ); }); }); @@ -764,7 +764,7 @@ describe('XY Chart', () => { // Check horizontal alignment (within tolerance) expect(textProps.x + textProps.width / 2).to.be.closeTo( barProps.x + barProps.width / 2, - 1 + 5 ); expect(textProps.y).to.be.greaterThan(barProps.y); @@ -822,7 +822,7 @@ describe('XY Chart', () => { expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height); expect(textProps.y + textProps.height / 2).to.be.closeTo( barProps.y + barProps.height / 2, - 1 + 5 ); }); }); From a1ba65c0c08432ec36e772570c3a5899cb57c102 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Mon, 14 Apr 2025 19:20:47 -0700 Subject: [PATCH 17/18] chore: Add changeset --- .changeset/soft-readers-tan.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/soft-readers-tan.md diff --git a/.changeset/soft-readers-tan.md b/.changeset/soft-readers-tan.md new file mode 100644 index 000000000..e01b68750 --- /dev/null +++ b/.changeset/soft-readers-tan.md @@ -0,0 +1,5 @@ +--- +"mermaid": minor +--- + +feat: Dynamically Render Data Labels Within Bar Charts From 11c3ac58fd0137bba35269dd8aeb045597488c71 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 02:25:23 +0000 Subject: [PATCH 18/18] [autofix.ci] apply automated fixes --- .changeset/soft-readers-tan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/soft-readers-tan.md b/.changeset/soft-readers-tan.md index e01b68750..ec3fa97af 100644 --- a/.changeset/soft-readers-tan.md +++ b/.changeset/soft-readers-tan.md @@ -1,5 +1,5 @@ --- -"mermaid": minor +'mermaid': minor --- feat: Dynamically Render Data Labels Within Bar Charts