Skip to content

Commit 88cdd3d

Browse files
committed
fix(sankey): address PR review feedback
- Restore SankeyLinkColor gradient meta:enum and default that were accidentally deleted - Rename labelStyle 'default' to 'outlined', default to 'legacy' (non-breaking) - Restore original position-based label positioning for legacy mode - Validate nodeColors values as CSS colors in sanitizeDirective - Use theme variables instead of hardcoded colors in styles.js - Add changeset
1 parent 2be4bf3 commit 88cdd3d

8 files changed

Lines changed: 60 additions & 37 deletions

File tree

.changeset/sankey-apple-style.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'mermaid': minor
3+
---
4+
5+
feat(sankey): add outlined label style, configurable nodeWidth/nodePadding, and custom node colors

cypress/integration/rendering/sankey.spec.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,13 @@ describe('Sankey Diagram', () => {
152152
).as('graph');
153153
});
154154

155-
it('should render with outlined style by default', function () {
155+
it('should render with legacy style by default', function () {
156156
renderGraph(this.graph, { sankey: {} });
157157

158-
// Default style should create background and foreground label elements
159-
cy.get('.node-labels .sankey-label-bg').should('exist');
160-
cy.get('.node-labels .sankey-label-fg').should('exist');
158+
// Default (legacy) style should not have the outlined label classes
159+
cy.get('.node-labels .sankey-label-bg').should('not.exist');
160+
cy.get('.node-labels .sankey-label-fg').should('not.exist');
161+
cy.get('.node-labels text').should('exist');
161162
});
162163

163164
it('should render legacy (plain) labels when labelStyle is legacy', function () {
@@ -169,8 +170,8 @@ describe('Sankey Diagram', () => {
169170
cy.get('.node-labels text').should('exist');
170171
});
171172

172-
it('should render outlined labels when labelStyle is default', function () {
173-
renderGraph(this.graph, { sankey: { labelStyle: 'default' } });
173+
it('should render outlined labels when labelStyle is outlined', function () {
174+
renderGraph(this.graph, { sankey: { labelStyle: 'outlined' } });
174175

175176
cy.get('.node-labels .sankey-label-bg').should('exist');
176177
cy.get('.node-labels .sankey-label-fg').should('exist');
@@ -228,7 +229,7 @@ describe('Sankey Diagram', () => {
228229
Op Profit,Tax,19
229230
Op Profit,Net Profit,100
230231
`,
231-
{ sankey: { width: 800, height: 500, labelStyle: 'default' } }
232+
{ sankey: { width: 800, height: 500, labelStyle: 'outlined' } }
232233
);
233234
});
234235
});
@@ -298,7 +299,7 @@ describe('Sankey Diagram', () => {
298299
sankey: {
299300
width: 800,
300301
height: 500,
301-
labelStyle: 'default',
302+
labelStyle: 'outlined',
302303
showValues: true,
303304
prefix: '$',
304305
suffix: 'B',

packages/mermaid/src/config.type.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export type DOMPurifyConfiguration = import('dompurify').Config;
5757
* This interface was referenced by `MermaidConfig`'s JSON-Schema
5858
* via the `definition` "SankeyLabelStyle".
5959
*/
60-
export type SankeyLabelStyle = 'default' | 'legacy';
60+
export type SankeyLabelStyle = 'legacy' | 'outlined';
6161
/**
6262
* The font size to use
6363
*/
@@ -1614,7 +1614,7 @@ export interface SankeyDiagramConfig extends BaseDiagramConfig {
16141614
* The style of labels in the sankey diagram. 'outlined' provides better readability with a white stroke behind the text.
16151615
*
16161616
*/
1617-
labelStyle?: 'default' | 'legacy';
1617+
labelStyle?: 'legacy' | 'outlined';
16181618
/**
16191619
* A mapping of node IDs to their colors. Nodes not specified will use the default color scheme.
16201620
*

packages/mermaid/src/defaultConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ const config: RequiredDeep<MermaidConfig> = {
273273
},
274274
sankey: {
275275
...defaultConfigJson.sankey,
276-
// Define nodeColors so it's included in configKeys (allows it through sanitizeDirective)
276+
// Set so that `configKeys` includes this key for sanitizeDirective
277277
nodeColors: undefined,
278278
},
279279
treemap: {

packages/mermaid/src/diagrams/sankey/sankeyRenderer.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb
9292
// New config options with defaults
9393
const nodeWidth = conf?.nodeWidth ?? defaultSankeyConfig.nodeWidth ?? 10;
9494
const nodePadding = conf?.nodePadding ?? defaultSankeyConfig.nodePadding ?? 12;
95-
const labelStyle = conf?.labelStyle ?? defaultSankeyConfig.labelStyle ?? 'default';
95+
const labelStyle = conf?.labelStyle ?? defaultSankeyConfig.labelStyle ?? 'legacy';
9696
const nodeColors: Record<string, string> = conf?.nodeColors ?? {};
9797

9898
// Prepare data for construction based on diagObj.db
@@ -173,29 +173,34 @@ export const draw = function (text: string, id: string, _version: string, diagOb
173173
};
174174

175175
/**
176-
* Determines label position based on node layer relative to central node.
177-
* Left-of-center nodes get labels on the left, right-of-center on the right.
176+
* Determines label position based on node position or layer.
177+
* For 'outlined' style, uses layer-based positioning relative to central node.
178+
* For 'legacy' style, uses position-based positioning (original behavior).
178179
*
179-
* @param d - Node data with layer information
180+
* @param d - Node data with layer/position information
180181
* @returns Object with x position and text-anchor
181182
*/
182183
const getLabelPosition = (d: any): { x: number; anchor: string } => {
183-
const nodeLayer = d.layer ?? 0;
184-
185-
if (nodeLayer < centralNodeLayer) {
186-
// Left of center: label on the left
187-
return { x: d.x0 - 6, anchor: 'end' };
188-
} else {
189-
// Right of center (or at center): label on the right
184+
if (labelStyle === 'outlined') {
185+
// Layer-based: left-of-center nodes get labels on the left
186+
const nodeLayer = d.layer ?? 0;
187+
if (nodeLayer < centralNodeLayer) {
188+
return { x: d.x0 - 6, anchor: 'end' };
189+
}
190+
return { x: d.x1 + 6, anchor: 'start' };
191+
}
192+
// Legacy: position-based (original behavior)
193+
if (d.x0 < width / 2) {
190194
return { x: d.x1 + 6, anchor: 'start' };
191195
}
196+
return { x: d.x0 - 6, anchor: 'end' };
192197
};
193198

194199
// Create labels for nodes
195200
const labelsGroup = svg.append('g').attr('class', 'node-labels').attr('font-size', 14);
196201

197-
if (labelStyle === 'default') {
198-
// Default style: render background stroke first, then foreground text for readability
202+
if (labelStyle === 'outlined') {
203+
// Outlined style: render background stroke first, then foreground text for readability
199204
// Background text with white stroke for readability
200205
labelsGroup
201206
.selectAll('.sankey-label-bg')

packages/mermaid/src/diagrams/sankey/styles.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ const getStyles = (options) =>
99
1010
/* Outlined label style - background stroke for better readability */
1111
.sankey-label-bg {
12-
stroke: rgba(255, 255, 255, 0.9);
12+
stroke: ${options.mainBkg || options.background || '#fff'};
1313
stroke-width: 4px;
1414
stroke-linejoin: round;
1515
paint-order: stroke;
1616
}
1717
1818
/* Foreground label text */
1919
.sankey-label-fg {
20-
fill: ${options.textColor || '#1d1d1f'};
20+
fill: ${options.textColor};
2121
}
2222
2323
/* Node styling */

packages/mermaid/src/schemas/config.schema.yaml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2219,17 +2219,19 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
22192219
meta:enum:
22202220
source: Use the source of sankey link for the color of the link.
22212221
target: Use the target of sankey link for the color of the link.
2222+
gradient: Use a gradient of the source and target for the color of the link.
2223+
default: gradient
22222224
SankeyLabelStyle:
22232225
description: |
22242226
The style of labels in the sankey diagram.
22252227
type: string
22262228
enum:
2227-
- default
22282229
- legacy
2230+
- outlined
22292231
meta:enum:
2230-
default: Labels with a white outline for better readability against colored backgrounds.
22312232
legacy: Plain text labels (original style).
2232-
default: default
2233+
outlined: Labels with a white outline for better readability against colored backgrounds.
2234+
default: legacy
22332235

22342236
SankeyNodeAlignment:
22352237
description: |
@@ -2303,9 +2305,9 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
23032305
default: 12
23042306
labelStyle:
23052307
description: |
2306-
The style of labels in the sankey diagram. 'default' provides better readability with a white stroke behind the text.
2308+
The style of labels in the sankey diagram. 'outlined' provides better readability with a white stroke behind the text.
23072309
$ref: '#/$defs/SankeyLabelStyle'
2308-
default: default
2310+
default: legacy
23092311
nodeColors:
23102312
description: |
23112313
A mapping of node IDs to their colors. Nodes not specified will use the default color scheme.

packages/mermaid/src/utils/sanitizeDirective.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,22 @@ export const sanitizeDirective = (args: any): void => {
3535
continue;
3636
}
3737

38-
// Recurse if an object, but skip known dictionary-style configs
39-
// that allow arbitrary keys (like nodeColors for sankey diagrams)
40-
const dictionaryConfigKeys = ['nodeColors'];
41-
if (typeof args[key] === 'object' && !dictionaryConfigKeys.includes(key)) {
42-
log.debug('sanitizing object', key);
43-
sanitizeDirective(args[key]);
38+
// Recurse if an object, but handle dictionary-style configs specially
39+
// (like nodeColors for sankey diagrams) by validating values as CSS colors
40+
if (typeof args[key] === 'object') {
41+
if (key === 'nodeColors') {
42+
// Validate each value is a valid CSS color
43+
const colorPattern = /^#[\dA-Fa-f]{3,8}$|^rgb\([\d\s,.%]+\)$|^hsl\([\d\s,.%]+\)$|^[a-z]+$/i;
44+
for (const colorKey of Object.keys(args[key])) {
45+
if (typeof args[key][colorKey] !== 'string' || !colorPattern.test(args[key][colorKey])) {
46+
log.debug('sanitize deleting invalid color:', colorKey, args[key][colorKey]);
47+
delete args[key][colorKey];
48+
}
49+
}
50+
} else {
51+
log.debug('sanitizing object', key);
52+
sanitizeDirective(args[key]);
53+
}
4454
continue;
4555
}
4656

0 commit comments

Comments
 (0)