update at 2026-02-14 11:37:46
This commit is contained in:
@@ -359,6 +359,16 @@ function getSvgLabelPlacement(layout, isSource, labelPositionMode) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签纵坐标限制在画布安全区内,避免上下溢出。
|
||||
*/
|
||||
function getClampedLabelY(nodeY, nodeHeight, canvasHeight, chartPadding) {
|
||||
const rawY = nodeY + getNodeLabelCenterY(nodeHeight);
|
||||
const safeTop = Math.max(0, chartPadding + 6);
|
||||
const safeBottom = Math.max(safeTop, canvasHeight - chartPadding - 6);
|
||||
return Math.min(safeBottom, Math.max(safeTop, rawY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一生成桑基图布局数据,供 canvas 渲染与 SVG 导出共用。
|
||||
*/
|
||||
@@ -368,7 +378,9 @@ function buildSankeyLayout(links, width, height, renderOptions) {
|
||||
}
|
||||
|
||||
const nodeGap = clampNumber(renderOptions && renderOptions.nodeGap, 0, 60, 8);
|
||||
const padding = clampNumber(renderOptions && renderOptions.chartPadding, 0, 120, 16);
|
||||
const rawPadding = clampNumber(renderOptions && renderOptions.chartPadding, 0, 120, 16);
|
||||
// 纵向限制:padding 不能超过可视高度的一半,避免节点整体被挤出画布。
|
||||
const padding = Math.min(rawPadding, Math.max(0, height / 2 - 4));
|
||||
const targetAlignMode = (renderOptions && renderOptions.targetAlignMode) || 'between';
|
||||
const nodeWidth = 10;
|
||||
const leftX = padding;
|
||||
@@ -399,7 +411,7 @@ function buildSankeyLayout(links, width, height, renderOptions) {
|
||||
}
|
||||
|
||||
const sourceGapCount = Math.max(0, sourceNames.length - 1);
|
||||
const sourceContentHeight = Math.max(10, height - padding * 2 - sourceGapCount * nodeGap);
|
||||
const sourceContentHeight = Math.max(0, height - padding * 2 - sourceGapCount * nodeGap);
|
||||
const sourceUnitHeight = sourceContentHeight / totalValue;
|
||||
const sourceTotalNodeHeight = totalValue * sourceUnitHeight;
|
||||
const sourceSpanHeight = sourceTotalNodeHeight + sourceGapCount * nodeGap;
|
||||
@@ -407,7 +419,7 @@ function buildSankeyLayout(links, width, height, renderOptions) {
|
||||
const sourcePos = {};
|
||||
let sourceCursorY = padding;
|
||||
sourceNames.forEach((name) => {
|
||||
const nodeHeight = Math.max(2, sourceValueMap[name] * sourceUnitHeight);
|
||||
const nodeHeight = sourceValueMap[name] * sourceUnitHeight;
|
||||
sourcePos[name] = { y: sourceCursorY, h: nodeHeight };
|
||||
sourceCursorY += nodeHeight + nodeGap;
|
||||
});
|
||||
@@ -436,7 +448,7 @@ function buildSankeyLayout(links, width, height, renderOptions) {
|
||||
const targetPos = {};
|
||||
let targetCursorY = targetStartY;
|
||||
targetNames.forEach((name) => {
|
||||
const nodeHeight = Math.max(2, targetValueMap[name] * targetUnitHeight);
|
||||
const nodeHeight = targetValueMap[name] * targetUnitHeight;
|
||||
targetPos[name] = { y: targetCursorY, h: nodeHeight };
|
||||
targetCursorY += nodeHeight + targetGap;
|
||||
});
|
||||
@@ -458,7 +470,10 @@ function buildSankeyLayout(links, width, height, renderOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const linkHeight = Math.max(1, Number(link.value || 0) * sourceUnitHeight);
|
||||
const linkHeight = Number(link.value || 0) * sourceUnitHeight;
|
||||
if (linkHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
const sy = sourceNode.y + sourceOffset[link.source] + linkHeight / 2;
|
||||
const ty = targetNode.y + targetOffset[link.target] + linkHeight / 2;
|
||||
sourceOffset[link.source] += linkHeight;
|
||||
@@ -539,7 +554,12 @@ function buildSankeySvgText(links, width, height, renderOptions) {
|
||||
const colorIndex = Number.isFinite(layout.nodeColorIndexMap[name])
|
||||
? layout.nodeColorIndexMap[name]
|
||||
: index;
|
||||
const textY = node.y + getNodeLabelCenterY(node.h);
|
||||
const textY = getClampedLabelY(
|
||||
node.y,
|
||||
node.h,
|
||||
height,
|
||||
clampNumber(renderOptions && renderOptions.chartPadding, 0, 120, 16)
|
||||
);
|
||||
const textPlacement = getSvgLabelPlacement(layout, true, labelPositionMode);
|
||||
segments.push(
|
||||
`<rect x="${formatSvgNumber(layout.leftX)}" y="${formatSvgNumber(node.y)}" width="${formatSvgNumber(
|
||||
@@ -558,7 +578,12 @@ function buildSankeySvgText(links, width, height, renderOptions) {
|
||||
const colorIndex = Number.isFinite(layout.nodeColorIndexMap[name])
|
||||
? layout.nodeColorIndexMap[name]
|
||||
: index;
|
||||
const textY = node.y + getNodeLabelCenterY(node.h);
|
||||
const textY = getClampedLabelY(
|
||||
node.y,
|
||||
node.h,
|
||||
height,
|
||||
clampNumber(renderOptions && renderOptions.chartPadding, 0, 120, 16)
|
||||
);
|
||||
const textPlacement = getSvgLabelPlacement(layout, false, labelPositionMode);
|
||||
segments.push(
|
||||
`<rect x="${formatSvgNumber(layout.rightX)}" y="${formatSvgNumber(node.y)}" width="${formatSvgNumber(
|
||||
@@ -1261,13 +1286,14 @@ Page({
|
||||
const colorIndex = Number.isFinite(layout.nodeColorIndexMap[name])
|
||||
? layout.nodeColorIndexMap[name]
|
||||
: index;
|
||||
const textY = getClampedLabelY(node.y, node.h, height, this.data.chartPadding);
|
||||
ctx.setFillStyle(getNodeColor(colorIndex, themeColors));
|
||||
ctx.fillRect(layout.leftX, node.y, layout.nodeWidth, node.h);
|
||||
ctx.setFillStyle('#4e5969');
|
||||
ctx.setFontSize(10);
|
||||
ctx.setTextAlign(labelPlacement.textAlign);
|
||||
ctx.setTextBaseline('middle');
|
||||
ctx.fillText(name, labelPlacement.x, node.y + getNodeLabelCenterY(node.h));
|
||||
ctx.fillText(name, labelPlacement.x, textY);
|
||||
});
|
||||
|
||||
layout.targetNames.forEach((name, index) => {
|
||||
@@ -1276,13 +1302,14 @@ Page({
|
||||
const colorIndex = Number.isFinite(layout.nodeColorIndexMap[name])
|
||||
? layout.nodeColorIndexMap[name]
|
||||
: index;
|
||||
const textY = getClampedLabelY(node.y, node.h, height, this.data.chartPadding);
|
||||
ctx.setFillStyle(getNodeColor(colorIndex, themeColors));
|
||||
ctx.fillRect(layout.rightX, node.y, layout.nodeWidth, node.h);
|
||||
ctx.setFillStyle('#4e5969');
|
||||
ctx.setFontSize(10);
|
||||
ctx.setTextAlign(labelPlacement.textAlign);
|
||||
ctx.setTextBaseline('middle');
|
||||
ctx.fillText(name, labelPlacement.x, node.y + getNodeLabelCenterY(node.h));
|
||||
ctx.fillText(name, labelPlacement.x, textY);
|
||||
});
|
||||
|
||||
ctx.draw();
|
||||
|
||||
@@ -234,7 +234,7 @@
|
||||
.preview-canvas {
|
||||
margin-top: 3px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
height: 0;
|
||||
flex: 1;
|
||||
min-height: 120px;
|
||||
border-radius: 4px;
|
||||
|
||||
Reference in New Issue
Block a user