From db738367cb1035ef58036838321d19e770beb7fe Mon Sep 17 00:00:00 2001 From: szxc Date: Tue, 15 Jul 2025 17:16:56 +0800 Subject: [PATCH 01/15] feat: add interface --- .../src/charts/candlestick/interface.ts | 8 ++ .../charts/candlestick/series/interface.ts | 89 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 packages/vchart-extension/src/charts/candlestick/interface.ts create mode 100644 packages/vchart-extension/src/charts/candlestick/series/interface.ts diff --git a/packages/vchart-extension/src/charts/candlestick/interface.ts b/packages/vchart-extension/src/charts/candlestick/interface.ts new file mode 100644 index 0000000000..31c8304213 --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/interface.ts @@ -0,0 +1,8 @@ +import type { IChartExtendsSeriesSpec, IChartSpec } from '@visactor/vchart'; +import type { ICandlestickSeriesSpec } from './series/interface'; + +export interface ICandlestickChartSpec extends IChartSpec, IChartExtendsSeriesSpec { + type: 'candlestick'; + /** 系列配置 */ + series?: ICandlestickSeriesSpec[]; +} diff --git a/packages/vchart-extension/src/charts/candlestick/series/interface.ts b/packages/vchart-extension/src/charts/candlestick/series/interface.ts new file mode 100644 index 0000000000..a6c94e4f1a --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/series/interface.ts @@ -0,0 +1,89 @@ +// candlestick/series/interface.ts +import type { + IAnimationSpec, + ICommonSpec, + IMarkSpec, + ICartesianSeriesSpec, + SeriesMarkNameEnum, + Datum +} from '@visactor/vchart'; + +export interface ICandlestickMarkSpec extends ICommonSpec { + /** + * box描边宽度 + */ + lineWidth?: number; + /** + * box宽度 + */ + boxWidth?: number; + /** + * 盒子填充颜色,为空则不填充 + */ + boxFill?: string | ((datum: Datum) => string); + /** + * 最低价 + */ + low?: (datum: Datum) => number; + /** + * 收盘价 + */ + close?: (datum: Datum) => number; + /** + * 开盘价 + */ + open?: (datum: Datum) => number; + /** + * 最高价 + */ + high?: (datum: Datum) => number; +} + +export type candlestickColorConfig = { + /** + * 上涨蜡烛图颜色 + */ + rising: string; + /** + * 下跌蜡烛图颜色 + */ + falling: string; + /** + * 平盘蜡烛图颜色 + */ + doji: string; +}; + +export interface ICandlestickSeriesSpec + extends Omit, + IAnimationSpec { + type: 'candlestick'; + /** + * 时间轴字段 + */ + xField: string; + /** + * 开盘价字段 + */ + openField?: string; + /** + * 最高价字段 + */ + highField?: string; + /** + * 最低价字段 + */ + lowField?: string; + /** + * 收盘价字段 + */ + closeField?: string; + /** + * 蜡烛图颜色配置 + */ + candlestickColor?: candlestickColorConfig; + /** + * 蜡烛图标记配置 + */ + candlestick?: IMarkSpec; +} From 541afdf7cf6458032bcdd082b1987d3583647999 Mon Sep 17 00:00:00 2001 From: szxc Date: Wed, 16 Jul 2025 17:47:49 +0800 Subject: [PATCH 02/15] feat: add candlestick mark --- .../src/charts/candlestick/candlestick.ts | 0 .../charts/candlestick/mark/candlestick.ts | 97 +++++++++++++++++++ .../src/charts/candlestick/mark/index.ts | 1 + .../src/charts/candlestick/mark/interface.ts | 32 ++++++ .../charts/candlestick/series/interface.ts | 43 ++------ packages/vchart/src/mark/index.ts | 3 + 6 files changed, 141 insertions(+), 35 deletions(-) create mode 100644 packages/vchart-extension/src/charts/candlestick/candlestick.ts create mode 100644 packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts create mode 100644 packages/vchart-extension/src/charts/candlestick/mark/index.ts create mode 100644 packages/vchart-extension/src/charts/candlestick/mark/interface.ts diff --git a/packages/vchart-extension/src/charts/candlestick/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/candlestick.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts new file mode 100644 index 0000000000..114909d814 --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts @@ -0,0 +1,97 @@ +import { registerLine, registerRect } from '@visactor/vrender-kits'; +import { GlyphMark, registerGlyphMark, IMarkRaw, IMarkStyle } from '@visactor/vchart'; +import { createLine, createRect, type IGlyph, type ILineGraphicAttribute } from '@visactor/vrender-core'; +import { Factory, Datum } from '@visactor/vchart'; +import type { ICandlestickMarkSpec } from './interface'; + +export type CandlestickShaftShape = 'line' | 'bar'; +export type ICandlestickMark = IMarkRaw; +export const CANDLESTICK_MARK_TYPE = 'candlestick'; + +export class CandlestickMark extends GlyphMark implements ICandlestickMark { + static readonly type = CANDLESTICK_MARK_TYPE; + readonly type = CandlestickMark.type; + + setGlyphConfig(): void { + this._subMarks = { + body: { type: 'rect' }, + upperWick: { type: 'line', defaultAttributes: { x: 0, y: 0 } }, + lowerWick: { type: 'line', defaultAttributes: { x: 0, y: 0 } } + }; + this._channelEncoder = null; + this._positionEncoder = (glyphAttrs: any, datum: Datum, g: IGlyph) => { + const { + x = g.attribute.x, + boxWidth = (g.attribute as any).boxWidth, + ruleWidth = (g.attribute as any).ruleWidth, + open = (g.attribute as any).open, + close = (g.attribute as any).close, + low = (g.attribute as any).low, + high = (g.attribute as any).high + } = glyphAttrs; + const attributes: any = {}; + attributes.box = { + x: x - boxWidth / 2, + x1: x + boxWidth / 2, + y: open, + y1: close + }; + attributes.high = { + points: [ + { + x: x - ruleWidth / 2, + y: high + }, + { + x: x + ruleWidth / 2, + y: high + } + ] + }; + attributes.low = { + points: [ + { + x: x - ruleWidth / 2, + y: low + }, + { + x: x + ruleWidth / 2, + y: low + } + ] + }; + attributes.shaft = { + points: [ + { + x: x, + y: low + }, + { + x: x, + y: high + } + ] + }; + return attributes; + }; + } + + protected _getDefaultStyle() { + const defaultStyle: IMarkStyle = { + ...super._getDefaultStyle(), + lineWidth: 2, + boxWidth: 30, + shaftWidth: 20 + }; + return defaultStyle; + } +} + +export const registerCandlestickMark = () => { + registerGlyphMark(); + registerLine(); + registerRect(); + Factory.registerGraphicComponent('line', (attrs: ILineGraphicAttribute) => createLine(attrs)); + Factory.registerGraphicComponent('rect', (attrs: ILineGraphicAttribute) => createRect(attrs)); + Factory.registerMark(CandlestickMark.type, CandlestickMark); +}; diff --git a/packages/vchart-extension/src/charts/candlestick/mark/index.ts b/packages/vchart-extension/src/charts/candlestick/mark/index.ts new file mode 100644 index 0000000000..d31df946f1 --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/mark/index.ts @@ -0,0 +1 @@ +export { ICandlestickMarkSpec } from './interface'; diff --git a/packages/vchart-extension/src/charts/candlestick/mark/interface.ts b/packages/vchart-extension/src/charts/candlestick/mark/interface.ts new file mode 100644 index 0000000000..eb52c0ba10 --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/mark/interface.ts @@ -0,0 +1,32 @@ +import type { Datum, ICommonSpec } from '@visactor/vchart'; + +export interface ICandlestickMarkSpec extends ICommonSpec { + /** + * box描边宽度 + */ + lineWidth?: number; + /** + * box宽度 + */ + boxWidth?: number; + /** + * 盒子填充颜色,为空则不填充 + */ + boxFill?: string | ((datum: Datum) => string); + /** + * 最低价 + */ + low?: (datum: Datum) => number; + /** + * 收盘价 + */ + close?: (datum: Datum) => number; + /** + * 开盘价 + */ + open?: (datum: Datum) => number; + /** + * 最高价 + */ + high?: (datum: Datum) => number; +} diff --git a/packages/vchart-extension/src/charts/candlestick/series/interface.ts b/packages/vchart-extension/src/charts/candlestick/series/interface.ts index a6c94e4f1a..6662afe89f 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/interface.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/interface.ts @@ -1,43 +1,12 @@ -// candlestick/series/interface.ts import type { IAnimationSpec, - ICommonSpec, IMarkSpec, ICartesianSeriesSpec, SeriesMarkNameEnum, - Datum + IMarkTheme, + ICartesianSeriesTheme } from '@visactor/vchart'; - -export interface ICandlestickMarkSpec extends ICommonSpec { - /** - * box描边宽度 - */ - lineWidth?: number; - /** - * box宽度 - */ - boxWidth?: number; - /** - * 盒子填充颜色,为空则不填充 - */ - boxFill?: string | ((datum: Datum) => string); - /** - * 最低价 - */ - low?: (datum: Datum) => number; - /** - * 收盘价 - */ - close?: (datum: Datum) => number; - /** - * 开盘价 - */ - open?: (datum: Datum) => number; - /** - * 最高价 - */ - high?: (datum: Datum) => number; -} +import type { ICandlestickMarkSpec } from '../mark'; export type candlestickColorConfig = { /** @@ -61,7 +30,7 @@ export interface ICandlestickSeriesSpec /** * 时间轴字段 */ - xField: string; + xField: string | string[]; /** * 开盘价字段 */ @@ -87,3 +56,7 @@ export interface ICandlestickSeriesSpec */ candlestick?: IMarkSpec; } + +export interface ICandlestickSeriesTheme extends ICartesianSeriesTheme { + candlestick?: Partial>; +} diff --git a/packages/vchart/src/mark/index.ts b/packages/vchart/src/mark/index.ts index 4dfc85b3aa..0a09bce2e7 100644 --- a/packages/vchart/src/mark/index.ts +++ b/packages/vchart/src/mark/index.ts @@ -14,6 +14,7 @@ import { ComponentMark, registerComponentMark } from './component'; import { LinkPathMark, registerLinkPathMark } from './link-path'; import { RippleMark, registerRippleMark } from './ripple'; import { CellMark, registerCellMark } from './cell'; +import { GlyphMark, registerGlyphMark } from './glyph'; import { BaseMark } from './base'; import { PolygonMark, registerPolygonMark } from './polygon/polygon'; import { ImageMark, registerImageMark } from './image'; @@ -57,6 +58,7 @@ export { AreaMark, RectMark, PathMark, + GlyphMark, BaseArcMark, ArcMark, ComponentMark, @@ -78,6 +80,7 @@ export { registerPathMark, registerArcMark, registerPolygonMark, + registerGlyphMark, registerRippleMark, registerImageMark, registerComponentMark, From 27de8d177bac8225533f2512ce075f684bffd59a Mon Sep 17 00:00:00 2001 From: szxc Date: Wed, 16 Jul 2025 20:26:25 +0800 Subject: [PATCH 03/15] feat: add basic candlestick series --- .../runtime/browser/test-page/candlestick.ts | 3 ++ .../charts/candlestick/mark/candlestick.ts | 4 +- .../src/charts/candlestick/mark/index.ts | 1 - .../charts/candlestick/series/candlestick.ts | 45 +++++++++++++++++++ packages/vchart/src/animation/index.ts | 1 + 5 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts delete mode 100644 packages/vchart-extension/src/charts/candlestick/mark/index.ts create mode 100644 packages/vchart-extension/src/charts/candlestick/series/candlestick.ts diff --git a/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts b/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts new file mode 100644 index 0000000000..8a63aaa08a --- /dev/null +++ b/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts @@ -0,0 +1,3 @@ +import { ICandlestickChartSpec } from '../../../../src/charts/candlestick/interface'; +import { registerCandlestickChart } from '../../../../src/charts/candlestick/candlestick'; +import { VChart } from '@visactor/vchart'; diff --git a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts index 114909d814..5fd8e49551 100644 --- a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts @@ -4,7 +4,6 @@ import { createLine, createRect, type IGlyph, type ILineGraphicAttribute } from import { Factory, Datum } from '@visactor/vchart'; import type { ICandlestickMarkSpec } from './interface'; -export type CandlestickShaftShape = 'line' | 'bar'; export type ICandlestickMark = IMarkRaw; export const CANDLESTICK_MARK_TYPE = 'candlestick'; @@ -80,8 +79,7 @@ export class CandlestickMark extends GlyphMark implements const defaultStyle: IMarkStyle = { ...super._getDefaultStyle(), lineWidth: 2, - boxWidth: 30, - shaftWidth: 20 + boxWidth: 30 }; return defaultStyle; } diff --git a/packages/vchart-extension/src/charts/candlestick/mark/index.ts b/packages/vchart-extension/src/charts/candlestick/mark/index.ts deleted file mode 100644 index d31df946f1..0000000000 --- a/packages/vchart-extension/src/charts/candlestick/mark/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ICandlestickMarkSpec } from './interface'; diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts new file mode 100644 index 0000000000..bce4f3d9b2 --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -0,0 +1,45 @@ +import { registerCandlestickMark, ICandlestickMark } from '../mark/candlestick'; +import { + registerSymbolMark, + registerScaleInOutAnimation, + registerCartesianBandAxis, + registerCartesianLinearAxis, + CartesianSeries, + IMark, + Factory +} from '@visactor/vchart'; +import type { ICandlestickSeriesSpec } from './interface'; + +export const CANDLESTICK_SERIES_TYPE = 'candlestick'; + +export class CandlestickSeries extends CartesianSeries { + static readonly type: string = CANDLESTICK_SERIES_TYPE; + type = CANDLESTICK_SERIES_TYPE; + + private _candlestickMark: ICandlestickMark; + initMark() { + this._candlestickMark = this._createMark( + { type: CANDLESTICK_SERIES_TYPE, name: CANDLESTICK_SERIES_TYPE }, + { + groupKey: this._seriesField, + isSeriesMark: true + } + ) as ICandlestickMark; + } + initMarkStyle() { + const candlestickMark = this._candlestickMark; + } + getActiveMarks(): IMark[] { + return [this._candlestickMark]; + } +} + +export const registerBoxplotSeries = () => { + registerCandlestickMark(); + registerSymbolMark(); + registerScaleInOutAnimation(); + registerCartesianBandAxis(); + registerCartesianLinearAxis(); + //registerCandlestickScaleAnimation(); + Factory.registerSeries(CandlestickSeries.type, CandlestickSeries); +}; diff --git a/packages/vchart/src/animation/index.ts b/packages/vchart/src/animation/index.ts index 84cd5fac85..74f0f72dde 100644 --- a/packages/vchart/src/animation/index.ts +++ b/packages/vchart/src/animation/index.ts @@ -5,6 +5,7 @@ export { registerPolygonAnimation, registerRectAnimation, registerArcAnimation, + registerScaleInOutAnimation, DEFAULT_ANIMATION_CONFIG } from './config'; export { animationConfig, userAnimationConfig, shouldMarkDoMorph } from './utils'; From 2893cdd7fb00abc2798b720fac6d3c9bf5f8f8cb Mon Sep 17 00:00:00 2001 From: szxc Date: Sun, 20 Jul 2025 00:55:51 +0800 Subject: [PATCH 04/15] feat: implement candlestick chart and transformer with test data --- .../runtime/browser/test-page/candlestick.ts | 38 +++++++++++++++++++ .../candlestick/candlestick-transformer.ts | 34 +++++++++++++++++ .../src/charts/candlestick/candlestick.ts | 18 +++++++++ .../src/charts/candlestick/interface.ts | 4 +- .../charts/candlestick/series/candlestick.ts | 2 +- .../charts/candlestick/series/interface.ts | 2 +- 6 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts diff --git a/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts b/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts index 8a63aaa08a..05da3e040c 100644 --- a/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts +++ b/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts @@ -1,3 +1,41 @@ import { ICandlestickChartSpec } from '../../../../src/charts/candlestick/interface'; import { registerCandlestickChart } from '../../../../src/charts/candlestick/candlestick'; import { VChart } from '@visactor/vchart'; + +const data = [ + { time: '2024-07-01', open: 100, close: 110, high: 115, low: 95 }, + { time: '2024-07-02', open: 110, close: 105, high: 112, low: 102 }, + { time: '2024-07-03', open: 105, close: 120, high: 125, low: 104 }, + { time: '2024-07-04', open: 120, close: 115, high: 122, low: 110 } +]; + +const spec: ICandlestickChartSpec = { + type: 'candlestick', + xField: 'time', + openField: 'open', + closeField: 'close', + highField: 'high', + lowField: 'low', + data: [ + { + id: 'main', + values: data + } + ] +}; + +const run = () => { + registerCandlestickChart(); + const cs = new VChart(spec, { + dom: document.getElementById('chart') as HTMLElement, + onError: err => { + console.error(err); + } + }); + console.time('renderTime'); + cs.renderSync(); + console.timeEnd('renderTime'); + window['vchart'] = cs; + console.log(cs); +}; +run(); diff --git a/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts b/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts new file mode 100644 index 0000000000..e03ed2044d --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts @@ -0,0 +1,34 @@ +import { CartesianChartSpecTransformer } from '@visactor/vchart'; +import type { ICandlestickChartSpec } from './interface'; + +export class CandlestickChartSpecTransformer< + T extends ICandlestickChartSpec = ICandlestickChartSpec +> extends CartesianChartSpecTransformer { + protected _getDefaultSeriesSpec(spec: T): any { + const dataFields = [spec.openField, spec.highField, spec.lowField, spec.closeField]; + const seriesSpec = super._getDefaultSeriesSpec(spec, [ + 'candlestick', + 'openField', + 'highField', + 'lowField', + 'closeField', + 'candlestickColor' + ]); + return seriesSpec; + } + + transformSpec(spec: T): void { + super.transformSpec(spec); + if (!spec.axes) { + spec.axes = [ + { + orient: 'bottom' + }, + { + orient: 'left' + } + ]; + } + //setDefaultCrosshairForCartesianChart(spec); + } +} diff --git a/packages/vchart-extension/src/charts/candlestick/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/candlestick.ts index e69de29bb2..ed5cf3cac8 100644 --- a/packages/vchart-extension/src/charts/candlestick/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/candlestick.ts @@ -0,0 +1,18 @@ +import { CandlestickChartSpecTransformer } from './candlestick-transformer'; +import { ICandlestickChartSpec } from './interface'; +import { registerCandlestickSeries } from './series/candlestick'; +import { BaseChart, Factory } from '@visactor/vchart'; +export class CandlestickChart extends BaseChart { + static readonly type: string = 'candlestick'; + static readonly seriesType: string = 'candlestick'; + static readonly transformerConstructor = CandlestickChartSpecTransformer; // CandlestickChartSpecTransformer; +} + +export const registerCandlestickChart = () => { + //registerDimensionTooltipProcessor(); + //registerMarkTooltipProcessor(); + //registerDimensionEvents(); + //registerDimensionHover(); + registerCandlestickSeries(); + Factory.registerChart(CandlestickChart.type, CandlestickChart); +}; diff --git a/packages/vchart-extension/src/charts/candlestick/interface.ts b/packages/vchart-extension/src/charts/candlestick/interface.ts index 31c8304213..5b9805d69e 100644 --- a/packages/vchart-extension/src/charts/candlestick/interface.ts +++ b/packages/vchart-extension/src/charts/candlestick/interface.ts @@ -1,7 +1,7 @@ -import type { IChartExtendsSeriesSpec, IChartSpec } from '@visactor/vchart'; +import type { IChartExtendsSeriesSpec, ICartesianChartSpec } from '@visactor/vchart'; import type { ICandlestickSeriesSpec } from './series/interface'; -export interface ICandlestickChartSpec extends IChartSpec, IChartExtendsSeriesSpec { +export interface ICandlestickChartSpec extends ICartesianChartSpec, IChartExtendsSeriesSpec { type: 'candlestick'; /** 系列配置 */ series?: ICandlestickSeriesSpec[]; diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts index bce4f3d9b2..821b355cd2 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -34,7 +34,7 @@ export class CandlestickSeries { +export const registerCandlestickSeries = () => { registerCandlestickMark(); registerSymbolMark(); registerScaleInOutAnimation(); diff --git a/packages/vchart-extension/src/charts/candlestick/series/interface.ts b/packages/vchart-extension/src/charts/candlestick/series/interface.ts index 6662afe89f..b231ff8517 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/interface.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/interface.ts @@ -6,7 +6,7 @@ import type { IMarkTheme, ICartesianSeriesTheme } from '@visactor/vchart'; -import type { ICandlestickMarkSpec } from '../mark'; +import type { ICandlestickMarkSpec } from '../mark/interface'; export type candlestickColorConfig = { /** From e53fb23ad34e8ab7e85dfd1189eb8e45b7ef4a4d Mon Sep 17 00:00:00 2001 From: szxc Date: Sun, 20 Jul 2025 17:11:34 +0800 Subject: [PATCH 05/15] feat: refactor candlestick chart constants and improve series initialization --- .../src/charts/candlestick/candlestick.ts | 5 +- .../charts/candlestick/mark/candlestick.ts | 34 +---- .../charts/candlestick/series/candlestick.ts | 122 ++++++++++++++++-- .../src/charts/candlestick/series/constant.ts | 13 ++ 4 files changed, 132 insertions(+), 42 deletions(-) create mode 100644 packages/vchart-extension/src/charts/candlestick/series/constant.ts diff --git a/packages/vchart-extension/src/charts/candlestick/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/candlestick.ts index ed5cf3cac8..301330db14 100644 --- a/packages/vchart-extension/src/charts/candlestick/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/candlestick.ts @@ -2,9 +2,10 @@ import { CandlestickChartSpecTransformer } from './candlestick-transformer'; import { ICandlestickChartSpec } from './interface'; import { registerCandlestickSeries } from './series/candlestick'; import { BaseChart, Factory } from '@visactor/vchart'; +import { CANDLESTICK_CHART_TYPE, CANDLESTICK_SERIES_TYPE } from './series/constant'; export class CandlestickChart extends BaseChart { - static readonly type: string = 'candlestick'; - static readonly seriesType: string = 'candlestick'; + static readonly type: string = CANDLESTICK_CHART_TYPE; + static readonly seriesType: string = CANDLESTICK_SERIES_TYPE; static readonly transformerConstructor = CandlestickChartSpecTransformer; // CandlestickChartSpecTransformer; } diff --git a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts index 5fd8e49551..7d107c029f 100644 --- a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts @@ -14,15 +14,13 @@ export class CandlestickMark extends GlyphMark implements setGlyphConfig(): void { this._subMarks = { body: { type: 'rect' }, - upperWick: { type: 'line', defaultAttributes: { x: 0, y: 0 } }, - lowerWick: { type: 'line', defaultAttributes: { x: 0, y: 0 } } + wick: { type: 'line', defaultAttributes: { x: 0, y: 0 } } }; this._channelEncoder = null; this._positionEncoder = (glyphAttrs: any, datum: Datum, g: IGlyph) => { const { x = g.attribute.x, boxWidth = (g.attribute as any).boxWidth, - ruleWidth = (g.attribute as any).ruleWidth, open = (g.attribute as any).open, close = (g.attribute as any).close, low = (g.attribute as any).low, @@ -32,34 +30,10 @@ export class CandlestickMark extends GlyphMark implements attributes.box = { x: x - boxWidth / 2, x1: x + boxWidth / 2, - y: open, - y1: close + y: Math.min(open, close), + y1: Math.max(open, close) }; - attributes.high = { - points: [ - { - x: x - ruleWidth / 2, - y: high - }, - { - x: x + ruleWidth / 2, - y: high - } - ] - }; - attributes.low = { - points: [ - { - x: x - ruleWidth / 2, - y: low - }, - { - x: x + ruleWidth / 2, - y: low - } - ] - }; - attributes.shaft = { + attributes.wick = { points: [ { x: x, diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts index 821b355cd2..2ec9b13662 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -6,29 +6,131 @@ import { registerCartesianLinearAxis, CartesianSeries, IMark, - Factory + Factory, + STATE_VALUE_ENUM, + AttributeLevel, + Datum, + IModelInitOption } from '@visactor/vchart'; +import { valueInScaleRange } from '@visactor/vchart/src/util'; import type { ICandlestickSeriesSpec } from './interface'; +import { CANDLESTICK_SERIES_TYPE, CandlestickSeriesMark } from './constant'; -export const CANDLESTICK_SERIES_TYPE = 'candlestick'; +const DEFAULT_STROKE_WIDTH = 2; +export const DEFAULT_FILL_COLOR = '#FFF'; +export const DEFAULT_STROKE_COLOR = '#000'; export class CandlestickSeries extends CartesianSeries { static readonly type: string = CANDLESTICK_SERIES_TYPE; type = CANDLESTICK_SERIES_TYPE; - private _candlestickMark: ICandlestickMark; + static readonly mark = CandlestickSeriesMark; + protected _openField: string; + getOpenField(): string { + return this._openField; + } + protected _highField: string; + getHighField(): string { + return this._highField; + } + protected _lowField: string; + getLowField(): string { + return this._lowField; + } + protected _closeField: string; + getCloseField(): string { + return this._closeField; + } + protected _lineWidth: number; + protected _boxWidth: number; + protected _boxFill: string | ((datum: Datum) => string); + + setAttrFromSpec() { + super.setAttrFromSpec(); + const spec = this._spec; + const CandlestickStyle: any = spec.candlestick?.style ?? {}; + this._openField = spec.openField || 'open'; + this._highField = spec.highField || 'high'; + this._lowField = spec.lowField || 'low'; + this._closeField = spec.closeField || 'close'; + this._lineWidth = CandlestickStyle.lineWidth ?? DEFAULT_STROKE_WIDTH; + this._boxWidth = CandlestickStyle.boxWidth ?? 10; + this._boxFill = CandlestickStyle.boxFill ?? DEFAULT_FILL_COLOR; + } + + private _candlestickMark?: ICandlestickMark; + initMark() { - this._candlestickMark = this._createMark( - { type: CANDLESTICK_SERIES_TYPE, name: CANDLESTICK_SERIES_TYPE }, - { - groupKey: this._seriesField, - isSeriesMark: true - } - ) as ICandlestickMark; + this._candlestickMark = this._createMark(CandlestickSeries.mark.candlestick, { + groupKey: this._seriesField, + isSeriesMark: true + }) as ICandlestickMark; } + initMarkStyle() { const candlestickMark = this._candlestickMark; + if (candlestickMark) { + const commonCandlestickStyles = { + lineWidth: this._lineWidth, + boxWidth: this._boxWidth, + fill: this._boxFill, + stroke: DEFAULT_STROKE_COLOR + }; + this.setMarkStyle(candlestickMark, commonCandlestickStyles, STATE_VALUE_ENUM.STATE_NORMAL, AttributeLevel.Series); + } + } + + initCandlestickMarkStyle() { + const candlestickMark = this._candlestickMark; + const axisHelper = this._yAxisHelper; + if (candlestickMark && axisHelper) { + const { dataToPosition } = axisHelper; + const scale = axisHelper?.getScale?.(0); + this.setMarkStyle( + candlestickMark, + { + boxWidth: this._boxWidth, + open: (datum: Datum) => + valueInScaleRange( + dataToPosition(this.getDatumPositionValues(datum, this._openField), { + bandPosition: this._bandPosition + }), + scale + ), + high: (datum: Datum) => + valueInScaleRange( + dataToPosition(this.getDatumPositionValues(datum, this._highField), { + bandPosition: this._bandPosition + }), + scale + ), + low: (datum: Datum) => + valueInScaleRange( + dataToPosition(this.getDatumPositionValues(datum, this._lowField), { + bandPosition: this._bandPosition + }), + scale + ), + close: (datum: Datum) => + valueInScaleRange( + dataToPosition(this.getDatumPositionValues(datum, this._closeField), { + bandPosition: this._bandPosition + }), + scale + ) + }, + STATE_VALUE_ENUM.STATE_NORMAL, + AttributeLevel.Series + ); + } } + + init(option: IModelInitOption): void { + super.init(option); + //init在axis初始化之后才被执行,此时axisHelper不为空 + this.initCandlestickMarkStyle(); + } + getActiveMarks(): IMark[] { return [this._candlestickMark]; } diff --git a/packages/vchart-extension/src/charts/candlestick/series/constant.ts b/packages/vchart-extension/src/charts/candlestick/series/constant.ts new file mode 100644 index 0000000000..2ed2e6d681 --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/series/constant.ts @@ -0,0 +1,13 @@ +import { baseSeriesMark } from '@visactor/vchart'; + +export const CANDLESTICK_CHART_TYPE = 'candlestick'; +export const CANDLESTICK_SERIES_TYPE = 'candlestick'; + +export const enum CandlestickMarkNameEnum { + candlestick = 'candlestick' +} + +export const CandlestickSeriesMark = { + ...baseSeriesMark, + [CandlestickMarkNameEnum.candlestick]: { name: CandlestickMarkNameEnum.candlestick, type: 'candlestick' } +}; From 6b1ec66f8a1d56d67e4c82937a8e98dfef64cc12 Mon Sep 17 00:00:00 2001 From: szxc Date: Tue, 22 Jul 2025 16:19:10 +0800 Subject: [PATCH 06/15] feat: enhance candlestick mark configuration and add glyph mark type --- .../charts/candlestick/mark/candlestick.ts | 6 +++-- .../charts/candlestick/series/candlestick.ts | 26 +++++++++++++------ packages/vchart/src/mark/index.ts | 2 +- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts index 7d107c029f..3980fa355c 100644 --- a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts @@ -11,11 +11,13 @@ export class CandlestickMark extends GlyphMark implements static readonly type = CANDLESTICK_MARK_TYPE; readonly type = CandlestickMark.type; - setGlyphConfig(): void { + setGlyphConfig(cfg: any): void { + super.setGlyphConfig(cfg); this._subMarks = { - body: { type: 'rect' }, + box: { type: 'rect' }, wick: { type: 'line', defaultAttributes: { x: 0, y: 0 } } }; + this._positionChannels = ['x', 'boxWidth', 'open', 'close', 'high', 'low']; this._channelEncoder = null; this._positionEncoder = (glyphAttrs: any, datum: Datum, g: IGlyph) => { const { diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts index 2ec9b13662..f88774fb28 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -13,6 +13,7 @@ import { IModelInitOption } from '@visactor/vchart'; import { valueInScaleRange } from '@visactor/vchart/src/util'; +import { IGlyphMark } from '@visactor/vchart'; import type { ICandlestickSeriesSpec } from './interface'; import { CANDLESTICK_SERIES_TYPE, CandlestickSeriesMark } from './constant'; @@ -44,38 +45,48 @@ export class CandlestickSeries string); + getBoxFillColor(): string | ((datum: Datum) => string) { + return this._boxFill; + } + protected _strokeColor: string; + getStrokeColor(): string { + return this._strokeColor; + } setAttrFromSpec() { super.setAttrFromSpec(); const spec = this._spec; const CandlestickStyle: any = spec.candlestick?.style ?? {}; - this._openField = spec.openField || 'open'; - this._highField = spec.highField || 'high'; - this._lowField = spec.lowField || 'low'; - this._closeField = spec.closeField || 'close'; + this._openField = spec.openField; + this._highField = spec.highField; + this._lowField = spec.lowField; + this._closeField = spec.closeField; this._lineWidth = CandlestickStyle.lineWidth ?? DEFAULT_STROKE_WIDTH; this._boxWidth = CandlestickStyle.boxWidth ?? 10; this._boxFill = CandlestickStyle.boxFill ?? DEFAULT_FILL_COLOR; + this._strokeColor = CandlestickStyle.strokeColor ?? DEFAULT_STROKE_COLOR; } private _candlestickMark?: ICandlestickMark; - initMark() { + initMark(): void { this._candlestickMark = this._createMark(CandlestickSeries.mark.candlestick, { groupKey: this._seriesField, isSeriesMark: true }) as ICandlestickMark; } - initMarkStyle() { + initMarkStyle(): void { const candlestickMark = this._candlestickMark; if (candlestickMark) { const commonCandlestickStyles = { lineWidth: this._lineWidth, boxWidth: this._boxWidth, fill: this._boxFill, - stroke: DEFAULT_STROKE_COLOR + stroke: this._strokeColor ?? DEFAULT_STROKE_COLOR, + x: this.dataToPositionX.bind(this) }; + (candlestickMark as IGlyphMark).setGlyphConfig({}); this.setMarkStyle(candlestickMark, commonCandlestickStyles, STATE_VALUE_ENUM.STATE_NORMAL, AttributeLevel.Series); } } @@ -89,7 +100,6 @@ export class CandlestickSeries valueInScaleRange( dataToPosition(this.getDatumPositionValues(datum, this._openField), { diff --git a/packages/vchart/src/mark/index.ts b/packages/vchart/src/mark/index.ts index 0a09bce2e7..c9cc10fc89 100644 --- a/packages/vchart/src/mark/index.ts +++ b/packages/vchart/src/mark/index.ts @@ -46,7 +46,7 @@ export type { } from '../typings/visual'; export type { IMarkRaw, IMark, IMarkStyle } from './interface/common'; -export type { ITextMark, ILabelMark, IRectMark, IRuleMark, IImageMark, IGroupMark } from './interface/mark'; +export type { ITextMark, ILabelMark, IRectMark, IRuleMark, IImageMark, IGroupMark, IGlyphMark } from './interface/mark'; export { MarkTypeEnum, From 9e55f93c61d96084997ea49345a71c6218855b27 Mon Sep 17 00:00:00 2001 From: szxc Date: Sun, 27 Jul 2025 12:07:11 +0800 Subject: [PATCH 07/15] feat: update candlestick chart data and enhance color configuration --- .../runtime/browser/test-page/candlestick.ts | 15 +++--- .../candlestick/candlestick-transformer.ts | 1 + .../charts/candlestick/mark/candlestick.ts | 21 ++++----- .../charts/candlestick/series/candlestick.ts | 46 ++++++++++++++----- .../charts/candlestick/series/interface.ts | 2 +- packages/vchart/src/index.ts | 1 + 6 files changed, 56 insertions(+), 30 deletions(-) diff --git a/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts b/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts index 05da3e040c..bd0eb723d5 100644 --- a/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts +++ b/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts @@ -1,12 +1,16 @@ import { ICandlestickChartSpec } from '../../../../src/charts/candlestick/interface'; import { registerCandlestickChart } from '../../../../src/charts/candlestick/candlestick'; -import { VChart } from '@visactor/vchart'; +import VChart from '@visactor/vchart'; const data = [ - { time: '2024-07-01', open: 100, close: 110, high: 115, low: 95 }, - { time: '2024-07-02', open: 110, close: 105, high: 112, low: 102 }, - { time: '2024-07-03', open: 105, close: 120, high: 125, low: 104 }, - { time: '2024-07-04', open: 120, close: 115, high: 122, low: 110 } + { time: '2024-07-01', open: 100, close: 130, high: 135, low: 90 }, + { time: '2024-07-02', open: 130, close: 80, high: 140, low: 75 }, + { time: '2024-07-03', open: 80, close: 150, high: 155, low: 70 }, + { time: '2024-07-04', open: 150, close: 120, high: 160, low: 110 }, + { time: '2024-07-05', open: 120, close: 170, high: 180, low: 115 }, + { time: '2024-07-06', open: 170, close: 170, high: 175, low: 95 }, + { time: '2024-07-07', open: 170, close: 100, high: 175, low: 95 }, + { time: '2024-07-08', open: 100, close: 200, high: 210, low: 90 } ]; const spec: ICandlestickChartSpec = { @@ -18,7 +22,6 @@ const spec: ICandlestickChartSpec = { lowField: 'low', data: [ { - id: 'main', values: data } ] diff --git a/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts b/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts index e03ed2044d..5151b079e6 100644 --- a/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts +++ b/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts @@ -14,6 +14,7 @@ export class CandlestickChartSpecTransformer< 'closeField', 'candlestickColor' ]); + seriesSpec.yField = dataFields; return seriesSpec; } diff --git a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts index 3980fa355c..874a153711 100644 --- a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts @@ -14,8 +14,8 @@ export class CandlestickMark extends GlyphMark implements setGlyphConfig(cfg: any): void { super.setGlyphConfig(cfg); this._subMarks = { - box: { type: 'rect' }, - wick: { type: 'line', defaultAttributes: { x: 0, y: 0 } } + line: { type: 'line', defaultAttributes: { x: 0, y: 0 } }, + box: { type: 'rect' } }; this._positionChannels = ['x', 'boxWidth', 'open', 'close', 'high', 'low']; this._channelEncoder = null; @@ -29,13 +29,7 @@ export class CandlestickMark extends GlyphMark implements high = (g.attribute as any).high } = glyphAttrs; const attributes: any = {}; - attributes.box = { - x: x - boxWidth / 2, - x1: x + boxWidth / 2, - y: Math.min(open, close), - y1: Math.max(open, close) - }; - attributes.wick = { + attributes.line = { points: [ { x: x, @@ -47,6 +41,12 @@ export class CandlestickMark extends GlyphMark implements } ] }; + attributes.box = { + x: x - boxWidth / 2, + x1: x + boxWidth / 2, + y: Math.min(open, close), + y1: Math.max(open, close) + }; return attributes; }; } @@ -54,8 +54,7 @@ export class CandlestickMark extends GlyphMark implements protected _getDefaultStyle() { const defaultStyle: IMarkStyle = { ...super._getDefaultStyle(), - lineWidth: 2, - boxWidth: 30 + boxWidth: 40 }; return defaultStyle; } diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts index f88774fb28..f74270b73d 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -12,14 +12,15 @@ import { Datum, IModelInitOption } from '@visactor/vchart'; -import { valueInScaleRange } from '@visactor/vchart/src/util'; +import { valueInScaleRange } from '@visactor/vchart'; import { IGlyphMark } from '@visactor/vchart'; import type { ICandlestickSeriesSpec } from './interface'; import { CANDLESTICK_SERIES_TYPE, CandlestickSeriesMark } from './constant'; const DEFAULT_STROKE_WIDTH = 2; -export const DEFAULT_FILL_COLOR = '#FFF'; export const DEFAULT_STROKE_COLOR = '#000'; +export const DEFAULT_RISE_COLOR = '#FF0000'; +export const DEFAULT_FALL_COLOR = '#00AA00'; export class CandlestickSeries extends CartesianSeries { static readonly type: string = CANDLESTICK_SERIES_TYPE; @@ -44,14 +45,23 @@ export class CandlestickSeries string); + protected _boxFillColor: string | ((datum: Datum) => string); getBoxFillColor(): string | ((datum: Datum) => string) { - return this._boxFill; + return this._boxFillColor; } protected _strokeColor: string; getStrokeColor(): string { return this._strokeColor; } + protected _riseColor: string; + getRiseColor(): string { + return this._riseColor; + } + protected _fallColor: string; + getFallColor(): string { + return this._fallColor; + } + private _autoBoxWidth: number; setAttrFromSpec() { super.setAttrFromSpec(); @@ -61,10 +71,12 @@ export class CandlestickSeries close) { + return this._fallColor; + } + return this._strokeColor ?? DEFAULT_STROKE_COLOR; + } + getActiveMarks(): IMark[] { return [this._candlestickMark]; } diff --git a/packages/vchart-extension/src/charts/candlestick/series/interface.ts b/packages/vchart-extension/src/charts/candlestick/series/interface.ts index b231ff8517..334aa8f20e 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/interface.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/interface.ts @@ -20,7 +20,7 @@ export type candlestickColorConfig = { /** * 平盘蜡烛图颜色 */ - doji: string; + doji?: string; }; export interface ICandlestickSeriesSpec diff --git a/packages/vchart/src/index.ts b/packages/vchart/src/index.ts index faeddf15d7..67948da7ff 100644 --- a/packages/vchart/src/index.ts +++ b/packages/vchart/src/index.ts @@ -27,6 +27,7 @@ export * from './util/data'; export * from './util/spec/transform'; export * from './util/mark'; export * from './util/region'; +export * from './util/scale'; // base component model for extension export * from './component/base'; From d8ac9608c3c8f0077fd0e423540eb2777643bbec Mon Sep 17 00:00:00 2001 From: szxc Date: Sun, 27 Jul 2025 19:55:28 +0800 Subject: [PATCH 08/15] feat: add animation --- .../runtime/browser/test-page/candlestick.ts | 6 +- .../charts/candlestick/series/animation.ts | 172 ++++++++++++++++++ .../charts/candlestick/series/candlestick.ts | 37 +++- packages/vchart/src/animation/index.ts | 2 +- packages/vchart/src/series/index.ts | 1 + 5 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 packages/vchart-extension/src/charts/candlestick/series/animation.ts diff --git a/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts b/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts index bd0eb723d5..321755b52e 100644 --- a/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts +++ b/packages/vchart-extension/__tests__/runtime/browser/test-page/candlestick.ts @@ -6,11 +6,11 @@ const data = [ { time: '2024-07-01', open: 100, close: 130, high: 135, low: 90 }, { time: '2024-07-02', open: 130, close: 80, high: 140, low: 75 }, { time: '2024-07-03', open: 80, close: 150, high: 155, low: 70 }, - { time: '2024-07-04', open: 150, close: 120, high: 160, low: 110 }, - { time: '2024-07-05', open: 120, close: 170, high: 180, low: 115 }, + { time: '2024-07-04', open: 150, close: 140, high: 160, low: 105 }, + { time: '2024-07-05', open: 140, close: 170, high: 180, low: 115 }, { time: '2024-07-06', open: 170, close: 170, high: 175, low: 95 }, { time: '2024-07-07', open: 170, close: 100, high: 175, low: 95 }, - { time: '2024-07-08', open: 100, close: 200, high: 210, low: 90 } + { time: '2024-07-08', open: 100, close: 160, high: 210, low: 90 } ]; const spec: ICandlestickChartSpec = { diff --git a/packages/vchart-extension/src/charts/candlestick/series/animation.ts b/packages/vchart-extension/src/charts/candlestick/series/animation.ts new file mode 100644 index 0000000000..ac6fa2a862 --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/series/animation.ts @@ -0,0 +1,172 @@ +import type { EasingType, ILineAttribute, IRectAttribute } from '@visactor/vrender-core'; +import type { IGlyph } from '@visactor/vrender-core'; +import type { IAnimationParameters } from '@visactor/vchart'; +import { isValidNumber } from '@visactor/vutils'; +import { ACustomAnimate, AnimateExecutor } from '@visactor/vrender-animate'; + +export interface ICandlestickScaleAnimationOptions { + center?: number; +} + +type TypeAnimation = ( + graphic: IGlyph, + options: ICandlestickScaleAnimationOptions, + animationParameters: IAnimationParameters +) => { from?: { [channel: string]: any }; to?: { [channel: string]: any } }; + +const scaleIn = ( + computeCenter: (graphic: IGlyph, options: ICandlestickScaleAnimationOptions) => number +): TypeAnimation => { + return (graphic: IGlyph, options: ICandlestickScaleAnimationOptions, animationParameters: IAnimationParameters) => { + const finalAttribute = graphic.getFinalAttribute(); + const center = computeCenter(graphic, options); + if (!isValidNumber(center)) { + return {}; + } + const { x, y, open, high, low, close } = finalAttribute; + const animateAttributes: any = { from: { x, y }, to: { x, y } }; + if (isValidNumber(open)) { + animateAttributes.from.open = center; + animateAttributes.to.open = open; + } + if (isValidNumber(high)) { + animateAttributes.from.high = center; + animateAttributes.to.high = high; + } + if (isValidNumber(low)) { + animateAttributes.from.low = center; + animateAttributes.to.low = low; + } + if (isValidNumber(close)) { + animateAttributes.from.close = center; + animateAttributes.to.close = close; + } + return animateAttributes; + }; +}; + +const scaleOut = ( + computeCenter: (mark: IGlyph, options: ICandlestickScaleAnimationOptions) => number +): TypeAnimation => { + return (graphic: IGlyph, options: ICandlestickScaleAnimationOptions, animationParameters: IAnimationParameters) => { + const finalAttribute = graphic.getFinalAttribute(); + const center = computeCenter(graphic, options); + if (!isValidNumber(center)) { + return {}; + } + const { x, y, open, high, low, close } = finalAttribute; + + const animateAttributes: any = { from: { x, y }, to: { x, y } }; + if (isValidNumber(open)) { + animateAttributes.to.open = center; + animateAttributes.from.open = open; + } + if (isValidNumber(high)) { + animateAttributes.to.high = center; + animateAttributes.from.high = high; + } + if (isValidNumber(low)) { + animateAttributes.to.low = center; + animateAttributes.from.low = low; + } + if (isValidNumber(close)) { + animateAttributes.to.close = center; + animateAttributes.from.close = close; + } + return animateAttributes; + }; +}; +const getGlyphChildByName = (mark: IGlyph, name: string) => { + return mark.getSubGraphic().find(child => child.name === name); +}; + +const computeCandlestickCenter = (glyphMark: IGlyph, options: ICandlestickScaleAnimationOptions) => { + if (options && isValidNumber(options.center)) { + return options.center; + } + const lineAttr = getGlyphChildByName(glyphMark, 'line')?.attribute as ILineAttribute | undefined; + const boxAttr = getGlyphChildByName(glyphMark, 'box')?.attribute as IRectAttribute | undefined; + + if (boxAttr && isValidNumber(boxAttr.y1) && isValidNumber(boxAttr.height)) { + const y0 = boxAttr.y1 - boxAttr.height; + const y1 = boxAttr.y1; + return (y0 + y1) / 2; + } + + if (lineAttr?.points?.length === 2) { + const y0 = lineAttr.points[0].y; + const y1 = lineAttr.points[1].y; + return (y0 + y1) / 2; + } + + return NaN; +}; + +export class CandlestickScaleIn extends ACustomAnimate> { + constructor(from: null, to: null, duration: number, easing: EasingType, params?: ICandlestickScaleAnimationOptions) { + super(from, to, duration, easing, params); + } + + onBind(): void { + super.onBind(); + const finalAttribute = this.target.getFinalAttribute(); + if (finalAttribute) { + this.target.setAttributes(finalAttribute); + } + const { from, to } = this.computeAttribute(); + this.propKeys = Object.keys(to).filter(key => to[key] != null); + this.animate.reSyncProps(); + this.from = from; + this.to = to; + this.target.setAttributes(this.from); + } + + computeAttribute() { + const attr = scaleIn(computeCandlestickCenter)(this.target as IGlyph, this.params, this.params.options); + return attr; + } + + onUpdate(end: boolean, ratio: number, out: Record): void { + const attribute: Record = this.target.attribute; + this.propKeys.forEach(key => { + attribute[key] = this.from[key] + (this.to[key] - this.from[key]) * ratio; + }); + this.target.setAttributes(attribute); + } +} + +export class CandlestickScaleOut extends ACustomAnimate> { + constructor(from: null, to: null, duration: number, easing: EasingType, params?: ICandlestickScaleAnimationOptions) { + super(from, to, duration, easing, params); + } + + onBind(): void { + if (this.params?.diffAttrs) { + this.target.setAttributes(this.params.diffAttrs); + } + const { from, to } = this.computeAttribute(); + this.propKeys = Object.keys(to).filter(key => to[key] != null); + this.animate.reSyncProps(); + this.from = from; + this.to = to; + this.target.setAttributes(this.from); + } + + computeAttribute() { + const attr = scaleOut(computeCandlestickCenter)(this.target as IGlyph, this.params, this.params.options); + return attr; + } + + onUpdate(end: boolean, ratio: number, out: Record): void { + const attribute: Record = this.target.attribute; + this.propKeys.forEach(key => { + attribute[key] = this.from[key] + (this.to[key] - this.from[key]) * ratio; + }); + this.target.setAttributes(attribute); + } +} + +export const registerCandlestickScaleAnimation = () => { + AnimateExecutor.registerBuiltInAnimate('candlestickScaleIn', CandlestickScaleIn); + AnimateExecutor.registerBuiltInAnimate('candlestickScaleOut', CandlestickScaleOut); +}; diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts index f74270b73d..bbf6ee0b94 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -10,11 +10,16 @@ import { STATE_VALUE_ENUM, AttributeLevel, Datum, - IModelInitOption + IModelInitOption, + getGroupAnimationParams, + animationConfig, + userAnimationConfig } from '@visactor/vchart'; import { valueInScaleRange } from '@visactor/vchart'; import { IGlyphMark } from '@visactor/vchart'; +import { merge } from '@visactor/vutils'; import type { ICandlestickSeriesSpec } from './interface'; +import { registerCandlestickScaleAnimation } from './animation'; import { CANDLESTICK_SERIES_TYPE, CandlestickSeriesMark } from './constant'; const DEFAULT_STROKE_WIDTH = 2; @@ -71,8 +76,8 @@ export class CandlestickSeries { + if (newConfig[state] && newConfig[state].type === 'scaleIn') { + newConfig[state].type = 'candlestickScaleIn'; + } else if (newConfig[state] && newConfig[state].type === 'scaleOut') { + newConfig[state].type = 'candlestickScaleOut'; + } + }); + return newConfig; + } + + initAnimation() { + const animationParams = getGroupAnimationParams(this); + + if (this._candlestickMark) { + const newDefaultConfig = this._initAnimationSpec(Factory.getAnimationInKey('scaleInOut')?.()); + const newConfig = this._initAnimationSpec( + userAnimationConfig(CANDLESTICK_SERIES_TYPE, this._spec, this._markAttributeContext) + ); + this._candlestickMark.setAnimationConfig(animationConfig(newDefaultConfig, newConfig, animationParams)); + } + } + getCandlestickColorAttribute(datum: Datum): string { const open = this.getDatumPositionValues(datum, this._openField); const close = this.getDatumPositionValues(datum, this._closeField); @@ -174,6 +203,6 @@ export const registerCandlestickSeries = () => { registerScaleInOutAnimation(); registerCartesianBandAxis(); registerCartesianLinearAxis(); - //registerCandlestickScaleAnimation(); + registerCandlestickScaleAnimation(); Factory.registerSeries(CandlestickSeries.type, CandlestickSeries); }; diff --git a/packages/vchart/src/animation/index.ts b/packages/vchart/src/animation/index.ts index 74f0f72dde..1305461a69 100644 --- a/packages/vchart/src/animation/index.ts +++ b/packages/vchart/src/animation/index.ts @@ -10,4 +10,4 @@ export { } from './config'; export { animationConfig, userAnimationConfig, shouldMarkDoMorph } from './utils'; export type { IAnimationSpec } from './spec'; -export type { IAnimationTypeConfig, IAnimationConfig } from './interface'; +export type { IAnimationTypeConfig, IAnimationConfig, IAnimationParameters } from './interface'; diff --git a/packages/vchart/src/series/index.ts b/packages/vchart/src/series/index.ts index df355a28af..2be7d5ffb8 100644 --- a/packages/vchart/src/series/index.ts +++ b/packages/vchart/src/series/index.ts @@ -221,3 +221,4 @@ export type { }; export * from './interface'; +export * from './util/utils'; From ebafda2c36ab4d4d2ee6fdc4ceeafc6ee1fa4367 Mon Sep 17 00:00:00 2001 From: szxc Date: Mon, 28 Jul 2025 00:00:35 +0800 Subject: [PATCH 09/15] feat: add tooltip --- .../candlestick/candlestick-transformer.ts | 4 +- .../src/charts/candlestick/candlestick.ts | 17 ++-- .../src/charts/candlestick/index.ts | 3 + .../charts/candlestick/series/candlestick.ts | 6 ++ .../src/charts/candlestick/series/constant.ts | 8 ++ .../candlestick/series/tooltip-helper.ts | 88 +++++++++++++++++++ packages/vchart/src/chart/index.ts | 2 + 7 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 packages/vchart-extension/src/charts/candlestick/index.ts create mode 100644 packages/vchart-extension/src/charts/candlestick/series/tooltip-helper.ts diff --git a/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts b/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts index 5151b079e6..38dc997031 100644 --- a/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts +++ b/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts @@ -1,4 +1,4 @@ -import { CartesianChartSpecTransformer } from '@visactor/vchart'; +import { CartesianChartSpecTransformer, setDefaultCrosshairForCartesianChart } from '@visactor/vchart'; import type { ICandlestickChartSpec } from './interface'; export class CandlestickChartSpecTransformer< @@ -30,6 +30,6 @@ export class CandlestickChartSpecTransformer< } ]; } - //setDefaultCrosshairForCartesianChart(spec); + setDefaultCrosshairForCartesianChart(spec); } } diff --git a/packages/vchart-extension/src/charts/candlestick/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/candlestick.ts index 301330db14..a4b427b18f 100644 --- a/packages/vchart-extension/src/charts/candlestick/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/candlestick.ts @@ -1,7 +1,14 @@ import { CandlestickChartSpecTransformer } from './candlestick-transformer'; import { ICandlestickChartSpec } from './interface'; import { registerCandlestickSeries } from './series/candlestick'; -import { BaseChart, Factory } from '@visactor/vchart'; +import { + BaseChart, + Factory, + registerMarkTooltipProcessor, + registerDimensionTooltipProcessor, + registerDimensionEvents, + registerDimensionHover +} from '@visactor/vchart'; import { CANDLESTICK_CHART_TYPE, CANDLESTICK_SERIES_TYPE } from './series/constant'; export class CandlestickChart extends BaseChart { static readonly type: string = CANDLESTICK_CHART_TYPE; @@ -10,10 +17,10 @@ export class CandlestickChart { - //registerDimensionTooltipProcessor(); - //registerMarkTooltipProcessor(); - //registerDimensionEvents(); - //registerDimensionHover(); + registerDimensionTooltipProcessor(); + registerMarkTooltipProcessor(); + registerDimensionEvents(); + registerDimensionHover(); registerCandlestickSeries(); Factory.registerChart(CandlestickChart.type, CandlestickChart); }; diff --git a/packages/vchart-extension/src/charts/candlestick/index.ts b/packages/vchart-extension/src/charts/candlestick/index.ts new file mode 100644 index 0000000000..96031419c2 --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/index.ts @@ -0,0 +1,3 @@ +export * from './candlestick'; +export * from './interface'; +export * from './candlestick-transformer'; diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts index bbf6ee0b94..444fc513aa 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -21,6 +21,7 @@ import { merge } from '@visactor/vutils'; import type { ICandlestickSeriesSpec } from './interface'; import { registerCandlestickScaleAnimation } from './animation'; import { CANDLESTICK_SERIES_TYPE, CandlestickSeriesMark } from './constant'; +import { CandlestickSeriesTooltipHelper } from './tooltip-helper'; const DEFAULT_STROKE_WIDTH = 2; export const DEFAULT_STROKE_COLOR = '#000'; @@ -181,6 +182,11 @@ export class CandlestickSeries (datum: any) => { + switch (contentType) { + case CANDLESTICK_TOOLTIP_KEYS.OPEN: { + const openField = (this.series as CandlestickSeries).getOpenField(); + return openField; + } + case CANDLESTICK_TOOLTIP_KEYS.HIGH: { + const highField = (this.series as CandlestickSeries).getHighField(); + return highField; + } + case CANDLESTICK_TOOLTIP_KEYS.LOW: { + const lowField = (this.series as CandlestickSeries).getLowField(); + return lowField; + } + case CANDLESTICK_TOOLTIP_KEYS.CLOSE: { + const closeField = (this.series as CandlestickSeries).getCloseField(); + return closeField; + } + case CANDLESTICK_TOOLTIP_KEYS.SERIES_FIELD: { + const seriesField = (this.series as CandlestickSeries).getSeriesField(); + return seriesField; + } + } + + return null; + }; + + getContentValue = (contentType: CANDLESTICK_TOOLTIP_KEYS) => (datum: any) => { + switch (contentType) { + case CANDLESTICK_TOOLTIP_KEYS.OPEN: { + const openField = (this.series as CandlestickSeries).getOpenField(); + return datum[openField]; + } + case CANDLESTICK_TOOLTIP_KEYS.HIGH: { + const highField = (this.series as CandlestickSeries).getHighField(); + return datum[highField]; + } + case CANDLESTICK_TOOLTIP_KEYS.LOW: { + const lowField = (this.series as CandlestickSeries).getLowField(); + return datum[lowField]; + } + case CANDLESTICK_TOOLTIP_KEYS.CLOSE: { + const closeField = (this.series as CandlestickSeries).getCloseField(); + return datum[closeField]; + } + case CANDLESTICK_TOOLTIP_KEYS.SERIES_FIELD: { + const seriesField = (this.series as CandlestickSeries).getSeriesField(); + return datum[seriesField]; + } + } + + return null; + }; + shapeColorCallback = (datum: Datum) => { + return this.series.getMarkInName('candlestick').getAttribute('stroke' as any, datum) as any; + }; +} diff --git a/packages/vchart/src/chart/index.ts b/packages/vchart/src/chart/index.ts index f87027eb1a..4eca053d91 100644 --- a/packages/vchart/src/chart/index.ts +++ b/packages/vchart/src/chart/index.ts @@ -176,3 +176,5 @@ export type { IVennChartSpec, IMosaicChartSpec }; + +export { setDefaultCrosshairForCartesianChart } from './util'; From c375dd5570d166bf9a04cb7f98244e059117bb95 Mon Sep 17 00:00:00 2001 From: szxc Date: Sun, 3 Aug 2025 15:30:33 +0800 Subject: [PATCH 10/15] feat: enhance candlestick chart --- .../src/charts/candlestick/candlestick.ts | 10 ++- .../charts/candlestick/series/animation.ts | 70 +++++++++++-------- .../charts/candlestick/series/candlestick.ts | 12 +++- 3 files changed, 58 insertions(+), 34 deletions(-) diff --git a/packages/vchart-extension/src/charts/candlestick/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/candlestick.ts index a4b427b18f..0685141c08 100644 --- a/packages/vchart-extension/src/charts/candlestick/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/candlestick.ts @@ -7,13 +7,21 @@ import { registerMarkTooltipProcessor, registerDimensionTooltipProcessor, registerDimensionEvents, - registerDimensionHover + registerDimensionHover, + getCartesianDimensionInfo, + getDimensionInfoByValue, + getCartesianCrosshairRect } from '@visactor/vchart'; import { CANDLESTICK_CHART_TYPE, CANDLESTICK_SERIES_TYPE } from './series/constant'; export class CandlestickChart extends BaseChart { static readonly type: string = CANDLESTICK_CHART_TYPE; static readonly seriesType: string = CANDLESTICK_SERIES_TYPE; static readonly transformerConstructor = CandlestickChartSpecTransformer; // CandlestickChartSpecTransformer; + protected _setModelOption() { + this._modelOption.getDimensionInfo = getCartesianDimensionInfo; + this._modelOption.getDimensionInfoByValue = getDimensionInfoByValue; + this._modelOption.getRectByDimensionData = getCartesianCrosshairRect; + } } export const registerCandlestickChart = () => { diff --git a/packages/vchart-extension/src/charts/candlestick/series/animation.ts b/packages/vchart-extension/src/charts/candlestick/series/animation.ts index ac6fa2a862..95b6e38f18 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/animation.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/animation.ts @@ -25,21 +25,26 @@ const scaleIn = ( } const { x, y, open, high, low, close } = finalAttribute; const animateAttributes: any = { from: { x, y }, to: { x, y } }; - if (isValidNumber(open)) { - animateAttributes.from.open = center; - animateAttributes.to.open = open; - } - if (isValidNumber(high)) { - animateAttributes.from.high = center; - animateAttributes.to.high = high; - } - if (isValidNumber(low)) { - animateAttributes.from.low = center; - animateAttributes.to.low = low; - } - if (isValidNumber(close)) { - animateAttributes.from.close = center; - animateAttributes.to.close = close; + if (isValidNumber(open) && isValidNumber(close)) { + if (open > close) { + animateAttributes.from.open = low; + animateAttributes.to.open = open; + animateAttributes.from.close = low; + animateAttributes.to.close = close; + if (isValidNumber(high)) { + animateAttributes.from.high = low; + animateAttributes.to.high = high; + } + } else { + animateAttributes.from.open = high; + animateAttributes.to.open = open; + animateAttributes.from.close = high; + animateAttributes.to.close = close; + if (isValidNumber(low)) { + animateAttributes.from.low = high; + animateAttributes.to.low = low; + } + } } return animateAttributes; }; @@ -57,21 +62,26 @@ const scaleOut = ( const { x, y, open, high, low, close } = finalAttribute; const animateAttributes: any = { from: { x, y }, to: { x, y } }; - if (isValidNumber(open)) { - animateAttributes.to.open = center; - animateAttributes.from.open = open; - } - if (isValidNumber(high)) { - animateAttributes.to.high = center; - animateAttributes.from.high = high; - } - if (isValidNumber(low)) { - animateAttributes.to.low = center; - animateAttributes.from.low = low; - } - if (isValidNumber(close)) { - animateAttributes.to.close = center; - animateAttributes.from.close = close; + if (isValidNumber(open) && isValidNumber(close)) { + if (open > close) { + animateAttributes.from.open = open; + animateAttributes.to.open = low; + animateAttributes.from.close = close; + animateAttributes.to.close = low; + if (isValidNumber(high)) { + animateAttributes.from.high = high; + animateAttributes.to.high = low; + } + } else { + animateAttributes.from.open = open; + animateAttributes.to.open = high; + animateAttributes.from.close = close; + animateAttributes.to.close = high; + if (isValidNumber(low)) { + animateAttributes.from.low = low; + animateAttributes.to.low = high; + } + } } return animateAttributes; }; diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts index 444fc513aa..18767574b3 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -67,7 +67,10 @@ export class CandlestickSeries close) { From 21f53b456e1cb41696367514aec7ae656b47cc61 Mon Sep 17 00:00:00 2001 From: szxc Date: Tue, 5 Aug 2025 17:56:40 +0800 Subject: [PATCH 11/15] feat: draw stroke when zero height --- .../src/charts/candlestick/mark/candlestick.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts index 874a153711..2016025bf7 100644 --- a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts @@ -1,6 +1,12 @@ import { registerLine, registerRect } from '@visactor/vrender-kits'; import { GlyphMark, registerGlyphMark, IMarkRaw, IMarkStyle } from '@visactor/vchart'; -import { createLine, createRect, type IGlyph, type ILineGraphicAttribute } from '@visactor/vrender-core'; +import { + createLine, + createRect, + type IGlyph, + type ILineGraphicAttribute, + IRectGraphicAttribute +} from '@visactor/vrender-core'; import { Factory, Datum } from '@visactor/vchart'; import type { ICandlestickMarkSpec } from './interface'; @@ -45,7 +51,8 @@ export class CandlestickMark extends GlyphMark implements x: x - boxWidth / 2, x1: x + boxWidth / 2, y: Math.min(open, close), - y1: Math.max(open, close) + y1: Math.max(open, close), + drawStrokeWhenZeroWH: true }; return attributes; }; @@ -65,6 +72,6 @@ export const registerCandlestickMark = () => { registerLine(); registerRect(); Factory.registerGraphicComponent('line', (attrs: ILineGraphicAttribute) => createLine(attrs)); - Factory.registerGraphicComponent('rect', (attrs: ILineGraphicAttribute) => createRect(attrs)); + Factory.registerGraphicComponent('rect', (attrs: IRectGraphicAttribute) => createRect(attrs)); Factory.registerMark(CandlestickMark.type, CandlestickMark); }; From b025bf322d4db2b9d770eccbc05e7a315f03ae12 Mon Sep 17 00:00:00 2001 From: szxc Date: Tue, 12 Aug 2025 09:54:03 +0800 Subject: [PATCH 12/15] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0k=E7=BA=BF?= =?UTF-8?q?=E5=9B=BE=E7=A4=BA=E4=BE=8B=E5=92=8C=E4=B8=BB=E9=A2=98=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../zh/candlestick/candlestick-basic.md | 46 ++++++++++++++ .../charts/candlestick/mark/candlestick.ts | 1 + .../charts/candlestick/series/candlestick.ts | 61 ++++++++++++------- .../charts/candlestick/series/interface.ts | 30 ++++----- .../src/charts/candlestick/series/theme.ts | 27 ++++++++ 5 files changed, 126 insertions(+), 39 deletions(-) create mode 100644 docs/assets/examples/zh/candlestick/candlestick-basic.md create mode 100644 packages/vchart-extension/src/charts/candlestick/series/theme.ts diff --git a/docs/assets/examples/zh/candlestick/candlestick-basic.md b/docs/assets/examples/zh/candlestick/candlestick-basic.md new file mode 100644 index 0000000000..1ff7d4d2be --- /dev/null +++ b/docs/assets/examples/zh/candlestick/candlestick-basic.md @@ -0,0 +1,46 @@ +--- +category: examples +group: candlestick chart +title: K线图 +keywords: candlestick, k线, 股票, 金融 +link: '../guide/candlestick/introduction' +option: Candlestick#basic +--- + +# K 线图 + +K 线图基本用法 + +## 关键配置 + +- `type: 'candlestick'` +- `xField`, `openField`, `closeField`, `highField`, `lowField` +- `data` + +## 代码演示 + +```javascript livedemo template=vchart +const data = [ + { time: '2024-07-01', open: 100, close: 130, high: 135, low: 90 }, + { time: '2024-07-02', open: 130, close: 80, high: 140, low: 75 }, + { time: '2024-07-03', open: 80, close: 150, high: 155, low: 70 }, + { time: '2024-07-04', open: 150, close: 140, high: 160, low: 105 }, + { time: '2024-07-05', open: 140, close: 170, high: 180, low: 115 } +]; + +const spec = { + type: 'candlestick', + xField: 'time', + openField: 'open', + closeField: 'close', + highField: 'high', + lowField: 'low', + data: [{ values: data }] +}; + +const chart = new VChart(spec, { + dom: document.getElementById(CONTAINER_ID) +}); + +window['chart'] = chart; +``` diff --git a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts index 2016025bf7..f59416d73c 100644 --- a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts @@ -52,6 +52,7 @@ export class CandlestickMark extends GlyphMark implements x1: x + boxWidth / 2, y: Math.min(open, close), y1: Math.max(open, close), + // 开盘收盘相同时绘制水平线 drawStrokeWhenZeroWH: true }; return attributes; diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts index 18767574b3..0bfb665749 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -22,16 +22,15 @@ import type { ICandlestickSeriesSpec } from './interface'; import { registerCandlestickScaleAnimation } from './animation'; import { CANDLESTICK_SERIES_TYPE, CandlestickSeriesMark } from './constant'; import { CandlestickSeriesTooltipHelper } from './tooltip-helper'; +import { candlestick } from './theme'; const DEFAULT_STROKE_WIDTH = 2; export const DEFAULT_STROKE_COLOR = '#000'; -export const DEFAULT_RISE_COLOR = '#FF0000'; -export const DEFAULT_FALL_COLOR = '#00AA00'; - export class CandlestickSeries extends CartesianSeries { static readonly type: string = CANDLESTICK_SERIES_TYPE; type = CANDLESTICK_SERIES_TYPE; + static readonly builtInTheme = { candlestick }; static readonly mark = CandlestickSeriesMark; protected _openField: string; getOpenField(): string { @@ -51,41 +50,59 @@ export class CandlestickSeries string); - getBoxFillColor(): string | ((datum: Datum) => string) { - return this._boxFillColor; + protected _boxFill: string | ((datum: Datum) => string); + getBoxFill(): string | ((datum: Datum) => string) { + return this._boxFill; } protected _strokeColor: string; getStrokeColor(): string { return this._strokeColor; } - protected _riseColor: string; - getRiseColor(): string { - return this._riseColor; + protected _riseFill: string; + getRiseFill(): string { + return this._riseFill; + } + protected _riseStroke: string; + getRiseStroke(): string { + return this._riseStroke; + } + protected _fallFill: string; + getFallFill(): string { + return this._fallFill; + } + protected _fallStroke: string; + getFallStroke(): string { + return this._fallStroke; } - protected _fallColor: string; - getFallColor(): string { - return this._fallColor; + protected _dojiFill: string; + getDojiFill(): string { + return this._dojiFill; } - protected _dojiColor: string; - getDojiColor(): string { - return this._dojiColor; + protected _dojiStroke: string; + getDojiStroke(): string { + return this._dojiStroke; } setAttrFromSpec() { super.setAttrFromSpec(); const spec = this._spec; const CandlestickStyle: any = spec.candlestick?.style ?? {}; + const risingStyle: any = spec.rising?.style ?? {}; + const fallingStyle: any = spec.falling?.style ?? {}; + const dojiStyle: any = spec.doji?.style ?? {}; this._openField = spec.openField; this._highField = spec.highField; this._lowField = spec.lowField; this._closeField = spec.closeField; - this._riseColor = spec.candlestickColor?.rising ?? DEFAULT_RISE_COLOR; - this._fallColor = spec.candlestickColor?.falling ?? DEFAULT_FALL_COLOR; - this._dojiColor = spec.candlestickColor?.doji ?? DEFAULT_STROKE_COLOR; this._lineWidth = CandlestickStyle.lineWidth ?? DEFAULT_STROKE_WIDTH; this._boxWidth = CandlestickStyle.boxWidth; - this._boxFillColor = CandlestickStyle.boxFill; + this._boxFill = CandlestickStyle.boxFill; + this._riseFill = risingStyle.boxFill; + this._riseStroke = risingStyle.stroke; + this._fallFill = fallingStyle.boxFill; + this._fallStroke = fallingStyle.stroke; + this._dojiFill = dojiStyle.boxFill; + this._dojiStroke = dojiStyle.stroke; this._strokeColor = CandlestickStyle.strokeColor; } @@ -103,7 +120,7 @@ export class CandlestickSeries close) { - return this._fallColor; + return this._fallFill; } return this._strokeColor ?? DEFAULT_STROKE_COLOR; } diff --git a/packages/vchart-extension/src/charts/candlestick/series/interface.ts b/packages/vchart-extension/src/charts/candlestick/series/interface.ts index 334aa8f20e..b60a1bccc6 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/interface.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/interface.ts @@ -8,21 +8,6 @@ import type { } from '@visactor/vchart'; import type { ICandlestickMarkSpec } from '../mark/interface'; -export type candlestickColorConfig = { - /** - * 上涨蜡烛图颜色 - */ - rising: string; - /** - * 下跌蜡烛图颜色 - */ - falling: string; - /** - * 平盘蜡烛图颜色 - */ - doji?: string; -}; - export interface ICandlestickSeriesSpec extends Omit, IAnimationSpec { @@ -48,9 +33,17 @@ export interface ICandlestickSeriesSpec */ closeField?: string; /** - * 蜡烛图颜色配置 + * 上涨蜡烛图颜色 + */ + rising?: IMarkSpec; + /** + * 下跌蜡烛图颜色 + */ + falling?: IMarkSpec; + /** + * 平盘蜡烛图颜色 */ - candlestickColor?: candlestickColorConfig; + doji?: IMarkSpec; /** * 蜡烛图标记配置 */ @@ -59,4 +52,7 @@ export interface ICandlestickSeriesSpec export interface ICandlestickSeriesTheme extends ICartesianSeriesTheme { candlestick?: Partial>; + rising?: Partial>; + falling?: Partial>; + doji?: Partial>; } diff --git a/packages/vchart-extension/src/charts/candlestick/series/theme.ts b/packages/vchart-extension/src/charts/candlestick/series/theme.ts new file mode 100644 index 0000000000..10711da3b0 --- /dev/null +++ b/packages/vchart-extension/src/charts/candlestick/series/theme.ts @@ -0,0 +1,27 @@ +import type { ICandlestickSeriesTheme } from '../series/interface'; + +export const getCandlestickTheme = (is3d?: boolean): ICandlestickSeriesTheme => { + const res: ICandlestickSeriesTheme = { + rising: { + style: { + boxFill: '#FF0000', + stroke: '#FF0000' + } + }, + falling: { + style: { + boxFill: '#00AA00', + stroke: '#00AA00' + } + }, + doji: { + style: { + boxFill: '#0000FF', + stroke: '#0000FF' + } + } + }; + return res; +}; + +export const candlestick: ICandlestickSeriesTheme = getCandlestickTheme(); From c2154cccfe3a8654cc874c03d3ac6ff414278410 Mon Sep 17 00:00:00 2001 From: szxc Date: Thu, 14 Aug 2025 12:10:21 +0800 Subject: [PATCH 13/15] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=85k=E7=BA=BF?= =?UTF-8?q?=E5=9B=BEdemo,=E6=B7=BB=E5=8A=A0=E5=AE=BD=E5=BA=A6=E8=87=AA?= =?UTF-8?q?=E9=80=82=E5=BA=94=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/assets/examples/menu.json | 30 +++++ .../candlestick-basic.md | 36 +++-- .../candlestick-chart/candlestick-with-MA.md | 100 ++++++++++++++ .../candlestick-with-volume.md | 125 ++++++++++++++++++ .../candlestick/candlestick-transformer.ts | 4 +- .../charts/candlestick/mark/candlestick.ts | 3 +- .../charts/candlestick/series/animation.ts | 51 +------ .../charts/candlestick/series/candlestick.ts | 23 +++- packages/vchart-extension/src/index.ts | 1 + 9 files changed, 311 insertions(+), 62 deletions(-) rename docs/assets/examples/zh/{candlestick => candlestick-chart}/candlestick-basic.md (53%) create mode 100644 docs/assets/examples/zh/candlestick-chart/candlestick-with-MA.md create mode 100644 docs/assets/examples/zh/candlestick-chart/candlestick-with-volume.md diff --git a/docs/assets/examples/menu.json b/docs/assets/examples/menu.json index 1971269e40..56a82f9080 100644 --- a/docs/assets/examples/menu.json +++ b/docs/assets/examples/menu.json @@ -2001,6 +2001,36 @@ } ] }, + { + "path": "candlestick-chart", + "title": { + "zh": "k线图", + "en": "candlestick-chart" + }, + "children": [ + { + "path": "candlestick-basic", + "title": { + "zh": "基础K线图", + "en": "Basic Candlestick Chart" + } + }, + { + "path": "candlestick-with-volume", + "title": { + "zh": "K线图与成交量", + "en": "Candlestick Chart with Volume" + } + }, + { + "path": "candlestick-with-MA", + "title": { + "zh": "K线图与日均线", + "en": "Candlestick Chart with MA" + } + } + ] + }, { "path": "chart-3d", "title": { diff --git a/docs/assets/examples/zh/candlestick/candlestick-basic.md b/docs/assets/examples/zh/candlestick-chart/candlestick-basic.md similarity index 53% rename from docs/assets/examples/zh/candlestick/candlestick-basic.md rename to docs/assets/examples/zh/candlestick-chart/candlestick-basic.md index 1ff7d4d2be..1622fca5c4 100644 --- a/docs/assets/examples/zh/candlestick/candlestick-basic.md +++ b/docs/assets/examples/zh/candlestick-chart/candlestick-basic.md @@ -1,10 +1,10 @@ --- category: examples group: candlestick chart -title: K线图 -keywords: candlestick, k线, 股票, 金融 -link: '../guide/candlestick/introduction' -option: Candlestick#basic +title: 基础k线图 +keywords: candlestick +order: 19-0 +option: candlestickChart --- # K 线图 @@ -13,19 +13,22 @@ K 线图基本用法 ## 关键配置 -- `type: 'candlestick'` -- `xField`, `openField`, `closeField`, `highField`, `lowField` -- `data` - ## 代码演示 -```javascript livedemo template=vchart +```javascript livedemo +if (VChartExtension.registerCandlestickChart) { + VChartExtension.registerCandlestickChart(); +} + const data = [ { time: '2024-07-01', open: 100, close: 130, high: 135, low: 90 }, { time: '2024-07-02', open: 130, close: 80, high: 140, low: 75 }, { time: '2024-07-03', open: 80, close: 150, high: 155, low: 70 }, { time: '2024-07-04', open: 150, close: 140, high: 160, low: 105 }, - { time: '2024-07-05', open: 140, close: 170, high: 180, low: 115 } + { time: '2024-07-05', open: 140, close: 170, high: 180, low: 115 }, + { time: '2024-07-06', open: 170, close: 170, high: 175, low: 95 }, + { time: '2024-07-07', open: 170, close: 100, high: 175, low: 95 }, + { time: '2024-07-08', open: 100, close: 160, high: 210, low: 90 } ]; const spec = { @@ -35,12 +38,17 @@ const spec = { closeField: 'close', highField: 'high', lowField: 'low', - data: [{ values: data }] + data: [ + { + values: data + } + ] }; -const chart = new VChart(spec, { - dom: document.getElementById(CONTAINER_ID) +const vchart = new VChart(spec, { + dom: CONTAINER_ID }); +vchart.renderSync(); -window['chart'] = chart; +window['vchart'] = vchart; ``` diff --git a/docs/assets/examples/zh/candlestick-chart/candlestick-with-MA.md b/docs/assets/examples/zh/candlestick-chart/candlestick-with-MA.md new file mode 100644 index 0000000000..edb45f83e5 --- /dev/null +++ b/docs/assets/examples/zh/candlestick-chart/candlestick-with-MA.md @@ -0,0 +1,100 @@ +--- +category: examples +group: candlestick chart +title: k线图组合显示 +keywords: candlestick MA +order: 19-0 +option: candlestickChart +--- + +# K 线图与均线组合 + +K 线图基本用法 + +## 关键配置 + +- `type: 'candlestick'` +- `xField`, `openField`, `closeField`, `highField`, `lowField` +- `data` + +## 代码演示 + +```javascript livedemo +if (VChartExtension.registerCandlestickChart) { + VChartExtension.registerCandlestickChart(); +} + +const data = [ + { time: '2024-07-01', open: 100, close: 130, high: 135, low: 90 }, + { time: '2024-07-02', open: 130, close: 80, high: 140, low: 75 }, + { time: '2024-07-03', open: 80, close: 150, high: 155, low: 70 }, + { time: '2024-07-04', open: 150, close: 140, high: 160, low: 105 }, + { time: '2024-07-05', open: 140, close: 170, high: 180, low: 115 }, + { time: '2024-07-06', open: 170, close: 170, high: 175, low: 95 }, + { time: '2024-07-07', open: 170, close: 100, high: 175, low: 95 }, + { time: '2024-07-08', open: 100, close: 160, high: 210, low: 90 }, + { time: '2024-07-09', open: 160, close: 180, high: 200, low: 150 }, + { time: '2024-07-10', open: 180, close: 175, high: 190, low: 160 }, + { time: '2024-07-11', open: 175, close: 190, high: 195, low: 170 }, + { time: '2024-07-12', open: 190, close: 210, high: 220, low: 185 }, + { time: '2024-07-13', open: 210, close: 200, high: 215, low: 195 }, + { time: '2024-07-14', open: 200, close: 220, high: 225, low: 198 }, + { time: '2024-07-15', open: 220, close: 230, high: 240, low: 215 } +]; + +function calcMA(data, window) { + const result = []; + for (let i = 0; i < data.length; i++) { + if (i < window - 1) { + result.push({ time: data[i].time, ma: null }); + } else { + let sum = 0; + for (let j = 0; j < window; j++) { + sum += data[i - j].close; + } + result.push({ time: data[i].time, ma: sum / window }); + } + } + return result; +} + +const spec = { + type: 'common', + data: [ + { id: 'k', values: data }, + { id: 'ma5', values: calcMA(data, 5) } + ], + series: [ + { + id: 'candlestick', + type: 'candlestick', + dataIndex: 0, + xField: 'time', + openField: 'open', + closeField: 'close', + highField: 'high', + lowField: 'low' + }, + { + type: 'line', + id: 'line', + dataIndex: 1, + label: { visible: true }, + xField: 'time', + yField: 'ma', + stack: false + } + ], + axes: [ + { orient: 'left', seriesIndex: [1] }, + { orient: 'bottom', label: { visible: true }, type: 'band' } + ] +}; + +const vchart = new VChart(spec, { + dom: CONTAINER_ID +}); +vchart.renderSync(); + +window['vchart'] = vchart; +``` diff --git a/docs/assets/examples/zh/candlestick-chart/candlestick-with-volume.md b/docs/assets/examples/zh/candlestick-chart/candlestick-with-volume.md new file mode 100644 index 0000000000..1ca4c94204 --- /dev/null +++ b/docs/assets/examples/zh/candlestick-chart/candlestick-with-volume.md @@ -0,0 +1,125 @@ +--- +category: examples +group: candlestick chart +title: 基础k线图 +keywords: candlestick +order: 19-0 +option: candlestickChart +--- + +# K 线图 + +K 线图基本用法 + +## 关键配置 + +- `type: 'candlestick'` +- `xField`, `openField`, `closeField`, `highField`, `lowField` +- `data` + +## 代码演示 + +```javascript livedemo +if (VChartExtension.registerCandlestickChart) { + VChartExtension.registerCandlestickChart(); +} + +const data = [ + { time: '2024-07-01', open: 100, close: 130, high: 135, low: 90, volume: 5000 }, + { time: '2024-07-02', open: 130, close: 80, high: 140, low: 75, volume: 8000 }, + { time: '2024-07-03', open: 80, close: 150, high: 155, low: 70, volume: 6000 }, + { time: '2024-07-04', open: 150, close: 140, high: 160, low: 105, volume: 7000 }, + { time: '2024-07-05', open: 140, close: 170, high: 180, low: 115, volume: 9000 }, + { time: '2024-07-06', open: 170, close: 170, high: 175, low: 95, volume: 4000 }, + { time: '2024-07-07', open: 170, close: 100, high: 175, low: 95, volume: 10000 }, + { time: '2024-07-08', open: 100, close: 160, high: 210, low: 90, volume: 11000 }, + { time: '2024-07-09', open: 160, close: 180, high: 200, low: 150, volume: 9500 }, + { time: '2024-07-10', open: 180, close: 170, high: 185, low: 160, volume: 8700 }, + { time: '2024-07-11', open: 170, close: 200, high: 210, low: 165, volume: 12000 }, + { time: '2024-07-12', open: 200, close: 210, high: 220, low: 190, volume: 10500 }, + { time: '2024-07-13', open: 210, close: 190, high: 215, low: 180, volume: 9800 }, + { time: '2024-07-14', open: 190, close: 195, high: 200, low: 185, volume: 7600 }, + { time: '2024-07-15', open: 195, close: 220, high: 225, low: 190, volume: 13000 }, + { time: '2024-07-16', open: 220, close: 210, high: 230, low: 205, volume: 9000 } +]; + +const spec = { + type: 'common', + data: [{ data }], + layout: { + type: 'grid', + col: 2, + row: 3, + elements: [ + { modelId: 'region-k', col: 1, row: 0 }, + { modelId: 'region-volume', col: 1, row: 2 }, + { modelId: 'axis-x', col: 1, row: 1 }, + { modelId: 'axis-y-k', col: 0, row: 0 }, + { modelId: 'axis-y-volume', col: 0, row: 2 } + ] + }, + region: [ + { id: 'region-k', height: '70%' }, + { id: 'region-volume', height: '30%' } + ], + padding: { + top: 10 + }, + series: [ + { + regionId: 'region-k', + type: 'candlestick', + xField: 'time', + openField: 'open', + closeField: 'close', + highField: 'high', + lowField: 'low', + data: { values: data } + }, + { + regionId: 'region-volume', + type: 'bar', + xField: 'time', + yField: 'volume', + data: { values: data }, + bar: { + style: { + fill: datum => { + if (datum.open < datum.close) { + return '#FF0000'; // 涨 + } else if (datum.open > datum.close) { + return '#00AA00'; // 跌 + } else { + return '#999999'; // 平 + } + } + } + } + } + ], + axes: [ + { + id: 'axis-y-k', + regionId: 'region-k', + orient: 'left' + }, + { + id: 'axis-y-volume', + regionId: 'region-volume', + orient: 'left' + }, + { + id: 'axis-x', + regionId: 'region-volume', + orient: 'bottom' + } + ] +}; + +const vchart = new VChart(spec, { + dom: CONTAINER_ID +}); +vchart.renderSync(); + +window['vchart'] = vchart; +``` diff --git a/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts b/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts index 38dc997031..4459601609 100644 --- a/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts +++ b/packages/vchart-extension/src/charts/candlestick/candlestick-transformer.ts @@ -12,7 +12,9 @@ export class CandlestickChartSpecTransformer< 'highField', 'lowField', 'closeField', - 'candlestickColor' + 'rising', + 'falling', + 'doji' ]); seriesSpec.yField = dataFields; return seriesSpec; diff --git a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts index f59416d73c..e73199da8f 100644 --- a/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/mark/candlestick.ts @@ -61,8 +61,7 @@ export class CandlestickMark extends GlyphMark implements protected _getDefaultStyle() { const defaultStyle: IMarkStyle = { - ...super._getDefaultStyle(), - boxWidth: 40 + ...super._getDefaultStyle() }; return defaultStyle; } diff --git a/packages/vchart-extension/src/charts/candlestick/series/animation.ts b/packages/vchart-extension/src/charts/candlestick/series/animation.ts index 95b6e38f18..42c5050a65 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/animation.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/animation.ts @@ -1,4 +1,4 @@ -import type { EasingType, ILineAttribute, IRectAttribute } from '@visactor/vrender-core'; +import type { EasingType } from '@visactor/vrender-core'; import type { IGlyph } from '@visactor/vrender-core'; import type { IAnimationParameters } from '@visactor/vchart'; import { isValidNumber } from '@visactor/vutils'; @@ -14,15 +14,9 @@ type TypeAnimation = ( animationParameters: IAnimationParameters ) => { from?: { [channel: string]: any }; to?: { [channel: string]: any } }; -const scaleIn = ( - computeCenter: (graphic: IGlyph, options: ICandlestickScaleAnimationOptions) => number -): TypeAnimation => { - return (graphic: IGlyph, options: ICandlestickScaleAnimationOptions, animationParameters: IAnimationParameters) => { +const scaleIn = (): TypeAnimation => { + return (graphic: IGlyph) => { const finalAttribute = graphic.getFinalAttribute(); - const center = computeCenter(graphic, options); - if (!isValidNumber(center)) { - return {}; - } const { x, y, open, high, low, close } = finalAttribute; const animateAttributes: any = { from: { x, y }, to: { x, y } }; if (isValidNumber(open) && isValidNumber(close)) { @@ -50,15 +44,9 @@ const scaleIn = ( }; }; -const scaleOut = ( - computeCenter: (mark: IGlyph, options: ICandlestickScaleAnimationOptions) => number -): TypeAnimation => { - return (graphic: IGlyph, options: ICandlestickScaleAnimationOptions, animationParameters: IAnimationParameters) => { +const scaleOut = (): TypeAnimation => { + return (graphic: IGlyph) => { const finalAttribute = graphic.getFinalAttribute(); - const center = computeCenter(graphic, options); - if (!isValidNumber(center)) { - return {}; - } const { x, y, open, high, low, close } = finalAttribute; const animateAttributes: any = { from: { x, y }, to: { x, y } }; @@ -86,31 +74,6 @@ const scaleOut = ( return animateAttributes; }; }; -const getGlyphChildByName = (mark: IGlyph, name: string) => { - return mark.getSubGraphic().find(child => child.name === name); -}; - -const computeCandlestickCenter = (glyphMark: IGlyph, options: ICandlestickScaleAnimationOptions) => { - if (options && isValidNumber(options.center)) { - return options.center; - } - const lineAttr = getGlyphChildByName(glyphMark, 'line')?.attribute as ILineAttribute | undefined; - const boxAttr = getGlyphChildByName(glyphMark, 'box')?.attribute as IRectAttribute | undefined; - - if (boxAttr && isValidNumber(boxAttr.y1) && isValidNumber(boxAttr.height)) { - const y0 = boxAttr.y1 - boxAttr.height; - const y1 = boxAttr.y1; - return (y0 + y1) / 2; - } - - if (lineAttr?.points?.length === 2) { - const y0 = lineAttr.points[0].y; - const y1 = lineAttr.points[1].y; - return (y0 + y1) / 2; - } - - return NaN; -}; export class CandlestickScaleIn extends ACustomAnimate> { constructor(from: null, to: null, duration: number, easing: EasingType, params?: ICandlestickScaleAnimationOptions) { @@ -132,7 +95,7 @@ export class CandlestickScaleIn extends ACustomAnimate> { } computeAttribute() { - const attr = scaleIn(computeCandlestickCenter)(this.target as IGlyph, this.params, this.params.options); + const attr = scaleIn()(this.target as IGlyph, this.params, this.params.options); return attr; } @@ -163,7 +126,7 @@ export class CandlestickScaleOut extends ACustomAnimate> } computeAttribute() { - const attr = scaleOut(computeCandlestickCenter)(this.target as IGlyph, this.params, this.params.options); + const attr = scaleOut()(this.target as IGlyph, this.params, this.params.options); return attr; } diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts index 0bfb665749..3f8571299e 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -82,6 +82,7 @@ export class CandlestickSeries Date: Wed, 20 Aug 2025 10:23:27 +0800 Subject: [PATCH 14/15] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E8=9C=A1?= =?UTF-8?q?=E7=83=9B=E5=9B=BE=E6=A0=B7=E5=BC=8F=E5=90=88=E5=B9=B6=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BC=98=E5=8C=96=E6=A0=B7=E5=BC=8F=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/charts/candlestick/mark/interface.ts | 4 - .../charts/candlestick/series/candlestick.ts | 82 ++++++++----------- .../src/charts/candlestick/series/theme.ts | 11 ++- 3 files changed, 44 insertions(+), 53 deletions(-) diff --git a/packages/vchart-extension/src/charts/candlestick/mark/interface.ts b/packages/vchart-extension/src/charts/candlestick/mark/interface.ts index eb52c0ba10..a821c0a47e 100644 --- a/packages/vchart-extension/src/charts/candlestick/mark/interface.ts +++ b/packages/vchart-extension/src/charts/candlestick/mark/interface.ts @@ -1,10 +1,6 @@ import type { Datum, ICommonSpec } from '@visactor/vchart'; export interface ICandlestickMarkSpec extends ICommonSpec { - /** - * box描边宽度 - */ - lineWidth?: number; /** * box宽度 */ diff --git a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts index 3f8571299e..d815c954c4 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/candlestick.ts @@ -24,7 +24,6 @@ import { CANDLESTICK_SERIES_TYPE, CandlestickSeriesMark } from './constant'; import { CandlestickSeriesTooltipHelper } from './tooltip-helper'; import { candlestick } from './theme'; -const DEFAULT_STROKE_WIDTH = 2; export const DEFAULT_STROKE_COLOR = '#000'; export class CandlestickSeries extends CartesianSeries { static readonly type: string = CANDLESTICK_SERIES_TYPE; @@ -48,7 +47,6 @@ export class CandlestickSeries string); getBoxFill(): string | ((datum: Datum) => string) { @@ -58,53 +56,30 @@ export class CandlestickSeries { + const boxFill = this.mergeStyle(datum).boxFill; + return boxFill; + }, + stroke: (datum: Datum) => { + const strokeColor = this.mergeStyle(datum).stroke; + return strokeColor; + }, + lineWidth: (datum: Datum) => { + const lineWidth = this.mergeStyle(datum).lineWidth; + return lineWidth; + }, boxWidth: this._boxWidth ?? this._getMarkWidth.bind(this), x: this.dataToPositionX.bind(this) }; @@ -209,17 +194,22 @@ export class CandlestickSeries close) { - return this._fallFill; + return this._mergedStyles.falling; + } else { + return this._mergedStyles.doji; } - return this._strokeColor ?? DEFAULT_STROKE_COLOR; } private _getMarkWidth() { diff --git a/packages/vchart-extension/src/charts/candlestick/series/theme.ts b/packages/vchart-extension/src/charts/candlestick/series/theme.ts index 10711da3b0..4cdacbd367 100644 --- a/packages/vchart-extension/src/charts/candlestick/series/theme.ts +++ b/packages/vchart-extension/src/charts/candlestick/series/theme.ts @@ -1,6 +1,6 @@ import type { ICandlestickSeriesTheme } from '../series/interface'; -export const getCandlestickTheme = (is3d?: boolean): ICandlestickSeriesTheme => { +export const getCandlestickTheme = (): ICandlestickSeriesTheme => { const res: ICandlestickSeriesTheme = { rising: { style: { @@ -16,8 +16,13 @@ export const getCandlestickTheme = (is3d?: boolean): ICandlestickSeriesTheme => }, doji: { style: { - boxFill: '#0000FF', - stroke: '#0000FF' + boxFill: '#000000', + stroke: '#000000' + } + }, + candlestick: { + style: { + lineWidth: 1 } } }; From 658ad7a8535abb002e3d34c5e203f76a9215840c Mon Sep 17 00:00:00 2001 From: szxc Date: Wed, 20 Aug 2025 12:48:34 +0800 Subject: [PATCH 15/15] feat: enhance candlestick demo --- .../candlestick-chart/candlestick-with-MA.md | 14 +++++++++++--- .../candlestick-with-volume.md | 18 ++++++++---------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/assets/examples/zh/candlestick-chart/candlestick-with-MA.md b/docs/assets/examples/zh/candlestick-chart/candlestick-with-MA.md index edb45f83e5..5040812603 100644 --- a/docs/assets/examples/zh/candlestick-chart/candlestick-with-MA.md +++ b/docs/assets/examples/zh/candlestick-chart/candlestick-with-MA.md @@ -70,6 +70,7 @@ const spec = { type: 'candlestick', dataIndex: 0, xField: 'time', + yField: ['open', 'close', 'high', 'low'], openField: 'open', closeField: 'close', highField: 'high', @@ -79,14 +80,21 @@ const spec = { type: 'line', id: 'line', dataIndex: 1, - label: { visible: true }, xField: 'time', yField: 'ma', - stack: false + line: { + style: { + stroke: 'rgb(229, 193, 160)', + lineWidth: 2 + } + }, + point: { + visible: false + } } ], axes: [ - { orient: 'left', seriesIndex: [1] }, + { orient: 'left', seriesIndex: [0, 1] }, { orient: 'bottom', label: { visible: true }, type: 'band' } ] }; diff --git a/docs/assets/examples/zh/candlestick-chart/candlestick-with-volume.md b/docs/assets/examples/zh/candlestick-chart/candlestick-with-volume.md index 1ca4c94204..12c510e612 100644 --- a/docs/assets/examples/zh/candlestick-chart/candlestick-with-volume.md +++ b/docs/assets/examples/zh/candlestick-chart/candlestick-with-volume.md @@ -45,7 +45,7 @@ const data = [ const spec = { type: 'common', - data: [{ data }], + data: [{ values: data }], layout: { type: 'grid', col: 2, @@ -58,10 +58,7 @@ const spec = { { modelId: 'axis-y-volume', col: 0, row: 2 } ] }, - region: [ - { id: 'region-k', height: '70%' }, - { id: 'region-volume', height: '30%' } - ], + region: [{ id: 'region-k', height: 0.7 }, { id: 'region-volume' }], padding: { top: 10 }, @@ -70,6 +67,7 @@ const spec = { regionId: 'region-k', type: 'candlestick', xField: 'time', + yField: ['open', 'close', 'high', 'low'], openField: 'open', closeField: 'close', highField: 'high', @@ -84,13 +82,13 @@ const spec = { data: { values: data }, bar: { style: { - fill: datum => { + fill: (datum: Datum) => { if (datum.open < datum.close) { - return '#FF0000'; // 涨 + return '#FF0000'; } else if (datum.open > datum.close) { - return '#00AA00'; // 跌 + return '#00AA00'; } else { - return '#999999'; // 平 + return '#000000'; } } } @@ -110,7 +108,7 @@ const spec = { }, { id: 'axis-x', - regionId: 'region-volume', + regionId: ['region-volume', 'region-k'], orient: 'bottom' } ]