diff --git a/docs/.vitepress/config/share.js b/docs/.vitepress/config/share.js
index ad81c1061..c0241a5a9 100644
--- a/docs/.vitepress/config/share.js
+++ b/docs/.vitepress/config/share.js
@@ -68,7 +68,9 @@ export function getInstanceApiMenus (lang = '') {
{ text: 'setPaneOptions', link: `${prefix}/setPaneOptions` },
{ text: 'getPaneOptions', link: `${prefix}/getPaneOptions` },
{ text: 'setZoomEnabled', link: `${prefix}/setZoomEnabled` },
+ { text: 'setZoomAnchor', link: `${prefix}/setZoomAnchor` },
{ text: 'isZoomEnabled', link: `${prefix}/isZoomEnabled` },
+ { text: 'getZoomAnchor', link: `${prefix}/getZoomAnchor` },
{ text: 'setScrollEnabled', link: `${prefix}/setScrollEnabled` },
{ text: 'isScrollEnabled', link: `${prefix}/isScrollEnabled` },
{ text: 'scrollByDistance', link: `${prefix}/scrollByDistance` },
diff --git a/docs/@views/api/references/chart/init.md b/docs/@views/api/references/chart/init.md
index 6076fe24d..0b4eebcb7 100644
--- a/docs/@views/api/references/chart/init.md
+++ b/docs/@views/api/references/chart/init.md
@@ -61,6 +61,7 @@
threshold?: number
format?: (value: number | string) => string
}
+ zoomAnchor?: { main?: 'cursor_point' | 'last_bar', xAxis?: 'cursor_point' | 'last_bar' }
}
) => Chart
```
\ No newline at end of file
diff --git a/docs/@views/api/samples/getZoomAnchor/index.data.js b/docs/@views/api/samples/getZoomAnchor/index.data.js
new file mode 100644
index 000000000..031b0d66f
--- /dev/null
+++ b/docs/@views/api/samples/getZoomAnchor/index.data.js
@@ -0,0 +1,20 @@
+import fs from 'fs'
+
+export default {
+ watch: ['./index.js'],
+ load (watchedFiles) {
+ return watchedFiles.reduce((data, file) => {
+ const result = fs.readFileSync(file, 'utf-8')
+ let key
+ if (file.match('index.js')) {
+ key = 'js'
+ } else if (file.match('index.css')) {
+ key = 'css'
+ } else {
+ key = 'html'
+ }
+ data[key] = result
+ return data
+ }, {})
+ }
+}
diff --git a/docs/@views/api/samples/getZoomAnchor/index.js b/docs/@views/api/samples/getZoomAnchor/index.js
new file mode 100644
index 000000000..3dadb3f0f
--- /dev/null
+++ b/docs/@views/api/samples/getZoomAnchor/index.js
@@ -0,0 +1,19 @@
+import { init } from 'klinecharts'
+
+const chart = init('getZoomAnchor-chart')
+
+chart.setSymbol({ ticker: 'TestSymbol' })
+chart.setPeriod({ span: 1, type: 'day' })
+chart.setDataLoader({
+ getBars: ({
+ callback
+ }) => {
+ fetch('https://klinecharts.com/datas/kline.json')
+ .then(res => res.json())
+ .then(dataList => {
+ callback(dataList)
+ })
+ }
+})
+
+const anchor = chart.zoomAnchor()
diff --git a/docs/@views/api/samples/getZoomAnchor/index.vue b/docs/@views/api/samples/getZoomAnchor/index.vue
new file mode 100644
index 000000000..afea06cbb
--- /dev/null
+++ b/docs/@views/api/samples/getZoomAnchor/index.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/@views/api/samples/init-zoomAnchor/index.data.js b/docs/@views/api/samples/init-zoomAnchor/index.data.js
new file mode 100644
index 000000000..031b0d66f
--- /dev/null
+++ b/docs/@views/api/samples/init-zoomAnchor/index.data.js
@@ -0,0 +1,20 @@
+import fs from 'fs'
+
+export default {
+ watch: ['./index.js'],
+ load (watchedFiles) {
+ return watchedFiles.reduce((data, file) => {
+ const result = fs.readFileSync(file, 'utf-8')
+ let key
+ if (file.match('index.js')) {
+ key = 'js'
+ } else if (file.match('index.css')) {
+ key = 'css'
+ } else {
+ key = 'html'
+ }
+ data[key] = result
+ return data
+ }, {})
+ }
+}
diff --git a/docs/@views/api/samples/init-zoomAnchor/index.js b/docs/@views/api/samples/init-zoomAnchor/index.js
new file mode 100644
index 000000000..61ee43a03
--- /dev/null
+++ b/docs/@views/api/samples/init-zoomAnchor/index.js
@@ -0,0 +1,20 @@
+import { init } from 'klinecharts'
+
+const chart = init(
+ 'init-zoomAnchor-chart',
+ { zoomAnchor: { main: 'last_bar', xAxis: 'last_bar' } }
+)
+
+chart.setSymbol({ ticker: 'TestSymbol' })
+chart.setPeriod({ span: 1, type: 'day' })
+chart.setDataLoader({
+ getBars: ({
+ callback
+ }) => {
+ fetch('https://klinecharts.com/datas/kline.json')
+ .then(res => res.json())
+ .then(dataList => {
+ callback(dataList)
+ })
+ }
+})
diff --git a/docs/@views/api/samples/init-zoomAnchor/index.vue b/docs/@views/api/samples/init-zoomAnchor/index.vue
new file mode 100644
index 000000000..05c14b896
--- /dev/null
+++ b/docs/@views/api/samples/init-zoomAnchor/index.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/@views/api/samples/setZoomAnchor/index.data.js b/docs/@views/api/samples/setZoomAnchor/index.data.js
new file mode 100644
index 000000000..031b0d66f
--- /dev/null
+++ b/docs/@views/api/samples/setZoomAnchor/index.data.js
@@ -0,0 +1,20 @@
+import fs from 'fs'
+
+export default {
+ watch: ['./index.js'],
+ load (watchedFiles) {
+ return watchedFiles.reduce((data, file) => {
+ const result = fs.readFileSync(file, 'utf-8')
+ let key
+ if (file.match('index.js')) {
+ key = 'js'
+ } else if (file.match('index.css')) {
+ key = 'css'
+ } else {
+ key = 'html'
+ }
+ data[key] = result
+ return data
+ }, {})
+ }
+}
diff --git a/docs/@views/api/samples/setZoomAnchor/index.js b/docs/@views/api/samples/setZoomAnchor/index.js
new file mode 100644
index 000000000..c58ffbf6c
--- /dev/null
+++ b/docs/@views/api/samples/setZoomAnchor/index.js
@@ -0,0 +1,19 @@
+import { init } from 'klinecharts'
+
+const chart = init('setZoomAnchor-chart')
+
+chart.setZoomAnchor({ main: 'last_bar', xAxis: 'last_bar' })
+
+chart.setSymbol({ ticker: 'TestSymbol' })
+chart.setPeriod({ span: 1, type: 'day' })
+chart.setDataLoader({
+ getBars: ({
+ callback
+ }) => {
+ fetch('https://klinecharts.com/datas/kline.json')
+ .then(res => res.json())
+ .then(dataList => {
+ callback(dataList)
+ })
+ }
+})
diff --git a/docs/@views/api/samples/setZoomAnchor/index.vue b/docs/@views/api/samples/setZoomAnchor/index.vue
new file mode 100644
index 000000000..38bc49059
--- /dev/null
+++ b/docs/@views/api/samples/setZoomAnchor/index.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/api/chart/init.md b/docs/api/chart/init.md
index d6841897e..92e615e19 100644
--- a/docs/api/chart/init.md
+++ b/docs/api/chart/init.md
@@ -48,6 +48,7 @@ outline: deep
- `decimalFold` 小数 0 折叠配置。
- `threshold` 折叠阈值。
- `format` 自定义格式化方法。
+ - `zoomAnchor` 将缩放图表时的锚点位置设置为 `last_bar` 或 `cursor_point`
### 返回值 {#returns}
`init` 返回一个图表实例对象 `Chart`。
@@ -68,6 +69,7 @@ import InitThousandsSeparatorNone from '../../@views/api/samples/init-thousandsS
import InitThousandsSeparatorFormat from '../../@views/api/samples/init-thousandsSeparator-format/index.vue'
import InitDecimalFoldNone from '../../@views/api/samples/init-decimalFold-none/index.vue'
import InitDecimalFoldFormat from '../../@views/api/samples/init-decimalFold-format/index.vue'
+import InitZoomAnchor from '../../@views/api/samples/init-zoomAnchor/index.vue'
### 基本使用 {#basic}
@@ -112,3 +114,6 @@ import InitDecimalFoldFormat from '../../@views/api/samples/init-decimalFold-for
### 小数 0 折叠自定义显示 {#init-decimalFold-format}
+### 将缩放图表时的缩放锚点位置设置为“last_bar” {#init-zoomAnchor}
+
+
diff --git a/docs/api/instance/getZoomAnchor.md b/docs/api/instance/getZoomAnchor.md
new file mode 100644
index 000000000..58aef6a07
--- /dev/null
+++ b/docs/api/instance/getZoomAnchor.md
@@ -0,0 +1,23 @@
+---
+outline: deep
+---
+
+# getZoomAnchor()
+`getZoomAnchor` 主窗格和 x 轴窗格的缩放锚点。
+
+## 参考 {#reference}
+
+
+### 参数 {#parameters}
+`getZoomAnchor` 不接受任何参数。
+
+### 返回值 {#returns}
+`getZoomAnchor` 回报 `ZoomAnchor` 。
+
+## 用法 {#usage}
+
+
+### 基本用法 {#basic}
+
\ No newline at end of file
diff --git a/docs/api/instance/setZoomAnchor.md b/docs/api/instance/setZoomAnchor.md
new file mode 100644
index 000000000..7e6653568
--- /dev/null
+++ b/docs/api/instance/setZoomAnchor.md
@@ -0,0 +1,23 @@
+---
+outline: deep
+---
+
+# setZoomAnchor(anchor)
+`setZoomAnchor` 设置主窗格和 x 轴窗格的缩放锚点。
+
+## 参考 {#reference}
+
+
+### 参数 {#parameters}
+- `anchor: ZoomAnchor` 主窗格和 x 轴窗格的缩放锚点类型。
+
+### 返回值 {#returns}
+`setZoomAnchor` 返回 `void` 。
+
+## 用法 {#usage}
+
+
+### 基本用法 {#basic}
+
\ No newline at end of file
diff --git a/docs/en-US/api/chart/init.md b/docs/en-US/api/chart/init.md
index 034bbb330..7a3a0b680 100644
--- a/docs/en-US/api/chart/init.md
+++ b/docs/en-US/api/chart/init.md
@@ -48,6 +48,7 @@ When calling, you need to wait until the container `dom` is ready.
- `decimalFold` Decimal 0 folds the configuration.
- `threshold` Fold threshold.
- `format` Custom formatting method.
+ - `zoomAnchor` Set the anchor position when zooming the chart to `last_bar` or `cursor_point`
### Returns {#returns}
`init` returns an object `Chart`。
@@ -68,6 +69,7 @@ import InitThousandsSeparatorNone from '../../../@views/api/samples/init-thousan
import InitThousandsSeparatorFormat from '../../../@views/api/samples/init-thousandsSeparator-format/index.vue'
import InitDecimalFoldNone from '../../../@views/api/samples/init-decimalFold-none/index.vue'
import InitDecimalFoldFormat from '../../../@views/api/samples/init-decimalFold-format/index.vue'
+import InitZoomAnchor from '../../../@views/api/samples/init-zoomAnchor/index.vue'
### Basic usage {#basic}
@@ -112,3 +114,6 @@ import InitDecimalFoldFormat from '../../../@views/api/samples/init-decimalFold-
### Decimal 0 fold custom display {#init-decimalFold-format}
+### Set the zoom anchor postion when zooming the chart to 'last_bar' {#init-zoomAnchor}
+
+
diff --git a/docs/en-US/api/instance/getZoomAnchor.md b/docs/en-US/api/instance/getZoomAnchor.md
new file mode 100644
index 000000000..6318da081
--- /dev/null
+++ b/docs/en-US/api/instance/getZoomAnchor.md
@@ -0,0 +1,23 @@
+---
+outline: deep
+---
+
+# getZoomAnchor()
+`getZoomAnchor` zoom anchor point for main and xAxis panes.
+
+## Reference {#reference}
+
+
+### Parameters {#parameters}
+`getZoomAnchor` does not accept any parameters.
+
+### Returns {#returns}
+`getZoomAnchor` returns `ZoomAnchor` .
+
+## Usage {#usage}
+
+
+### Basic usage {#basic}
+
\ No newline at end of file
diff --git a/docs/en-US/api/instance/setZoomAnchor.md b/docs/en-US/api/instance/setZoomAnchor.md
new file mode 100644
index 000000000..604e5dcb1
--- /dev/null
+++ b/docs/en-US/api/instance/setZoomAnchor.md
@@ -0,0 +1,23 @@
+---
+outline: deep
+---
+
+# setZoomAnchor(anchor)
+`setZoomAnchor` set zoom anchor point for main and xAxis panes.
+
+## Reference {#reference}
+
+
+### Parameters {#parameters}
+- `anchor: ZoomAnchor` zoom anchor point type for main and xAxis pane.
+
+### Returns {#returns}
+`setZoomAnchor` returns `void` .
+
+## Usage {#usage}
+
+
+### Basic usage {#basic}
+
\ No newline at end of file
diff --git a/src/Chart.ts b/src/Chart.ts
index 43a89a4d1..05b30a60c 100644
--- a/src/Chart.ts
+++ b/src/Chart.ts
@@ -25,7 +25,7 @@ import type Crosshair from './common/Crosshair'
import type { ActionType, ActionCallback } from './common/Action'
import type { DataLoader } from './common/DataLoader'
import type VisibleRange from './common/VisibleRange'
-import type { Formatter, DecimalFold, LayoutChild, Options, ThousandsSeparator } from './Options'
+import type { Formatter, DecimalFold, LayoutChild, Options, ThousandsSeparator, ZoomAnchor } from './Options'
import Animation from './common/Animation'
import { createId } from './common/utils/id'
import { createDom } from './common/utils/dom'
@@ -969,6 +969,14 @@ export default class ChartImp implements Chart {
return this._chartStore.isZoomEnabled()
}
+ setZoomAnchor (anchor: ZoomAnchor): void {
+ this._chartStore.setZoomAnchor(anchor)
+ }
+
+ zoomAnchor (): ZoomAnchor {
+ return this._chartStore.zoomAnchor()
+ }
+
setScrollEnabled (enabled: boolean): void {
this._chartStore.setScrollEnabled(enabled)
}
diff --git a/src/Event.ts b/src/Event.ts
index 07ff451ad..1cb484d07 100644
--- a/src/Event.ts
+++ b/src/Event.ts
@@ -72,11 +72,11 @@ export default class Event implements EventHandler {
if (event.shiftKey) {
switch (event.code) {
case 'Equal': {
- this._chart.getChartStore().zoom(0.5)
+ this._chart.getChartStore().zoom(0.5, this._chart.getChartStore().zoomAnchor().main === 'last_bar' ? this.lastDataCoordinate() : undefined)
break
}
case 'Minus': {
- this._chart.getChartStore().zoom(-0.5)
+ this._chart.getChartStore().zoom(-0.5, this._chart.getChartStore().zoomAnchor().main === 'last_bar' ? this.lastDataCoordinate() : undefined)
break
}
case 'ArrowLeft': {
@@ -108,6 +108,11 @@ export default class Event implements EventHandler {
container.addEventListener('keydown', this._boundKeyBoardDownEvent)
}
+ private lastDataCoordinate (): Partial {
+ const store = this._chart.getChartStore()
+ return { x: store.dataIndexToCoordinate(store.getDataList().length - 1) }
+ }
+
pinchStartEvent (): boolean {
this._touchZoomed = true
this._pinchScale = 1
@@ -120,7 +125,7 @@ export default class Event implements EventHandler {
const event = this._makeWidgetEvent(e, widget)
const zoomScale = (scale - this._pinchScale) * 5
this._pinchScale = scale
- this._chart.getChartStore().zoom(zoomScale, { x: event.x, y: event.y })
+ this._chart.getChartStore().zoom(zoomScale, this._chart.getChartStore().zoomAnchor().main === 'last_bar' ? this.lastDataCoordinate() : { x: event.x, y: event.y })
return true
}
return false
@@ -138,7 +143,7 @@ export default class Event implements EventHandler {
const event = this._makeWidgetEvent(e, widget)
const name = widget?.getName()
if (name === WidgetNameConstants.MAIN) {
- this._chart.getChartStore().zoom(scale, { x: event.x, y: event.y })
+ this._chart.getChartStore().zoom(scale, this._chart.getChartStore().zoomAnchor().main === 'last_bar' ? this.lastDataCoordinate() : { x: event.x, y: event.y })
return true
}
return false
@@ -583,7 +588,7 @@ export default class Event implements EventHandler {
if (Number.isFinite(scale)) {
const zoomScale = (scale - this._xAxisScale) * 10
this._xAxisScale = scale
- this._chart.getChartStore().zoom(zoomScale, this._xAxisStartScaleCoordinate ?? undefined)
+ this._chart.getChartStore().zoom(zoomScale, this._chart.getChartStore().zoomAnchor().xAxis === 'last_bar' ? this.lastDataCoordinate() : (this._xAxisStartScaleCoordinate ?? undefined))
}
}
} else {
diff --git a/src/Options.ts b/src/Options.ts
index 413e1c4f9..a64712e17 100644
--- a/src/Options.ts
+++ b/src/Options.ts
@@ -85,6 +85,11 @@ export interface ThousandsSeparator {
format: (value: string | number) => string
}
+export interface ZoomAnchor {
+ main: 'cursor_point' | 'last_bar'
+ xAxis: 'cursor_point' | 'last_bar'
+}
+
export interface Options {
locale?: string
timezone?: string
@@ -93,4 +98,5 @@ export interface Options {
thousandsSeparator?: Partial
decimalFold?: Partial
layout?: LayoutChild[]
+ zoomAnchor?: Partial
}
diff --git a/src/Store.ts b/src/Store.ts
index fc5a4afae..bbd5d4a50 100644
--- a/src/Store.ts
+++ b/src/Store.ts
@@ -37,7 +37,7 @@ import { logWarn } from './common/utils/logger'
import { UpdateLevel } from './common/Updater'
import type { DataLoader, DataLoaderGetBarsParams, DataLoadMore, DataLoadType } from './common/DataLoader'
-import type { Options, Formatter, ThousandsSeparator, DecimalFold, FormatDateType, FormatDateParams, FormatBigNumber, FormatExtendText, FormatExtendTextParams } from './Options'
+import type { Options, Formatter, ThousandsSeparator, DecimalFold, FormatDateType, FormatDateParams, FormatBigNumber, FormatExtendText, FormatExtendTextParams, ZoomAnchor } from './Options'
import type { IndicatorOverride, IndicatorCreate, IndicatorFilter } from './component/Indicator'
import type IndicatorImp from './component/Indicator'
@@ -122,6 +122,8 @@ export interface Store {
removeOverlay: (filter?: OverlayFilter) => boolean
setZoomEnabled: (enabled: boolean) => void
isZoomEnabled: () => boolean
+ setZoomAnchor: (behavior: ZoomAnchor) => void
+ zoomAnchor: () => ZoomAnchor
setScrollEnabled: (enabled: boolean) => void
isScrollEnabled: () => boolean
resetData: () => void
@@ -222,6 +224,14 @@ export default class StoreImp implements Store {
*/
private _zoomEnabled = true
+ /**
+ * Zoom anchor point flag
+ */
+ private readonly _zoomAnchor: ZoomAnchor = {
+ main: 'cursor_point',
+ xAxis: 'cursor_point'
+ }
+
/**
* Scroll enabled flag
*/
@@ -359,7 +369,7 @@ export default class StoreImp implements Store {
this._chart = chart
this._calcOptimalBarSpace()
this._lastBarRightSideDiffBarCount = this._offsetRightDistance / this._barSpace
- const { styles, locale, timezone, formatter, thousandsSeparator, decimalFold } = options ?? {}
+ const { styles, locale, timezone, formatter, thousandsSeparator, decimalFold, zoomAnchor } = options ?? {}
if (isValid(styles)) {
this.setStyles(styles)
}
@@ -376,6 +386,9 @@ export default class StoreImp implements Store {
if (isValid(decimalFold)) {
this.setDecimalFold(decimalFold)
}
+ if (isValid(zoomAnchor)) {
+ this.setZoomAnchor(zoomAnchor)
+ }
}
setStyles (value: string | DeepPartial): void {
@@ -1035,6 +1048,19 @@ export default class StoreImp implements Store {
return this._zoomEnabled
}
+ setZoomAnchor (anchor: Partial): void {
+ if (isValid(anchor.main) && isString(anchor.main)) {
+ this._zoomAnchor.main = anchor.main
+ }
+ if (isValid(anchor.xAxis) && isString(anchor.xAxis)) {
+ this._zoomAnchor.xAxis = anchor.xAxis
+ }
+ }
+
+ zoomAnchor (): ZoomAnchor {
+ return { ...this._zoomAnchor }
+ }
+
setScrollEnabled (enabled: boolean): void {
this._scrollEnabled = enabled
}
diff --git a/src/index.ts b/src/index.ts
index d89833c04..672f722c2 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -48,7 +48,7 @@ import type { ActionType } from './common/Action'
import type { IndicatorSeries } from './component/Indicator'
import type { OverlayMode } from './component/Overlay'
-import type { FormatDateType, Options } from './Options'
+import type { FormatDateType, Options, ZoomAnchor } from './Options'
import ChartImp, { type Chart, type DomPosition } from './Chart'
import { checkCoordinateOnArc } from './extension/figure/arc'
@@ -174,6 +174,6 @@ export {
registerXAxis, registerYAxis,
utils,
type LineType, type PolygonType, type TooltipShowRule, type TooltipShowType, type FeatureType, type TooltipFeaturePosition, type CandleTooltipRectPosition,
- type CandleType, type FormatDateType,
+ type CandleType, type FormatDateType, type ZoomAnchor,
type DomPosition, type ActionType, type IndicatorSeries, type OverlayMode
}