251 lines
6.1 KiB
Vue

<template>
<div class="chart-panel card">
<el-tabs v-model="period" class="tabs">
<el-tab-pane label="分时" name="intraday" />
<el-tab-pane label="五日" name="five" />
<el-tab-pane label="日K" name="day" />
<el-tab-pane label="周K" name="week" />
<el-tab-pane label="月K" name="month" />
</el-tabs>
<v-chart class="chart" :option="option" autoresize />
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { LineChart, BarChart, CandlestickChart } from 'echarts/charts'
import {
GridComponent,
TooltipComponent,
DataZoomComponent,
LegendComponent,
} from 'echarts/components'
import type { EChartsOption } from 'echarts'
import VChart from 'vue-echarts'
import type { Candle, QuoteData } from '../../types'
use([
CanvasRenderer,
LineChart,
BarChart,
CandlestickChart,
GridComponent,
TooltipComponent,
DataZoomComponent,
LegendComponent,
])
type ChartPeriod = 'intraday' | 'five' | 'day' | 'week' | 'month'
interface ColorParam {
dataIndex?: number
}
const props = defineProps<{
quote: QuoteData
}>()
const period = ref<ChartPeriod>('intraday')
const option = computed((): EChartsOption => {
if (period.value === 'intraday' || period.value === 'five') {
return buildIntradayOption(props.quote, period.value === 'five')
}
const key = period.value === 'day' ? 'day' : period.value === 'week' ? 'week' : 'month'
return buildCandleOption(props.quote.candles[key])
})
function buildIntradayOption(quote: QuoteData, fiveDay: boolean): EChartsOption {
const points = fiveDay
? [...quote.intraday, ...quote.intraday.map((p) => ({ ...p, price: p.price + 1.5 }))]
: quote.intraday
const times = points.map((p) => p.time)
const prices = points.map((p) => p.price)
const avgs = points.map((p) => p.avg)
const vols = points.map((p) => p.volume)
const base = quote.prevSettlement
return {
animation: false,
legend: { data: ['分时', '均价'], top: 0, textStyle: { fontSize: 11 } },
tooltip: { trigger: 'axis' },
axisPointer: { link: [{ xAxisIndex: 'all' }] },
grid: [
{ left: 48, right: 48, top: 28, height: '58%' },
{ left: 48, right: 48, top: '78%', height: '14%' },
],
xAxis: [
{
type: 'category',
data: times,
boundaryGap: false,
axisLabel: { fontSize: 10 },
gridIndex: 0,
},
{
type: 'category',
data: times,
boundaryGap: false,
axisLabel: { show: false },
gridIndex: 1,
},
],
yAxis: [
{
type: 'value',
scale: true,
axisLabel: { fontSize: 10 },
splitLine: { lineStyle: { type: 'dashed', color: '#eee' } },
gridIndex: 0,
},
{
type: 'value',
scale: true,
position: 'right',
axisLabel: {
fontSize: 10,
formatter: (v: number) => `${(((v - base) / base) * 100).toFixed(2)}%`,
},
splitLine: { show: false },
gridIndex: 0,
},
{
type: 'value',
axisLabel: { show: false },
splitLine: { show: false },
gridIndex: 1,
},
],
dataZoom: [{ type: 'inside', xAxisIndex: [0, 1] }],
series: [
{
name: '分时',
type: 'line',
data: prices,
showSymbol: false,
lineStyle: { width: 1.5, color: '#1677ff' },
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(22,119,255,0.25)' },
{ offset: 1, color: 'rgba(22,119,255,0.02)' },
],
},
},
xAxisIndex: 0,
yAxisIndex: 0,
},
{
name: '均价',
type: 'line',
data: avgs,
showSymbol: false,
lineStyle: { width: 1, color: '#fa8c16' },
xAxisIndex: 0,
yAxisIndex: 0,
},
{
name: '成交量',
type: 'bar',
data: vols,
itemStyle: {
color: (p: ColorParam) => {
const idx = p.dataIndex ?? 0
return prices[idx] >= (prices[idx - 1] ?? prices[idx]) ? '#e54545' : '#12b37b'
},
},
xAxisIndex: 1,
yAxisIndex: 2,
},
],
}
}
function buildCandleOption(candles: Candle[]): EChartsOption {
const dates = candles.map((c) => c.date)
const ohlc = candles.map((c) => [c.open, c.close, c.low, c.high])
const vols = candles.map((c) => c.volume)
return {
animation: false,
tooltip: { trigger: 'axis' },
grid: [
{ left: 48, right: 16, top: 16, height: '58%' },
{ left: 48, right: 16, top: '78%', height: '14%' },
],
xAxis: [
{ type: 'category', data: dates, axisLabel: { fontSize: 10 }, gridIndex: 0 },
{
type: 'category',
data: dates,
axisLabel: { show: false },
gridIndex: 1,
},
],
yAxis: [
{
scale: true,
axisLabel: { fontSize: 10 },
splitLine: { lineStyle: { type: 'dashed', color: '#eee' } },
gridIndex: 0,
},
{
scale: true,
axisLabel: { show: false },
splitLine: { show: false },
gridIndex: 1,
},
],
dataZoom: [{ type: 'inside', xAxisIndex: [0, 1] }],
series: [
{
type: 'candlestick',
data: ohlc,
itemStyle: {
color: '#e54545',
color0: '#12b37b',
borderColor: '#e54545',
borderColor0: '#12b37b',
},
xAxisIndex: 0,
yAxisIndex: 0,
},
{
type: 'bar',
data: vols,
itemStyle: {
color: (p: ColorParam) => {
const d = ohlc[p.dataIndex ?? 0]
return d[1] >= d[0] ? '#e54545' : '#12b37b'
},
},
xAxisIndex: 1,
yAxisIndex: 1,
},
],
}
}
</script>
<style scoped>
.chart-panel {
padding: 8px 12px 12px;
}
.tabs :deep(.el-tabs__header) {
margin-bottom: 4px;
}
.chart {
height: 420px;
width: 100%;
}
</style>