feat: 构建泡泡玛特相关图表
This commit is contained in:
+247
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* 方木风格主题 —— 统一的配色、字体、坐标轴、水印
|
||||
* 所有图表共用这一份配置,保证视觉一致性
|
||||
*/
|
||||
|
||||
export const WATERMARK_TEXT = '@思考的Joey';
|
||||
|
||||
// 字体名用单引号:SVG 渲染时字符串会被塞进 style="..." 属性,
|
||||
// 用双引号会提前闭合属性、生成非法 SVG(canvas 渲染看不出来,导出 SVG 时才炸)
|
||||
export const FONT = "'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif";
|
||||
|
||||
/** 调色板:低饱和、克制,接近财经媒体常用的莫兰迪系 */
|
||||
export const PALETTE = {
|
||||
brown: '#6b4a2b',
|
||||
sand: '#d5c7b4',
|
||||
coral: '#ec7b73',
|
||||
green: '#2f8a5b',
|
||||
orange: '#d98324',
|
||||
purple: '#9b4fa8',
|
||||
blue: '#3b6ea5',
|
||||
navy: '#1f4e79',
|
||||
red: '#b8342a',
|
||||
sage: '#8aab7e',
|
||||
ink: '#333333',
|
||||
grey: '#8c8c8c',
|
||||
gridLine: '#e8e8e8',
|
||||
axisLine: '#d0d0d0',
|
||||
};
|
||||
|
||||
/** 坐标轴通用样式:只留浅色横向网格,去掉轴线的存在感 */
|
||||
export const axisCommon = {
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: PALETTE.grey,
|
||||
fontSize: 13,
|
||||
fontFamily: FONT,
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: { color: PALETTE.gridLine, width: 1 },
|
||||
},
|
||||
};
|
||||
|
||||
/** X 轴:显示轴线,不显示网格 */
|
||||
export const categoryAxis = {
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
axisLine: { show: true, lineStyle: { color: PALETTE.axisLine } },
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: PALETTE.ink,
|
||||
fontSize: 14,
|
||||
fontFamily: FONT,
|
||||
margin: 14,
|
||||
},
|
||||
splitLine: { show: false },
|
||||
};
|
||||
|
||||
/**
|
||||
* 期间标签简写:2026H1 → 26H1,2026H2 → 26全年。
|
||||
* CSV 里保留四位年份(无歧义、便于核对),只在轴上简化显示。
|
||||
* 非 20xx 开头的标签(如"年初")原样返回。
|
||||
*/
|
||||
export function shortPeriod(label) {
|
||||
return String(label)
|
||||
.replace(/^20(\d{2})H2$/, '$1全年') // H2 = 全年累计
|
||||
.replace(/^20(\d{2})/, '$1'); // 其余去掉世纪前缀
|
||||
}
|
||||
|
||||
/** X 轴(期间):在 categoryAxis 基础上套用简写 */
|
||||
export const periodAxis = {
|
||||
...categoryAxis,
|
||||
axisLabel: {
|
||||
...categoryAxis.axisLabel,
|
||||
formatter: shortPeriod,
|
||||
},
|
||||
};
|
||||
|
||||
export const titleStyle = {
|
||||
left: 'center',
|
||||
top: 18,
|
||||
textStyle: {
|
||||
color: PALETTE.ink,
|
||||
fontSize: 19,
|
||||
fontWeight: 'bold',
|
||||
fontFamily: FONT,
|
||||
},
|
||||
subtextStyle: {
|
||||
color: PALETTE.grey,
|
||||
fontSize: 13,
|
||||
fontFamily: FONT,
|
||||
lineHeight: 20,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 标准布局常量
|
||||
* 经多图表调试验证的标题-图例-绘图区间距,确保视觉呼吸感一致
|
||||
*/
|
||||
export const LAYOUT = {
|
||||
titleTop: 18, // 标题顶部边距(已内置在 titleStyle 中)
|
||||
legendTop: 85, // 图例顶部边距(副标题到图例约 25px)
|
||||
gridTop: 145, // 绘图区顶部边距(图例到绘图区约 60px)
|
||||
};
|
||||
|
||||
export const legendStyle = {
|
||||
top: 62, // ⚠️ 已废弃:请改用 LAYOUT.legendTop 或 standardLegend()
|
||||
itemGap: 26,
|
||||
itemWidth: 18,
|
||||
// ❌ 删除 icon: 'roundRect'
|
||||
// 原因:它会覆盖 ECharts 按系列类型自动生成的图例样式(折线图的"短线+空心圆")
|
||||
// 现在让每个图表按需设置,或使用 standardLegend() 获得正确默认值
|
||||
textStyle: {
|
||||
color: PALETTE.ink,
|
||||
fontSize: 14,
|
||||
fontFamily: FONT,
|
||||
},
|
||||
};
|
||||
|
||||
/** 折线标注:带白底描边,避免压在网格线上看不清 */
|
||||
export function labelStyle(color, position = 'top') {
|
||||
return {
|
||||
show: true,
|
||||
position,
|
||||
color,
|
||||
fontSize: 13,
|
||||
fontFamily: FONT,
|
||||
fontWeight: 500,
|
||||
distance: 8,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准图例配置
|
||||
* 不设置 icon,让 ECharts 按系列类型自动生成(折线→短线+空心圆,柱状→矩形)
|
||||
* 统一使用 LAYOUT.legendTop 保证间距一致
|
||||
*/
|
||||
export function standardLegend(data, options = {}) {
|
||||
return {
|
||||
top: LAYOUT.legendTop,
|
||||
itemGap: options.itemGap ?? 28,
|
||||
textStyle: {
|
||||
color: PALETTE.ink,
|
||||
fontSize: 14,
|
||||
fontFamily: FONT,
|
||||
},
|
||||
data,
|
||||
...options, // 允许覆盖任何字段
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 空心圆点折线系列 —— 标准配置
|
||||
* 白心 + 彩色边框,视觉统一(symbolSize: 9, borderWidth: 2.5)
|
||||
*/
|
||||
export function lineSeries({ name, data, color, labelPosition = 'top', formatter, width = 3 }) {
|
||||
return {
|
||||
name,
|
||||
type: 'line',
|
||||
data,
|
||||
smooth: false,
|
||||
symbol: 'circle',
|
||||
symbolSize: 9,
|
||||
itemStyle: {
|
||||
color: '#fff',
|
||||
borderColor: color,
|
||||
borderWidth: 2.5,
|
||||
},
|
||||
lineStyle: { color, width },
|
||||
emphasis: { focus: 'series' },
|
||||
label: {
|
||||
...labelStyle(color, labelPosition),
|
||||
formatter,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 柱状图系列标准配置
|
||||
* 强制显示所有标签(overflow: 'none'),避免小柱子标签被 ECharts 自动隐藏
|
||||
*/
|
||||
export function barSeries({ name, data, color, barWidth, formatter, ...rest }) {
|
||||
return {
|
||||
name,
|
||||
type: 'bar',
|
||||
data,
|
||||
barWidth,
|
||||
itemStyle: { color },
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
fontFamily: FONT,
|
||||
fontSize: 11,
|
||||
color: PALETTE.ink,
|
||||
formatter: formatter ?? ((p) => p.value),
|
||||
overflow: 'none', // 关键:强制显示所有标签
|
||||
},
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
|
||||
/** 底部注释 */
|
||||
export function footnote(text) {
|
||||
return {
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
bottom: 12,
|
||||
style: {
|
||||
text,
|
||||
fill: PALETTE.grey,
|
||||
fontSize: 12,
|
||||
fontFamily: FONT,
|
||||
lineHeight: 18,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 平铺水印。ECharts 没有原生水印,用 graphic 数组铺满画布。
|
||||
* 旋转 -20°,透明度压到 0.08,不干扰读数。
|
||||
*/
|
||||
export function watermarks(width, height, text = WATERMARK_TEXT) {
|
||||
const items = [];
|
||||
const stepX = 300;
|
||||
const stepY = 200;
|
||||
for (let x = -100; x < width + stepX; x += stepX) {
|
||||
for (let y = 40; y < height + stepY; y += stepY) {
|
||||
const offset = (Math.floor(y / stepY) % 2) * (stepX / 2);
|
||||
items.push({
|
||||
type: 'text',
|
||||
left: x + offset,
|
||||
top: y,
|
||||
silent: true,
|
||||
rotation: 0.35,
|
||||
style: {
|
||||
text,
|
||||
fill: 'rgba(120, 100, 80, 0.10)',
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
fontFamily: FONT,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
Reference in New Issue
Block a user