emberwick 0.3.0 → 0.4.0
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.
- package/README.md +84 -0
- package/index.d.ts +90 -0
- package/index.js +325 -13
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/umd/emberwick.umd.js +1 -1
- package/umd/emberwick.umd.js.map +1 -1
package/README.md
CHANGED
|
@@ -225,6 +225,10 @@ const chart = createChart(el, {
|
|
|
225
225
|
| `setAnimate(bool)` | Toggle live-candle easing |
|
|
226
226
|
| `setMagnet(bool)` | Toggle crosshair OHLC snapping |
|
|
227
227
|
| `snapToRealtime()` | Jump back to the newest bar and re-enable autoscale |
|
|
228
|
+
| `startReplay(options?)` | Begin bar-by-bar playback. Returns the `Replay`, or `null` if there is nothing to replay |
|
|
229
|
+
| `stopReplay()` | Leave replay and reveal the whole dataset again |
|
|
230
|
+
| `replayState()` | Current playback state. `{ active: false, ... }` when not replaying |
|
|
231
|
+
| `chart.replay` | Getter — the active `Replay` controller, or `null` |
|
|
228
232
|
| `toImage()` | PNG data URL of the composited layers |
|
|
229
233
|
| `destroy()` | Remove listeners, stop the loop, drop canvases |
|
|
230
234
|
| `chart.fps` | Getter — measured frames per second |
|
|
@@ -248,6 +252,7 @@ off() // unsubscribe
|
|
|
248
252
|
| `'markerHover'` | The marker under the pointer, or `null` when none is |
|
|
249
253
|
| `'markerClick'` | The clicked marker. Only fires on a hit, never with `null` |
|
|
250
254
|
| `'visibleRange'` | `{ from, to, fromTime, toTime, barCount, spacing, settled }` |
|
|
255
|
+
| `'replay'` | `{ active, playing, index, length, progress, speed, time, bar, atEnd }` |
|
|
251
256
|
|
|
252
257
|
A drag that happens to end on top of a marker does not fire `'markerClick'` —
|
|
253
258
|
panning and clicking stay distinct.
|
|
@@ -289,6 +294,84 @@ payload on demand if you would rather poll than subscribe.
|
|
|
289
294
|
|
|
290
295
|
---
|
|
291
296
|
|
|
297
|
+
## Replay
|
|
298
|
+
|
|
299
|
+
Play a fixed dataset back bar by bar — backtesting playback, a market-open
|
|
300
|
+
recap, a training drill.
|
|
301
|
+
|
|
302
|
+
```js
|
|
303
|
+
chart.setData(bars)
|
|
304
|
+
|
|
305
|
+
const replay = chart.startReplay({ from: 200, speed: 4 })
|
|
306
|
+
replay.play()
|
|
307
|
+
|
|
308
|
+
chart.subscribe('replay', (s) => {
|
|
309
|
+
scrubber.value = s.index
|
|
310
|
+
clock.textContent = new Date(s.time).toLocaleTimeString()
|
|
311
|
+
if (s.atEnd) playBtn.textContent = 'Restart'
|
|
312
|
+
})
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The chart is never put into a special mode. The controller keeps the dataset
|
|
316
|
+
aside and hands the chart only the **revealed prefix**, so scales, crosshair,
|
|
317
|
+
annotations and `'visibleRange'` behave exactly as they do on live data that
|
|
318
|
+
happens to end at the cursor.
|
|
319
|
+
|
|
320
|
+
Revealing the next bar goes through the same path a feed tick takes, so the
|
|
321
|
+
candle grows in and the axis glides. Scrubbing swaps the prefix and jumps —
|
|
322
|
+
easing a scrub would read as lag, the same rule the pan gesture follows.
|
|
323
|
+
|
|
324
|
+
### `chart.startReplay(options?)`
|
|
325
|
+
|
|
326
|
+
| Option | Default | Notes |
|
|
327
|
+
|---|---|---|
|
|
328
|
+
| `bars` | the chart's current bars | The dataset to replay. Never mutated |
|
|
329
|
+
| `from` | midpoint | Starting cursor index |
|
|
330
|
+
| `speed` | `1` | Multiplier, clamped to `0.25`–`500` |
|
|
331
|
+
| `baseInterval` | `1000` | Real ms one bar takes at 1× |
|
|
332
|
+
| `loop` | `false` | Restart at the end instead of stopping |
|
|
333
|
+
| `follow` | `true` | Re-anchor the right edge on the cursor when scrubbing |
|
|
334
|
+
|
|
335
|
+
Returns the `Replay`, or `null` when there are fewer than two bars. While a
|
|
336
|
+
replay is active an attached feed is ignored, so live ticks cannot fight the
|
|
337
|
+
cursor; `stopReplay()` restores the full dataset and resumes normal service.
|
|
338
|
+
|
|
339
|
+
### Transport
|
|
340
|
+
|
|
341
|
+
Every method returns the controller, so calls chain.
|
|
342
|
+
|
|
343
|
+
| Method | Description |
|
|
344
|
+
|---|---|
|
|
345
|
+
| `play()` / `pause()` / `toggle()` | Pressing play at the end restarts from the beginning |
|
|
346
|
+
| `seek(index)` | Move the cursor. Out-of-range values clamp |
|
|
347
|
+
| `step(n = 1)` | Relative move; `step(-1)` goes back a bar |
|
|
348
|
+
| `toStart()` / `toEnd()` | Jump to either end |
|
|
349
|
+
| `setSpeed(x)` | Clamped to `0.25`–`500`. Never bursts bars on a rate change |
|
|
350
|
+
| `setLoop(bool)` | Toggle looping |
|
|
351
|
+
|
|
352
|
+
Readable state: `index`, `length`, `progress` (0–1), `speed`, `time`, `bar`,
|
|
353
|
+
`atEnd`, `playing`, and `interval` (real ms between bars at the current
|
|
354
|
+
speed).
|
|
355
|
+
|
|
356
|
+
### The `'replay'` event
|
|
357
|
+
|
|
358
|
+
`{ active, playing, index, length, progress, speed, time, bar, atEnd }`.
|
|
359
|
+
|
|
360
|
+
Like `'visibleRange'` it is a **state** event: a new subscriber is called
|
|
361
|
+
immediately, and after `stopReplay()` it fires once with `active: false` so a
|
|
362
|
+
UI can reset itself without special-casing teardown. `chart.replayState()`
|
|
363
|
+
returns the same payload on demand.
|
|
364
|
+
|
|
365
|
+
**Markers after the cursor are hidden, not clamped.** Time→index resolution
|
|
366
|
+
snaps to the nearest bar, so without that filter every future trade would pile
|
|
367
|
+
onto the newest revealed candle — and a replay that shows you tomorrow's
|
|
368
|
+
entries is worse than no replay at all.
|
|
369
|
+
|
|
370
|
+
The cursor never goes below index 1: the scales infer the timeframe from the
|
|
371
|
+
first pair of bars.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
292
375
|
## Annotations
|
|
293
376
|
|
|
294
377
|
Three independent collections, each replaced wholesale. Zones paint behind the
|
|
@@ -566,6 +649,7 @@ import {
|
|
|
566
649
|
TimeScale, PriceScale, // scales (advanced)
|
|
567
650
|
Smoothed, Tween, Inertia, // motion primitives
|
|
568
651
|
LiveCandle,
|
|
652
|
+
Replay, MIN_SPEED, MAX_SPEED, // bar-by-bar playback
|
|
569
653
|
easeOutCubic, easeInOutCubic,
|
|
570
654
|
mulberry32, // seeded PRNG
|
|
571
655
|
version,
|
package/index.d.ts
CHANGED
|
@@ -289,6 +289,80 @@ export declare class PriceScale {
|
|
|
289
289
|
readonly hi: number
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
/* ---------------------------------------------------------------- replay -- */
|
|
293
|
+
|
|
294
|
+
export interface ReplayOptions {
|
|
295
|
+
/** Dataset to replay. Defaults to the chart's current bars. */
|
|
296
|
+
bars?: Bar[]
|
|
297
|
+
/** Starting cursor index. Default: the midpoint of the dataset. */
|
|
298
|
+
from?: number
|
|
299
|
+
/** Rate multiplier, clamped to 0.25–500. Default 1. */
|
|
300
|
+
speed?: number
|
|
301
|
+
/** Real milliseconds one bar takes at 1×. Default 1000. */
|
|
302
|
+
baseInterval?: number
|
|
303
|
+
/** Restart from the beginning instead of stopping at the end. Default false. */
|
|
304
|
+
loop?: boolean
|
|
305
|
+
/** Re-anchor the right edge on the cursor when scrubbing. Default true. */
|
|
306
|
+
follow?: boolean
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Payload of the 'replay' event, and the return of chart.replayState(). */
|
|
310
|
+
export interface ReplayState {
|
|
311
|
+
/** False when the chart is not replaying; every other field is then inert. */
|
|
312
|
+
active: boolean
|
|
313
|
+
playing: boolean
|
|
314
|
+
/** Cursor: index of the newest revealed bar. -1 when inactive. */
|
|
315
|
+
index: number
|
|
316
|
+
/** Size of the dataset being replayed. */
|
|
317
|
+
length: number
|
|
318
|
+
/** 0 at the first playable bar, 1 at the last. */
|
|
319
|
+
progress: number
|
|
320
|
+
speed: number
|
|
321
|
+
/** `time` of the bar at the cursor. */
|
|
322
|
+
time: number | null
|
|
323
|
+
bar: Bar | null
|
|
324
|
+
atEnd: boolean
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Bar-by-bar playback over a fixed dataset. Obtained from
|
|
329
|
+
* `chart.startReplay()` or `chart.replay`; not constructed directly.
|
|
330
|
+
*/
|
|
331
|
+
export declare class Replay {
|
|
332
|
+
readonly source: Bar[]
|
|
333
|
+
readonly length: number
|
|
334
|
+
readonly lastIndex: number
|
|
335
|
+
readonly atEnd: boolean
|
|
336
|
+
readonly bar: Bar | null
|
|
337
|
+
readonly time: number | null
|
|
338
|
+
readonly progress: number
|
|
339
|
+
/** Real ms between bars at the current speed. */
|
|
340
|
+
readonly interval: number
|
|
341
|
+
index: number
|
|
342
|
+
speed: number
|
|
343
|
+
playing: boolean
|
|
344
|
+
looping: boolean
|
|
345
|
+
follow: boolean
|
|
346
|
+
baseInterval: number
|
|
347
|
+
|
|
348
|
+
play(): this
|
|
349
|
+
pause(): this
|
|
350
|
+
toggle(): this
|
|
351
|
+
/** Clamped to 0.25–500. */
|
|
352
|
+
setSpeed(speed: number): this
|
|
353
|
+
setLoop(on: boolean): this
|
|
354
|
+
/** Move the cursor. Out-of-range values clamp. */
|
|
355
|
+
seek(index: number): this
|
|
356
|
+
step(n?: number): this
|
|
357
|
+
toStart(): this
|
|
358
|
+
toEnd(): this
|
|
359
|
+
state(): ReplayState
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Speed bounds accepted by `setSpeed`. */
|
|
363
|
+
export declare const MIN_SPEED: number
|
|
364
|
+
export declare const MAX_SPEED: number
|
|
365
|
+
|
|
292
366
|
/* ----------------------------------------------------------------- chart -- */
|
|
293
367
|
|
|
294
368
|
export declare class Chart {
|
|
@@ -317,6 +391,21 @@ export declare class Chart {
|
|
|
317
391
|
/** Topmost marker under a plot-relative point, else null. */
|
|
318
392
|
markerAt(x: number, y: number): ResolvedMarker | null
|
|
319
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Start bar-by-bar playback. With no `bars`, the chart's current data is
|
|
396
|
+
* the dataset. Returns null if there are fewer than two bars to replay.
|
|
397
|
+
*
|
|
398
|
+
* chart.startReplay({ from: 200, speed: 4 })
|
|
399
|
+
* chart.replay.play()
|
|
400
|
+
*/
|
|
401
|
+
startReplay(options?: ReplayOptions): Replay | null
|
|
402
|
+
/** Leave replay and reveal the whole dataset again. */
|
|
403
|
+
stopReplay(): void
|
|
404
|
+
/** The active controller, or null. */
|
|
405
|
+
readonly replay: Replay | null
|
|
406
|
+
/** Current playback state; `{ active: false, ... }` when not replaying. */
|
|
407
|
+
replayState(): ReplayState
|
|
408
|
+
|
|
320
409
|
setTheme(theme: Partial<Theme>): void
|
|
321
410
|
setPriceMode(mode: PriceMode): void
|
|
322
411
|
setAnimate(on: boolean): void
|
|
@@ -342,6 +431,7 @@ export declare class Chart {
|
|
|
342
431
|
subscribe(event: 'markerClick', fn: (marker: ResolvedMarker) => void): () => void
|
|
343
432
|
subscribe(event: 'markerHover', fn: (marker: ResolvedMarker | null) => void): () => void
|
|
344
433
|
subscribe(event: 'visibleRange', fn: (range: VisibleRangePayload) => void): () => void
|
|
434
|
+
subscribe(event: 'replay', fn: (state: ReplayState) => void): () => void
|
|
345
435
|
|
|
346
436
|
/** Removes listeners, canvases and the render loop. */
|
|
347
437
|
destroy(): void
|