@baron1996/klinecharts-adapter 0.9.21 → 0.9.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ import type { AxisCreateTicksParams, AxisTick } from 'klinecharts';
2
+ /** Positive candle prices: fixed log lattice, shared by ticks and grid lines. */
3
+ export declare function createLogPriceTicks({ range, bounding, defaultTicks }: AxisCreateTicksParams, reverse: boolean, presentation?: {
4
+ textHeight?: number;
5
+ formatText?: (text: string) => string;
6
+ }): AxisTick[];
7
+ //# sourceMappingURL=log-price-ticks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-price-ticks.d.ts","sourceRoot":"","sources":["../../src/conversion/log-price-ticks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAwBnE,iFAAiF;AACjF,wBAAgB,mBAAmB,CAClC,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,qBAAqB,EACxD,OAAO,EAAE,OAAO,EAChB,YAAY,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CAAO,GAC/E,QAAQ,EAAE,CA2BZ"}
@@ -0,0 +1,52 @@
1
+ const MIN_TICK_SPACING = 48;
2
+ /** Choose density in log space, independent of the translated price bounds. */
3
+ function logStep(span, height, textHeight) {
4
+ const target = span * Math.max(MIN_TICK_SPACING, textHeight * 3) / height;
5
+ const magnitude = 10 ** Math.floor(Math.log10(target));
6
+ const normalized = target / magnitude;
7
+ // Tolerate floating-point noise at a density boundary across repeated pans.
8
+ const factor = [1, 2, 2.5, 5, 10].find((value) => value >= normalized * (1 - 1e-10));
9
+ return Number((factor * magnitude).toPrecision(12));
10
+ }
11
+ function priceText(value, step) {
12
+ // Retain the usual two decimals; tiny prices/close ticks need enough digits
13
+ // to avoid adjacent labels becoming identical after rounding.
14
+ const difference = value * Math.expm1(step * Math.LN10);
15
+ const precision = Math.max(2, 1 - Math.floor(Math.log10(difference)));
16
+ return precision > 12 || value >= 1e12
17
+ ? value.toExponential(6)
18
+ : value.toFixed(precision);
19
+ }
20
+ /** Positive candle prices: fixed log lattice, shared by ticks and grid lines. */
21
+ export function createLogPriceTicks({ range, bounding, defaultTicks }, reverse, presentation = {}) {
22
+ const { realFrom, realTo, realRange } = range;
23
+ const { height } = bounding;
24
+ if (!(range.from > 0 && range.to > range.from && realRange > 0 && height > 0)
25
+ || ![realFrom, realTo, realRange, height].every(Number.isFinite)) {
26
+ return defaultTicks;
27
+ }
28
+ const textHeight = presentation.textHeight ?? 12;
29
+ const step = logStep(realRange, height, textHeight);
30
+ if (!(step > 0 && Number.isFinite(step)))
31
+ return defaultTicks;
32
+ const first = Math.ceil(realFrom / step);
33
+ const last = Math.floor(realTo / step);
34
+ if (!Number.isSafeInteger(first) || !Number.isSafeInteger(last))
35
+ return defaultTicks;
36
+ const ticks = [];
37
+ // Anchor to log10(price)=0, not the viewport edge. Compute each tick from
38
+ // its integer index so newly exposed ticks extend the same price sequence.
39
+ for (let index = first; index <= last && ticks.length < 1000; index++) {
40
+ const logValue = Number((index * step).toPrecision(14));
41
+ const value = 10 ** logValue;
42
+ const fraction = (logValue - realFrom) / realRange;
43
+ const coord = Math.round(height * (reverse ? fraction : 1 - fraction));
44
+ if (coord <= textHeight || coord >= height - textHeight)
45
+ continue;
46
+ if (!(value > 0 && Number.isFinite(value)))
47
+ continue;
48
+ const text = priceText(value, step);
49
+ ticks.push({ value, coord, text: presentation.formatText?.(text) ?? text });
50
+ }
51
+ return ticks;
52
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"panes.d.ts","sourceRoot":"","sources":["../../src/conversion/panes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAErF,OAAO,KAAK,EAAE,KAAK,EAAS,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAS/C,iDAAiD;AACjD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjE;AA2BD,qDAAqD;AACrD,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,aAAa,UAAQ,GACnB,IAAI,CAIN;AAED,wCAAwC;AACxC,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CA4EpF"}
1
+ {"version":3,"file":"panes.d.ts","sourceRoot":"","sources":["../../src/conversion/panes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAErF,OAAO,KAAK,EAAyB,KAAK,EAAS,MAAM,aAAa,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAU/C,iDAAiD;AACjD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjE;AAmCD,qDAAqD;AACrD,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,aAAa,UAAQ,GACnB,IAAI,CAIN;AAED,wCAAwC;AACxC,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CA6EpF"}
@@ -1,16 +1,24 @@
1
1
  import { SceneError } from '@baron1996/kline-scene-schema';
2
2
  import { requireMappedId } from './id-map.js';
3
3
  import { createPaneIndicators } from './indicators.js';
4
+ import { createLogPriceTicks } from './log-price-ticks.js';
4
5
  /** 蜡烛主价格轴默认只格式化展示文本,不改变 Scene 与 Drawing 数值精度。 */
5
6
  export function formatDefaultPriceAxisValue(value) {
6
7
  const rounded = Number(value.toFixed(2));
7
8
  return (Object.is(rounded, -0) ? 0 : rounded).toFixed(2);
8
9
  }
9
- function axisOverride(axis, enginePaneId, engineAxisId, formatAsPrice) {
10
+ function axisOverride(chart, axis, enginePaneId, engineAxisId, formatAsPrice) {
10
11
  return {
11
12
  id: engineAxisId,
12
13
  paneId: enginePaneId,
13
14
  name: axis.scale === 'logarithmic' ? 'logarithm' : 'normal',
15
+ // Explicit passthrough also clears our callback when switching to linear.
16
+ createTicks: (params) => formatAsPrice && axis.scale === 'logarithmic'
17
+ ? createLogPriceTicks(params, axis.reverse, {
18
+ textHeight: chart.getStyles().yAxis.tickText.size,
19
+ formatText: (text) => chart.getDecimalFold().format(chart.getThousandsSeparator().format(text)),
20
+ })
21
+ : params.defaultTicks,
14
22
  reverse: axis.reverse,
15
23
  inside: axis.inside,
16
24
  position: axis.position,
@@ -29,7 +37,7 @@ function axisOverride(axis, enginePaneId, engineAxisId, formatAsPrice) {
29
37
  export function overrideSceneYAxis(chart, idMap, axis, paneId, path, formatAsPrice = false) {
30
38
  const enginePaneId = requireMappedId(idMap.paneToEngine, paneId, `${path}/paneId`, 'Pane');
31
39
  const engineAxisId = requireMappedId(idMap.yAxisToEngine, axis.id, `${path}/id`, 'Y-axis');
32
- chart.overrideYAxis(axisOverride(axis, enginePaneId, engineAxisId, formatAsPrice));
40
+ chart.overrideYAxis(axisOverride(chart, axis, enginePaneId, engineAxisId, formatAsPrice));
33
41
  }
34
42
  /** 按 Scene 顺序创建 Pane、Y 轴和指标,并核对每个映射。 */
35
43
  export function applyPanes(scene, chart, idMap) {
@@ -56,7 +64,7 @@ export function applyPanes(scene, chart, idMap) {
56
64
  continue;
57
65
  }
58
66
  const engineAxisId = requireMappedId(idMap.yAxisToEngine, axis.id, `/panes/${paneIndex}/yAxes/${axisIndex}/id`, 'Y-axis');
59
- const override = axisOverride(axis, enginePaneId, engineAxisId, pane.kind === 'candle' && axis.role === 'primary');
67
+ const override = axisOverride(chart, axis, enginePaneId, engineAxisId, pane.kind === 'candle' && axis.role === 'primary');
60
68
  const axes = chart.getYAxes({ paneId: enginePaneId });
61
69
  if (axes.some((candidate) => candidate.id === engineAxisId)) {
62
70
  chart.overrideYAxis(override);
@@ -0,0 +1,16 @@
1
+ import type { Chart, YAxis, AxisRange } from 'klinecharts';
2
+ interface PanAxis extends YAxis {
3
+ getRange(): AxisRange;
4
+ setRange(range: AxisRange): void;
5
+ }
6
+ /** Translate in the coordinate system used by convertToPixel, not in raw prices. */
7
+ export declare function translatePanRange(axis: Pick<PanAxis, 'name' | 'reverse' | 'realValueToValue' | 'realValueToDisplayValue'>, start: AxisRange, deltaY: number, height: number): AxisRange | null;
8
+ /**
9
+ * KLineCharts 10.0.0 has no public pan-range hook. Like the click arbitration
10
+ * bridge, this instance-local compatibility shim is guarded by engine identity
11
+ * and fails explicitly if the private event boundary changes.
12
+ * Axis zoom, drawings and pointer arbitration remain owned by the engine.
13
+ */
14
+ export declare function installScalePreservingPan(chart: Chart): void;
15
+ export {};
16
+ //# sourceMappingURL=engine-pan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-pan.d.ts","sourceRoot":"","sources":["../src/engine-pan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI3D,UAAU,OAAQ,SAAQ,KAAK;IAC9B,QAAQ,IAAI,SAAS,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;CACjC;AAkBD,oFAAoF;AACpF,wBAAgB,iBAAiB,CAChC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,kBAAkB,GAAG,yBAAyB,CAAC,EACxF,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACZ,SAAS,GAAG,IAAI,CAqBlB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAgD5D"}
@@ -0,0 +1,83 @@
1
+ import { SceneError } from '@baron1996/kline-scene-schema';
2
+ import { KLINECHARTS_ENGINE_VERSION } from './version.js';
3
+ /** Translate in the coordinate system used by convertToPixel, not in raw prices. */
4
+ export function translatePanRange(axis, start, deltaY, height) {
5
+ if (!(height > 0) || !(start.realRange > 0))
6
+ return null;
7
+ const shift = (axis.reverse ? -deltaY : deltaY) / height * start.realRange;
8
+ const realFrom = start.realFrom + shift;
9
+ const realTo = start.realTo + shift;
10
+ // KLineCharts 10's signed logarithm inverse treats negative log coordinates
11
+ // as negative prices. Positive prices below 1 still need the positive inverse.
12
+ const positiveLog = axis.name === 'logarithm' && start.from > 0 && start.to > 0;
13
+ const from = positiveLog ? 10 ** realFrom : axis.realValueToValue(realFrom, { range: start });
14
+ const to = positiveLog ? 10 ** realTo : axis.realValueToValue(realTo, { range: start });
15
+ const displayFrom = positiveLog ? from : axis.realValueToDisplayValue(realFrom, { range: start });
16
+ const displayTo = positiveLog ? to : axis.realValueToDisplayValue(realTo, { range: start });
17
+ const result = {
18
+ from, to, range: to - from,
19
+ realFrom, realTo, realRange: start.realRange,
20
+ displayFrom, displayTo, displayRange: displayTo - displayFrom,
21
+ };
22
+ // Never commit an overflowed/collapsed range after an extreme drag.
23
+ return Object.values(result).every(Number.isFinite) && to > from && realTo > realFrom
24
+ ? result
25
+ : null;
26
+ }
27
+ /**
28
+ * KLineCharts 10.0.0 has no public pan-range hook. Like the click arbitration
29
+ * bridge, this instance-local compatibility shim is guarded by engine identity
30
+ * and fails explicitly if the private event boundary changes.
31
+ * Axis zoom, drawings and pointer arbitration remain owned by the engine.
32
+ */
33
+ export function installScalePreservingPan(chart) {
34
+ const internal = chart;
35
+ const events = internal._chartEvent;
36
+ if (events === undefined ||
37
+ typeof events._processMainScrollingEvent !== 'function' ||
38
+ !('_startScrollCoordinate' in events) ||
39
+ typeof internal.getChartStore !== 'function') {
40
+ throw new SceneError('RUNTIME_INIT_FAILED', '/runtime', `KLineCharts ${KLINECHARTS_ENGINE_VERSION} pan compatibility hook is unavailable.`);
41
+ }
42
+ const store = internal.getChartStore();
43
+ let gesture = null;
44
+ let ranges = new Map();
45
+ events._processMainScrollingEvent = (widget, event) => {
46
+ const start = events._startScrollCoordinate;
47
+ if (start === null || !chart.isScrollEnabled())
48
+ return;
49
+ const deltaX = event.x - start.x;
50
+ const deltaY = event.y - start.y;
51
+ if (gesture !== start) {
52
+ // A click (or an overlay consuming the gesture) must not disable auto-fit.
53
+ if (deltaX === 0 && deltaY === 0)
54
+ return;
55
+ gesture = start;
56
+ ranges = new Map();
57
+ // All panes share X. Freeze their enabled Y axes before scrolling can
58
+ // recalculate visible extrema, including a gesture starting in auto-fit.
59
+ for (const item of chart.getYAxes({})) {
60
+ const axis = item;
61
+ if (!axis.scrollZoomEnabled)
62
+ continue;
63
+ const range = { ...axis.getRange() };
64
+ if (!Object.values(range).every(Number.isFinite) || !(range.realRange > 0))
65
+ continue;
66
+ ranges.set(axis, range);
67
+ axis.setRange(range);
68
+ }
69
+ }
70
+ event.preventDefault?.();
71
+ for (const axis of widget.getPane().getYAxisComponents()) {
72
+ const range = ranges.get(axis);
73
+ if (range === undefined || !axis.scrollZoomEnabled)
74
+ continue;
75
+ const translated = translatePanRange(axis, range, deltaY, widget.getBounding().height);
76
+ if (translated !== null)
77
+ axis.setRange(translated);
78
+ }
79
+ // This preserves the engine's bounds, historical loading and scroll events.
80
+ // Keep the ranges manual on release; double-clicking Y restores auto-fit.
81
+ store.scroll(deltaX);
82
+ };
83
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAWzC,MAAM,MAAM,iBAAiB,GAAG,cAAc,aAAa,CAAC,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,4CAA4C;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAC3C,kCAAkC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACpE,gCAAgC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACrE;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAqFD,qCAAqC;AACrC,wBAAsB,YAAY,CACjC,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,UAAU,EACjB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,OAAO,CAAC,YAAY,CAAC,CAyCvB"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAYzC,MAAM,MAAM,iBAAiB,GAAG,cAAc,aAAa,CAAC,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,4CAA4C;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAC3C,kCAAkC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACpE,gCAAgC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACrE;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAqFD,qCAAqC;AACrC,wBAAsB,YAAY,CACjC,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,UAAU,EACjB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,OAAO,CAAC,YAAY,CAAC,CA0CvB"}
package/dist/engine.js CHANGED
@@ -2,6 +2,7 @@ import { SceneError } from '@baron1996/kline-scene-schema';
2
2
  import { toKLineChartsOptions } from './conversion/chart-options.js';
3
3
  import { createStaticDataLoader } from './static-data-loader.js';
4
4
  import { engineDataForScene } from './gap-aware-series.js';
5
+ import { installScalePreservingPan } from './engine-pan.js';
5
6
  import { KLINECHARTS_ENGINE_VERSION, KLINECHARTS_RUNTIME_VERSION, SUPPORTED_KLINECHARTS_RUNTIME_VERSIONS, } from './version.js';
6
7
  function resolveMouseInteractionDispatch(chart) {
7
8
  const chartEvent = chart._chartEvent;
@@ -56,6 +57,7 @@ export async function createEngine(container, scene, options) {
56
57
  throw new SceneError('RUNTIME_INIT_FAILED', '/', 'KLineCharts did not create an interactive chart root.');
57
58
  }
58
59
  engineRoot.style.touchAction = 'none';
60
+ installScalePreservingPan(chart);
59
61
  chart.setSymbol({
60
62
  ticker: scene.symbol.ticker,
61
63
  pricePrecision: scene.symbol.pricePrecision,
package/dist/version.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Adapter 包版本。 */
2
- export declare const ADAPTER_PACKAGE_VERSION: "0.9.21";
2
+ export declare const ADAPTER_PACKAGE_VERSION: "0.9.23";
3
3
  /** 唯一允许加载的 KLineCharts 引擎版本。 */
4
4
  export declare const KLINECHARTS_ENGINE_VERSION: "10.0.0";
5
5
  /** 与 ChartScene 绑定的 Runtime 协议版本。 */
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Adapter 包版本。 */
2
- export const ADAPTER_PACKAGE_VERSION = '0.9.21';
2
+ export const ADAPTER_PACKAGE_VERSION = '0.9.23';
3
3
  /** 唯一允许加载的 KLineCharts 引擎版本。 */
4
4
  export const KLINECHARTS_ENGINE_VERSION = '10.0.0';
5
5
  /** 与 ChartScene 绑定的 Runtime 协议版本。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baron1996/klinecharts-adapter",
3
- "version": "0.9.21",
3
+ "version": "0.9.23",
4
4
  "description": "Controlled ChartScene adapter for KLineCharts.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -38,7 +38,7 @@
38
38
  "typecheck": "tsc -p tsconfig.json --noEmit"
39
39
  },
40
40
  "dependencies": {
41
- "@baron1996/kline-scene-schema": "0.9.21",
41
+ "@baron1996/kline-scene-schema": "0.9.23",
42
42
  "klinecharts": "10.0.0"
43
43
  }
44
44
  }