Standardize financial chart colors and improve label placement
This commit is contained in:
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
import {
|
import {
|
||||||
PALETTE, FONT, axisCommon, periodAxis, titleStyle,
|
PALETTE, FINANCIAL_COLORS, BRIDGE_COLORS, CHANNEL_COLORS, FONT, axisCommon, periodAxis, titleStyle,
|
||||||
lineSeries, barSeries, standardLegend, footnote, watermarks, LAYOUT,
|
lineSeries, lineDataWithSafeLabels, barSeries, standardLegend, footnote, watermarks, LAYOUT,
|
||||||
} from '../theme.js';
|
} from '../theme.js';
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -27,13 +27,13 @@ pnpm test # 全量,只在改了 t
|
|||||||
|
|
||||||
全量跑纯属浪费,而且十几张无关图表的输出会盖住本次改动的真实问题。不传参数时才是全量,所以 `pnpm test` 保持向后兼容。
|
全量跑纯属浪费,而且十几张无关图表的输出会盖住本次改动的真实问题。不传参数时才是全量,所以 `pnpm test` 保持向后兼容。
|
||||||
|
|
||||||
它会断言 `legend.top`、`grid.top`、折线 `symbolSize`/`borderWidth`,并检查 SVG 字体引号。加新图记得同步往 `regress.mjs` 顶部的 `all` 数组加一行,否则不会被测到。
|
它会断言 `legend.top`、`grid.top`、折线 `symbolSize`/`borderWidth`,并检查 SVG 字体引号。回归测试直接遍历 `manifest.json` 的 `chartCatalog`,新增图只要完成 registry 与 manifest 登记就会被测到。
|
||||||
|
|
||||||
**右侧纵向图例的图会报 `legend.top=middle (期望85)`,这是预期的,不要去"修"。** 那条断言的 85px 基准是给顶部横向图例定的,对 `orient: 'vertical'` + `top: 'middle'` 的图不适用(`ppmt_ip_share`、`ppmt_ip_share_history` 都会报)。
|
**右侧纵向图例的图会报 `legend.top=middle (期望85)`,这是预期的,不要去"修"。** 那条断言的 85px 基准是给顶部横向图例定的,对 `orient: 'vertical'` + `top: 'middle'` 的图不适用(`ppmt_ip_share`、`ppmt_ip_share_history` 都会报)。
|
||||||
|
|
||||||
## 踩过的坑
|
## 踩过的坑
|
||||||
|
|
||||||
**不要用 `...legendStyle`。** 它遗留 `top: 62` 且曾带 `icon: 'roundRect'`——后者会盖掉 ECharts 按系列类型自动生成的图例(折线本该是「短线+空心圆」)。用 `standardLegend()`。
|
**优先用 `standardLegend()`,且不要手工设置 `legend.icon`。** ECharts 会按系列类型自动生成图例:柱状图为矩形,折线图为「短线 + 空心圆」。需要特殊位置时可扩展 `legendStyle`,但不得覆盖这一自动类型图标。
|
||||||
|
|
||||||
**`label.position` 不接受函数。** 写了会被静默忽略、退化成默认位置,实测确认过。要让同一系列内不同点朝向不同,只能逐点覆盖 `data`:`{ value, label: { position: 'top' } }`。
|
**`label.position` 不接受函数。** 写了会被静默忽略、退化成默认位置,实测确认过。要让同一系列内不同点朝向不同,只能逐点覆盖 `data`:`{ value, label: { position: 'top' } }`。
|
||||||
|
|
||||||
@@ -59,6 +59,12 @@ pnpm test # 全量,只在改了 t
|
|||||||
|
|
||||||
## 配色
|
## 配色
|
||||||
|
|
||||||
|
**正式方向:轻快消费。** 柱线组合图的现金流、利润与同比使用 `FINANCIAL_COLORS`,渠道结构使用 `CHANNEL_COLORS`,瀑布图使用 `BRIDGE_COLORS`(蓝色基准、青绿色正向、珊瑚红负向);不要在图表文件里为相同财务/渠道语义重写十六进制颜色。柱状系列负责分类,折线只保留一条高辨识度强调色。
|
||||||
|
|
||||||
|
**一张图只留一个视觉主角。** 辅助柱使用中等饱和度;避免红、橙、紫等多个强调色同时竞争。分类色要同时拉开色相和明度;折线除颜色外还使用线宽、实虚线、空心圆和标签优先级辅助区分。
|
||||||
|
|
||||||
|
**柱线组合的标签不能压在柱体端点。** 使用 `lineDataWithSafeLabels(rows, { lineKey, barKeys })`:正值标签在上、负值标签在下,接近柱值时自动增加距离,并配合 ECharts `labelLayout` 兜底;不要按某个年份写特判。
|
||||||
|
|
||||||
先分清两件事,别混用同一个门槛。**选色的功夫要和系列数成正比**——两个系列的图不该选半天。
|
先分清两件事,别混用同一个门槛。**选色的功夫要和系列数成正比**——两个系列的图不该选半天。
|
||||||
|
|
||||||
**① 同一张图内的系列要能互相区分。** 这条永远要满足,是可读性问题:
|
**① 同一张图内的系列要能互相区分。** 这条永远要满足,是可读性问题:
|
||||||
|
|||||||
@@ -156,6 +156,9 @@ js/main.js + js/ui.js ← 解析引用、渲染三栏、导出和
|
|||||||
新增图表必须复用 `theme.js` 的 `FONT`、`PALETTE`、`LAYOUT`、`periodAxis()`、`standardLegend()`、`lineSeries()`、`barSeries()`、`footnote()` 与 `watermarks()`,而不是在单个图表中重复手写基础样式。
|
新增图表必须复用 `theme.js` 的 `FONT`、`PALETTE`、`LAYOUT`、`periodAxis()`、`standardLegend()`、`lineSeries()`、`barSeries()`、`footnote()` 与 `watermarks()`,而不是在单个图表中重复手写基础样式。
|
||||||
|
|
||||||
- 财务含义相同的系列应使用固定语义色;IP、区域、品类等多分类数据使用统一分类色板。
|
- 财务含义相同的系列应使用固定语义色;IP、区域、品类等多分类数据使用统一分类色板。
|
||||||
|
- 柱线组合图采用已确认的“轻快消费”色板:柱状系列用不同色相承载分类,折线只保留一条高辨识度强调色。现金流、利润与同比使用 `FINANCIAL_COLORS`,渠道使用 `CHANNEL_COLORS`;瀑布图使用 `BRIDGE_COLORS`(蓝色基准、青绿色正向、珊瑚红负向)。新图不得在图表文件内散落同等语义的十六进制颜色。
|
||||||
|
- 一张图只保留一个视觉主角。辅助柱使用中等饱和度,避免多个红、橙、紫同时争夺注意力;分类图的相邻系列须同时拉开色相与明度。
|
||||||
|
- 颜色不是唯一编码:折线还须依靠线宽、实虚线、空心圆点和标签优先级;柱线混合图的折线标签使用 `lineDataWithSafeLabels()` 等通用避让规则,避免文字压在柱端。
|
||||||
- 新图的业务差异由 chart 文件表达;统一视觉由主题文件表达;展示位置与 CSV 由 manifest 表达。
|
- 新图的业务差异由 chart 文件表达;统一视觉由主题文件表达;展示位置与 CSV 由 manifest 表达。
|
||||||
- 新增或调整图表后,必须检查标签、图例、脚注和控制器是否重叠,以及颜色与文字是否有足够对比度。
|
- 新增或调整图表后,必须检查标签、图例、脚注和控制器是否重叠,以及颜色与文字是否有足够对比度。
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { col } from '../csv.js';
|
import { col } from '../csv.js';
|
||||||
import {
|
import {
|
||||||
PALETTE, FONT, axisCommon, titleStyle, footnote, watermarks,
|
PALETTE, BRIDGE_COLORS, FONT, axisCommon, titleStyle, footnote, watermarks,
|
||||||
} from '../theme.js';
|
} from '../theme.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,12 +13,6 @@ import {
|
|||||||
* 税率变化单独成项。恒等式 净利率 = 税前利润率 × (1-税率),
|
* 税率变化单独成项。恒等式 净利率 = 税前利润率 × (1-税率),
|
||||||
* Δ净 = Δ税前×(1-t₁) − 税前₂×Δt,实测闭合误差 0.00pct。
|
* Δ净 = Δ税前×(1-t₁) − 税前₂×Δt,实测闭合误差 0.00pct。
|
||||||
*/
|
*/
|
||||||
const COLOR = {
|
|
||||||
total: '#2F5C85', // 首尾锚点
|
|
||||||
neg: '#C0392B', // 拖累
|
|
||||||
pos: '#1A9E5C', // 利好
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ppmtBridgeChart(rows, { width, height }) {
|
export function ppmtBridgeChart(rows, { width, height }) {
|
||||||
const items = col(rows, 'item');
|
const items = col(rows, 'item');
|
||||||
const types = col(rows, 'type');
|
const types = col(rows, 'type');
|
||||||
@@ -92,7 +86,7 @@ export function ppmtBridgeChart(rows, { width, height }) {
|
|||||||
barWidth: 51,
|
barWidth: 51,
|
||||||
data: values.map((v, i) => ({
|
data: values.map((v, i) => ({
|
||||||
value: v,
|
value: v,
|
||||||
itemStyle: { color: COLOR[types[i]] },
|
itemStyle: { color: BRIDGE_COLORS[types[i]] },
|
||||||
})),
|
})),
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { col } from '../csv.js';
|
import { col } from '../csv.js';
|
||||||
import {
|
import {
|
||||||
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
PALETTE, FINANCIAL_COLORS, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
||||||
footnote, watermarks, LAYOUT,
|
footnote, watermarks, LAYOUT,
|
||||||
} from '../theme.js';
|
} from '../theme.js';
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ export function ppmtCoreProfitChart(rows, { width, height }) {
|
|||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
data: core,
|
data: core,
|
||||||
barWidth: 32,
|
barWidth: 32,
|
||||||
itemStyle: { color: PALETTE.navy },
|
itemStyle: { color: FINANCIAL_COLORS.netProfit },
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
@@ -107,12 +107,12 @@ export function ppmtCoreProfitChart(rows, { width, height }) {
|
|||||||
type: 'line',
|
type: 'line',
|
||||||
yAxisIndex: 1,
|
yAxisIndex: 1,
|
||||||
data: yoy,
|
data: yoy,
|
||||||
lineStyle: { color: PALETTE.coral, width: 3.08 }, // 与拆解图毛利率线一致
|
lineStyle: { color: FINANCIAL_COLORS.cashConversion, width: 3.08 },
|
||||||
// 空心圆点:白心 + 彩色描边。图例会自动继承这个样式,
|
// 空心圆点:白心 + 彩色描边。图例会自动继承这个样式,
|
||||||
// 渲染成「短线 + 空心圆」
|
// 渲染成「短线 + 空心圆」
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
borderColor: PALETTE.coral,
|
borderColor: FINANCIAL_COLORS.cashConversion,
|
||||||
borderWidth: 2.5, // 与 theme.js lineSeries 一致
|
borderWidth: 2.5, // 与 theme.js lineSeries 一致
|
||||||
},
|
},
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
@@ -123,7 +123,7 @@ export function ppmtCoreProfitChart(rows, { width, height }) {
|
|||||||
position: 'top',
|
position: 'top',
|
||||||
fontFamily: FONT,
|
fontFamily: FONT,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: PALETTE.coral,
|
color: FINANCIAL_COLORS.cashConversion,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
formatter: (p) => {
|
formatter: (p) => {
|
||||||
if (p.value === null || p.value === '') return '';
|
if (p.value === null || p.value === '') return '';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { col } from '../csv.js';
|
import { col } from '../csv.js';
|
||||||
import {
|
import {
|
||||||
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
PALETTE, FINANCIAL_COLORS, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
||||||
lineSeries, footnote, watermarks, LAYOUT,
|
lineDataWithSafeLabels, lineSeries, barSeries, footnote, watermarks, LAYOUT,
|
||||||
} from '../theme.js';
|
} from '../theme.js';
|
||||||
|
|
||||||
/** 泡泡玛特现金、负债与净营运资本:观察流动性与资金占用。 */
|
/** 泡泡玛特现金、负债与净营运资本:观察流动性与资金占用。 */
|
||||||
@@ -39,26 +39,35 @@ export function liquidityWorkingCapitalChart(rows, { width, height }) {
|
|||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
|
...barSeries({
|
||||||
name: '货币资金',
|
name: '货币资金',
|
||||||
type: 'bar',
|
|
||||||
data: col(rows, 'cash'),
|
data: col(rows, 'cash'),
|
||||||
|
color: FINANCIAL_COLORS.investingCashFlow,
|
||||||
barWidth: 18,
|
barWidth: 18,
|
||||||
itemStyle: { color: PALETTE.blue },
|
formatter: (p) => p.value.toFixed(1),
|
||||||
|
}),
|
||||||
emphasis: { focus: 'series' },
|
emphasis: { focus: 'series' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
...barSeries({
|
||||||
name: '负债合计',
|
name: '负债合计',
|
||||||
type: 'bar',
|
|
||||||
data: col(rows, 'total_liabilities'),
|
data: col(rows, 'total_liabilities'),
|
||||||
|
color: FINANCIAL_COLORS.financingCashFlow,
|
||||||
barWidth: 18,
|
barWidth: 18,
|
||||||
itemStyle: { color: PALETTE.orange },
|
formatter: (p) => p.value.toFixed(1),
|
||||||
|
}),
|
||||||
emphasis: { focus: 'series' },
|
emphasis: { focus: 'series' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...lineSeries({
|
...lineSeries({
|
||||||
name: '净营运资本',
|
name: '净营运资本',
|
||||||
data: col(rows, 'net_working_capital'),
|
data: lineDataWithSafeLabels(rows, {
|
||||||
color: PALETTE.red,
|
lineKey: 'net_working_capital',
|
||||||
|
barKeys: ['cash', 'total_liabilities'],
|
||||||
|
// 净营运资本接近货币资金时,标签置于柱体另一侧。
|
||||||
|
closePosition: 'bottom',
|
||||||
|
}),
|
||||||
|
color: FINANCIAL_COLORS.operatingCashFlow,
|
||||||
formatter: (p) => p.value.toFixed(1),
|
formatter: (p) => p.value.toFixed(1),
|
||||||
width: 3,
|
width: 3,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,18 +1,36 @@
|
|||||||
import { col } from '../csv.js';
|
import { col } from '../csv.js';
|
||||||
import {
|
import {
|
||||||
PALETTE, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
PALETTE, FINANCIAL_COLORS, FONT, axisCommon, periodAxis, titleStyle, legendStyle,
|
||||||
lineSeries, footnote, watermarks, LAYOUT,
|
lineSeries, footnote, watermarks, LAYOUT,
|
||||||
} from '../theme.js';
|
} from '../theme.js';
|
||||||
|
|
||||||
/** 泡泡玛特营运资本效率:存货、应收、应付与现金转换周期。 */
|
/** 泡泡玛特营运资本效率:存货、应收、应付与现金转换周期。 */
|
||||||
export function workingCapitalDaysChart(rows, { width, height }) {
|
const SERIES = [
|
||||||
const series = [
|
{ key: 'inventory_days', name: '存货周转天数', color: FINANCIAL_COLORS.financingCashFlow, width: 2.4 },
|
||||||
{ key: 'inventory_days', name: '存货周转天数', color: PALETTE.orange, width: 2.4 },
|
{ key: 'receivable_days', name: '应收周转天数', color: FINANCIAL_COLORS.investingCashFlow, width: 2.2 },
|
||||||
{ key: 'receivable_days', name: '应收周转天数', color: PALETTE.blue, width: 2.2 },
|
{ key: 'payable_days', name: '应付周转天数', color: FINANCIAL_COLORS.cashConversion, width: 2.2 },
|
||||||
{ key: 'payable_days', name: '应付周转天数', color: PALETTE.purple, width: 2.2 },
|
{ key: 'cash_conversion_cycle', name: '现金转换周期', color: FINANCIAL_COLORS.netCashIncrease, width: 3.2 },
|
||||||
{ key: 'cash_conversion_cycle', name: '现金转换周期', color: PALETTE.red, width: 3.2 },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同一期间的多条线将标签放到离最近相邻值更远的一侧,确保每个数值都可见。
|
||||||
|
*/
|
||||||
|
function labeledData(rows, key) {
|
||||||
|
return rows.map((row) => {
|
||||||
|
const value = row[key];
|
||||||
|
const ranked = SERIES.map((series) => row[series.key]).sort((a, b) => b - a);
|
||||||
|
const rank = ranked.indexOf(value);
|
||||||
|
const gapUp = rank > 0 ? ranked[rank - 1] - value : Infinity;
|
||||||
|
const gapDown = rank < ranked.length - 1 ? value - ranked[rank + 1] : Infinity;
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
label: { position: gapUp >= gapDown ? 'top' : 'bottom', distance: 9 },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function workingCapitalDaysChart(rows, { width, height }) {
|
||||||
return {
|
return {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
title: {
|
title: {
|
||||||
@@ -20,7 +38,7 @@ export function workingCapitalDaysChart(rows, { width, height }) {
|
|||||||
text: '泡泡玛特营运资本效率',
|
text: '泡泡玛特营运资本效率',
|
||||||
subtext: '现金转换周期 = 存货周转天数 + 应收周转天数 − 应付周转天数',
|
subtext: '现金转换周期 = 存货周转天数 + 应收周转天数 − 应付周转天数',
|
||||||
},
|
},
|
||||||
legend: { ...legendStyle, top: LAYOUT.legendTop, data: series.map((item) => item.name) },
|
legend: { ...legendStyle, top: LAYOUT.legendTop, data: SERIES.map((item) => item.name) },
|
||||||
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 90 },
|
grid: { left: 78, right: 42, top: LAYOUT.gridTop, bottom: 90 },
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
@@ -37,17 +55,25 @@ export function workingCapitalDaysChart(rows, { width, height }) {
|
|||||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||||
min: 0,
|
min: 0,
|
||||||
},
|
},
|
||||||
series: series.map((item) => ({
|
series: SERIES.map((item) => ({
|
||||||
...lineSeries({
|
...lineSeries({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
data: col(rows, item.key),
|
data: labeledData(rows, item.key),
|
||||||
color: item.color,
|
color: item.color,
|
||||||
formatter: (p) => p.value.toFixed(0),
|
formatter: (p) => p.value.toFixed(0),
|
||||||
width: item.width,
|
width: item.width,
|
||||||
}),
|
}),
|
||||||
label: item.key === 'cash_conversion_cycle'
|
label: {
|
||||||
? { show: true, color: item.color, fontFamily: FONT, fontSize: 11, formatter: (p) => `${p.value.toFixed(0)}天` }
|
show: true,
|
||||||
: { show: false },
|
color: item.color,
|
||||||
|
fontFamily: FONT,
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 500,
|
||||||
|
formatter: (p) => `${p.value.toFixed(0)}天`,
|
||||||
|
textBorderColor: '#fff',
|
||||||
|
textBorderWidth: 3,
|
||||||
|
},
|
||||||
|
labelLayout: { hideOverlap: true },
|
||||||
z: item.key === 'cash_conversion_cycle' ? 10 : 1,
|
z: item.key === 'cash_conversion_cycle' ? 10 : 1,
|
||||||
})),
|
})),
|
||||||
graphic: [
|
graphic: [
|
||||||
|
|||||||
+31
-16
@@ -28,24 +28,34 @@ export const PALETTE = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 试行语义色:柱线组合图中,柱采用低饱和辅助色,折线保留单一强调色。
|
* 正式色板:轻快消费。
|
||||||
* 待页面视觉确认后再固化为新增图表的正式规范。
|
* 柱线组合中,柱用不同色相承载分类,折线保留高辨识度的单一强调色。
|
||||||
*/
|
*/
|
||||||
export const FINANCIAL_COLORS = {
|
export const FINANCIAL_COLORS = {
|
||||||
operatingCashFlow: '#6E9A83',
|
operatingCashFlow: '#36A28F',
|
||||||
investingCashFlow: '#7897B2',
|
investingCashFlow: '#4D83D1',
|
||||||
financingCashFlow: '#B18A66',
|
financingCashFlow: '#EEA64F',
|
||||||
netCashIncrease: '#A8465C',
|
netCashIncrease: '#D95567',
|
||||||
netProfit: '#C9885C',
|
netProfit: '#F08B5A',
|
||||||
cashConversion: '#6B4A6C',
|
cashConversion: '#8256B2',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 瀑布图语义色:基准值、正向贡献、负向贡献。
|
||||||
|
* 复用轻快消费财务色板,避免利润率拆解图另起一套颜色。
|
||||||
|
*/
|
||||||
|
export const BRIDGE_COLORS = {
|
||||||
|
total: FINANCIAL_COLORS.investingCashFlow,
|
||||||
|
pos: FINANCIAL_COLORS.operatingCashFlow,
|
||||||
|
neg: FINANCIAL_COLORS.netCashIncrease,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CHANNEL_COLORS = {
|
export const CHANNEL_COLORS = {
|
||||||
retail: '#597A9B',
|
retail: '#3F7FBE',
|
||||||
online: '#C87368',
|
online: '#E36D63',
|
||||||
robotStore: '#98A77A',
|
robotStore: '#71AD81',
|
||||||
wholesale: '#BB9A5C',
|
wholesale: '#E4A349',
|
||||||
revenueGrowth: '#775D8B',
|
revenueGrowth: '#8256B2',
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 坐标轴通用样式:只留浅色横向网格,去掉轴线的存在感 */
|
/** 坐标轴通用样式:只留浅色横向网格,去掉轴线的存在感 */
|
||||||
@@ -201,9 +211,12 @@ export function lineSeries({ name, data, color, labelPosition = 'top', formatter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 柱线组合图的折线标签:正值放在点上方、负值放在点下方;
|
* 柱线组合图的折线标签:正值放在点上方、负值放在点下方;
|
||||||
* 若与同期间柱值接近,则加大距离,避免文字压在柱体端点附近。
|
* 若与同期间柱值接近,则加大距离;也可指定将标签放到柱体另一侧,
|
||||||
|
* 避免文字压在柱体端点附近。
|
||||||
*/
|
*/
|
||||||
export function lineDataWithSafeLabels(rows, { lineKey, barKeys, distance = 8, closeDistance = 14 }) {
|
export function lineDataWithSafeLabels(rows, {
|
||||||
|
lineKey, barKeys, distance = 8, closeDistance = 14, closePosition,
|
||||||
|
}) {
|
||||||
const values = rows.map((row) => row[lineKey]).filter(Number.isFinite);
|
const values = rows.map((row) => row[lineKey]).filter(Number.isFinite);
|
||||||
const range = Math.max(...values) - Math.min(...values) || 1;
|
const range = Math.max(...values) - Math.min(...values) || 1;
|
||||||
const threshold = range * 0.06;
|
const threshold = range * 0.06;
|
||||||
@@ -217,7 +230,9 @@ export function lineDataWithSafeLabels(rows, { lineKey, barKeys, distance = 8, c
|
|||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
label: {
|
label: {
|
||||||
position: value < 0 ? 'bottom' : 'top',
|
position: nearestBarGap <= threshold && closePosition
|
||||||
|
? closePosition
|
||||||
|
: (value < 0 ? 'bottom' : 'top'),
|
||||||
distance: nearestBarGap <= threshold ? closeDistance : distance,
|
distance: nearestBarGap <= threshold ? closeDistance : distance,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user