| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- // VOC 报告 inline JS · PPT 式 deck 驱动
- // 特性:
- // 1) IntersectionObserver 驱动的 fade-up / rise-in / headline-anim
- // 2) 键盘导航(↑↓ PgUp PgDn Home End j k)逐张切换
- // 3) slide counter (n/N + 进度条)
- // 4) ambient 光晕跟随当前 slide 偏移
- // 5) 首次滚动后 5s 自动隐藏键盘提示
- // 6) ECharts 各图按需初始化(首次进入视口时才渲染)
- module.exports = `
- (function(){
- 'use strict';
- var slides = [];
- var currentSlide = 0;
- var firstScrolled = false;
- // —— 1) 分片标题动画:把每个 .headline-anim 文本切成 .char 逐字入场 ——
- function splitHeadlines(){
- var els = document.querySelectorAll('.headline-anim');
- els.forEach(function(el){
- if (el.dataset.split === '1') return;
- el.dataset.split = '1';
- var text = el.textContent;
- el.textContent = '';
- var chars = Array.from(text);
- chars.forEach(function(ch, i){
- var span = document.createElement('span');
- span.className = 'char';
- span.textContent = ch === ' ' ? '\\u00A0' : ch;
- span.style.transitionDelay = (i * 28) + 'ms';
- el.appendChild(span);
- });
- });
- }
- // —— 2) Fade / Rise / Headline 统一的 IntersectionObserver ——
- function initReveal(){
- var selectors = ['.fade-up', '.rise-in', '.headline-anim'];
- var els = document.querySelectorAll(selectors.join(','));
- if (!('IntersectionObserver' in window)){
- els.forEach(function(el){ el.classList.add('is-visible'); });
- return;
- }
- var io = new IntersectionObserver(function(entries){
- entries.forEach(function(e){
- if (e.isIntersecting){
- e.target.classList.add('is-visible');
- io.unobserve(e.target);
- }
- });
- }, { threshold: 0.08, rootMargin: '0px 0px -5% 0px' });
- els.forEach(function(el){ io.observe(el); });
- // 首屏兜底
- setTimeout(function(){
- els.forEach(function(el){
- var r = el.getBoundingClientRect();
- if (r.top < window.innerHeight && r.bottom > 0) el.classList.add('is-visible');
- });
- }, 100);
- // 终极兜底
- setTimeout(function(){
- document.querySelectorAll('.fade-up:not(.is-visible),.rise-in:not(.is-visible),.headline-anim:not(.is-visible)').forEach(function(el){
- el.classList.add('is-visible');
- });
- }, 4500);
- }
- // —— 3) 数字计数动画 ——
- function initCountUp(){
- var els = document.querySelectorAll('.metric-value[data-count]');
- if (!('IntersectionObserver' in window)){
- els.forEach(function(el){ el.textContent = el.dataset.count; });
- return;
- }
- var io = new IntersectionObserver(function(entries){
- entries.forEach(function(e){
- if (!e.isIntersecting) return;
- var el = e.target;
- var target = parseFloat(el.dataset.count);
- if (isNaN(target)) return;
- var isFloat = !Number.isInteger(target);
- var start = 0, duration = 1400, t0 = performance.now();
- function tick(now){
- var p = Math.min(1, (now - t0) / duration);
- var eased = 1 - Math.pow(1 - p, 3);
- var v = start + (target - start) * eased;
- el.textContent = isFloat ? v.toFixed(1) : Math.floor(v).toLocaleString('en-US');
- if (p < 1) requestAnimationFrame(tick);
- else el.textContent = isFloat ? target.toFixed(1) : target.toLocaleString('en-US');
- }
- requestAnimationFrame(tick);
- io.unobserve(el);
- });
- }, { threshold: 0.4 });
- els.forEach(function(el){ io.observe(el); });
- }
- // —— 4) Slide 进度 · nav 高亮 · ambient 光晕跟随 · slide counter ——
- function initDeck(){
- slides = Array.prototype.slice.call(document.querySelectorAll('section.report-section'));
- var progressLine = document.getElementById('progress-line');
- var navDots = document.querySelectorAll('.nav-dot');
- var counter = document.querySelector('.slide-counter');
- var counterNow = counter && counter.querySelector('.now');
- var counterTotal = counter && counter.querySelector('.total');
- var counterBar = counter && counter.querySelector('.bar');
- if (counterTotal) counterTotal.textContent = String(slides.length).padStart(2, '0');
- // 预设每张 slide 的 ambient 偏移(用 CSS 变量驱动)
- var ambientPresets = [
- { x: '15%', y: '25%' },
- { x: '75%', y: '30%' },
- { x: '25%', y: '70%' },
- { x: '80%', y: '65%' },
- { x: '45%', y: '20%' },
- { x: '55%', y: '75%' },
- { x: '20%', y: '50%' },
- { x: '85%', y: '50%' },
- { x: '50%', y: '40%' },
- { x: '30%', y: '30%' },
- { x: '70%', y: '70%' },
- { x: '40%', y: '60%' }
- ];
- function setActive(idx){
- if (idx < 0) idx = 0;
- if (idx >= slides.length) idx = slides.length - 1;
- currentSlide = idx;
- slides.forEach(function(s, i){
- if (i === idx) s.classList.add('in-view');
- else s.classList.remove('in-view');
- });
- for (var j = 0; j < navDots.length; j++){
- if (j === idx) navDots[j].classList.add('active');
- else navDots[j].classList.remove('active');
- }
- if (counterNow) counterNow.textContent = String(idx + 1).padStart(2, '0');
- if (counterBar) {
- var pct = slides.length > 1 ? ((idx + 1) / slides.length) * 100 : 100;
- counterBar.style.setProperty('--pct', pct.toFixed(1) + '%');
- }
- // 根据当前 slide 切换 ambient 位置
- var p = ambientPresets[idx % ambientPresets.length];
- document.body.style.setProperty('--amb-x', p.x);
- document.body.style.setProperty('--amb-y', p.y);
- }
- function onScroll(){
- if (!firstScrolled && window.scrollY > 20){
- firstScrolled = true;
- setTimeout(function(){
- var hint = document.querySelector('.kbd-hint');
- if (hint) hint.classList.add('fade');
- }, 4000);
- }
- var h = document.documentElement.scrollHeight - window.innerHeight;
- var p = h > 0 ? (window.scrollY / h) * 100 : 0;
- if (progressLine) progressLine.style.width = p + '%';
- // 找出离视口中心最近的 slide
- var mid = window.innerHeight * 0.45;
- var bestIdx = 0, bestDist = Infinity;
- for (var i = 0; i < slides.length; i++){
- var r = slides[i].getBoundingClientRect();
- var center = (r.top + r.bottom) / 2;
- var d = Math.abs(center - mid);
- if (d < bestDist){ bestDist = d; bestIdx = i; }
- }
- if (bestIdx !== currentSlide) setActive(bestIdx);
- }
- window.addEventListener('scroll', onScroll, { passive: true });
- onScroll();
- navDots.forEach(function(d, i){
- d.addEventListener('click', function(e){
- e.preventDefault();
- var tid = d.dataset.target;
- var t = tid ? document.getElementById(tid) : slides[i];
- if (t) t.scrollIntoView({ behavior: 'smooth', block: 'start' });
- });
- });
- // —— 键盘导航 ——
- function goto(idx){
- idx = Math.max(0, Math.min(slides.length - 1, idx));
- slides[idx].scrollIntoView({ behavior: 'smooth', block: 'start' });
- }
- window.addEventListener('keydown', function(e){
- // 不在输入框里
- var tag = (e.target && e.target.tagName || '').toLowerCase();
- if (tag === 'input' || tag === 'textarea' || e.target.isContentEditable) return;
- if (e.key === 'ArrowDown' || e.key === 'PageDown' || e.key === 'j'){ e.preventDefault(); goto(currentSlide + 1); }
- else if (e.key === 'ArrowUp' || e.key === 'PageUp' || e.key === 'k'){ e.preventDefault(); goto(currentSlide - 1); }
- else if (e.key === 'Home'){ e.preventDefault(); goto(0); }
- else if (e.key === 'End'){ e.preventDefault(); goto(slides.length - 1); }
- else if (e.key === ' '){ e.preventDefault(); goto(currentSlide + (e.shiftKey ? -1 : 1)); }
- });
- }
- // —— 5) ECharts 懒加载(slide 进入视口才渲染,避免首屏卡顿)——
- var COMMON = {
- backgroundColor: 'transparent',
- textStyle: { fontFamily: 'Inter, Noto Sans SC, system-ui', color: '#A8A8A8' },
- grid: { left: '3%', right: '4%', bottom: '3%', top: '12%', containLabel: true }
- };
- var AMBER='#F5A623', GREEN='#00DC82', PURPLE='#8B5CF6', ROSE='#FF4D8D', BLUE='#3B82F6';
- var PALETTE = [AMBER, GREEN, PURPLE, ROSE, BLUE, '#EF4444', '#06B6D4', '#F59E0B'];
- var CHART_REGISTRY = [];
- function initChart(id, cb){
- var el = document.getElementById(id);
- if (!el) return;
- CHART_REGISTRY.push({ id: id, el: el, cb: cb, rendered: false });
- }
- function renderChart(item){
- if (item.rendered || !window.echarts) return;
- var chart = window.echarts.init(item.el, null, { renderer: 'canvas' });
- try { item.cb(chart); item.rendered = true; } catch(e){ console.error(item.id, e); }
- window.addEventListener('resize', function(){ chart.resize(); });
- }
- function bootCharts(){
- if (!window.echarts) { setTimeout(bootCharts, 300); return; }
- if (!('IntersectionObserver' in window)){
- CHART_REGISTRY.forEach(renderChart);
- return;
- }
- var io = new IntersectionObserver(function(entries){
- entries.forEach(function(e){
- if (!e.isIntersecting) return;
- var item = CHART_REGISTRY.find(function(x){ return x.el === e.target; });
- if (item) renderChart(item);
- io.unobserve(e.target);
- });
- }, { threshold: 0.1, rootMargin: '200px 0px' });
- CHART_REGISTRY.forEach(function(item){ io.observe(item.el); });
- }
- function initCharts(){
- var D = window.__CHART_DATA__ || {};
- // 样本分布
- initChart('chartSample', function(chart){
- var data = (D.noteHeatmap || []).slice(0, 5).map(function(n, i){
- return {
- value: (n.liked || 0) + (n.comments || 0) + (n.collected || 0),
- name: (n.title || '').slice(0, 14) || '笔记' + (i + 1),
- itemStyle: { color: PALETTE[i % PALETTE.length] }
- };
- });
- chart.setOption(Object.assign({}, COMMON, {
- tooltip: { trigger: 'item', backgroundColor: '#1A1A1A', borderColor: '#252525', textStyle: { color: '#F5F5F5' } },
- legend: { bottom: 0, textStyle: { color: '#A8A8A8', fontSize: 11 } },
- animationDuration: 1000, animationEasing: 'cubicOut',
- series: [{
- type: 'pie', radius: ['52%', '76%'], center: ['50%', '45%'],
- label: { color: '#F5F5F5', fontSize: 11, formatter: '{b}\\n{c}' },
- itemStyle: { borderColor: '#0A0A0A', borderWidth: 2 },
- data: data
- }]
- }));
- });
- // IP 地域
- initChart('chartGeo', function(chart){
- var ips = (D.topIPs || []).slice(0, 10).reverse();
- chart.setOption(Object.assign({}, COMMON, {
- tooltip: { trigger: 'axis', backgroundColor: '#1A1A1A', borderColor: '#252525', textStyle: { color: '#F5F5F5' } },
- animationDuration: 1200, animationEasing: 'cubicOut',
- xAxis: { type: 'value', axisLine: { lineStyle: { color: '#252525' } }, splitLine: { lineStyle: { color: '#1A1A1A' } }, axisLabel: { color: '#6B6B6B' } },
- yAxis: { type: 'category', data: ips.map(function(x){return x[0];}), axisLine: { lineStyle: { color: '#252525' } }, axisLabel: { color: '#A8A8A8', fontSize: 12 } },
- series: [{
- type: 'bar', data: ips.map(function(x){return x[1];}),
- itemStyle: { color: AMBER, borderRadius: [0, 3, 3, 0] }, barWidth: '60%',
- label: { show: true, position: 'right', color: '#F5A623', fontFamily: 'JetBrains Mono', fontSize: 11 }
- }]
- }));
- });
- // 笔记热力散点
- initChart('chartNoteHeatmap', function(chart){
- chart.setOption(Object.assign({}, COMMON, {
- tooltip: {
- trigger: 'item', backgroundColor: '#1A1A1A', borderColor: '#252525', textStyle: { color: '#F5F5F5' },
- formatter: function(p){
- return '<div style="max-width:260px;"><strong style="color:#F5A623;">' + p.data[3] +
- '</strong><br>点赞 ' + p.data[0] + ' · 评论 ' + p.data[1] + ' · 收藏 ' + p.data[2] + '</div>';
- }
- },
- animationDuration: 1400, animationDelay: function(i){ return i * 30; },
- xAxis: { type: 'value', name: '点赞数', nameTextStyle: { color: '#A8A8A8' }, axisLine: { lineStyle: { color: '#252525' } }, splitLine: { lineStyle: { color: '#1A1A1A' } }, axisLabel: { color: '#6B6B6B' } },
- yAxis: { type: 'value', name: '评论数', nameTextStyle: { color: '#A8A8A8' }, axisLine: { lineStyle: { color: '#252525' } }, splitLine: { lineStyle: { color: '#1A1A1A' } }, axisLabel: { color: '#6B6B6B' } },
- series: [{
- type: 'scatter',
- data: (D.noteHeatmap || []).map(function(n){ return [n.liked, n.comments, n.collected, n.title]; }),
- symbolSize: function(v){ return Math.max(10, Math.min(56, Math.sqrt(v[2] || 1) * 3.5)); },
- itemStyle: {
- color: new echarts.graphic.RadialGradient(0.4, 0.4, 0.6, [
- { offset: 0, color: 'rgba(245,166,35,0.9)' },
- { offset: 1, color: 'rgba(245,166,35,0.15)' }
- ]),
- borderColor: '#F5A623', borderWidth: 1
- },
- emphasis: { itemStyle: { borderWidth: 2, shadowBlur: 16, shadowColor: 'rgba(245,166,35,0.5)' } }
- }]
- }));
- });
- // 价格带
- initChart('chartPrice', function(chart){
- var keys = Object.keys(D.priceBuckets || {});
- var vals = keys.map(function(k){ return D.priceBuckets[k]; });
- chart.setOption(Object.assign({}, COMMON, {
- tooltip: { trigger: 'axis', backgroundColor: '#1A1A1A', borderColor: '#252525', textStyle: { color: '#F5F5F5' } },
- animationDuration: 1200,
- xAxis: { type: 'category', data: keys, axisLine: { lineStyle: { color: '#252525' } }, axisLabel: { color: '#A8A8A8', fontFamily: 'JetBrains Mono' } },
- yAxis: { type: 'value', axisLine: { lineStyle: { color: '#252525' } }, splitLine: { lineStyle: { color: '#1A1A1A' } }, axisLabel: { color: '#6B6B6B' } },
- series: [{
- type: 'bar', data: vals,
- itemStyle: {
- color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
- colorStops: [{ offset: 0, color: '#F5A623' }, { offset: 1, color: 'rgba(245,166,35,0.3)' }] },
- borderRadius: [4, 4, 0, 0]
- },
- barWidth: '56%',
- label: { show: true, position: 'top', color: '#F5A623', fontFamily: 'JetBrains Mono', fontWeight: 600 }
- }]
- }));
- });
- // 品牌月销(横条,兼容)
- initChart('chartBrands', function(chart){
- var brands = (D.topBrands || []).slice().reverse();
- chart.setOption(Object.assign({}, COMMON, {
- tooltip: { trigger: 'axis', backgroundColor: '#1A1A1A', borderColor: '#252525', textStyle: { color: '#F5F5F5' },
- formatter: function(p){ return p.map(function(x){ return x.name + ': $' + (x.value/100).toLocaleString('en-US',{maximumFractionDigits:0}); }).join('<br>'); }
- },
- animationDuration: 1200,
- xAxis: { type: 'value', axisLine: { lineStyle: { color: '#252525' } }, splitLine: { lineStyle: { color: '#1A1A1A' } }, axisLabel: { color: '#6B6B6B', formatter: function(v){ return '$' + (v/100000).toFixed(0) + 'k'; } } },
- yAxis: { type: 'category', data: brands.map(function(x){return x[0];}), axisLine: { lineStyle: { color: '#252525' } }, axisLabel: { color: '#A8A8A8', fontSize: 12 } },
- series: [{
- type: 'bar', data: brands.map(function(x){return x[1];}),
- itemStyle: {
- color: function(params){ return PALETTE[params.dataIndex % PALETTE.length]; },
- borderRadius: [0, 3, 3, 0]
- },
- barWidth: '58%',
- label: { show: true, position: 'right', color: '#A8A8A8', fontFamily: 'JetBrains Mono', fontSize: 10, formatter: function(p){ return '$' + (p.value/100000).toFixed(0) + 'k'; } }
- }]
- }));
- });
- // 评分分布
- initChart('chartRatingDist', function(chart){
- var data = D.ratingBuckets || {};
- var keys = Object.keys(data);
- var vals = keys.map(function(k){ return data[k]; });
- var colors = ['#EF4444','#F59E0B','#F5A623','#06B6D4','#00DC82'];
- chart.setOption(Object.assign({}, COMMON, {
- tooltip: { trigger: 'axis', backgroundColor: '#1A1A1A', borderColor: '#252525', textStyle: { color: '#F5F5F5' },
- formatter: function(p){ return p.map(function(x){ return x.name + ': ' + x.value + ' SKU'; }).join('<br>'); }
- },
- animationDuration: 1200,
- xAxis: { type: 'category', data: keys, axisLine: { lineStyle: { color: '#252525' } }, axisLabel: { color: '#A8A8A8', fontFamily: 'JetBrains Mono', fontSize: 11 } },
- yAxis: { type: 'value', axisLine: { lineStyle: { color: '#252525' } }, splitLine: { lineStyle: { color: '#1A1A1A' } }, axisLabel: { color: '#6B6B6B' } },
- series: [{
- type: 'bar', data: vals.map(function(v, i){ return { value: v, itemStyle: { color: colors[i] || GREEN, borderRadius: [4, 4, 0, 0] } }; }),
- barWidth: '54%',
- label: { show: true, position: 'top', color: '#F5F5F5', fontFamily: 'JetBrains Mono', fontWeight: 600 }
- }]
- }));
- });
- // 品牌份额(donut)
- initChart('chartBrandShare', function(chart){
- var data = (D.topBrandShares || []).map(function(b, i){
- return { value: b.value, name: b.name, itemStyle: { color: PALETTE[i % PALETTE.length] }, _sales: b.sales, _count: b.count };
- });
- chart.setOption(Object.assign({}, COMMON, {
- tooltip: { trigger: 'item', backgroundColor: '#1A1A1A', borderColor: '#252525', textStyle: { color: '#F5F5F5' },
- formatter: function(p){ return '<strong>'+p.name+'</strong><br>' + p.value.toFixed(1) + '%<br>SKU '+(p.data._count||0)+' · 月销 $'+((p.data._sales||0)/100).toLocaleString('en-US',{maximumFractionDigits:0}); }
- },
- legend: { bottom: 0, textStyle: { color: '#A8A8A8', fontSize: 10 }, itemWidth: 8, itemHeight: 8 },
- animationDuration: 1400, animationEasing: 'cubicOut',
- series: [{
- type: 'pie', radius: ['48%', '72%'], center: ['50%', '45%'],
- label: { color: '#F5F5F5', fontSize: 10, formatter: function(p){ return p.value > 3 ? p.name + '\\n' + p.value.toFixed(1) + '%' : ''; } },
- labelLine: { show: true, length: 8, length2: 8 },
- itemStyle: { borderColor: '#0A0A0A', borderWidth: 2 },
- data: data
- }]
- }));
- });
- // 评论数分布(横条)
- initChart('chartReviewCount', function(chart){
- var data = D.reviewCountBuckets || {};
- var keys = Object.keys(data);
- var vals = keys.map(function(k){ return data[k]; });
- chart.setOption(Object.assign({}, COMMON, {
- tooltip: { trigger: 'axis', backgroundColor: '#1A1A1A', borderColor: '#252525', textStyle: { color: '#F5F5F5' } },
- animationDuration: 1200,
- xAxis: { type: 'value', axisLine: { lineStyle: { color: '#252525' } }, splitLine: { lineStyle: { color: '#1A1A1A' } }, axisLabel: { color: '#6B6B6B' } },
- yAxis: { type: 'category', data: keys, axisLine: { lineStyle: { color: '#252525' } }, axisLabel: { color: '#A8A8A8', fontFamily: 'JetBrains Mono', fontSize: 11 } },
- series: [{
- type: 'bar', data: vals,
- itemStyle: {
- color: { type: 'linear', x: 0, y: 0, x2: 1, y2: 0,
- colorStops: [{ offset: 0, color: 'rgba(139,92,246,0.2)' }, { offset: 1, color: '#8B5CF6' }] },
- borderRadius: [0, 3, 3, 0]
- },
- barWidth: '56%',
- label: { show: true, position: 'right', color: '#A8A8A8', fontFamily: 'JetBrains Mono', fontSize: 11 }
- }]
- }));
- });
- // 星级分布
- initChart('chartStarDist', function(chart){
- var products = D.starDistProducts || [];
- if (!products.length) return;
- var categories = products.map(function(p){ return p.brand + ' · ' + p.asin; });
- var colors = ['#00DC82','#06B6D4','#F5A623','#F59E0B','#EF4444'];
- var labels = ['5★','4★','3★','2★','1★'];
- var series = [0,1,2,3,4].map(function(i){
- return {
- name: labels[i], type: 'bar', stack: 'stars',
- data: products.map(function(p){ return p.stars[i]; }),
- itemStyle: { color: colors[i] }
- };
- });
- chart.setOption(Object.assign({}, COMMON, {
- tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, backgroundColor: '#1A1A1A', borderColor: '#252525', textStyle: { color: '#F5F5F5' } },
- legend: { bottom: 0, textStyle: { color: '#A8A8A8', fontSize: 10 }, itemWidth: 10, itemHeight: 10 },
- animationDuration: 1400,
- grid: { left: '3%', right: '4%', bottom: '16%', top: '6%', containLabel: true },
- xAxis: { type: 'value', axisLine: { lineStyle: { color: '#252525' } }, splitLine: { lineStyle: { color: '#1A1A1A' } }, axisLabel: { color: '#6B6B6B' } },
- yAxis: { type: 'category', data: categories, axisLine: { lineStyle: { color: '#252525' } }, axisLabel: { color: '#A8A8A8', fontSize: 11 } },
- series: series
- }));
- });
- // 抖音账号作品热度散点
- initChart('chartDyPosts', function(chart){
- var posts = (D.douyinPosts || []);
- if (!posts.length) return;
- chart.setOption(Object.assign({}, COMMON, {
- tooltip: { trigger: 'item', backgroundColor: '#1A1A1A', borderColor: '#252525', textStyle: { color: '#F5F5F5' },
- formatter: function(p){
- return '<div style="max-width:280px;"><strong style="color:#F5A623;">' + (p.data[3]||'').slice(0,60) +
- '</strong><br>点赞 ' + p.data[0].toLocaleString() + ' · 评论 ' + p.data[1] + ' · 转发 ' + p.data[2] + '</div>';
- }
- },
- animationDuration: 1200,
- xAxis: { type: 'value', name: '点赞', nameTextStyle: { color: '#A8A8A8' }, axisLine: { lineStyle: { color: '#252525' } }, splitLine: { lineStyle: { color: '#1A1A1A' } }, axisLabel: { color: '#6B6B6B', formatter: function(v){ return v >= 10000 ? (v/10000).toFixed(1) + 'w' : v; } } },
- yAxis: { type: 'value', name: '评论', nameTextStyle: { color: '#A8A8A8' }, axisLine: { lineStyle: { color: '#252525' } }, splitLine: { lineStyle: { color: '#1A1A1A' } }, axisLabel: { color: '#6B6B6B' } },
- series: [{
- type: 'scatter',
- data: posts.map(function(p){ return [p.digg||0, p.comment||0, p.share||0, p.desc||'']; }),
- symbolSize: function(v){ return Math.max(12, Math.min(60, Math.sqrt((v[2]||0) + 1) * 4)); },
- itemStyle: {
- color: new echarts.graphic.RadialGradient(0.4, 0.4, 0.6, [
- { offset: 0, color: 'rgba(255,77,141,0.9)' },
- { offset: 1, color: 'rgba(255,77,141,0.2)' }
- ]),
- borderColor: '#FF4D8D', borderWidth: 1
- },
- emphasis: { itemStyle: { borderWidth: 2, shadowBlur: 16, shadowColor: 'rgba(255,77,141,0.5)' } }
- }]
- }));
- });
- }
- // —— Boot ——
- function boot(){
- try { splitHeadlines(); } catch(e){ console.error('split', e); }
- try { initReveal(); } catch(e){ console.error('reveal', e); }
- try { initCountUp(); } catch(e){ console.error('countup', e); }
- try { initDeck(); } catch(e){ console.error('deck', e); }
- try { initCharts(); bootCharts(); } catch(e){ console.error('charts', e); }
- }
- if (document.readyState === 'loading'){
- document.addEventListener('DOMContentLoaded', boot);
- } else {
- boot();
- }
- })();
- `;
|