st-comp 0.0.44 → 0.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bundle.js +134920 -134058
- package/lib/bundle.umd.cjs +57 -57
- package/lib/style.css +1 -1
- package/package.json +1 -1
- package/packages/Kline/index.vue +201 -7
- package/packages/Kline/option.ts +169 -8
- package/packages/Kline/type.d.ts +1 -0
- package/packages/KlineNew/components/KlineSlide/index.vue +174 -0
- package/packages/KlineNew/components/KlineSub/index.vue +331 -0
- package/packages/KlineNew/components/MainTips/index.vue +61 -0
- package/packages/KlineNew/components/Tips/index.vue +33 -0
- package/packages/KlineNew/index.vue +221 -168
- package/packages/KlineNew/klineConfig.ts +44 -0
- package/packages/KlineNew/option.ts +70 -109
- package/packages/KlineNew/utils.ts +146 -3
- package/src/pages/Kline/api.ts +81 -39
- package/src/pages/Kline/components/SingleCycleSingleVariety.vue +155 -78
- package/src/pages/KlineNew/index.vue +42 -2
package/package.json
CHANGED
package/packages/Kline/index.vue
CHANGED
|
@@ -41,6 +41,18 @@ const props = defineProps({
|
|
|
41
41
|
type: Array,
|
|
42
42
|
default: () => [],
|
|
43
43
|
},
|
|
44
|
+
relevanceData: {
|
|
45
|
+
type: Array,
|
|
46
|
+
default: () => [],
|
|
47
|
+
},
|
|
48
|
+
priceTrendData: {
|
|
49
|
+
type: Array,
|
|
50
|
+
default: () => [],
|
|
51
|
+
},
|
|
52
|
+
priceTrendPercentData: {
|
|
53
|
+
type: Array,
|
|
54
|
+
default: () => [],
|
|
55
|
+
},
|
|
44
56
|
brushRange: {
|
|
45
57
|
type: Array,
|
|
46
58
|
default: () => [],
|
|
@@ -123,6 +135,8 @@ const defaultConfig: InConfig = {
|
|
|
123
135
|
},
|
|
124
136
|
// 是否禁用鼠标滚轮缩放K线,在开启动态后续数据加载时,建议开启,否则会引起datazoom内部缩放拖拽触发后续加载冲突
|
|
125
137
|
zoomLock: false,
|
|
138
|
+
// 是否启用计算收盘价分布统计功能
|
|
139
|
+
isOpenDS: false,
|
|
126
140
|
};
|
|
127
141
|
|
|
128
142
|
/**
|
|
@@ -171,10 +185,58 @@ const indicatorTips = computed(() => {
|
|
|
171
185
|
}
|
|
172
186
|
return [];
|
|
173
187
|
});
|
|
188
|
+
// Tips: 价格趋势[价差定制]
|
|
189
|
+
const priceTrendTips = computed(() => {
|
|
190
|
+
// 颜色配置
|
|
191
|
+
const colorList = [
|
|
192
|
+
"#FFFFFF",
|
|
193
|
+
"#FFDD00",
|
|
194
|
+
"#FF00FF",
|
|
195
|
+
"#00FF00",
|
|
196
|
+
"#FF6000",
|
|
197
|
+
"#1677FF",
|
|
198
|
+
"#7C3CC9",
|
|
199
|
+
"#FF0000",
|
|
200
|
+
"#FB9A0E",
|
|
201
|
+
"#00B7FF",
|
|
202
|
+
];
|
|
203
|
+
// 价格趋势
|
|
204
|
+
if (props.priceTrendData.length > 0) {
|
|
205
|
+
const result = [];
|
|
206
|
+
props.priceTrendData.forEach((item: any, index: number) => {
|
|
207
|
+
const value = item.data[activeIndex.value] ? formatValue(item.data[activeIndex.value][1]) : null;
|
|
208
|
+
if (value !== null) {
|
|
209
|
+
result.push({
|
|
210
|
+
label: item.name,
|
|
211
|
+
value: formatValue(item.data[activeIndex.value]?.[1]),
|
|
212
|
+
color: colorList[index],
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
// 价格趋势百分比
|
|
219
|
+
if (props.priceTrendPercentData.length > 0) {
|
|
220
|
+
const result = [];
|
|
221
|
+
const chartOption = echartsInstance.getOption()
|
|
222
|
+
chartOption.series.filter(line => line.name.includes('二腿价格走势百分比图')).forEach((item: any, index: number) => {
|
|
223
|
+
const value = item.data[activeIndex.value] ? formatValue(item.data[activeIndex.value][1]) : null;
|
|
224
|
+
if (value !== null) {
|
|
225
|
+
result.push({
|
|
226
|
+
label: item.name.split('-')[1],
|
|
227
|
+
value: `${formatValue(item.data[activeIndex.value]?.[1])}%`,
|
|
228
|
+
color: colorList[index],
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
return [];
|
|
235
|
+
});
|
|
174
236
|
|
|
175
|
-
// 监视: props[klineData, indicator, markData, netPositionData] -> 重绘全部
|
|
237
|
+
// 监视: props[klineData, indicator, markData, netPositionData, relevanceData] -> 重绘全部
|
|
176
238
|
watch(
|
|
177
|
-
() => [props.klineData, props.indicator, props.markData, props.netPositionData],
|
|
239
|
+
() => [props.klineData, props.indicator, props.markData, props.netPositionData, props.relevanceData],
|
|
178
240
|
() => {
|
|
179
241
|
draw();
|
|
180
242
|
},
|
|
@@ -208,6 +270,26 @@ watch(
|
|
|
208
270
|
deep: true,
|
|
209
271
|
}
|
|
210
272
|
);
|
|
273
|
+
// 监视: props.priceTrendData - 二腿价格趋势数据 -> 重绘趋势
|
|
274
|
+
watch(
|
|
275
|
+
() => props.priceTrendData,
|
|
276
|
+
async () => {
|
|
277
|
+
draw("priceTrend");
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
deep: true,
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
// 监视: props.priceTrendPercentData - 二腿价格趋势百分比数据 -> 重绘趋势百分比
|
|
284
|
+
watch(
|
|
285
|
+
() => props.priceTrendPercentData,
|
|
286
|
+
async () => {
|
|
287
|
+
draw("priceTrendPercent");
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
deep: true,
|
|
291
|
+
}
|
|
292
|
+
);
|
|
211
293
|
|
|
212
294
|
/**
|
|
213
295
|
* @description: 图表绘制函数
|
|
@@ -224,7 +306,8 @@ const draw = async (type?: string) => {
|
|
|
224
306
|
props.markData,
|
|
225
307
|
props.indicator,
|
|
226
308
|
config.value,
|
|
227
|
-
props.netPositionData
|
|
309
|
+
props.netPositionData,
|
|
310
|
+
props.relevanceData
|
|
228
311
|
);
|
|
229
312
|
const { graphic } = echartsInstance.getOption() ?? { graphic: [] };
|
|
230
313
|
option.value = { ...newOption, graphic };
|
|
@@ -271,6 +354,8 @@ const draw = async (type?: string) => {
|
|
|
271
354
|
async () => {
|
|
272
355
|
await draw("kline");
|
|
273
356
|
await draw("line");
|
|
357
|
+
await draw("priceTrend");
|
|
358
|
+
await draw("priceTrendPercent");
|
|
274
359
|
await draw("brush");
|
|
275
360
|
},
|
|
276
361
|
],
|
|
@@ -284,7 +369,8 @@ const draw = async (type?: string) => {
|
|
|
284
369
|
props.markData,
|
|
285
370
|
props.indicator,
|
|
286
371
|
config.value,
|
|
287
|
-
props.netPositionData
|
|
372
|
+
props.netPositionData,
|
|
373
|
+
props.relevanceData
|
|
288
374
|
);
|
|
289
375
|
const addDataLength =
|
|
290
376
|
newOption.dataset.source.klineData.length - chartOption.dataset[0].source.klineData.length;
|
|
@@ -304,9 +390,10 @@ const draw = async (type?: string) => {
|
|
|
304
390
|
},
|
|
305
391
|
true
|
|
306
392
|
);
|
|
393
|
+
await draw("priceTrend");
|
|
307
394
|
},
|
|
308
395
|
],
|
|
309
|
-
// future
|
|
396
|
+
// future-K线后续数据绘制
|
|
310
397
|
[
|
|
311
398
|
"future",
|
|
312
399
|
async () => {
|
|
@@ -316,11 +403,115 @@ const draw = async (type?: string) => {
|
|
|
316
403
|
props.markData,
|
|
317
404
|
props.indicator,
|
|
318
405
|
config.value,
|
|
319
|
-
props.netPositionData
|
|
406
|
+
props.netPositionData,
|
|
407
|
+
props.relevanceData
|
|
320
408
|
);
|
|
321
409
|
newOption.dataZoom[0].start =
|
|
322
|
-
((props.klineData.length - chartOption.dataZoom[0].endValue + chartOption.dataZoom[0].startValue) /
|
|
410
|
+
((props.klineData.length - chartOption.dataZoom[0].endValue + chartOption.dataZoom[0].startValue) /
|
|
411
|
+
props.klineData.length) *
|
|
412
|
+
100;
|
|
323
413
|
echartsInstance.setOption(newOption, true);
|
|
414
|
+
await draw("priceTrend");
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
// priceTrend-二腿价格趋势绘制
|
|
418
|
+
[
|
|
419
|
+
"priceTrend",
|
|
420
|
+
() => {
|
|
421
|
+
if (props.priceTrendData.length === 0) return;
|
|
422
|
+
const echartsOptions = echartsInstance.getOption();
|
|
423
|
+
// 颜色配置
|
|
424
|
+
const colorList = [
|
|
425
|
+
"#FFFFFF",
|
|
426
|
+
"#FFDD00",
|
|
427
|
+
"#FF00FF",
|
|
428
|
+
"#00FF00",
|
|
429
|
+
"#FF6000",
|
|
430
|
+
"#1677FF",
|
|
431
|
+
"#7C3CC9",
|
|
432
|
+
"#FF0000",
|
|
433
|
+
"#FB9A0E",
|
|
434
|
+
"#00B7FF",
|
|
435
|
+
];
|
|
436
|
+
// 格式化series配置
|
|
437
|
+
const priceTrendLineSeries = props.priceTrendData.reduce((result, next, index) => {
|
|
438
|
+
const { name, data } = next;
|
|
439
|
+
const line = {
|
|
440
|
+
name: `二腿价格走势图-${name}`,
|
|
441
|
+
type: "line",
|
|
442
|
+
data,
|
|
443
|
+
symbol: "none",
|
|
444
|
+
yAxisIndex: 1,
|
|
445
|
+
connectNulls: true,
|
|
446
|
+
itemStyle: {
|
|
447
|
+
color: colorList[index],
|
|
448
|
+
},
|
|
449
|
+
lineStyle: {
|
|
450
|
+
type: "dashed", // 虚线
|
|
451
|
+
width: 1,
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
result.push(line);
|
|
455
|
+
return result;
|
|
456
|
+
}, []);
|
|
457
|
+
option.value = { ...echartsOptions, series: [...echartsOptions.series, ...priceTrendLineSeries] };
|
|
458
|
+
echartsInstance.setOption(option.value, {
|
|
459
|
+
replaceMerge: ["series"],
|
|
460
|
+
});
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
// priceTrendPercent-二腿价格趋势百分比绘制-初始绘制
|
|
464
|
+
[
|
|
465
|
+
"priceTrendPercent",
|
|
466
|
+
async () => {
|
|
467
|
+
if (props.priceTrendPercentData.length === 0) return;
|
|
468
|
+
const chartOption = echartsInstance.getOption();
|
|
469
|
+
// 颜色配置
|
|
470
|
+
const colorList = [
|
|
471
|
+
"#FFFFFF",
|
|
472
|
+
"#FFDD00",
|
|
473
|
+
"#FF00FF",
|
|
474
|
+
"#00FF00",
|
|
475
|
+
"#FF6000",
|
|
476
|
+
"#1677FF",
|
|
477
|
+
"#7C3CC9",
|
|
478
|
+
"#FF0000",
|
|
479
|
+
"#FB9A0E",
|
|
480
|
+
"#00B7FF",
|
|
481
|
+
];
|
|
482
|
+
// 获取当屏首根K线索引
|
|
483
|
+
const startIndex = chartOption.dataZoom[0].startValue;
|
|
484
|
+
// 处理趋势数据(百分比)
|
|
485
|
+
const handlePriceTrendSeries = props.priceTrendPercentData.reduce((result, next, index) => {
|
|
486
|
+
const { name, data } = next;
|
|
487
|
+
const handleData = data.length > config.value.totalBarCount ? data.slice(data.length - config.value.totalBarCount) : data
|
|
488
|
+
const priceTrendBase = handleData[startIndex][1];
|
|
489
|
+
const lineData = handleData.map((item) => {
|
|
490
|
+
return [item[0], ((item[1] - priceTrendBase) / priceTrendBase) * 100];
|
|
491
|
+
});
|
|
492
|
+
const line = {
|
|
493
|
+
name: `二腿价格走势百分比图-${name}`,
|
|
494
|
+
type: "line",
|
|
495
|
+
data: lineData,
|
|
496
|
+
symbol: "none",
|
|
497
|
+
yAxisIndex: 1,
|
|
498
|
+
connectNulls: true,
|
|
499
|
+
itemStyle: {
|
|
500
|
+
color: colorList[index],
|
|
501
|
+
},
|
|
502
|
+
lineStyle: {
|
|
503
|
+
type: "dashed", // 虚线
|
|
504
|
+
width: 1,
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
result.push(line);
|
|
508
|
+
return result;
|
|
509
|
+
}, []);
|
|
510
|
+
// 填充价格趋势线(避免重复)
|
|
511
|
+
chartOption.series = [...chartOption.series.filter(line => {
|
|
512
|
+
return !line.name.includes('二腿价格走势百分比图')
|
|
513
|
+
}), ...handlePriceTrendSeries];
|
|
514
|
+
echartsInstance.setOption(chartOption, true);
|
|
324
515
|
},
|
|
325
516
|
],
|
|
326
517
|
]);
|
|
@@ -444,6 +635,7 @@ const datazoom = async (params: any) => {
|
|
|
444
635
|
clearTimeout(datazoomTimer);
|
|
445
636
|
datazoomTimer = setTimeout(() => {
|
|
446
637
|
draw("line");
|
|
638
|
+
draw("priceTrendPercent");
|
|
447
639
|
clearTimeout(datazoomTimer);
|
|
448
640
|
datazoomTimer = null;
|
|
449
641
|
}, datazoomTime);
|
|
@@ -633,6 +825,8 @@ onUnmounted(() => {
|
|
|
633
825
|
<Tips :data="kLineTips" />
|
|
634
826
|
<!-- 指标 -->
|
|
635
827
|
<Tips :data="indicatorTips" />
|
|
828
|
+
<!-- 价格趋势[价差定制] -->
|
|
829
|
+
<Tips :data="priceTrendTips" />
|
|
636
830
|
</div>
|
|
637
831
|
<!-- 图表 + 菜单 -->
|
|
638
832
|
<Contextmenu class="st-kline-body" @closeContextMenuCallBack="closeContextMenuCallBack">
|
package/packages/Kline/option.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stMath } from "st-func";
|
|
1
2
|
import formatKlineData from "./formatKlineData";
|
|
2
3
|
import type { EChartsType } from "echarts";
|
|
3
4
|
import type {
|
|
@@ -26,9 +27,11 @@ export const getOption = async (
|
|
|
26
27
|
markData: any,
|
|
27
28
|
indicatorConfig: IndicatorConfigType,
|
|
28
29
|
config: InConfig,
|
|
29
|
-
netPositionData: any
|
|
30
|
+
netPositionData: any[],
|
|
31
|
+
relevanceData: any[],
|
|
30
32
|
) => {
|
|
31
|
-
const { totalBarCount, defaultShowBarCount, maxValueSpan, gridLeft, gridTop, gridRight, gridBottom, zoomLock } =
|
|
33
|
+
const { totalBarCount, defaultShowBarCount, maxValueSpan, gridLeft, gridTop, gridRight, gridBottom, zoomLock } =
|
|
34
|
+
config;
|
|
32
35
|
/**
|
|
33
36
|
* @todo: 1.数据处理
|
|
34
37
|
* @returns {Array<'YYYY-MM-DD HH:mm:ss'>} time 用于X轴的时间数组
|
|
@@ -62,13 +65,13 @@ export const getOption = async (
|
|
|
62
65
|
const markPointData = handlePoint(markData, originData);
|
|
63
66
|
/**
|
|
64
67
|
* @todo: 4.标注点位连线渲染处理
|
|
65
|
-
|
|
66
|
-
let markLineData = []
|
|
67
|
-
markPointData.forEach(item => {
|
|
68
|
+
*/
|
|
69
|
+
let markLineData = [];
|
|
70
|
+
markPointData.forEach((item) => {
|
|
68
71
|
if (item.markLineTarget) {
|
|
69
|
-
markLineData = [...markLineData, ...item.markLineTarget]
|
|
72
|
+
markLineData = [...markLineData, ...item.markLineTarget];
|
|
70
73
|
}
|
|
71
|
-
})
|
|
74
|
+
});
|
|
72
75
|
// -----return------
|
|
73
76
|
return {
|
|
74
77
|
animation: false,
|
|
@@ -253,6 +256,32 @@ export const getOption = async (
|
|
|
253
256
|
color: "#666",
|
|
254
257
|
},
|
|
255
258
|
},
|
|
259
|
+
{
|
|
260
|
+
name: "二腿相关度",
|
|
261
|
+
type: "line",
|
|
262
|
+
data: relevanceData ?? [],
|
|
263
|
+
symbol: "none",
|
|
264
|
+
yAxisIndex: 1,
|
|
265
|
+
connectNulls: true,
|
|
266
|
+
itemStyle: {
|
|
267
|
+
color: "#FFF",
|
|
268
|
+
},
|
|
269
|
+
lineStyle: {
|
|
270
|
+
// type: "dashed", // 虚线
|
|
271
|
+
width: 2,
|
|
272
|
+
},
|
|
273
|
+
label: {
|
|
274
|
+
show: true, // 显示文字
|
|
275
|
+
position: "top", // 文字位置
|
|
276
|
+
formatter: (params: any) => {
|
|
277
|
+
return `相关度: ${params.data[1]}`;
|
|
278
|
+
},
|
|
279
|
+
textStyle: {
|
|
280
|
+
color: "#FFF", // 文字颜色
|
|
281
|
+
fontSize: 14, // 文字字体大小
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
}
|
|
256
285
|
],
|
|
257
286
|
toolbox: {
|
|
258
287
|
show: false,
|
|
@@ -281,7 +310,7 @@ export const getOption = async (
|
|
|
281
310
|
* @return {graphicType} graphic绘线配置
|
|
282
311
|
*/
|
|
283
312
|
export const getLineOption = (data: LineDataType, config: InConfig, echartsInstance: EChartsType) => {
|
|
284
|
-
const { gridLeft, gridRight, warningConfig, positionConfig, conditionConfig } = config;
|
|
313
|
+
const { gridLeft, gridRight, gridBottom, warningConfig, positionConfig, conditionConfig, isOpenDS } = config;
|
|
285
314
|
let elements: any = [];
|
|
286
315
|
|
|
287
316
|
if ((echartsInstance as any)?.getModel()?.getComponent) {
|
|
@@ -371,8 +400,140 @@ export const getLineOption = (data: LineDataType, config: InConfig, echartsInsta
|
|
|
371
400
|
return [...res, getConditionItem(params)];
|
|
372
401
|
}, elements);
|
|
373
402
|
}
|
|
403
|
+
// -----处理分布图-----
|
|
404
|
+
if (isOpenDS) {
|
|
405
|
+
const option = echartsInstance.getOption() as any;
|
|
406
|
+
const countKline = option.dataset[0].source.klineData.slice(
|
|
407
|
+
option.dataZoom[0].startValue,
|
|
408
|
+
option.dataZoom[0].endValue + 1
|
|
409
|
+
);
|
|
410
|
+
// 1.统计它们的收盘价出现的次数
|
|
411
|
+
const countMap = {};
|
|
412
|
+
countKline.forEach((item: any) => {
|
|
413
|
+
if (countMap[item[1]]) {
|
|
414
|
+
countMap[item[1]] += 1;
|
|
415
|
+
} else {
|
|
416
|
+
countMap[item[1]] = 1;
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
const lastKlineValue = countKline[countKline.length - 1][1];
|
|
420
|
+
const meanValue =
|
|
421
|
+
countKline.reduce((res: number, item: any) => {
|
|
422
|
+
return res + item[1];
|
|
423
|
+
}, 0) / countKline.length;
|
|
424
|
+
const disMeanValue = lastKlineValue - meanValue;
|
|
425
|
+
const medianValue = percentToValue(countKline, 1, 50);
|
|
426
|
+
const standardDeviationValue = calculateStandardDeviation(countKline);
|
|
427
|
+
const disMeanSDValue = standardDeviationValue ? disMeanValue / standardDeviationValue : 0;
|
|
428
|
+
const percentileValue = valueToPercent(countKline, 1, lastKlineValue);
|
|
429
|
+
// 2.生成对应的分布图配置样式
|
|
430
|
+
elements = [
|
|
431
|
+
...elements,
|
|
432
|
+
{
|
|
433
|
+
type: "group",
|
|
434
|
+
draggable: false,
|
|
435
|
+
children: Object.keys(countMap).map((key: any) => {
|
|
436
|
+
return {
|
|
437
|
+
type: "line",
|
|
438
|
+
shape: {
|
|
439
|
+
x1: echartsInstance.getWidth() - gridRight - 100 * countMap[key],
|
|
440
|
+
y1: echartsInstance.convertToPixel({ yAxisIndex: 0 }, key),
|
|
441
|
+
x2: echartsInstance.getWidth() - gridRight,
|
|
442
|
+
y2: echartsInstance.convertToPixel({ yAxisIndex: 0 }, key),
|
|
443
|
+
},
|
|
444
|
+
style: {
|
|
445
|
+
stroke: "rgb(124,124,124)",
|
|
446
|
+
lineWidth: 1,
|
|
447
|
+
},
|
|
448
|
+
z: 10,
|
|
449
|
+
};
|
|
450
|
+
}),
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
type: "text",
|
|
454
|
+
x: gridLeft + 6,
|
|
455
|
+
y: echartsInstance.getHeight() - gridBottom - 20,
|
|
456
|
+
style: {
|
|
457
|
+
fill: "#FFF",
|
|
458
|
+
text: `均值: ${formatValue(meanValue)}\xa0\xa0\xa0\xa0距均值: ${formatValue(
|
|
459
|
+
disMeanValue
|
|
460
|
+
)}\xa0\xa0\xa0\xa0中值: ${formatValue(medianValue)}\xa0\xa0\xa0\xa0标准差: ${formatValue(
|
|
461
|
+
standardDeviationValue
|
|
462
|
+
)}\xa0\xa0\xa0\xa0距均值/标准差: ${formatValue(disMeanSDValue)}\xa0\xa0\xa0\xa0百分位: ${formatValue(
|
|
463
|
+
percentileValue
|
|
464
|
+
)}%`,
|
|
465
|
+
lineWidth: 1,
|
|
466
|
+
opacity: 1,
|
|
467
|
+
},
|
|
468
|
+
z: 10,
|
|
469
|
+
},
|
|
470
|
+
];
|
|
471
|
+
}
|
|
374
472
|
}
|
|
375
473
|
|
|
376
474
|
// -----return------
|
|
377
475
|
return { elements };
|
|
378
476
|
};
|
|
477
|
+
|
|
478
|
+
// 根据值计算分位
|
|
479
|
+
const valueToPercent = (data, dataIndex, value) => {
|
|
480
|
+
const { round, add, subtract, multiply, divide } = stMath;
|
|
481
|
+
const sortData = data.map((i) => i[dataIndex]).sort((a, b) => a - b);
|
|
482
|
+
let position = 0;
|
|
483
|
+
for (let i = 0; i < sortData.length; i++) {
|
|
484
|
+
if (sortData[i] === value) {
|
|
485
|
+
// 位置为整数
|
|
486
|
+
position = i;
|
|
487
|
+
break;
|
|
488
|
+
} else if (sortData[i] >= value) {
|
|
489
|
+
// 位置为小数
|
|
490
|
+
position = add(i, divide(subtract(sortData[i + 1], sortData[i]), sortData[i]));
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return round(divide(multiply(position, 100), data.length + 1));
|
|
495
|
+
};
|
|
496
|
+
// 根据分位计算值
|
|
497
|
+
const percentToValue = (data, dataIndex, percent) => {
|
|
498
|
+
const { round, add, subtract, multiply, divide } = stMath;
|
|
499
|
+
const sortData = data.map((i) => i[dataIndex]).sort((a, b) => a - b);
|
|
500
|
+
const position = multiply(data.length + 1, divide(percent, 100));
|
|
501
|
+
const positionInterger = Math.floor(position);
|
|
502
|
+
const positionDecimal = subtract(position, positionInterger);
|
|
503
|
+
if (positionDecimal === 0) {
|
|
504
|
+
// 位置为整数
|
|
505
|
+
return round(sortData[position]);
|
|
506
|
+
} else {
|
|
507
|
+
// 位置为小数
|
|
508
|
+
return round(
|
|
509
|
+
add(
|
|
510
|
+
sortData[positionInterger],
|
|
511
|
+
multiply(
|
|
512
|
+
subtract(
|
|
513
|
+
sortData[positionInterger >= data.length - 1 ? positionInterger : positionInterger + 1],
|
|
514
|
+
sortData[positionInterger]
|
|
515
|
+
),
|
|
516
|
+
positionDecimal
|
|
517
|
+
)
|
|
518
|
+
)
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
// 计算K线数组中数值的标准差
|
|
523
|
+
function calculateStandardDeviation(data) {
|
|
524
|
+
const numbers = data.map((i) => i[1]);
|
|
525
|
+
const n = numbers.length;
|
|
526
|
+
let sum = 0;
|
|
527
|
+
let squaredSum = 0;
|
|
528
|
+
// 计算平均值(均值)
|
|
529
|
+
for (const num of numbers) {
|
|
530
|
+
sum += num;
|
|
531
|
+
}
|
|
532
|
+
const mean = sum / n;
|
|
533
|
+
// 计算平方和
|
|
534
|
+
for (const num of numbers) {
|
|
535
|
+
squaredSum += Math.pow(num - mean, 2);
|
|
536
|
+
}
|
|
537
|
+
// 计算并返回标准差
|
|
538
|
+
return Math.sqrt(squaredSum / n);
|
|
539
|
+
}
|
package/packages/Kline/type.d.ts
CHANGED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="klineSlide" ref="slideChartRef"></div>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
|
7
|
+
import * as echarts from 'echarts'
|
|
8
|
+
import type { EChartsType } from 'echarts'
|
|
9
|
+
|
|
10
|
+
let slideChart: EChartsType
|
|
11
|
+
let resizeRo: any // dom元素监听事件
|
|
12
|
+
let slideDataZoomTimer: any = null // 滑动图数据缩放定时器
|
|
13
|
+
const defaultCOnfig = {
|
|
14
|
+
preLoadDays: 50, // 预加载天数
|
|
15
|
+
defaultShowDays: 50, // 默认显示天数
|
|
16
|
+
maxShowDays: 200, // 最大展示天数
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const emit = defineEmits(['change'])
|
|
20
|
+
const props = defineProps({
|
|
21
|
+
data: { type: Array, default: () => [] }, // 时间数据
|
|
22
|
+
config: { type: Object, default: () => {} }, // 配置数据
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const slideChartRef = ref<HTMLElement>() // 拖动轴
|
|
26
|
+
|
|
27
|
+
watch(
|
|
28
|
+
() => props.data,
|
|
29
|
+
() => {
|
|
30
|
+
draw()
|
|
31
|
+
change(props.data.length - 1 - defaultCOnfig.defaultShowDays, props.data.length - 1)
|
|
32
|
+
},
|
|
33
|
+
{ deep: true },
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
onMounted(() => {
|
|
37
|
+
slideChart = echarts.init(slideChartRef.value)
|
|
38
|
+
addEventListener()
|
|
39
|
+
draw()
|
|
40
|
+
change(props.data.length - 1 - defaultCOnfig.defaultShowDays, props.data.length - 1)
|
|
41
|
+
// 绑定resize事件
|
|
42
|
+
let isFirst: boolean | null = true
|
|
43
|
+
resizeRo = new ResizeObserver(() => {
|
|
44
|
+
if (isFirst) {
|
|
45
|
+
isFirst = null
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
slideChart.resize()
|
|
49
|
+
})
|
|
50
|
+
resizeRo.observe(slideChartRef.value)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
onUnmounted(() => {
|
|
54
|
+
slideChart.off("datazoom");
|
|
55
|
+
slideChart.dispose()
|
|
56
|
+
resizeRo.disconnect()
|
|
57
|
+
resizeRo = null
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// 绑定事件
|
|
61
|
+
const addEventListener = () => {
|
|
62
|
+
slideChart.on('datazoom', (params: any) => {
|
|
63
|
+
if(!params.dataZoomId) return
|
|
64
|
+
clearTimeout(slideDataZoomTimer)
|
|
65
|
+
slideDataZoomTimer = setTimeout(() => {
|
|
66
|
+
const { data } = props
|
|
67
|
+
const { start, end } = params
|
|
68
|
+
const startIndex = Math.floor(start * data.length / 100)
|
|
69
|
+
const endIndex = end === 100 ? data.length - 1 : Math.floor(end * data.length / 100)
|
|
70
|
+
change(startIndex, endIndex)
|
|
71
|
+
clearTimeout(slideDataZoomTimer)
|
|
72
|
+
}, 100)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 拖动回调
|
|
77
|
+
const change = (startIndex: number, endIndex: number) => {
|
|
78
|
+
const { preLoadDays } = defaultCOnfig
|
|
79
|
+
const data: any = props.data
|
|
80
|
+
if (data.length === 0) return
|
|
81
|
+
const startTime = (data[startIndex - preLoadDays < 0 ? 0 : startIndex - preLoadDays][0] as string).split(' ')[0]
|
|
82
|
+
const showStartTime = (data[startIndex][0] as string).split(' ')[0]
|
|
83
|
+
const endTime = (data[endIndex + preLoadDays > data.length ? data.length - 1 : endIndex + preLoadDays][0] as string).split(' ')[0]
|
|
84
|
+
const showEndTime = (data[endIndex][0] as string).split(' ')[0]
|
|
85
|
+
emit('change', {
|
|
86
|
+
startTime: `${startTime} 00:00:00`,
|
|
87
|
+
showStartTime: `${showStartTime} 00:00:00`,
|
|
88
|
+
endTime: `${endTime} 24:00:00`,
|
|
89
|
+
showEndTime: `${showEndTime} 24:00:00`,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const draw = () => {
|
|
94
|
+
const { data } = props
|
|
95
|
+
if(data.length === 0) return
|
|
96
|
+
const xAxisData = data.map((item: any) => item[0].split(' ')[0])
|
|
97
|
+
const lineData = data.map((item: any) => item[1])
|
|
98
|
+
slideChart.setOption({
|
|
99
|
+
grid: {
|
|
100
|
+
height: 0,
|
|
101
|
+
left: '80px',
|
|
102
|
+
right: '80px',
|
|
103
|
+
},
|
|
104
|
+
dataZoom: [
|
|
105
|
+
{
|
|
106
|
+
show: true,
|
|
107
|
+
startValue: data.length - 1 - defaultCOnfig.defaultShowDays,
|
|
108
|
+
endValue: data.length - 1,
|
|
109
|
+
maxValueSpan: defaultCOnfig.maxShowDays,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: 'inside',
|
|
113
|
+
startValue: data.length - 1 - defaultCOnfig.defaultShowDays,
|
|
114
|
+
endValue: data.length - 1,
|
|
115
|
+
maxValueSpan: defaultCOnfig.maxShowDays,
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
xAxis: {
|
|
119
|
+
type: 'category',
|
|
120
|
+
data: xAxisData,
|
|
121
|
+
show: false,
|
|
122
|
+
},
|
|
123
|
+
yAxis: {
|
|
124
|
+
type: 'value'
|
|
125
|
+
},
|
|
126
|
+
series: [
|
|
127
|
+
{
|
|
128
|
+
data: lineData,
|
|
129
|
+
type: 'line'
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}, true)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
defineExpose({
|
|
136
|
+
reset: () => {
|
|
137
|
+
slideChart.dispatchAction({
|
|
138
|
+
type: 'dataZoom',
|
|
139
|
+
startValue: props.data.length - 1 - defaultCOnfig.defaultShowDays,
|
|
140
|
+
endValue: props.data.length - 1,
|
|
141
|
+
})
|
|
142
|
+
change(props.data.length - 1 - defaultCOnfig.defaultShowDays, props.data.length - 1)
|
|
143
|
+
}, // 重置
|
|
144
|
+
resetSlide: (startTime, endTime) => {
|
|
145
|
+
let slideStartIndex = -1
|
|
146
|
+
let slideEndIndex = -1
|
|
147
|
+
props.data.forEach((item: any, index: number) => {
|
|
148
|
+
if (slideStartIndex === -1 && item[0] === startTime) {
|
|
149
|
+
slideStartIndex = index
|
|
150
|
+
} else if (slideStartIndex === -1 && new Date(item[0]) > new Date(startTime)) {
|
|
151
|
+
slideStartIndex = index - 1
|
|
152
|
+
}
|
|
153
|
+
if (slideEndIndex === -1 && item[0] === endTime) {
|
|
154
|
+
slideEndIndex = index
|
|
155
|
+
} else if (slideEndIndex === -1 && new Date(item[0]) > new Date(endTime)) {
|
|
156
|
+
slideEndIndex = index - 1
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
})
|
|
160
|
+
slideChart.dispatchAction({
|
|
161
|
+
type: 'dataZoom',
|
|
162
|
+
startValue: slideStartIndex,
|
|
163
|
+
endValue: slideEndIndex
|
|
164
|
+
})
|
|
165
|
+
}, // 重置
|
|
166
|
+
})
|
|
167
|
+
</script>
|
|
168
|
+
|
|
169
|
+
<style lang="scss" scoped>
|
|
170
|
+
.klineSlide {
|
|
171
|
+
width: 100%;
|
|
172
|
+
height: 50px;
|
|
173
|
+
}
|
|
174
|
+
</style>
|