fix: stable ECharts init with fixed DOM + proper tab switching
This commit is contained in:
1 parent
f69b730169
commit
cc9140b45c
1 file changed
+129
-269
+129
-269
@@ -11,7 +11,6 @@
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
/* IBM Carbon Design System — Dark 10 */
|
||||
--bg: #0a0a0a;
|
||||
--surface: #161616;
|
||||
--surface-hover: #1f1f1f;
|
||||
@@ -22,17 +21,14 @@
|
||||
--text-tertiary: #6f6f6f;
|
||||
--accent: #0f6fff;
|
||||
--accent-hover: #3d8eff;
|
||||
--accent-dim: rgba(15,111,255,0.1);
|
||||
--green: #16a34a;
|
||||
--green-dim: rgba(22,163,74,0.12);
|
||||
--red: #e21d1d;
|
||||
--red-dim: rgba(226,29,29,0.12);
|
||||
--yellow: #eac518;
|
||||
--yellow-dim: rgba(234,197,24,0.12);
|
||||
--mono: 'IBM Plex Mono', monospace;
|
||||
--sans: 'IBM Plex Sans', -apple-system, sans-serif;
|
||||
--ease-out: cubic-bezier(0.2, 0, 0, 1);
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--ease: cubic-bezier(0.2, 0, 0, 1);
|
||||
}
|
||||
|
||||
html { font-size: 14px; }
|
||||
@@ -43,13 +39,10 @@ body {
|
||||
color: var(--text-primary);
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TOP BAR — IBM style
|
||||
============================================ */
|
||||
/* TOP BAR */
|
||||
.topbar {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0 32px; height: 48px;
|
||||
@@ -59,8 +52,7 @@ body {
|
||||
}
|
||||
.topbar-brand {
|
||||
font-family: var(--mono); font-size: 13px; font-weight: 600;
|
||||
letter-spacing: 0.5px; color: var(--text-primary);
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
letter-spacing: 0.5px; display: flex; align-items: center; gap: 10px;
|
||||
}
|
||||
.topbar-brand .icon {
|
||||
width: 20px; height: 20px; background: var(--accent);
|
||||
@@ -69,33 +61,23 @@ body {
|
||||
}
|
||||
.topbar-right { display: flex; align-items: center; gap: 20px; font-size: 12px; }
|
||||
.status { display: flex; align-items: center; gap: 6px; color: var(--green); font-weight: 500; }
|
||||
.status-dot { width: 6px; height: 6px; background: var(--green); border-radius: 0; animation: blink 2s ease-in-out infinite; }
|
||||
.status-dot { width: 6px; height: 6px; background: var(--green); animation: blink 2s ease-in-out infinite; }
|
||||
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
||||
.clock { font-family: var(--mono); color: var(--text-tertiary); font-size: 11px; letter-spacing: 0.3px; }
|
||||
|
||||
/* ============================================
|
||||
PAGE HEADER — IBM heading style
|
||||
============================================ */
|
||||
/* PAGE HEADER */
|
||||
.page-header {
|
||||
max-width: 1280px; margin: 0 auto;
|
||||
padding: 32px 32px 0;
|
||||
}
|
||||
.page-title {
|
||||
font-family: var(--sans);
|
||||
font-size: 28px; font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 1.2;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.page-desc {
|
||||
font-size: 14px; color: var(--text-secondary);
|
||||
font-weight: 400; line-height: 1.5;
|
||||
letter-spacing: -0.5px; line-height: 1.2;
|
||||
color: var(--text-primary); margin-bottom: 8px;
|
||||
}
|
||||
.page-desc { font-size: 14px; color: var(--text-secondary); font-weight: 400; }
|
||||
|
||||
/* ============================================
|
||||
IBM TABS — Horizontal navigation tabs
|
||||
============================================ */
|
||||
/* TABS */
|
||||
.tabs-section {
|
||||
max-width: 1280px; margin: 0 auto;
|
||||
padding: 24px 32px 0;
|
||||
@@ -103,52 +85,31 @@ body {
|
||||
.tabs {
|
||||
display: flex; align-items: center; gap: 0;
|
||||
border-bottom: 1px solid var(--border-strong);
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
overflow-x: auto; scrollbar-width: none;
|
||||
}
|
||||
.tabs::-webkit-scrollbar { display: none; }
|
||||
.tab-btn {
|
||||
padding: 12px 20px;
|
||||
border: none; background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-family: var(--sans);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
transition: color 0.15s var(--ease-standard);
|
||||
padding: 12px 20px; border: none; background: transparent;
|
||||
color: var(--text-secondary); font-family: var(--sans);
|
||||
font-size: 14px; font-weight: 600; cursor: pointer;
|
||||
position: relative; white-space: nowrap;
|
||||
transition: color 0.15s var(--ease);
|
||||
}
|
||||
.tab-btn:hover { color: var(--text-primary); }
|
||||
.tab-btn::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -1px; left: 0; right: 0;
|
||||
height: 2px;
|
||||
background: var(--accent);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.2s var(--ease-standard);
|
||||
}
|
||||
.tab-btn.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
.tab-btn.active::after {
|
||||
transform: scaleX(1);
|
||||
content: ''; position: absolute;
|
||||
bottom: -1px; left: 0; right: 0; height: 2px;
|
||||
background: var(--accent); transform: scaleX(0);
|
||||
transform-origin: left; transition: transform 0.2s var(--ease);
|
||||
}
|
||||
.tab-btn.active { color: var(--accent); }
|
||||
.tab-btn.active::after { transform: scaleX(1); }
|
||||
.tab-label { display: flex; align-items: center; gap: 8px; }
|
||||
.tab-icon {
|
||||
width: 16px; height: 16px; border-radius: 0;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
KPI GRID — IBM cards, brutal
|
||||
============================================ */
|
||||
/* KPI GRID — IBM Carbon style: single border around all */
|
||||
.kpi-grid {
|
||||
display: grid; grid-template-columns: repeat(6, 1fr);
|
||||
gap: 1px; padding: 24px 32px; max-width: 1280px; margin: 0 auto;
|
||||
gap: 1px; padding: 0; max-width: 1280px; margin: 0 auto;
|
||||
background: var(--border); border: 1px solid var(--border);
|
||||
border-radius: 4px; overflow: hidden;
|
||||
}
|
||||
@@ -174,49 +135,37 @@ body {
|
||||
}
|
||||
.kpi-badge.up { color: var(--green); background: var(--green-dim); }
|
||||
.kpi-badge.down { color: var(--red); background: var(--red-dim); }
|
||||
.kpi-badge.neutral { color: var(--accent); background: var(--accent-dim); }
|
||||
.kpi-badge.neutral { color: var(--accent); background: rgba(15,111,255,0.1); }
|
||||
.kpi-value {
|
||||
font-family: var(--mono);
|
||||
font-size: 24px; font-weight: 600;
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 1.1;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: -0.5px; line-height: 1.1;
|
||||
color: var(--text-primary); margin-bottom: 8px;
|
||||
}
|
||||
.kpi-context { font-size: 11px; color: var(--text-tertiary); }
|
||||
|
||||
/* ============================================
|
||||
CHART PANELS — IBM structure
|
||||
============================================ */
|
||||
/* CHARTS WRAPPER */
|
||||
.charts-wrap {
|
||||
max-width: 1280px; margin: 0 auto;
|
||||
padding: 0 32px 32px;
|
||||
}
|
||||
.charts-grid {
|
||||
display: grid; gap: 1px;
|
||||
background: var(--border);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px; overflow: hidden;
|
||||
}
|
||||
.charts-row-main { grid-template-columns: 2fr 1fr; }
|
||||
.charts-row-sub { grid-template-columns: 1fr 1fr 1fr; }
|
||||
|
||||
.chart-panel {
|
||||
background: var(--surface);
|
||||
padding: 24px;
|
||||
min-height: 280px;
|
||||
}
|
||||
.chart-header { margin-bottom: 20px; }
|
||||
.chart-title {
|
||||
font-size: 14px; font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.chart-subtitle {
|
||||
font-size: 12px; color: var(--text-tertiary);
|
||||
}
|
||||
.chart-title { font-size: 14px; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
|
||||
.chart-subtitle { font-size: 12px; color: var(--text-tertiary); }
|
||||
.chart-canvas { width: 100%; height: 260px; }
|
||||
.chart-canvas.small { height: 200px; }
|
||||
|
||||
/* Metric list inside chart panel */
|
||||
/* Metric list */
|
||||
.metric-list { display: flex; flex-direction: column; gap: 0; margin-top: 8px; }
|
||||
.metric-item {
|
||||
display: flex; align-items: center;
|
||||
@@ -224,75 +173,62 @@ body {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.metric-item:last-child { border-bottom: none; }
|
||||
.metric-item:first-child { padding-top: 0; }
|
||||
.metric-item:last-child { padding-bottom: 0; }
|
||||
.m-label { font-size: 12px; color: var(--text-secondary); flex: 1; font-weight: 400; }
|
||||
.m-label { font-size: 12px; color: var(--text-secondary); flex: 1; }
|
||||
.m-value { font-family: var(--mono); font-size: 13px; font-weight: 600; color: var(--text-primary); margin: 0 16px; min-width: 64px; text-align: right; }
|
||||
.m-bar { width: 80px; height: 4px; background: var(--border-strong); border-radius: 0; overflow: hidden; }
|
||||
.m-fill { height: 100%; width: 0; transition: width 0.8s var(--ease-standard); }
|
||||
.m-bar { width: 80px; height: 4px; background: var(--border-strong); overflow: hidden; }
|
||||
.m-fill { height: 100%; width: 0; transition: width 0.8s var(--ease); }
|
||||
.m-fill.green { background: var(--green); }
|
||||
.m-fill.red { background: var(--red); }
|
||||
.m-fill.yellow { background: var(--yellow); }
|
||||
.m-fill.blue { background: var(--accent); }
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE
|
||||
============================================ */
|
||||
/* RESPONSIVE */
|
||||
@media(max-width: 1024px) {
|
||||
.kpi-grid { grid-template-columns: repeat(3, 1fr); }
|
||||
.charts-row-main, .charts-row-sub { grid-template-columns: 1fr; }
|
||||
.page-title { font-size: 22px; }
|
||||
}
|
||||
@media(max-width: 640px) {
|
||||
.kpi-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.topbar { padding: 0 16px; }
|
||||
.page-header, .tabs-section, .kpi-grid, .charts-wrap { padding-left: 16px; padding-right: 16px; }
|
||||
.page-header, .tabs-section, .charts-wrap { padding-left: 16px; padding-right: 16px; }
|
||||
.tab-btn { padding: 12px 14px; font-size: 13px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- TOP BAR -->
|
||||
<div class="topbar">
|
||||
<div class="topbar-brand">
|
||||
<div class="icon">R</div>
|
||||
ROI24
|
||||
</div>
|
||||
<div class="topbar-brand"><div class="icon">R</div>ROI24</div>
|
||||
<div class="topbar-right">
|
||||
<div class="status"><div class="status-dot"></div> Live data</div>
|
||||
<div class="clock" id="clock"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PAGE HEADER -->
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Аналитика бизнеса</h1>
|
||||
<p class="page-desc">Ключевые показатели эффективности в реальном времени</p>
|
||||
</div>
|
||||
|
||||
<!-- IBM-STYLE TABS -->
|
||||
<div class="tabs-section">
|
||||
<div class="tabs" id="tabs">
|
||||
<button class="tab-btn active" data-seg="ecom">
|
||||
<span class="tab-label"><span class="tab-icon">🛒</span>E-commerce</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-seg="services">
|
||||
<span class="tab-label"><span class="tab-icon">💼</span>Услуги</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-seg="production">
|
||||
<span class="tab-label"><span class="tab-icon">🏭</span>Производство</span>
|
||||
</button>
|
||||
<button class="tab-btn active" data-seg="ecom"><span class="tab-label"><span class="tab-icon">🛒</span>E-commerce</span></button>
|
||||
<button class="tab-btn" data-seg="services"><span class="tab-label"><span class="tab-icon">💼</span>Услуги</span></button>
|
||||
<button class="tab-btn" data-seg="production"><span class="tab-label"><span class="tab-icon">🏭</span>Производство</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI GRID -->
|
||||
<div class="kpi-grid" id="kpi-grid"></div>
|
||||
|
||||
<!-- CHARTS -->
|
||||
<div class="charts-wrap">
|
||||
<div class="charts-row-main" id="charts-main"></div>
|
||||
<div class="charts-row-sub" id="charts-sub"></div>
|
||||
<div class="charts-grid">
|
||||
<!-- Row 1: Main + Side -->
|
||||
<div class="chart-panel"><div class="chart-header"><div class="chart-title" id="mc-title"></div><div class="chart-subtitle" id="mc-sub"></div></div><div class="chart-canvas" id="mc-canvas"></div></div>
|
||||
<div class="chart-panel"><div class="chart-header"><div class="chart-title" id="sc-title"></div><div class="chart-subtitle" id="sc-sub"></div></div><div class="chart-canvas" id="sc-canvas"></div></div>
|
||||
<!-- Row 2: Funnel + Second + Metrics -->
|
||||
<div class="chart-panel"><div class="chart-header"><div class="chart-title" id="c1-title"></div><div class="chart-subtitle" id="c1-sub"></div></div><div class="chart-canvas small" id="c1-canvas"></div></div>
|
||||
<div class="chart-panel"><div class="chart-header"><div class="chart-title" id="c2-title"></div><div class="chart-subtitle" id="c2-sub"></div></div><div class="chart-canvas small" id="c2-canvas"></div></div>
|
||||
<div class="chart-panel"><div class="chart-header"><div class="chart-title">Выполнение плана</div><div class="chart-subtitle">Ключевые метрики</div></div><div class="metric-list" id="metric-list"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
|
||||
@@ -424,13 +360,12 @@ function seg(key) {
|
||||
|
||||
// KPIs
|
||||
const g = document.getElementById('kpi-grid');
|
||||
g.innerHTML = s.kpis.map((d,i) => {
|
||||
const badgeClass = d.t;
|
||||
g.innerHTML = s.kpis.map((d, i) => {
|
||||
const arrow = d.t === 'up' ? '▲' : d.t === 'down' ? '▼' : '●';
|
||||
return `<div class="kpi" style="transition-delay:${i*60}ms">
|
||||
<div class="kpi-top">
|
||||
<span class="kpi-name">${d.n}</span>
|
||||
<span class="kpi-badge ${badgeClass}">${arrow} ${d.d}</span>
|
||||
<span class="kpi-badge ${d.t}">${arrow} ${d.d}</span>
|
||||
</div>
|
||||
<div class="kpi-value">${d.v}</div>
|
||||
<div class="kpi-context">${d.p}</div>
|
||||
@@ -438,49 +373,32 @@ function seg(key) {
|
||||
}).join('');
|
||||
requestAnimationFrame(() => g.querySelectorAll('.kpi').forEach(c => c.classList.add('visible')));
|
||||
|
||||
// Main chart area
|
||||
const mcDom = document.getElementById('charts-main');
|
||||
const mc = s.mc;
|
||||
if (!charts.mc) charts.mc = echarts.init(document.createElement('div'));
|
||||
mcDom.innerHTML = `<div class="chart-panel" id="cp-main">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">${mc.title}</div>
|
||||
<div class="chart-subtitle">${mc.sub}</div>
|
||||
</div>
|
||||
<div class="chart-canvas" id="mc-canvas"></div>
|
||||
</div>
|
||||
<div class="chart-panel" id="cp-side">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">${s.sc.title}</div>
|
||||
<div class="chart-subtitle">${s.sc.sub}</div>
|
||||
</div>
|
||||
<div class="chart-canvas" id="sc-canvas"></div>
|
||||
</div>`;
|
||||
|
||||
// Main chart
|
||||
document.getElementById('mc-title').textContent = s.mc.title;
|
||||
document.getElementById('mc-sub').textContent = s.mc.sub;
|
||||
const mcEl = document.getElementById('mc-canvas');
|
||||
charts.mc.dispose();
|
||||
charts.mc = echarts.init(mcEl);
|
||||
if (!charts.mc) charts.mc = echarts.init(mcEl);
|
||||
charts.mc.clear();
|
||||
let mcOpt;
|
||||
if (mc.type === 'dual') {
|
||||
if (s.mc.type === 'dual') {
|
||||
mcOpt = {
|
||||
tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 12, fontFamily: 'IBM Plex Mono' } },
|
||||
legend: { data: ['Доход', 'Расход'], textStyle: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Sans' }, top: 0, itemWidth: 12, itemHeight: 2 },
|
||||
tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 12 } },
|
||||
legend: { data: ['Доход', 'Расход'], textStyle: { color: '#6f6f6f', fontSize: 11 }, top: 0, itemWidth: 12, itemHeight: 2 },
|
||||
grid: { left: 48, right: 20, top: 32, bottom: 28 },
|
||||
xAxis: {
|
||||
type: 'category', data: mc.labels,
|
||||
type: 'category', data: s.mc.labels,
|
||||
axisLine: { lineStyle: { color: '#262626' } },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Mono' },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 11 },
|
||||
axisTick: { show: false },
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitLine: { lineStyle: { color: '#1f1f1f' } },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Mono', formatter: '{value}М' },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 11, formatter: '{value}М' },
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: 'Доход', type: 'line', data: mc.rev, smooth: 0.4, symbol: 'none',
|
||||
name: 'Доход', type: 'line', data: s.mc.rev, smooth: 0.4, symbol: 'none',
|
||||
lineStyle: { width: 2.5, color: '#16a34a' },
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
@@ -490,58 +408,54 @@ function seg(key) {
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Расход', type: 'line', data: mc.ads, smooth: 0.4, symbol: 'none',
|
||||
name: 'Расход', type: 'line', data: s.mc.ads, smooth: 0.4, symbol: 'none',
|
||||
lineStyle: { width: 1.5, color: '#0f6fff', type: 'dashed' },
|
||||
},
|
||||
],
|
||||
animationDuration: 500,
|
||||
animationEasing: 'cubicOut',
|
||||
};
|
||||
} else {
|
||||
mcOpt = {
|
||||
tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 12, fontFamily: 'IBM Plex Mono' } },
|
||||
legend: { data: ['План', 'Факт'], textStyle: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Sans' }, top: 0, itemWidth: 12, itemHeight: 14 },
|
||||
tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 12 } },
|
||||
legend: { data: ['План', 'Факт'], textStyle: { color: '#6f6f6f', fontSize: 11 }, top: 0, itemWidth: 12, itemHeight: 14 },
|
||||
grid: { left: 48, right: 20, top: 32, bottom: 28 },
|
||||
xAxis: {
|
||||
type: 'category', data: mc.labels,
|
||||
type: 'category', data: s.mc.labels,
|
||||
axisLine: { lineStyle: { color: '#262626' } },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Mono' },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 11 },
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitLine: { lineStyle: { color: '#1f1f1f' } },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 11, fontFamily: 'IBM Plex Mono', formatter: '{value}К' },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 11, formatter: '{value}К' },
|
||||
},
|
||||
series: [
|
||||
{ name: 'План', type: 'bar', data: mc.plan, barWidth: '30%', itemStyle: { color: 'rgba(15,111,255,0.1)' } },
|
||||
{ name: 'Факт', type: 'bar', data: mc.fact, barWidth: '30%', itemStyle: { color: '#0f6fff' } },
|
||||
{ name: 'План', type: 'bar', data: s.mc.plan, barWidth: '30%', itemStyle: { color: 'rgba(15,111,255,0.1)' } },
|
||||
{ name: 'Факт', type: 'bar', data: s.mc.fact, barWidth: '30%', itemStyle: { color: '#0f6fff' } },
|
||||
],
|
||||
animationDuration: 500,
|
||||
animationEasing: 'cubicOut',
|
||||
};
|
||||
}
|
||||
charts.mc.setOption(mcOpt, true);
|
||||
charts.mc.setOption(mcOpt);
|
||||
|
||||
// Side chart
|
||||
const sc = s.sc;
|
||||
const scEl = document.getElementById('sc-canvas');
|
||||
if (!charts.sc) charts.sc = echarts.init(scEl);
|
||||
charts.sc.dispose();
|
||||
charts.sc = echarts.init(scEl);
|
||||
const scData = sc.data.reverse();
|
||||
document.getElementById('sc-title').textContent = s.sc.title;
|
||||
document.getElementById('sc-sub').textContent = s.sc.sub;
|
||||
const scData = s.sc.data.slice().reverse();
|
||||
const scColors = ['#0f6fff', '#3d8eff', '#16a34a', '#06b6d4', '#a855f7', '#eac518'];
|
||||
const scOpt = {
|
||||
tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 11 } },
|
||||
if (!charts.sc) charts.sc = echarts.init(document.getElementById('sc-canvas'));
|
||||
charts.sc.clear();
|
||||
charts.sc.setOption({
|
||||
grid: { left: 90, right: 28, top: 8, bottom: 8 },
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
splitLine: { lineStyle: { color: '#1f1f1f' } },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 10, fontFamily: 'IBM Plex Mono', formatter: '{value}%' },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 10, formatter: '{value}%' },
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category', data: scData.map(d => d.n),
|
||||
axisLine: { lineStyle: { color: '#262626' } },
|
||||
axisLabel: { color: '#a6a6a6', fontSize: 11, fontFamily: 'IBM Plex Sans' },
|
||||
axisLabel: { color: '#a6a6a6', fontSize: 11 },
|
||||
},
|
||||
series: [{
|
||||
type: 'bar', data: scData.map((d, i) => ({
|
||||
@@ -556,120 +470,66 @@ function seg(key) {
|
||||
barWidth: 28,
|
||||
}],
|
||||
animationDuration: 500,
|
||||
};
|
||||
charts.sc.setOption(scOpt, true);
|
||||
|
||||
// Sub row charts
|
||||
const subEl = document.getElementById('charts-sub');
|
||||
const c1 = s.c1;
|
||||
const c2 = s.c2;
|
||||
|
||||
// Funnel chart
|
||||
let funnelHtml = `<div class="chart-panel">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">${c1.title}</div>
|
||||
<div class="chart-subtitle">${c1.sub}</div>
|
||||
</div>
|
||||
<div class="chart-canvas small" id="c1-canvas"></div>
|
||||
</div>`;
|
||||
|
||||
// Second chart
|
||||
let secondHtml;
|
||||
if (c2.labels) {
|
||||
const isPie = c2.data.length && c2.data.reduce((a, b) => a + b, 0) <= 100;
|
||||
const pc = ['#0f6fff', '#16a34a', '#eac518', '#a855f7', '#e21d1d'];
|
||||
if (isPie) {
|
||||
secondHtml = `<div class="chart-panel">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">${c2.title}</div>
|
||||
<div class="chart-subtitle">${c2.sub}</div>
|
||||
</div>
|
||||
<div class="chart-canvas small" id="c2-canvas"></div>
|
||||
</div>`;
|
||||
} else {
|
||||
secondHtml = `<div class="chart-panel">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">${c2.title}</div>
|
||||
<div class="chart-subtitle">${c2.sub}</div>
|
||||
</div>
|
||||
<div class="chart-canvas small" id="c2-canvas"></div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Metrics panel
|
||||
const metricsHtml = `<div class="chart-panel">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title">Выполнение плана</div>
|
||||
<div class="chart-subtitle">Ключевые метрики</div>
|
||||
</div>
|
||||
<div class="metric-list" id="metric-list"></div>
|
||||
</div>`;
|
||||
|
||||
subEl.innerHTML = funnelHtml + secondHtml + metricsHtml;
|
||||
});
|
||||
|
||||
// Funnel
|
||||
document.getElementById('c1-title').textContent = s.c1.title;
|
||||
document.getElementById('c1-sub').textContent = s.c1.sub;
|
||||
const fc = ['#0f6fff', '#3d8eff', '#16a34a', '#e21d1d'];
|
||||
const c1Dom = document.getElementById('c1-canvas');
|
||||
if (!charts.c1) charts.c1 = echarts.init(c1Dom);
|
||||
charts.c1.dispose();
|
||||
charts.c1 = echarts.init(c1Dom);
|
||||
if (!charts.c1) charts.c1 = echarts.init(document.getElementById('c1-canvas'));
|
||||
charts.c1.clear();
|
||||
charts.c1.setOption({
|
||||
tooltip: { trigger: 'item', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 11 } },
|
||||
series: [{
|
||||
type: 'funnel', left: '8%', bottom: 5, width: '84%', min: 0, max: c1.data[0].v,
|
||||
type: 'funnel', left: '8%', bottom: 5, width: '84%', min: 0, max: s.c1.data[0].v,
|
||||
minSize: '0%', maxSize: '100%', sort: 'descending', gap: 2,
|
||||
label: { show: true, position: 'inside', color: '#f4f4f4', fontSize: 11, fontFamily: 'IBM Plex Mono', formatter: '{b}\n{d}%' },
|
||||
label: { show: true, position: 'inside', color: '#f4f4f4', fontSize: 11, formatter: '{b}\n{d}%' },
|
||||
labelLine: { show: false },
|
||||
itemStyle: { borderColor: '#161616', borderWidth: 2 },
|
||||
data: c1.data.map((d, i) => ({ name: d.n, value: d.v, itemStyle: { color: fc[i % fc.length] } })),
|
||||
data: s.c1.data.map((d, i) => ({ name: d.n, value: d.v, itemStyle: { color: fc[i % fc.length] } })),
|
||||
}],
|
||||
animationDuration: 500,
|
||||
}, true);
|
||||
});
|
||||
|
||||
// Second chart
|
||||
const c2Dom = document.getElementById('c2-canvas');
|
||||
if (!charts.c2) charts.c2 = echarts.init(c2Dom);
|
||||
charts.c2.dispose();
|
||||
charts.c2 = echarts.init(c2Dom);
|
||||
if (c2.labels) {
|
||||
const isPie = c2.data.length && c2.data.reduce((a, b) => a + b, 0) <= 100;
|
||||
const pc = ['#0f6fff', '#16a34a', '#eac518', '#a855f7', '#e21d1d'];
|
||||
if (isPie) {
|
||||
charts.c2.setOption({
|
||||
tooltip: { trigger: 'item', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 11 } },
|
||||
series: [{
|
||||
type: 'pie', radius: ['40%', '65%'], center: ['50%', '55%'],
|
||||
itemStyle: { borderRadius: 0, borderColor: '#161616', borderWidth: 2 },
|
||||
label: { show: false }, labelLine: { show: false },
|
||||
data: c2.labels.map((l, i) => ({ name: l, value: c2.data[i], itemStyle: { color: pc[i % pc.length] } })),
|
||||
}],
|
||||
animationDuration: 500,
|
||||
}, true);
|
||||
} else {
|
||||
charts.c2.setOption({
|
||||
tooltip: { trigger: 'axis', backgroundColor: '#161616', borderColor: '#262626', textStyle: { color: '#f4f4f4', fontSize: 11 } },
|
||||
grid: { left: 60, right: 16, top: 8, bottom: 28 },
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
splitLine: { lineStyle: { color: '#1f1f1f' } },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 10, formatter: '{value}%' },
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category', data: c2.labels,
|
||||
axisLine: { lineStyle: { color: '#262626' } },
|
||||
axisLabel: { color: '#a6a6a6', fontSize: 11, fontFamily: 'IBM Plex Sans' },
|
||||
},
|
||||
series: [{
|
||||
type: 'bar', data: c2.data.map(v => ({
|
||||
value: v,
|
||||
itemStyle: { color: v >= 100 ? '#16a34a' : '#eac518' }
|
||||
})),
|
||||
barWidth: 24,
|
||||
}],
|
||||
animationDuration: 500,
|
||||
}, true);
|
||||
}
|
||||
document.getElementById('c2-title').textContent = s.c2.title;
|
||||
document.getElementById('c2-sub').textContent = s.c2.sub;
|
||||
const c2 = s.c2;
|
||||
const isPie = c2.labels && c2.data.reduce((a, b) => a + b, 0) <= 100;
|
||||
const pc = ['#0f6fff', '#16a34a', '#eac518', '#a855f7', '#e21d1d'];
|
||||
if (!charts.c2) charts.c2 = echarts.init(document.getElementById('c2-canvas'));
|
||||
charts.c2.clear();
|
||||
if (isPie) {
|
||||
charts.c2.setOption({
|
||||
series: [{
|
||||
type: 'pie', radius: ['40%', '65%'], center: ['50%', '55%'],
|
||||
itemStyle: { borderRadius: 0, borderColor: '#161616', borderWidth: 2 },
|
||||
label: { show: false }, labelLine: { show: false },
|
||||
data: c2.labels.map((l, i) => ({ name: l, value: c2.data[i], itemStyle: { color: pc[i % pc.length] } })),
|
||||
}],
|
||||
animationDuration: 500,
|
||||
});
|
||||
} else {
|
||||
charts.c2.setOption({
|
||||
grid: { left: 60, right: 16, top: 8, bottom: 28 },
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
splitLine: { lineStyle: { color: '#1f1f1f' } },
|
||||
axisLabel: { color: '#6f6f6f', fontSize: 10, formatter: '{value}%' },
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category', data: c2.labels,
|
||||
axisLine: { lineStyle: { color: '#262626' } },
|
||||
axisLabel: { color: '#a6a6a6', fontSize: 11 },
|
||||
},
|
||||
series: [{
|
||||
type: 'bar', data: c2.data.map(v => ({
|
||||
value: v,
|
||||
itemStyle: { color: v >= 100 ? '#16a34a' : '#eac518' }
|
||||
})),
|
||||
barWidth: 24,
|
||||
}],
|
||||
animationDuration: 500,
|
||||
});
|
||||
}
|
||||
|
||||
// Metrics list
|
||||
@@ -699,7 +559,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
String(n.getHours()).padStart(2, '0') + ':' + String(n.getMinutes()).padStart(2, '0') + ':' + String(n.getSeconds()).padStart(2, '0');
|
||||
}
|
||||
tick(); setInterval(tick, 1000);
|
||||
new ResizeObserver(() => Object.values(charts).forEach(c => c.resize())).observe(document.getElementById('charts-wrap'));
|
||||
new ResizeObserver(() => Object.values(charts).forEach(c => c.resize())).observe(document.querySelector('.charts-grid'));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in new issue
Block a user