feat: move legend to top-right box with hover highlighting

- Move legend from bottom to vertical top-right box with background
- Highlight hovered category in legend (bold text, others dimmed)
- Dim legend color swatches to 0.15 opacity for non-hovered items
- Make drill-down title larger (22px bold centered)
- Reserve right margin (180px) for legend, free up bottom space

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Anton Volnuhin 2026-02-06 18:11:51 +03:00
parent b91aa65f61
commit 9b848610ae

50
app.js
View File

@ -614,9 +614,14 @@ function buildTimelineOption(drillCategory) {
const chartTitle = drillCategory ? { const chartTitle = drillCategory ? {
text: '← ' + drillCategory, text: '← ' + drillCategory,
left: 20, left: 'center',
top: 10, top: 6,
textStyle: { fontSize: 16, fontWeight: 500, color: '#555' }, textStyle: {
fontSize: 22,
fontWeight: 'bold',
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI", system-ui, sans-serif',
color: '#333'
},
triggerEvent: true triggerEvent: true
} : { show: false }; } : { show: false };
@ -626,18 +631,23 @@ function buildTimelineOption(drillCategory) {
tooltip: { show: false }, tooltip: { show: false },
legend: { legend: {
data: legendData, data: legendData,
bottom: 0, top: drillCategory ? 42 : 10,
left: 'center', right: 10,
itemWidth: 14, orient: 'vertical',
itemHeight: 14, itemWidth: 12,
itemHeight: 12,
textStyle: { fontSize: 13 }, textStyle: { fontSize: 13 },
itemGap: 16 itemGap: 8,
backgroundColor: 'rgba(255,255,255,0.85)',
borderRadius: 6,
padding: [8, 12],
selector: false
}, },
grid: { grid: {
left: 30, left: 30,
right: 30, right: 180,
top: drillCategory ? 50 : 40, top: drillCategory ? 55 : 40,
bottom: 50, bottom: 30,
containLabel: true containLabel: true
}, },
xAxis: { xAxis: {
@ -675,23 +685,41 @@ function renderTimelineChart(drillCategory) {
myChart.setOption(tlOption, true); myChart.setOption(tlOption, true);
// Highlight entire series on hover so all bars show emphasis.label // Highlight entire series on hover so all bars show emphasis.label
// Also highlight the corresponding legend item
const legendNames = tlOption.legend.data;
let hlSeries = null; let hlSeries = null;
function updateLegend(activeName) {
myChart.setOption({
legend: {
data: legendNames.map(name => {
if (!activeName) return { name, textStyle: { fontWeight: 'normal', color: '#333' }, itemStyle: { opacity: 1 } };
if (name === activeName) return { name, textStyle: { fontWeight: 'bold', color: '#333' }, itemStyle: { opacity: 1 } };
return { name, textStyle: { fontWeight: 'normal', color: '#ccc' }, itemStyle: { opacity: 0.15 } };
})
}
});
}
myChart.on('mouseover', function(params) { myChart.on('mouseover', function(params) {
if (params.componentType !== 'series' || params.seriesName === '__total__') return; if (params.componentType !== 'series' || params.seriesName === '__total__') return;
if (hlSeries === params.seriesName) return; if (hlSeries === params.seriesName) return;
if (hlSeries) myChart.dispatchAction({ type: 'downplay', seriesName: hlSeries }); if (hlSeries) myChart.dispatchAction({ type: 'downplay', seriesName: hlSeries });
hlSeries = params.seriesName; hlSeries = params.seriesName;
myChart.dispatchAction({ type: 'highlight', seriesName: params.seriesName }); myChart.dispatchAction({ type: 'highlight', seriesName: params.seriesName });
updateLegend(params.seriesName);
}); });
myChart.on('mouseout', function(params) { myChart.on('mouseout', function(params) {
if (params.componentType !== 'series' || !hlSeries) return; if (params.componentType !== 'series' || !hlSeries) return;
myChart.dispatchAction({ type: 'downplay', seriesName: hlSeries }); myChart.dispatchAction({ type: 'downplay', seriesName: hlSeries });
hlSeries = null; hlSeries = null;
updateLegend(null);
}); });
myChart.on('globalout', function() { myChart.on('globalout', function() {
if (hlSeries) { if (hlSeries) {
myChart.dispatchAction({ type: 'downplay', seriesName: hlSeries }); myChart.dispatchAction({ type: 'downplay', seriesName: hlSeries });
hlSeries = null; hlSeries = null;
updateLegend(null);
} }
}); });