feat: 构建泡泡玛特相关图表
This commit is contained in:
+187
@@ -0,0 +1,187 @@
|
||||
import {
|
||||
PALETTE, FONT, axisCommon, categoryAxis, titleStyle,
|
||||
lineSeries, footnote, watermarks,
|
||||
} from '../theme.js';
|
||||
|
||||
/** 圆角卡片:ECharts 的 graphic.group + rect + 多段文字 */
|
||||
function infoCard({ left, top, width, title, lines, fill, stroke, titleColor }) {
|
||||
const children = [
|
||||
{
|
||||
type: 'rect',
|
||||
shape: { x: 0, y: 0, width, height: 108, r: 8 },
|
||||
style: { fill, stroke, lineWidth: 1 },
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
style: {
|
||||
x: 20, y: 20,
|
||||
text: title,
|
||||
fill: titleColor,
|
||||
fontSize: 15,
|
||||
fontWeight: 'bold',
|
||||
fontFamily: FONT,
|
||||
},
|
||||
},
|
||||
];
|
||||
lines.forEach((line, i) => {
|
||||
children.push({
|
||||
type: 'text',
|
||||
style: {
|
||||
x: 20, y: 52 + i * 22,
|
||||
text: line,
|
||||
fill: '#555',
|
||||
fontSize: 12.5,
|
||||
fontFamily: FONT,
|
||||
},
|
||||
});
|
||||
});
|
||||
return { type: 'group', left, top, silent: true, children };
|
||||
}
|
||||
|
||||
/**
|
||||
* 图 5:人民币兑美元两种口径对比
|
||||
* 只有两个时间点,重点在文字说明和端点标注,不在曲线本身
|
||||
*/
|
||||
export function fxChart(rows, { width, height }) {
|
||||
const stages = rows.map((r) => r.stage);
|
||||
const mid = rows.map((r) => r.mid_price);
|
||||
const spot = rows.map((r) => r.onshore_spot);
|
||||
|
||||
// label.position 不接受函数(写函数会被静默忽略,退化成默认位置),
|
||||
// 想让首尾点位置不同只能逐点覆盖:起点标在上方,末点标在右侧
|
||||
const endpointLabels = (values) =>
|
||||
values.map((v, i) => ({
|
||||
value: v,
|
||||
label: { position: i === values.length - 1 ? 'right' : 'top' },
|
||||
}));
|
||||
|
||||
// 偏离点数 = (中间价 - 即期)× 10000
|
||||
const gapStart = Math.round((mid[0] - spot[0]) * 10000);
|
||||
const gapEnd = Math.round((mid[1] - spot[1]) * 10000);
|
||||
const midGain = Math.round((mid[0] - mid[1]) * 10000);
|
||||
const spotGain = Math.round((spot[0] - spot[1]) * 10000);
|
||||
const midPct = (((mid[0] - mid[1]) / mid[0]) * 100).toFixed(1);
|
||||
const spotPct = (((spot[0] - spot[1]) / spot[0]) * 100).toFixed(1);
|
||||
|
||||
const cardWidth = 340;
|
||||
const cardTop = 132;
|
||||
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: {
|
||||
...titleStyle,
|
||||
text: '人民币兑美元的两种「升值」口径:中间价 vs 在岸即期',
|
||||
subtext: '口径区别在「谁定的价」:中间价是央行每日公布的锚,即期是市场在锚附近交易出来的成交价',
|
||||
top: 20,
|
||||
},
|
||||
grid: { left: 96, right: 110, top: 340, bottom: 88 },
|
||||
tooltip: { trigger: 'axis', textStyle: { fontFamily: FONT } },
|
||||
xAxis: {
|
||||
...categoryAxis,
|
||||
boundaryGap: ['12%', '12%'],
|
||||
data: stages,
|
||||
},
|
||||
yAxis: {
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
name: 'USDCNY(数值越小=人民币越强)',
|
||||
nameLocation: 'middle',
|
||||
nameGap: 62,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
min: 6.75,
|
||||
max: 7.05,
|
||||
interval: 0.1,
|
||||
axisLabel: {
|
||||
...axisCommon.axisLabel,
|
||||
formatter: (v) => v.toFixed(2),
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
...lineSeries({
|
||||
name: '中间价',
|
||||
data: mid,
|
||||
color: '#3b6ea5',
|
||||
formatter: (p) => p.value.toFixed(4),
|
||||
}),
|
||||
data: endpointLabels(mid),
|
||||
symbolSize: 11,
|
||||
itemStyle: { color: '#3b6ea5' },
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 15,
|
||||
fontWeight: 'bold',
|
||||
color: '#3b6ea5',
|
||||
fontFamily: FONT,
|
||||
distance: 10,
|
||||
formatter: (p) => p.value.toFixed(4),
|
||||
},
|
||||
},
|
||||
{
|
||||
...lineSeries({
|
||||
name: '在岸即期',
|
||||
data: spot,
|
||||
color: PALETTE.coral,
|
||||
formatter: (p) => p.value.toFixed(4),
|
||||
}),
|
||||
data: endpointLabels(spot),
|
||||
symbolSize: 11,
|
||||
itemStyle: { color: PALETTE.coral },
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 15,
|
||||
fontWeight: 'bold',
|
||||
color: PALETTE.coral,
|
||||
fontFamily: FONT,
|
||||
distance: 10,
|
||||
formatter: (p) => p.value.toFixed(4),
|
||||
},
|
||||
},
|
||||
],
|
||||
graphic: [
|
||||
...watermarks(width, height),
|
||||
|
||||
infoCard({
|
||||
left: 88, top: cardTop, width: cardWidth,
|
||||
title: '中间价(央行公布的「锚」)',
|
||||
lines: [
|
||||
'每交易日 9:15 由央行公布:做市商报价',
|
||||
'加权+逆周期因子,是当日波动中心与政策信号',
|
||||
],
|
||||
fill: '#eef4fa', stroke: '#c7dcf0', titleColor: '#2f5f8f',
|
||||
}),
|
||||
infoCard({
|
||||
left: 88 + cardWidth + 24, top: cardTop, width: cardWidth,
|
||||
title: '在岸即期(市场成交价)',
|
||||
lines: [
|
||||
'银行间外汇市场实时成交形成,16:30 收盘价',
|
||||
'最常被引用;每日只能在中间价 ±2% 区间内波动',
|
||||
],
|
||||
fill: '#fdf1ed', stroke: '#f5d3c8', titleColor: '#c05a45',
|
||||
}),
|
||||
|
||||
// 图例改成两行文字说明,信息量比色块图例大
|
||||
{
|
||||
type: 'text',
|
||||
left: 108, top: 272, silent: true,
|
||||
style: {
|
||||
text: `● 中间价:${mid[0]} → ${mid[1]},累计升值 ${midGain}点(+${midPct}%)`,
|
||||
fill: '#3b6ea5', fontSize: 14, fontFamily: FONT,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
left: 108, top: 296, silent: true,
|
||||
style: {
|
||||
text: `● 在岸即期:${spot[0]} → ${spot[1]},累计升值 ${spotGain}点(+${spotPct}%)`,
|
||||
fill: PALETTE.coral, fontSize: 14, fontFamily: FONT,
|
||||
},
|
||||
},
|
||||
|
||||
footnote(
|
||||
`注:涨幅差${midGain - spotGain}点=期初偏离${gapStart}−期末偏离${gapEnd}(即期偏强收窄)|` +
|
||||
'汇兑重估用市场汇率而非中间价|数据:新华财经|Joey 谨制'
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user