Public
Edited
Jan 17, 2023
Insert cell
Insert cell
main = html`<div style="height: 100%; min-height:300px;width:100%;" />`
Insert cell
dimensions = ["MONTH", "集团"]
Insert cell
timeDim = _.find(["YEAR", "MONTH", "DAY"], (dimName) =>
_.get(data, [0, dimName])
) || "MONTH"
Insert cell
metrics = ['curr', 'base']
Insert cell
finalMatrix = _.includes(translate(metrics[0]), "本期")
? [...metrics, translate(metrics[0]).replace("本期", "预测")]
: metrics
Insert cell
data = [
{ MONTH: "202207", 集团: "集团", curr: "--", base: 827294 },
{ MONTH: "202206", 集团: "集团", curr: "--", base: 905192 },
{ MONTH: "202205", 集团: "集团", curr: "--", base: 871170 },
{ MONTH: "202204", 集团: "集团", curr: "--", base: 785446 },
{ MONTH: "202203", 集团: "集团", curr: "--", base: 696175 },
{ MONTH: "202202", 集团: "集团", curr: "--", base: 458607 },
{ MONTH: "202201", 集团: "集团", curr: "--", base: 771020 },
{ MONTH: "202112", 集团: "集团", curr: "--", base: 711092 },
{ MONTH: "202111", 集团: "集团", curr: "--", base: 707067 },
{ MONTH: "202110", 集团: "集团", curr: "--", base: 750171 },
{ MONTH: "202109", 集团: "集团", curr: "--", base: 619362 },
{ MONTH: "202108", 集团: "集团", curr: "--", base: 604062 },
{ MONTH: "202107", 集团: "集团", curr: 827294, base: 562431 },
{ MONTH: "202106", 集团: "集团", curr: 905192, base: 846339 },
{ MONTH: "202105", 集团: "集团", curr: 871170, base: 914876 },
{ MONTH: "202104", 集团: "集团", curr: 785446, base: 632328 },
{ MONTH: "202103", 集团: "集团", curr: 696175, base: 351872 },
{ MONTH: "202102", 集团: "集团", curr: 458607, base: 105200 },
{ MONTH: "202101", 集团: "集团", curr: 771020, base: 1073008 }
]
Insert cell
finalData = {
// 增长率=本期(累计)/基期(累计)
// 预测值=基期 * 增长率

const sorted = _.orderBy(data, timeDim, "asc");
if (finalMatrix.length < 3) {
return sorted;
}
const [currMetric, basePeriodMetric, predMetric] = finalMatrix;
const sortedValid = _.dropRightWhile(
sorted,
(d) => Number.isNaN(+d[currMetric]) || _.isNil(d[currMetric])
);
const growRatio =
_.sumBy(sortedValid, (d) =>
Number.isNaN(+d[currMetric]) ? 0 : +d[currMetric]
) /
_.sumBy(sortedValid, (d) =>
Number.isNaN(+d[basePeriodMetric]) ? 0 : +d[basePeriodMetric]
);
const beginPredIdx = sortedValid.length - 1;
return sorted.map((d, di) =>
di < beginPredIdx
? d
: {
...d,
[predMetric]:
beginPredIdx === di
? d[currMetric]
: _.round(d[basePeriodMetric] * growRatio, 2)
}
);
}
Insert cell
translationDict = ({curr: '本期', base: '基期'})
Insert cell
translate = k => translationDict[k] || k
Insert cell
metricsFormatDict = ({
[metrics[0]]: x => x
})
Insert cell
metricFormatterFn = metricsFormatDict[metrics[0]]
Insert cell
echartsLikeTooltipFormatter = (params) => {
let arr = _(params)
.filter((p) => p.value !== undefined && isFinite(p.value))
.take(15)
.map((p, pi) => {
let seriesName = p.seriesName;
let seriesFormatted = seriesName;
const metricIdx =
p.seriesIndex === finalMatrix.length - 1 ? 0 : p.seriesIndex;
const metricFormatterFn = metricsFormatDict[finalMatrix[metricIdx]];
//let val = p.value
let val = (
_.isFunction(metricFormatterFn) ? metricFormatterFn : _.identity
)(p.value);
//let valHtml = `<span style='float:right;margin-left:20px;font-size:14px;color:#666;font-weight:900'>${val}</span>`
let valHtml = `<span style='float:right;margin-left:10px;'>${val}</span>`;
return `<span style='display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:${p.color}' ></span>${seriesFormatted} : ${valHtml}`;
})
.value();

let xVal = params[0].name;

return `${xVal}<br />${arr.join("<br />")}`;
}
Insert cell
option = ({
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow"
},
formatter: echartsLikeTooltipFormatter
},
legend: {
type: "scroll"
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true
},
xAxis: {
type: "category",
data: (finalData || []).map((d) => d[timeDim]),
//axisLabel: { rotate: 30 },
axisTick: { alignWithLabel: true },
axisLine: { onZero: false }
},
yAxis: {
type: "value",
boundaryGap: [0, 0.01],
scale: true,
axisLabel: {
formatter: (value) => {
return (
_.isFunction(metricFormatterFn) ? metricFormatterFn : _.identity
)(value);
}
}
},
color: [
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
"#ea7ccc"
],
series: [
...finalMatrix.map((metric) => {
return {
name: translationDict[metric] || metric,
type: "line",
symbol: "circle",
data: finalData.map((d) => d[metric]),
lineStyle: {
type: _.includes(metric, "预测") ? "dotted" : "solid"
}
};
})
]
})
Insert cell
chartInst = echarts.init(main)
Insert cell
chartInst.setOption(option, { notMerge: true, lazyUpdate: true })
Insert cell
onEvents = ({})
Insert cell
function bindEvents(instance, events) {
function _bindEvent(eventName, func) {
if (eventName && func) {
instance.on(eventName, param => {
func(param, instance)
})
}
}

for (const eventName in events) {
_bindEvent(eventName, events[eventName])
}
}
Insert cell
bindEvents(chartInst, onEvents || {}); //调用bindEvents
Insert cell
(width, chartInst.resize()) // 自适应宽度
Insert cell
echarts = window.echarts || require("https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js")
Insert cell
_ = window._ || require("lodash")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more