Fix timeline reset label alignment and hover state

This commit is contained in:
Anton Volnuhin 2026-02-06 20:07:48 +03:00
parent 06fb6bf768
commit 400c0ac765

114
app.js
View File

@ -683,22 +683,30 @@ function buildTimelineOption(drillPath) {
triggerEvent: true triggerEvent: true
} : { show: false }; } : { show: false };
const legendTop = isDrilled ? 42 : 10;
const legendRight = 10;
const legendItemWidth = 12;
const legendItemHeight = 12;
const legendItemGap = 8;
const legendPadding = [8, 12];
const legendFontSize = 13;
return { return {
backgroundColor: '#fff', backgroundColor: '#fff',
title: chartTitle, title: chartTitle,
tooltip: { show: false }, tooltip: { show: false },
legend: { legend: {
data: legendData, data: legendData,
top: isDrilled ? 42 : 10, top: legendTop,
right: 10, right: legendRight,
orient: 'vertical', orient: 'vertical',
itemWidth: 12, itemWidth: legendItemWidth,
itemHeight: 12, itemHeight: legendItemHeight,
textStyle: { fontSize: 13 }, textStyle: { fontSize: legendFontSize },
itemGap: 8, itemGap: legendItemGap,
backgroundColor: 'rgba(255,255,255,0.85)', backgroundColor: 'rgba(255,255,255,0.85)',
borderRadius: 6, borderRadius: 6,
padding: [8, 12], padding: legendPadding,
selector: false selector: false
}, },
grid: { grid: {
@ -725,7 +733,16 @@ function buildTimelineOption(drillPath) {
}, },
series: seriesList, series: seriesList,
_seriesDataMap: seriesDataMap, _seriesDataMap: seriesDataMap,
_monthCount: months.length _monthCount: months.length,
_legendLayout: {
top: legendTop,
right: legendRight,
itemWidth: legendItemWidth,
itemHeight: legendItemHeight,
itemGap: legendItemGap,
padding: legendPadding,
fontSize: legendFontSize
}
}; };
} }
@ -824,6 +841,7 @@ function renderTimelineChart(drillPath, historyAction, legendSelected) {
const n = tlOption._monthCount; const n = tlOption._monthCount;
const visibleTotals = new Array(n).fill(0); const visibleTotals = new Array(n).fill(0);
const allNames = tlOption.legend.data; const allNames = tlOption.legend.data;
const legendLayout = tlOption._legendLayout;
let allSelected = true; let allSelected = true;
Object.keys(dataMap).forEach(name => { Object.keys(dataMap).forEach(name => {
if (selected[name] !== false) { if (selected[name] !== false) {
@ -832,22 +850,71 @@ function renderTimelineChart(drillPath, historyAction, legendSelected) {
allSelected = false; allSelected = false;
} }
}); });
myChart.setOption({ const legendFont = `${legendLayout.fontSize}px sans-serif`;
series: [{ name: '__total__', label: { const resetFontSize = Math.max(11, legendLayout.fontSize - 1);
formatter: (p) => Math.round(visibleTotals[p.dataIndex] / 1000) + 'к' const resetFont = `${resetFontSize}px sans-serif`;
}}], const resetXOffset = 3;
graphic: [{ const maxLegendTextWidth = allNames.reduce((maxWidth, name) => {
return Math.max(maxWidth, echarts.format.getTextRect(name, legendFont).width);
}, 0);
const iconTextGap = 5; // ECharts default icon/text gap for legend items
const legendBoxWidth =
legendLayout.padding[1] * 2 +
legendLayout.itemWidth +
iconTextGap +
maxLegendTextWidth;
const resetTextWidth = echarts.format.getTextRect('× сбросить', resetFont).width;
const resetRight =
legendLayout.right +
legendBoxWidth -
legendLayout.padding[1] -
resetTextWidth -
resetXOffset;
const legendView = (myChart._componentsViews || []).find(v => v && v.__model && v.__model.mainType === 'legend');
const resetLeft = legendView && legendView.group ? legendView.group.x : null;
let resetTop;
if (legendView && legendView.group && legendView._contentGroup) {
const legendItems = legendView._contentGroup.children();
if (legendItems.length > 0) {
const lastItem = legendItems[legendItems.length - 1];
const prevItem = legendItems.length > 1 ? legendItems[legendItems.length - 2] : null;
const rowStep = prevItem ? (lastItem.y - prevItem.y) : (legendLayout.itemHeight + legendLayout.itemGap);
const lastRect = lastItem.getBoundingRect();
resetTop =
legendView.group.y +
(legendView._contentGroup.y || 0) +
lastItem.y +
lastRect.y +
rowStep;
}
}
if (resetTop === undefined) {
const nextRowTop =
legendLayout.top +
legendLayout.padding[0] +
allNames.length * (legendLayout.itemHeight + legendLayout.itemGap);
resetTop = nextRowTop + 11;
}
const resetGraphic = {
type: 'text', type: 'text',
id: 'resetBtn', id: 'resetBtn',
right: 22, top: resetTop,
top: (drillPath.length > 0 ? 42 : 10) + allNames.length * 20 + 16, z: 100,
zlevel: 1,
style: { style: {
text: allSelected ? '' : 'Очистить', text: allSelected ? '' : '× сбросить',
fontSize: 13, fontSize: resetFontSize,
fill: '#999', fill: '#999',
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI", system-ui, sans-serif' fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI", system-ui, sans-serif'
}, },
cursor: 'pointer', cursor: 'pointer',
onmouseover: function() {
if (this && this.setStyle) this.setStyle({ fill: '#000' });
},
onmouseout: function() {
if (this && this.setStyle) this.setStyle({ fill: '#999' });
},
onclick: function() { onclick: function() {
window._suppressLegendHistory = true; window._suppressLegendHistory = true;
allNames.forEach(name => { allNames.forEach(name => {
@ -859,7 +926,18 @@ function renderTimelineChart(drillPath, historyAction, legendSelected) {
updateTotalsAndResetBtn(resetSelected); updateTotalsAndResetBtn(resetSelected);
history.pushState({ timelineDrill: drillPath }, ''); history.pushState({ timelineDrill: drillPath }, '');
} }
}] };
if (resetLeft !== null) {
resetGraphic.left = resetLeft + resetXOffset;
} else {
resetGraphic.right = resetRight;
}
myChart.setOption({
series: [{ name: '__total__', label: {
formatter: (p) => Math.round(visibleTotals[p.dataIndex] / 1000) + 'к'
}}],
graphic: [resetGraphic]
}); });
} }