chart-model.ts
9.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
/**
* echarts demo: https://www.echartsjs.com/examples/zh/editor.html?c=line-simple
* 通过输入固定格式的 echarts dataset,得到 echarts options
*
* TODO X轴 formatter:1.只针对 X轴,不针对 tooltip 2. tooltip 和 X轴同时生效
* TODO legend(series) 到多Y轴的映射
*/
import _ from "lodash";
// import numeral from "numeral";
/**
*
yIndexMap: {
系列1: 0,
系列2: 1
}
const users = [
{
x: "2010/01/01 00:00:00",
y: 500,
s: "系列1"
},
{
x: "2010/01/01 00:00:00",
y: 180,
s: "系列2"
},
{
x: "2010/02/01 00:00:00",
y: 250,
s: "系列1"
},
{
x: "2010/02/01 00:00:00",
y: 100,
s: "系列2"
},
{
x: "2010/03/01 00:00:00",
y: 325,
s: "系列1"
},
{
x: "2010/03/01 00:00:00",
y: 175,
s: "系列2"
},
{
x: "2010/04/01 00:00:00",
y: 190,
s: "系列1"
},
{
x: "2010/04/01 00:00:00",
y: 110,
s: "系列2"
},
{
x: "2010/05/01 00:00:00",
y: 260,
s: "系列1"
},
{
x: "2010/05/01 00:00:00",
y: 60,
s: "系列2"
},
{
x: "2010/05/19 00:00:00",
y: 90,
s: "系列2"
},
{
x: "2010/05/23 00:00:00",
y: 90,
s: "系列2"
}
];
*
*/
// 固定格式
interface DataSetItem {
x: string; // x轴
y: string | number; // y轴,也就是 value
s: string | number; // s 也就是 legend
[propName: string]: any;
}
interface SeriesItemDict {
[propName: string]: any;
}
interface SeriesItem {
name?: string; // 可选
type: string;
data: Array<string | number>;
itemStyle: {
normal: {
color: string;
};
// emphasis: {
// color: '#59a4ed',
// },
};
yAxisIndex?: number
// TODO 指定 Y轴
// TODO zlevel: 0,
}
interface YAxisOption {
show: boolean;
axisLabelFormatter: (value: any) => any;
}
interface formatDictType {
[propName: string]: string;
}
interface uniqByResultType {
[legendKey: string]: string // legendValue
}
interface LineChartConfig {
xAxis?: Array<string|number>;
dataset: DataSetItem[];
yIndexMap: {
[legendkey: string]: number
}
}
const formatDict: formatDictType = {
gp: "$0.00",
gross_profit: "$0.00",
revenue: "$0.00",
reveue_f: "$0.00",
cvr: "0.00%",
click_count: "0,00",
conv: "0,00",
show_rate: "0.00%",
fill_rate: "0.00%"
};
export class LineChart {
// 指定折线采用哪一条Y轴
yIndexMap: {
// 纬度/折线
[legendName: string]: number
};
chartType: string; // 'line' | 'bar' | 'pie'
// TODO class 里面如何定义 ts
colors: {
series: any;
labelText: any;
splitLine: string;
};
// 数据源
dataset: DataSetItem[];
keys: {
xAxis: string;
yAxis: string;
legend: string;
};
legends: string[];
xAxis: Array<string | number>;
yAxis: any[];
series: Array<SeriesItem>;
tooltip: any;
constructor({
xAxis = [],
yIndexMap = {},
dataset
} : LineChartConfig
) {
this.chartType = "line";
this.colors = {
series: [
{ value: "#57b2ff" },
{ value: "#d70b24" },
{ value: "#48c765" },
{ value: "#fbb64e" },
{ value: "#f95e58" }
],
labelText: {
xAxis: "rgba(0,0,0,.6)",
yAxis: "rgba(0,0,0,.6)"
},
splitLine: "rgba(236, 237, 239, 0.26)"
};
this.yIndexMap = yIndexMap;
this.dataset = dataset;
this.keys = { xAxis: "x", yAxis: "y", legend: "s" };
this.legends = this.getLegends();
this.xAxis = (xAxis.length && xAxis) || this.getXAxis();
this.yAxis = this.getYAxis(this.legends);
this.series = this.getSeries(this.legends, this.xAxis);
this.tooltip = this.getTooltip();
}
/**
* 填补缺失值,说白了其实就是 merge
* seriesTemplate: [{x: '01-01', y: null}, {x: '02-01', y: null}, {x: '03-01', y: null}]
* seriesItemsFromDataSet: [{ x: '01-01', y: 11}, {x: '03-01', y: 33 }]
*
* => [{x: '01-01', y: 11}, {x: '02-01', y: null}, {x: '03-01', y: 33}]
*
* seriesTemplate (X轴所有点): ['01-01', '01-02', '01-03]
* seriesItemsFromDataSet: 可能缺失的数据作为 填充值
* options: { legend: string // 某条折线图名字 }
*
* seriesTemplate -> 循环填充默认值 -> [Object, Object]
* seriesItemsFromDataSet -> 字典{} -> 循环模板(X轴所有点)-> 找到X轴上点在 seriesItemsFromDataSet字典中的值 ->,替换模板中的默认值 -> 返回填充后的模板
*/
padMissingValues(
seriesTemplate: Array<string | number>,
seriesItemsFromDataSet: DataSetItem[],
options: { legend: string }
): DataSetItem[] {
const { xAxis, yAxis, legend } = this.keys;
const seriesItemsFromDataSetDict: SeriesItemDict = seriesItemsFromDataSet.reduce(
(obj, curr: DataSetItem) => ({
...obj,
[curr[xAxis]]: curr
}),
{}
);
const templateWithValue = seriesTemplate.map(xAxis => {
return (
seriesItemsFromDataSetDict[xAxis] || {
[legend]: options.legend,
[xAxis]: xAxis,
[yAxis]: null
}
);
});
return templateWithValue;
}
/**
* 对于复杂的数据转换,需要写 ts,方便看懂代码之间的数据流转
* @returns: ['折线图1', '折线图2']
*/
getLegends(): string[] {
const legendKey = this.keys.legend;
const allLegends: string[] = _.uniqBy(this.dataset, legendKey).map(
(item: uniqByResultType) => item[legendKey]
);
const legends: string[] = Array.from(new Set(allLegends));
return legends;
}
/**
* 获取X轴的数据
* demo: ['01-01', '02-01', '03-01', '04-01']
*/
getXAxis(): Array<string | number> {
const xAxis = this.dataset.map(item => item[this.keys.xAxis]);
return xAxis;
}
getTooltip(): any {
return {
trigger: "axis"
};
}
getDefaultYAxis(option: YAxisOption) {
const { show, axisLabelFormatter } = option;
return {
show,
type: "value",
axisLabel: {
// Y轴文字颜色,
color: this.colors.labelText.xAxis,
// formatter: '{value}',
formatter: axisLabelFormatter
},
// 不需要对 Y 轴顶部的文字做定制
// nameTextStyle: {
// color: [yAxisTitleFontColor],
// fontSize: 14,
// fontWeight: 600,
// },
// y轴坐标轴轴线, 也就是y轴的一条竖线
axisLine: {
show: false,
lineStyle: {
// y轴上数字的颜色
fontSize: 15,
color: this.colors.labelText.yAxis
// opacity: 1
}
},
// 显示轴线与数值之间的 「-」
axisTick: { show: false },
splitLine: {
lineStyle: {
// 使用深浅的间隔色
// 类似四条三格的横线的颜色
color: [this.colors.splitLine]
}
}
};
}
/**
* Y轴数据
* @param legends
*/
getYAxis(legends: string[]): any[] {
// TODO 如果返回值 函数怎么办呢?
function getAxisLabelFormatter(legend: string) {
return function(value: any) {
// const format = formatDict[legend] || "0.0a";
// return numeral(value).format(format);
// TODO 自己实现 formatter 函数
return value
};
}
const yAxis = legends.map(legend =>
this.getDefaultYAxis({
show: window.innerWidth > 768,
// TODO 外界传进来legendValueFormatter
axisLabelFormatter: getAxisLabelFormatter(legend)
})
);
return yAxis;
}
/**
*
* @param {*} legends
* @param {*} xAxis
*
interface Series {
name: string,
data: seriesData,
}
*/
getSeries(legends: string[], xAxis: Array<string | number>): SeriesItem[] {
/**
interface groupbyLegend {
[legendKey1]: [
[legendKey1]: legendValue,
[xAxiskey]: xAxisValue,
[yAxiskey]: yAxisValue
]
}
*/
const groupbyLegend = _.groupBy(this.dataset, this.keys.legend);
const series = legends.map((legend: string, index: number) => {
const seriesItemsFromDataSet: DataSetItem[] = groupbyLegend[legend];
const data: Array<string | number> = this.padMissingValues(
xAxis,
seriesItemsFromDataSet,
{ legend }
).map(item => item[this.keys.yAxis]);
return {
name: legend, // 可选
type: this.chartType,
// TODO 值和 value 对应起来
data,
yAxisIndex: this.yIndexMap[legend] || 0,
itemStyle: {
normal: {
color: this.colors.series[index].value
}
// emphasis: {
// color: '#59a4ed',
// },
}
// TODO 指定 Y轴
// TODO zlevel: 0,
};
});
return series;
}
getDefaultOption() {
return {
tooltip: {
show: true,
trigger: "axis"
},
axisTick: { show: false },
xAxis: {
type: "category",
boundaryGap: true,
axisTick: { show: false },
axisLine: {
lineStyle: {
// x轴颜色, 包含:x轴的颜色、文字颜色
// 图例: https://jietu.qq.com/upload/index.html?image=http://jietu-10024907.file.myqcloud.com/hwfezrujmosnhjesfoweuqdguwrwyekw.jpg
color: "rgba(0,0,0,.6)"
}
}
},
grid: {
y: 40, // 图表和legend之间的距离
left: "5%", // 图表的x轴的零点距离容器左侧的距离
right: "4%", // 图表的x轴的最右点距离容器右侧的距离
bottom: 30
},
color: this.colors.series
};
}
getOption() {
const option = {
textStyle: {
fontFamily: "Roboto"
},
tooltip: this.tooltip,
series: this.series,
legend: {
data: this.legends,
show: true
},
xAxis: {
data: this.xAxis
},
yAxis: this.yAxis
};
return _.merge(option, this.getDefaultOption());
}
}