mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-21 09:16:41 +02:00
dynamically resizes vertical xychart bar
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
This commit is contained in:
@@ -50,6 +50,16 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
|||||||
|
|
||||||
const groups: Record<string, any> = {};
|
const groups: Record<string, any> = {};
|
||||||
|
|
||||||
|
interface BarItem {
|
||||||
|
data: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
function getGroup(gList: string[]) {
|
function getGroup(gList: string[]) {
|
||||||
let elem = group;
|
let elem = group;
|
||||||
let prefix = '';
|
let prefix = '';
|
||||||
@@ -121,33 +131,70 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
|
|||||||
.enter()
|
.enter()
|
||||||
.append('text')
|
.append('text')
|
||||||
.attr('x', (data) => data.x + data.width - longestTextWidth - 5)
|
.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('text-anchor', 'start')
|
||||||
.attr('dominant-baseline', 'middle')
|
.attr('dominant-baseline', 'middle')
|
||||||
.attr('fill', 'black')
|
.attr('fill', 'black')
|
||||||
.attr('font-size', `${uniformFontSize}px`)
|
.attr('font-size', `${uniformFontSize}px`)
|
||||||
.text((data, index) => labelData[index]);
|
.text((data, index) => labelData[index]);
|
||||||
} else {
|
} else {
|
||||||
// Compute candidate font sizes for each bar using width only.
|
const yOffset = 10;
|
||||||
const candidateFontSizes = shape.data.map((data, index) => {
|
|
||||||
const label = labelData[index].toString();
|
// filter out bars that have zero width or height.
|
||||||
return data.width / (label.length * 0.6);
|
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));
|
const uniformFontSize = Math.floor(Math.min(...candidateFontSizes));
|
||||||
|
|
||||||
|
// Render text only for valid items.
|
||||||
shapeGroup
|
shapeGroup
|
||||||
.selectAll('text')
|
.selectAll('text')
|
||||||
.data(shape.data)
|
.data(validItems)
|
||||||
.enter()
|
.enter()
|
||||||
.append('text')
|
.append('text')
|
||||||
.attr('x', (data) => data.x + data.width / 2)
|
.attr('x', (item) => item.data.x + item.data.width / 2)
|
||||||
.attr('y', (data) => data.y + 25)
|
.attr('y', (item) => item.data.y + yOffset)
|
||||||
.attr('text-anchor', 'middle')
|
.attr('text-anchor', 'middle')
|
||||||
|
.attr('dominant-baseline', 'hanging')
|
||||||
.attr('fill', 'black')
|
.attr('fill', 'black')
|
||||||
.attr('font-size', `${uniformFontSize}px`)
|
.attr('font-size', `${uniformFontSize}px`)
|
||||||
.text((data, index) => labelData[index]);
|
.text((item) => item.label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
Reference in New Issue
Block a user