update at 2026-02-13 23:00:20
This commit is contained in:
@@ -174,6 +174,10 @@ const DEFAULT_THEME_INDEX = (() => {
|
||||
const index = themePresets.findIndex((item) => item && item.id === DEFAULT_THEME_ID);
|
||||
return index >= 0 ? index : 0;
|
||||
})();
|
||||
const THEME_ROW_HEIGHT_PX = 36;
|
||||
const THEME_VISIBLE_ROW_COUNT = 6;
|
||||
const THEME_LIST_HEIGHT_PX = THEME_ROW_HEIGHT_PX * THEME_VISIBLE_ROW_COUNT;
|
||||
const THEME_LIST_EDGE_SPACER_PX = (THEME_LIST_HEIGHT_PX - THEME_ROW_HEIGHT_PX) / 2;
|
||||
|
||||
/**
|
||||
* 数值限制,避免 UI 参数导致布局异常。
|
||||
@@ -186,6 +190,19 @@ function clampNumber(value, min, max, fallback) {
|
||||
return Math.min(max, Math.max(min, normalized));
|
||||
}
|
||||
|
||||
/**
|
||||
* 主题弹层打开时的滚动定位:
|
||||
* 通过上下占位留白,让任意索引都可滚动到视图中心。
|
||||
*/
|
||||
function getThemeListScrollTopByIndex(index, totalCount) {
|
||||
const total = Number(totalCount);
|
||||
if (!Number.isFinite(total) || total <= 0) {
|
||||
return 0;
|
||||
}
|
||||
const safeIndex = clampNumber(index, 0, total - 1, 0);
|
||||
return safeIndex * THEME_ROW_HEIGHT_PX;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一错误文案:
|
||||
* - xlsx 解析能力缺失时,固定提示用户去“构建 npm”
|
||||
@@ -286,17 +303,23 @@ function buildSankeyLayout(links, width, height, renderOptions) {
|
||||
|
||||
const sourceValueMap = {};
|
||||
const targetValueMap = {};
|
||||
const nodeColorIndexMap = {};
|
||||
let nodeColorIndexCursor = 0;
|
||||
links.forEach((link) => {
|
||||
sourceValueMap[link.source] = (sourceValueMap[link.source] || 0) + Number(link.value || 0);
|
||||
targetValueMap[link.target] = (targetValueMap[link.target] || 0) + Number(link.value || 0);
|
||||
if (!Object.prototype.hasOwnProperty.call(nodeColorIndexMap, link.source)) {
|
||||
nodeColorIndexMap[link.source] = nodeColorIndexCursor;
|
||||
nodeColorIndexCursor += 1;
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(nodeColorIndexMap, link.target)) {
|
||||
nodeColorIndexMap[link.target] = nodeColorIndexCursor;
|
||||
nodeColorIndexCursor += 1;
|
||||
}
|
||||
});
|
||||
|
||||
const sourceNames = Object.keys(sourceValueMap);
|
||||
const targetNames = Object.keys(targetValueMap);
|
||||
const sourceIndexMap = {};
|
||||
sourceNames.forEach((name, index) => {
|
||||
sourceIndexMap[name] = index;
|
||||
});
|
||||
const totalValue = sourceNames.reduce((sum, name) => sum + sourceValueMap[name], 0);
|
||||
if (totalValue <= 0) {
|
||||
return null;
|
||||
@@ -371,7 +394,7 @@ function buildSankeyLayout(links, width, height, renderOptions) {
|
||||
sy,
|
||||
ty,
|
||||
linkHeight,
|
||||
sourceIndex: Number.isFinite(sourceIndexMap[link.source]) ? sourceIndexMap[link.source] : 0
|
||||
sourceIndex: Number.isFinite(nodeColorIndexMap[link.source]) ? nodeColorIndexMap[link.source] : 0
|
||||
});
|
||||
});
|
||||
|
||||
@@ -383,6 +406,7 @@ function buildSankeyLayout(links, width, height, renderOptions) {
|
||||
targetNames,
|
||||
sourcePos,
|
||||
targetPos,
|
||||
nodeColorIndexMap,
|
||||
linkSegments
|
||||
};
|
||||
}
|
||||
@@ -431,12 +455,15 @@ function buildSankeySvgText(links, width, height, renderOptions) {
|
||||
|
||||
layout.sourceNames.forEach((name, index) => {
|
||||
const node = layout.sourcePos[name];
|
||||
const colorIndex = Number.isFinite(layout.nodeColorIndexMap[name])
|
||||
? layout.nodeColorIndexMap[name]
|
||||
: index;
|
||||
const textY = node.y + getNodeLabelCenterY(node.h);
|
||||
const textPlacement = getSvgLabelPlacement(layout, true, labelPositionMode);
|
||||
segments.push(
|
||||
`<rect x="${formatSvgNumber(layout.leftX)}" y="${formatSvgNumber(node.y)}" width="${formatSvgNumber(
|
||||
layout.nodeWidth
|
||||
)}" height="${formatSvgNumber(node.h)}" fill="${getNodeColor(index, themeColors)}" />`
|
||||
)}" height="${formatSvgNumber(node.h)}" fill="${getNodeColor(colorIndex, themeColors)}" />`
|
||||
);
|
||||
segments.push(
|
||||
`<text x="${formatSvgNumber(textPlacement.x)}" y="${formatSvgNumber(textY)}" fill="#4e5969" font-size="10" text-anchor="${
|
||||
@@ -447,12 +474,15 @@ function buildSankeySvgText(links, width, height, renderOptions) {
|
||||
|
||||
layout.targetNames.forEach((name, index) => {
|
||||
const node = layout.targetPos[name];
|
||||
const colorIndex = Number.isFinite(layout.nodeColorIndexMap[name])
|
||||
? layout.nodeColorIndexMap[name]
|
||||
: index;
|
||||
const textY = node.y + getNodeLabelCenterY(node.h);
|
||||
const textPlacement = getSvgLabelPlacement(layout, false, labelPositionMode);
|
||||
segments.push(
|
||||
`<rect x="${formatSvgNumber(layout.rightX)}" y="${formatSvgNumber(node.y)}" width="${formatSvgNumber(
|
||||
layout.nodeWidth
|
||||
)}" height="${formatSvgNumber(node.h)}" fill="${getNodeColor(index, themeColors)}" />`
|
||||
)}" height="${formatSvgNumber(node.h)}" fill="${getNodeColor(colorIndex, themeColors)}" />`
|
||||
);
|
||||
segments.push(
|
||||
`<text x="${formatSvgNumber(textPlacement.x)}" y="${formatSvgNumber(textY)}" fill="#4e5969" font-size="10" text-anchor="${
|
||||
@@ -470,6 +500,10 @@ Page({
|
||||
selectedThemeIndex: DEFAULT_THEME_INDEX,
|
||||
themes: Array.isArray(themePresets) ? themePresets : [],
|
||||
showThemeSheet: false,
|
||||
themeListScrollTop: getThemeListScrollTopByIndex(
|
||||
DEFAULT_THEME_INDEX,
|
||||
Array.isArray(themePresets) ? themePresets.length : 0
|
||||
),
|
||||
uploadMessage: '默认加载 data/sankey.xlsx 中...',
|
||||
parseError: '',
|
||||
buildError: '',
|
||||
@@ -478,6 +512,9 @@ Page({
|
||||
sourceDataColumn: null,
|
||||
sourceDescriptionColumns: [],
|
||||
targetDescriptionColumns: [],
|
||||
sectionVisibleSourceData: true,
|
||||
sectionVisibleSourceDesc: true,
|
||||
sectionVisibleTargetDesc: true,
|
||||
nodesCount: 0,
|
||||
linksCount: 0,
|
||||
droppedRows: 0,
|
||||
@@ -567,7 +604,32 @@ Page({
|
||||
* 主题选择按钮点击后,切换底部选择器。
|
||||
*/
|
||||
onToggleThemeSheet() {
|
||||
this.setData({ showThemeSheet: !this.data.showThemeSheet });
|
||||
const nextVisible = !this.data.showThemeSheet;
|
||||
if (!nextVisible) {
|
||||
this.setData({ showThemeSheet: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const targetScrollTop = getThemeListScrollTopByIndex(
|
||||
this.data.selectedThemeIndex,
|
||||
(this.data.themes || []).length
|
||||
);
|
||||
this.setData(
|
||||
{
|
||||
showThemeSheet: true,
|
||||
themeListScrollTop: 0
|
||||
},
|
||||
() => {
|
||||
const applyScrollTop = () => {
|
||||
this.setData({ themeListScrollTop: targetScrollTop });
|
||||
};
|
||||
if (typeof wx.nextTick === 'function') {
|
||||
wx.nextTick(applyScrollTop);
|
||||
return;
|
||||
}
|
||||
setTimeout(applyScrollTop, 0);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -577,6 +639,24 @@ Page({
|
||||
this.setData({ showThemeSheet: false });
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换数据映射区块展开/收起状态,对齐 Web 的 expand/zhedie 行为。
|
||||
*/
|
||||
onToggleSection(e) {
|
||||
const section = String((e.currentTarget.dataset && e.currentTarget.dataset.section) || '');
|
||||
if (section === 'sourceData') {
|
||||
this.setData({ sectionVisibleSourceData: !this.data.sectionVisibleSourceData });
|
||||
return;
|
||||
}
|
||||
if (section === 'sourceDesc') {
|
||||
this.setData({ sectionVisibleSourceDesc: !this.data.sectionVisibleSourceDesc });
|
||||
return;
|
||||
}
|
||||
if (section === 'targetDesc') {
|
||||
this.setData({ sectionVisibleTargetDesc: !this.data.sectionVisibleTargetDesc });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回当前选中主题色带。
|
||||
*/
|
||||
@@ -1063,7 +1143,10 @@ Page({
|
||||
layout.sourceNames.forEach((name, index) => {
|
||||
const node = layout.sourcePos[name];
|
||||
const labelPlacement = getCanvasLabelPlacement(layout, true, this.data.labelPositionMode);
|
||||
ctx.setFillStyle(getNodeColor(index, themeColors));
|
||||
const colorIndex = Number.isFinite(layout.nodeColorIndexMap[name])
|
||||
? layout.nodeColorIndexMap[name]
|
||||
: index;
|
||||
ctx.setFillStyle(getNodeColor(colorIndex, themeColors));
|
||||
ctx.fillRect(layout.leftX, node.y, layout.nodeWidth, node.h);
|
||||
ctx.setFillStyle('#4e5969');
|
||||
ctx.setFontSize(10);
|
||||
@@ -1075,7 +1158,10 @@ Page({
|
||||
layout.targetNames.forEach((name, index) => {
|
||||
const node = layout.targetPos[name];
|
||||
const labelPlacement = getCanvasLabelPlacement(layout, false, this.data.labelPositionMode);
|
||||
ctx.setFillStyle(getNodeColor(index, themeColors));
|
||||
const colorIndex = Number.isFinite(layout.nodeColorIndexMap[name])
|
||||
? layout.nodeColorIndexMap[name]
|
||||
: index;
|
||||
ctx.setFillStyle(getNodeColor(colorIndex, themeColors));
|
||||
ctx.fillRect(layout.rightX, node.y, layout.nodeWidth, node.h);
|
||||
ctx.setFillStyle('#4e5969');
|
||||
ctx.setFontSize(10);
|
||||
|
||||
Reference in New Issue
Block a user