From 635d70fe56240ae0232eb6c7f0701b4d6b0da34b Mon Sep 17 00:00:00 2001 From: Tako Date: Fri, 1 Dec 2023 23:40:30 +0000 Subject: [PATCH] pct tool draft --- src/auto-imports.d.ts | 10 +++ src/components/charts/CandleChart.vue | 2 + src/composables/inputListener.ts | 30 ++++++++ src/composables/percentageTool.ts | 106 ++++++++++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 src/composables/inputListener.ts create mode 100644 src/composables/percentageTool.ts diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index 60dae5df..459d569b 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -8,6 +8,7 @@ declare global { const ColorPreferences: typeof import('./stores/colors')['ColorPreferences'] const DashboardLayout: typeof import('./stores/layout')['DashboardLayout'] const EffectScope: typeof import('vue')['EffectScope'] + const KeyCode: typeof import('./composables/inputListener')['KeyCode'] const OpenTradeVizOptions: typeof import('./stores/settings')['OpenTradeVizOptions'] const TradeLayout: typeof import('./stores/layout')['TradeLayout'] const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] @@ -186,6 +187,7 @@ declare global { const useIdle: typeof import('@vueuse/core')['useIdle'] const useImage: typeof import('@vueuse/core')['useImage'] const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] + const useInputListener: typeof import('./composables/inputListener')['useInputListener'] const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] const useInterval: typeof import('@vueuse/core')['useInterval'] const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn'] @@ -215,6 +217,7 @@ declare global { const usePairlistConfigStore: typeof import('./stores/pairlistConfig')['usePairlistConfigStore'] const useParallax: typeof import('@vueuse/core')['useParallax'] const useParentElement: typeof import('@vueuse/core')['useParentElement'] + const usePercentageTool: typeof import('./composables/percentageTool')['usePercentageTool'] const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver'] const usePermission: typeof import('@vueuse/core')['usePermission'] const usePlotConfigStore: typeof import('./stores/plotConfig')['usePlotConfigStore'] @@ -302,6 +305,7 @@ declare global { declare global { // @ts-ignore export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' + import('vue') } // for vue template auto import import { UnwrapRef } from 'vue' @@ -310,6 +314,7 @@ declare module 'vue' { readonly ColorPreferences: UnwrapRef readonly DashboardLayout: UnwrapRef readonly EffectScope: UnwrapRef + readonly KeyCode: UnwrapRef readonly OpenTradeVizOptions: UnwrapRef readonly TradeLayout: UnwrapRef readonly asyncComputed: UnwrapRef @@ -488,6 +493,7 @@ declare module 'vue' { readonly useIdle: UnwrapRef readonly useImage: UnwrapRef readonly useInfiniteScroll: UnwrapRef + readonly useInputListener: UnwrapRef readonly useIntersectionObserver: UnwrapRef readonly useInterval: UnwrapRef readonly useIntervalFn: UnwrapRef @@ -517,6 +523,7 @@ declare module 'vue' { readonly usePairlistConfigStore: UnwrapRef readonly useParallax: UnwrapRef readonly useParentElement: UnwrapRef + readonly usePercentageTool: UnwrapRef readonly usePerformanceObserver: UnwrapRef readonly usePermission: UnwrapRef readonly usePlotConfigStore: UnwrapRef @@ -606,6 +613,7 @@ declare module '@vue/runtime-core' { readonly ColorPreferences: UnwrapRef readonly DashboardLayout: UnwrapRef readonly EffectScope: UnwrapRef + readonly KeyCode: UnwrapRef readonly OpenTradeVizOptions: UnwrapRef readonly TradeLayout: UnwrapRef readonly asyncComputed: UnwrapRef @@ -784,6 +792,7 @@ declare module '@vue/runtime-core' { readonly useIdle: UnwrapRef readonly useImage: UnwrapRef readonly useInfiniteScroll: UnwrapRef + readonly useInputListener: UnwrapRef readonly useIntersectionObserver: UnwrapRef readonly useInterval: UnwrapRef readonly useIntervalFn: UnwrapRef @@ -813,6 +822,7 @@ declare module '@vue/runtime-core' { readonly usePairlistConfigStore: UnwrapRef readonly useParallax: UnwrapRef readonly useParentElement: UnwrapRef + readonly usePercentageTool: UnwrapRef readonly usePerformanceObserver: UnwrapRef readonly usePermission: UnwrapRef readonly usePlotConfigStore: UnwrapRef diff --git a/src/components/charts/CandleChart.vue b/src/components/charts/CandleChart.vue index 25a86fb1..de23efcf 100644 --- a/src/components/charts/CandleChart.vue +++ b/src/components/charts/CandleChart.vue @@ -133,6 +133,8 @@ const diffCols = computed(() => { return getDiffColumnsFromPlotConfig(props.plotConfig); }); +usePercentageTool(candleChart); + function updateChart(initial = false) { if (!hasData.value) { return; diff --git a/src/composables/inputListener.ts b/src/composables/inputListener.ts new file mode 100644 index 00000000..a680869c --- /dev/null +++ b/src/composables/inputListener.ts @@ -0,0 +1,30 @@ +// codes we care about +export enum KeyCode { + SHIFT_LEFT = 'ShiftLeft', + CTRL_LEFT = 'ControlLeft', +} + +export function useInputListener() { + const allCodes = Object.values(KeyCode) as string[]; + const pressed = ref([]); + + function onKeyDown(e: KeyboardEvent) { + if (!allCodes.includes(e.code) || pressed.value.includes(e.code)) return; + pressed.value.push(e.code); + } + + function onKeyUp(e: KeyboardEvent) { + const i = pressed.value.indexOf(e.code); + if (i > -1) { + pressed.value.splice(i, 1); + } + } + + useEventListener(document, 'keydown', onKeyDown); + useEventListener(document, 'keyup', onKeyUp); + + return { + isKeyPressed: (key: KeyCode) => pressed.value.includes(key), + isAnyPressed: computed(() => pressed.value.length > 0), + }; +} diff --git a/src/composables/percentageTool.ts b/src/composables/percentageTool.ts new file mode 100644 index 00000000..6ca29ad0 --- /dev/null +++ b/src/composables/percentageTool.ts @@ -0,0 +1,106 @@ +import { use } from 'echarts/core'; +import { ElementEvent } from 'echarts'; +import { useInputListener } from './inputListener'; +import { GraphicComponent } from 'echarts/components'; + +use([GraphicComponent]); + +export function usePercentageTool(chartRef: Ref) { + const inputListener = useInputListener(); + + const mousePos = ref({ x: 0, y: 0 }); + const startPos = ref({ x: 0, y: 0 }); + const drawLimitPerSecond = 144; + const canDraw = ref(true); + const active = ref(false); + + function mouseMove(e: ElementEvent) { + mousePos.value.x = e.offsetX; + mousePos.value.y = e.offsetY; + + if (!active.value || !canDraw.value) return; + draw(e.offsetX, e.offsetY); + } + + function drawStart() { + active.value = true; + startPos.value = { ...mousePos.value }; + chartRef.value?.setOption({ + dataZoom: [{ disabled: true }], + graphic: [ + { + type: 'line', + shape: { + x1: mousePos.value.x, + x2: mousePos.value.x, + y1: mousePos.value.y, + y2: mousePos.value.y, + }, + style: { + stroke: 'white', + }, + }, + { type: 'text', z: 5 }, + ], + }); + } + + function drawEnd() { + active.value = false; + chartRef.value?.setOption({ + dataZoom: [{ disabled: false }], + graphic: [{ $action: 'remove' }, { $action: 'remove' }], + }); + } + + function draw(x: number, y: number) { + const startPrice = Number( + chartRef.value?.convertFromPixel({ seriesIndex: 0 }, [startPos.value.x, startPos.value.y])[1], + ); + const endPrice = Number(chartRef.value?.convertFromPixel({ seriesIndex: 0 }, [x, y])[1]); + const pct = Math.abs(((startPrice - endPrice) / startPrice) * 100).toFixed(2); + + chartRef.value?.setOption({ + graphic: [ + { + shape: { + x2: x, + y2: y, + }, + }, + { + style: { + x: x, + y: y - 20, + text: (startPrice < endPrice ? pct : '-' + pct) + '%', + font: '16px sans-serif', + fill: 'white', + }, + }, + ], + }); + + canDraw.value = false; + setTimeout(() => { + canDraw.value = true; + }, 1000 / drawLimitPerSecond); + } + + watch( + () => inputListener.isAnyPressed.value, + () => { + if (inputListener.isKeyPressed(KeyCode.SHIFT_LEFT)) { + drawStart(); + } else if (active.value) { + drawEnd(); + } + }, + ); + + onMounted(() => { + chartRef.value?.chart.getZr().on('mousemove', mouseMove); + }); + onUnmounted(() => { + chartRef.value?.chart.getZr().off('mousemove', mouseMove); + }); +}