diff --git a/app.js b/app.js index def9bb5..6547132 100644 --- a/app.js +++ b/app.js @@ -683,22 +683,30 @@ function buildTimelineOption(drillPath) { triggerEvent: true } : { 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 { backgroundColor: '#fff', title: chartTitle, tooltip: { show: false }, legend: { data: legendData, - top: isDrilled ? 42 : 10, - right: 10, + top: legendTop, + right: legendRight, orient: 'vertical', - itemWidth: 12, - itemHeight: 12, - textStyle: { fontSize: 13 }, - itemGap: 8, + itemWidth: legendItemWidth, + itemHeight: legendItemHeight, + textStyle: { fontSize: legendFontSize }, + itemGap: legendItemGap, backgroundColor: 'rgba(255,255,255,0.85)', borderRadius: 6, - padding: [8, 12], + padding: legendPadding, selector: false }, grid: { @@ -725,7 +733,16 @@ function buildTimelineOption(drillPath) { }, series: seriesList, _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 visibleTotals = new Array(n).fill(0); const allNames = tlOption.legend.data; + const legendLayout = tlOption._legendLayout; let allSelected = true; Object.keys(dataMap).forEach(name => { if (selected[name] !== false) { @@ -832,34 +850,94 @@ function renderTimelineChart(drillPath, historyAction, legendSelected) { allSelected = false; } }); + const legendFont = `${legendLayout.fontSize}px sans-serif`; + const resetFontSize = Math.max(11, legendLayout.fontSize - 1); + const resetFont = `${resetFontSize}px sans-serif`; + const resetXOffset = 3; + 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', + id: 'resetBtn', + top: resetTop, + z: 100, + zlevel: 1, + style: { + text: allSelected ? '' : '× сбросить', + fontSize: resetFontSize, + fill: '#999', + fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI", system-ui, sans-serif' + }, + cursor: 'pointer', + onmouseover: function() { + if (this && this.setStyle) this.setStyle({ fill: '#000' }); + }, + onmouseout: function() { + if (this && this.setStyle) this.setStyle({ fill: '#999' }); + }, + onclick: function() { + window._suppressLegendHistory = true; + allNames.forEach(name => { + myChart.dispatchAction({ type: 'legendSelect', name: name }); + }); + window._suppressLegendHistory = false; + const resetSelected = {}; + allNames.forEach(name => { resetSelected[name] = true; }); + updateTotalsAndResetBtn(resetSelected); + 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: [{ - type: 'text', - id: 'resetBtn', - right: 22, - top: (drillPath.length > 0 ? 42 : 10) + allNames.length * 20 + 16, - style: { - text: allSelected ? '' : 'Очистить', - fontSize: 13, - fill: '#999', - fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI", system-ui, sans-serif' - }, - cursor: 'pointer', - onclick: function() { - window._suppressLegendHistory = true; - allNames.forEach(name => { - myChart.dispatchAction({ type: 'legendSelect', name: name }); - }); - window._suppressLegendHistory = false; - const resetSelected = {}; - allNames.forEach(name => { resetSelected[name] = true; }); - updateTotalsAndResetBtn(resetSelected); - history.pushState({ timelineDrill: drillPath }, ''); - } - }] + graphic: [resetGraphic] }); }