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.
@@ -0,0 +1,331 @@
1
+ <template>
2
+ <div class="klineSub">
3
+ <div class="klineSub-tips">
4
+ <div class="klineSub-tips-select" v-if="subIndicatorTips.length">
5
+ <el-select
6
+ v-model="subIndicator"
7
+ size="small"
8
+ popper-class="element-dark"
9
+ class="element-dark subIndicator"
10
+ >
11
+ <el-option
12
+ v-for="item in subIndicatorList"
13
+ :key="item.value"
14
+ :label="item.label"
15
+ :value="item.value"
16
+ />
17
+ </el-select>
18
+ </div>
19
+ <Tips :data="subIndicatorTips" />
20
+ </div>
21
+ <div class="klineSub-chart" ref="subChartRef"></div>
22
+ </div>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import { ref, onMounted, onUnmounted, watch, computed } from "vue"
27
+ import * as echarts from 'echarts'
28
+ import type { EChartsType } from 'echarts'
29
+ import Tips from '../Tips/index.vue'
30
+ import { stMath } from 'st-func'
31
+ const { round } = stMath
32
+
33
+ let subChart: EChartsType
34
+ let resizeRo: any // dom元素监听事件
35
+
36
+ const emit = defineEmits(['update:modelValue'])
37
+ const props = defineProps({
38
+ drawData: { type: Object, require: true },
39
+ activeIndex: { type: Number, require: true },
40
+ modelValue: { type: String, required: true }, // 副图指标
41
+ subIndicatorList: { type: Array, required: true }, // 副图指标列表
42
+ })
43
+
44
+ const subChartRef = ref<HTMLElement>() // 拖动轴
45
+ const subIndicator = computed({
46
+ get() {
47
+ return props.modelValue
48
+ },
49
+ set(val) {
50
+ emit('update:modelValue', val)
51
+ }
52
+ })
53
+
54
+ const subIndicatorTips = computed(() => {
55
+ const { drawData, activeIndex } = props
56
+ const subIndicatorInfo = props.subIndicatorList.find((item: any) => item.value === subIndicator.value)
57
+ if (drawData.originData[activeIndex]) {
58
+ return subIndicatorInfo.config.map((item: any) => {
59
+ let value = '-'
60
+ if (item.source === 'origin') {
61
+ value = drawData.originData[activeIndex][item.dataIndex] || '-'
62
+ } else if (item.source === 'calculation') {
63
+ const itemIndicatorData = drawData.subIndicatorData?.find(i => i.key === item.key)?.data || []
64
+ value = itemIndicatorData[activeIndex]
65
+ }
66
+ return {
67
+ label: item.key,
68
+ color: item.color,
69
+ value: value === '-' ? value : round(value),
70
+ }
71
+ })
72
+ }
73
+ return []
74
+ })
75
+ onMounted(() => {
76
+ subChart = echarts.init(subChartRef.value)
77
+ // 绑定resize事件
78
+ let isFirst: boolean | null = true
79
+ resizeRo = new ResizeObserver(() => {
80
+ if (isFirst) {
81
+ isFirst = null
82
+ return
83
+ }
84
+ subChart.resize()
85
+ })
86
+ resizeRo.observe(subChartRef.value)
87
+ })
88
+
89
+ onUnmounted(() => {
90
+ subChart.dispose()
91
+ resizeRo.disconnect()
92
+ resizeRo = null
93
+ })
94
+
95
+ // 获取副图颜色
96
+ const getSubBarStyle = (data: any[], index: number) => {
97
+ const itemData = data[index]
98
+ const preItemData = index === 0 ? data[index] : data[index - 1]
99
+ if (itemData[0] === itemData[1]) {
100
+ return itemData[0] >= preItemData[1] ? {
101
+ color: 'transparent',
102
+ borderColor: '#FF0000',
103
+ } : {
104
+ color: '#00FFFF',
105
+ }
106
+ } else {
107
+ return itemData[1] > itemData[0] ? {
108
+ color: 'transparent',
109
+ borderColor: '#FF0000',
110
+ } : {
111
+ color: '#00FFFF',
112
+ }
113
+ }
114
+ }
115
+
116
+ defineExpose({
117
+ connect: (chart) => {
118
+ echarts.connect([chart, subChart])
119
+ },// 联动
120
+ draw: (config) => {
121
+ const { showStartTime, showEndTime, drawData, subIndicator, subIndicatorList, maxShowDays } = config
122
+ const { xAxisData, subIndicatorData, candlestickData, originData } = drawData
123
+ const subIndicatorInfo = subIndicatorList.find((item: any) => item.value === subIndicator)
124
+ let startValue = -1
125
+ let endValue = -1
126
+ let dayPerTime: any = {}
127
+ xAxisData.forEach((time: any, index: number) => {
128
+ dayPerTime[time.split(' ')[0]] = dayPerTime[time.split(' ')[0]] || 0
129
+ dayPerTime[time.split(' ')[0]] += 1
130
+ if (new Date(time) >= new Date(showStartTime) && startValue === -1) {
131
+ startValue = index
132
+ }
133
+ if (new Date(time) <= new Date(showEndTime)) {
134
+ endValue = index
135
+ }
136
+ })
137
+ const maxShowCount = Math.max(...Object.values(dayPerTime)) * maxShowDays
138
+ const series: any[] = []
139
+ // 主图
140
+ if (subIndicatorInfo.series === 'bar') {
141
+ let data = []
142
+ if (subIndicatorInfo.source === 'origin') {
143
+ data = originData.map((i: any) => i[subIndicatorInfo.dataIndex])
144
+ } else {
145
+ data = subIndicatorData?.find((i: any) => i.key === subIndicatorInfo.dataIndex)?.data || []
146
+ }
147
+
148
+ series.push({
149
+ name: 'subMain',
150
+ xAxisIndex: 0,
151
+ yAxisIndex: 1,
152
+ type: 'bar',
153
+ silent: true,
154
+ symbol: 'none',
155
+ data: data.map((item: any, index: number) => {
156
+ if (subIndicatorInfo.color === 'kline') {
157
+ return {
158
+ value: item,
159
+ itemStyle: getSubBarStyle(candlestickData, index),
160
+ }
161
+ } else if (subIndicatorInfo.color === 'value') {
162
+ return {
163
+ value: item,
164
+ itemStyle: {
165
+ color: item >= 0 ? '#FF0000' : '#00FFFF',
166
+ },
167
+ }
168
+ } else {
169
+ return {
170
+ value: item,
171
+ itemStyle: {
172
+ color: subIndicatorInfo.color,
173
+ },
174
+ }
175
+ }
176
+ }),
177
+ })
178
+ }
179
+
180
+ // 处理副图指标线数据
181
+ subIndicatorData.forEach((item: any) => {
182
+ if (item.series === 'line') {
183
+ let data = []
184
+ if (item.source === 'origin') {
185
+ data = originData.map((i: any) => i[item.dataIndex])
186
+ } else if (item.source === 'calculation') {
187
+ data = item.data
188
+ }
189
+ series.push({
190
+ xAxisIndex: 0,
191
+ yAxisIndex: item.yAxis === 'right' ? 2 : 1,
192
+ name: item.key,
193
+ type: 'line',
194
+ silent: true,
195
+ symbol: 'none',
196
+ data,
197
+ lineStyle: {
198
+ width: 1,
199
+ },
200
+ itemStyle: {
201
+ color: item.color,
202
+ },
203
+ })
204
+ }
205
+ })
206
+
207
+ subChart.setOption({
208
+ animation: false,
209
+ grid: {
210
+ left: '60px',
211
+ top: '10px',
212
+ right: '20px',
213
+ bottom: '20px',
214
+ },
215
+ dataZoom: [
216
+ {
217
+ type: 'inside',
218
+ startValue,
219
+ endValue,
220
+ maxValueSpan: maxShowCount,
221
+ }
222
+ ],
223
+ tooltip: {
224
+ trigger: 'axis',
225
+ appendToBody: true,
226
+ confine: true,
227
+ axisPointer: {
228
+ type: 'cross',
229
+ label: {
230
+ rich: {},
231
+ formatter: (data: any) => {
232
+ const { axisDimension, value } = data
233
+ if(axisDimension === 'x') {
234
+ return value
235
+ } else if(data.axisIndex === 1) {
236
+ return String(round(value))
237
+ }
238
+ }
239
+ },
240
+ },
241
+ formatter: () => ''
242
+ },
243
+ xAxis: {
244
+ type: "category",
245
+ data: xAxisData,
246
+ axisLine: {
247
+ show: true,
248
+ },
249
+ splitLine: {
250
+ show: false,
251
+ },
252
+ axisLabel: {
253
+ show: true,
254
+ formatter: (data: string) => data,
255
+ },
256
+ },
257
+ yAxis: [
258
+ {
259
+ position: 'right',
260
+ },
261
+ {
262
+ position: 'left',
263
+ min: subIndicatorInfo.leftYAxisRange === 'cover' ? (value: any) => value.min : null,
264
+ max: subIndicatorInfo.leftYAxisRange === 'cover' ? (value: any) => value.max : null,
265
+ splitNumber: 1,
266
+ axisLine: {
267
+ show: true,
268
+ },
269
+ splitLine: {
270
+ show: true,
271
+ lineStyle: {
272
+ type: 'dotted',
273
+ color: '#333',
274
+ },
275
+ },
276
+ },
277
+ {
278
+ position: 'right',
279
+ min: subIndicatorInfo.rightYAxisRange === 'cover' ? (value: any) => value.min : null,
280
+ max: subIndicatorInfo.rightYAxisRange === 'cover' ? (value: any) => value.max : null,
281
+ splitNumber: 1,
282
+ axisLine: {
283
+ show: false,
284
+ },
285
+ splitLine: {
286
+ show: false,
287
+ },
288
+ axisLabel: {
289
+ show: false,
290
+ }
291
+ },
292
+ ],
293
+ series,
294
+ }, true)
295
+ }, // 重置
296
+ })
297
+ </script>
298
+
299
+ <style lang="scss" scoped>
300
+ .klineSub {
301
+ width: 100%;
302
+ height: 100%;
303
+ &-tips {
304
+ position: relative;
305
+ padding-left: 24px;
306
+ height: 16px;
307
+ &-select {
308
+ position: absolute;
309
+ left: 6px;
310
+ :deep(.subIndicator) {
311
+ width: 14px;
312
+ vertical-align: 4px;
313
+ .el-input__wrapper {
314
+ padding: 0;
315
+ border-radius: 50%;
316
+ background-color: #666;
317
+ height: 14px;
318
+ }
319
+ .el-select__icon{
320
+ margin-left: 0;
321
+ color: black;
322
+ }
323
+ }
324
+ }
325
+ }
326
+ &-chart {
327
+ width: 100%;
328
+ height: calc(100% - 26px);
329
+ }
330
+ }
331
+ </style>
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <div class="kline-mainTips">
3
+ <Tips :data="mainTips" />
4
+ <Tips :data="mainIndicatorTips" />
5
+ </div>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import { computed } from 'vue'
10
+ import Tips from '../Tips/index.vue'
11
+ import { stMath } from 'st-func'
12
+ const { round, multiply, divide, subtract } = stMath
13
+
14
+ const props = defineProps({
15
+ // 提示数据
16
+ drawData: {
17
+ type: Object,
18
+ require: true,
19
+ },
20
+ activeIndex: {
21
+ type: Number,
22
+ require: true,
23
+ },
24
+ })
25
+
26
+ const mainTips = computed(() => {
27
+ const { drawData, activeIndex } = props
28
+ const itemData = drawData.candlestickData[activeIndex]
29
+ if (itemData) {
30
+ const diff = round(multiply(divide(subtract(itemData[2], itemData[4]), itemData[4]), 100))
31
+ let diffColor
32
+ if (diff > 0) {
33
+ diffColor = 'red'
34
+ } else if (diff < 0) {
35
+ diffColor = 'green'
36
+ }
37
+ return [
38
+ { label: '开', value: round(itemData[0]) },
39
+ { label: '高', value: round(itemData[3]) },
40
+ { label: '低', value: round(itemData[1]) },
41
+ { label: '收', value: round(itemData[2]) },
42
+ { label: '涨跌', value: `${diff}%`, color: diffColor },
43
+ ]
44
+ }
45
+ return []
46
+ })
47
+
48
+ const mainIndicatorTips = computed(() => {
49
+ const { drawData, activeIndex } = props
50
+ return drawData.mainIndicatorData.map(item => {
51
+ return { label: item.key, value: round(item.data[activeIndex]), color: item.color }
52
+ })
53
+ })
54
+ </script>
55
+
56
+ <style lang="scss" scoped>
57
+ .kline-mainTips {
58
+ padding-left: 3px;
59
+ width: 100%;
60
+ }
61
+ </style>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div class="kline-tips">
3
+ <div
4
+ v-for="(item, index) in data"
5
+ :key="index"
6
+ class="kline-tips-item"
7
+ :style="{ color: item.color }"
8
+ >{{ item.label }} {{ item.value }}</div>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ defineProps({
14
+ // 提示数据
15
+ data: {
16
+ type: Array,
17
+ require: true,
18
+ },
19
+ })
20
+ </script>
21
+
22
+ <style lang="scss" scoped>
23
+ .kline-tips {
24
+ width: 100%;
25
+ color: #fff;
26
+ font-size: 12px;
27
+ line-height: 1.5;
28
+ &-item {
29
+ display: inline-block;
30
+ margin-right: 10px;
31
+ }
32
+ }
33
+ </style>