emberwick 0.1.0 → 0.2.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 +113 -12
- package/index.d.ts +120 -0
- package/index.js +434 -12
- 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
|
@@ -10,6 +10,8 @@ point it at your own market data.
|
|
|
10
10
|
to new bounds, zoom is cursor-anchored and eased, panning has inertia.
|
|
11
11
|
- **Fast on big data.** One `requestAnimationFrame` loop, dirty-flag driven,
|
|
12
12
|
with visible-range culling — 500k bars loaded costs only the ~200 on screen.
|
|
13
|
+
- **Annotated.** Nine marker shapes, price lines and shaded zones, with
|
|
14
|
+
collision-aware stacking and hit-testing for hover and click.
|
|
13
15
|
|
|
14
16
|
---
|
|
15
17
|
|
|
@@ -25,10 +27,6 @@ npm install emberwick
|
|
|
25
27
|
import { createChart, RandomFeed } from 'emberwick'
|
|
26
28
|
```
|
|
27
29
|
|
|
28
|
-
> **Not published yet.** The package builds and packs (`npm run release`), but
|
|
29
|
-
> it has not been pushed to the registry, and the name has not been confirmed
|
|
30
|
-
> available — run `npm view emberwick` before relying on it.
|
|
31
|
-
|
|
32
30
|
### From a CDN, no build step
|
|
33
31
|
|
|
34
32
|
```html
|
|
@@ -213,7 +211,15 @@ const chart = createChart(el, {
|
|
|
213
211
|
| `append(bar)` | Open a new candle |
|
|
214
212
|
| `setFeed(feed)` | `async` — loads history, then subscribes. Detaches any previous feed |
|
|
215
213
|
| `detachFeed()` | Unsubscribe, keep the bars on screen |
|
|
216
|
-
| `subscribe(
|
|
214
|
+
| `subscribe(event, fn)` | Returns an unsubscribe fn. See events below |
|
|
215
|
+
| `setMarkers(markers)` | Replace every marker |
|
|
216
|
+
| `getMarkers()` | Current markers, normalised, each with its resolved bar index |
|
|
217
|
+
| `addMarker(marker)` | Append one marker |
|
|
218
|
+
| `removeMarker(id)` | Remove by id |
|
|
219
|
+
| `clearMarkers()` | Remove all markers |
|
|
220
|
+
| `setPriceLines(lines)` | Replace every horizontal price line |
|
|
221
|
+
| `setZones(zones)` | Replace every shaded region |
|
|
222
|
+
| `markerAt(x, y)` | Hit-test plot coordinates, returns a marker or `null` |
|
|
217
223
|
| `setTheme(partial)` | Merge theme keys and repaint |
|
|
218
224
|
| `setPriceMode(mode)` | `'linear'` or `'log'` |
|
|
219
225
|
| `setAnimate(bool)` | Toggle live-candle easing |
|
|
@@ -234,7 +240,100 @@ const off = chart.subscribe('crosshair', (payload) => {
|
|
|
234
240
|
off() // unsubscribe
|
|
235
241
|
```
|
|
236
242
|
|
|
237
|
-
|
|
243
|
+
| Event | Payload |
|
|
244
|
+
|---|---|
|
|
245
|
+
| `'crosshair'` | `{ index, bar, price }`, or `null` when the pointer leaves the plot |
|
|
246
|
+
| `'markerHover'` | The marker under the pointer, or `null` when none is |
|
|
247
|
+
| `'markerClick'` | The clicked marker. Only fires on a hit, never with `null` |
|
|
248
|
+
| `'visibleRange'` | **Accepted but never emitted.** See *Known gaps* |
|
|
249
|
+
|
|
250
|
+
A drag that happens to end on top of a marker does not fire `'markerClick'` —
|
|
251
|
+
panning and clicking stay distinct.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Annotations
|
|
256
|
+
|
|
257
|
+
Three independent collections, each replaced wholesale. Zones paint behind the
|
|
258
|
+
candles; price lines and markers paint in front.
|
|
259
|
+
|
|
260
|
+
```js
|
|
261
|
+
chart.setMarkers([
|
|
262
|
+
{ time: 1717070400000, shape: 'arrowUp', text: 'BUY 120',
|
|
263
|
+
data: { orderId: 'A-7741' } },
|
|
264
|
+
{ time: 1717074000000, shape: 'flag', text: 'Earnings', color: '#c084fc' },
|
|
265
|
+
{ time: 1717077600000, shape: 'arrowDown', text: 'SELL 160' },
|
|
266
|
+
])
|
|
267
|
+
|
|
268
|
+
chart.setPriceLines([
|
|
269
|
+
{ price: 148.20, title: 'target', color: '#26a69a' },
|
|
270
|
+
{ price: 141.05, title: 'stop', lineStyle: 'dotted' },
|
|
271
|
+
])
|
|
272
|
+
|
|
273
|
+
chart.setZones([
|
|
274
|
+
{ from: 143.5, to: 145.9, label: 'value area' },
|
|
275
|
+
])
|
|
276
|
+
|
|
277
|
+
chart.subscribe('markerClick', (m) => openTicket(m.data.orderId))
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Markers
|
|
281
|
+
|
|
282
|
+
A marker is pinned to a **timestamp**, not a bar index, and resolves to the
|
|
283
|
+
nearest bar. Load an older page of history and every marker re-resolves, so
|
|
284
|
+
nothing drifts off its candle.
|
|
285
|
+
|
|
286
|
+
| Field | Default | Notes |
|
|
287
|
+
|---|---|---|
|
|
288
|
+
| `time` | *required* | ms since epoch, snapped to the closest bar |
|
|
289
|
+
| `id` | generated | Needed for `removeMarker(id)` |
|
|
290
|
+
| `shape` | `'circle'` | See the list below |
|
|
291
|
+
| `position` | shape-dependent | `'aboveBar'`, `'belowBar'`, `'inBar'`, `'atPrice'` |
|
|
292
|
+
| `price` | — | Only used when `position` is `'atPrice'` |
|
|
293
|
+
| `color` | `theme.up` / `theme.down` | Down-pointing shapes default to the down colour |
|
|
294
|
+
| `text` | — | Short caption. For `'label'` it is drawn inside the pill |
|
|
295
|
+
| `textColor` | theme | Caption colour |
|
|
296
|
+
| `size` | `1` | Scale factor |
|
|
297
|
+
| `data` | — | Anything. Handed straight back on hover and click |
|
|
298
|
+
|
|
299
|
+
Shapes: `arrowUp`, `arrowDown`, `triangleUp`, `triangleDown`, `circle`,
|
|
300
|
+
`square`, `diamond`, `flag`, `label`.
|
|
301
|
+
|
|
302
|
+
Position defaults follow the trading convention: up-pointing shapes sit
|
|
303
|
+
*below* the bar, down-pointing shapes *above* it, everything else above.
|
|
304
|
+
|
|
305
|
+
**Overlap and density.** Markers sharing a bar are stacked rather than drawn on
|
|
306
|
+
top of each other. Below about 3px per bar, dense runs thin to roughly one
|
|
307
|
+
marker per 4px — a thousand trades stay legible and stay fast. A marker on the
|
|
308
|
+
forming candle anchors to the animated values, so it flows with the live bar.
|
|
309
|
+
|
|
310
|
+
### Price lines
|
|
311
|
+
|
|
312
|
+
| Field | Default | Notes |
|
|
313
|
+
|---|---|---|
|
|
314
|
+
| `price` | *required* | |
|
|
315
|
+
| `color` | `theme.textStrong` | |
|
|
316
|
+
| `lineStyle` | `'dashed'` | `'solid'`, `'dashed'`, `'dotted'` |
|
|
317
|
+
| `lineWidth` | `1` | |
|
|
318
|
+
| `title` | — | Pill drawn at the left end |
|
|
319
|
+
| `axisLabel` | `true` | Price tag on the axis |
|
|
320
|
+
|
|
321
|
+
### Zones
|
|
322
|
+
|
|
323
|
+
Supply `from`/`to` for a **price band** spanning the full width, or
|
|
324
|
+
`fromTime`/`toTime` for a **time band** spanning the full height.
|
|
325
|
+
|
|
326
|
+
```js
|
|
327
|
+
chart.setZones([
|
|
328
|
+
{ from: 143.5, to: 145.9, label: 'value area' },
|
|
329
|
+
{ fromTime: 1717070400000, toTime: 1717074000000,
|
|
330
|
+
color: 'rgba(239,83,80,0.07)', border: 'rgba(239,83,80,0.3)' },
|
|
331
|
+
])
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Autoscale fits the **bars**, not the annotations — a price line far outside the
|
|
335
|
+
data range is simply off-screen. Derive extreme values from the visible range
|
|
336
|
+
if you need them guaranteed visible.
|
|
238
337
|
|
|
239
338
|
---
|
|
240
339
|
|
|
@@ -475,9 +574,12 @@ npm run build:umd # minified UMD -> dist-lib/umd/emberwick.umd.js
|
|
|
475
574
|
npm run pack:lib # manifest + README + .d.ts into dist-lib/
|
|
476
575
|
npm run size # gzipped size budget check
|
|
477
576
|
npm run release # all four, in order
|
|
478
|
-
npm publish dist-lib
|
|
577
|
+
npm publish ./dist-lib # publish the assembled directory — note the ./
|
|
479
578
|
```
|
|
480
579
|
|
|
580
|
+
> The leading `./` is required. `npm publish dist-lib` makes npm look for a
|
|
581
|
+
> *registry package* named `dist-lib` and fail with `E404`.
|
|
582
|
+
|
|
481
583
|
Publishing from `dist-lib/` keeps the playground, the build configs and the
|
|
482
584
|
app's private `package.json` out of the artifact. `package.lib.json` is the
|
|
483
585
|
manifest that becomes the published `package.json`.
|
|
@@ -507,11 +609,10 @@ Honest list of what isn't there yet:
|
|
|
507
609
|
|
|
508
610
|
- **`subscribe('visibleRange', fn)` never fires.** The event is accepted and
|
|
509
611
|
the handler is stored, but nothing emits it. Use `'crosshair'` for now.
|
|
510
|
-
- **
|
|
511
|
-
|
|
512
|
-
- **
|
|
513
|
-
|
|
514
|
-
`npm run release` locally before publishing.
|
|
612
|
+
- **No OHLCV legend in the package.** `subscribe('crosshair', fn)` gives you
|
|
613
|
+
the hovered bar; rendering the readout is still yours to do.
|
|
614
|
+
- **Markers are not draggable.** They are hit-tested for hover and click, but
|
|
615
|
+
there is no drag-to-move or editing interaction.
|
|
515
616
|
- **No indicators or drawing tools.** SMA/EMA/RSI/MACD, multi-pane layout and
|
|
516
617
|
trendlines are planned but not implemented.
|
|
517
618
|
- **Candlesticks only.** No Heikin-Ashi, line, area or baseline series yet.
|
package/index.d.ts
CHANGED
|
@@ -40,6 +40,97 @@ export interface Theme {
|
|
|
40
40
|
timeAxisHeight: number
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/* --------------------------------------------------------------- markers -- */
|
|
44
|
+
|
|
45
|
+
export type MarkerShape =
|
|
46
|
+
| 'arrowUp'
|
|
47
|
+
| 'arrowDown'
|
|
48
|
+
| 'triangleUp'
|
|
49
|
+
| 'triangleDown'
|
|
50
|
+
| 'circle'
|
|
51
|
+
| 'square'
|
|
52
|
+
| 'diamond'
|
|
53
|
+
| 'flag'
|
|
54
|
+
| 'label'
|
|
55
|
+
|
|
56
|
+
export type MarkerPosition = 'aboveBar' | 'belowBar' | 'inBar' | 'atPrice'
|
|
57
|
+
|
|
58
|
+
/** A point annotation pinned to the bar nearest `time`. */
|
|
59
|
+
export interface Marker<T = unknown> {
|
|
60
|
+
/** Stable id. Generated when omitted; required for removeMarker(). */
|
|
61
|
+
id?: string
|
|
62
|
+
/** ms since epoch — snapped to the closest bar. */
|
|
63
|
+
time: number
|
|
64
|
+
/** Only used when position is 'atPrice'. */
|
|
65
|
+
price?: number
|
|
66
|
+
/** Default 'circle'. */
|
|
67
|
+
shape?: MarkerShape
|
|
68
|
+
/**
|
|
69
|
+
* Default depends on the shape: up shapes sit below the bar, down shapes
|
|
70
|
+
* above it, everything else above.
|
|
71
|
+
*/
|
|
72
|
+
position?: MarkerPosition
|
|
73
|
+
/** Defaults to theme.up, or theme.down for the down-pointing shapes. */
|
|
74
|
+
color?: string
|
|
75
|
+
textColor?: string
|
|
76
|
+
/** Short caption. For shape 'label' it is drawn inside the pill. */
|
|
77
|
+
text?: string
|
|
78
|
+
/** Scale factor. Default 1. */
|
|
79
|
+
size?: number
|
|
80
|
+
/** Passed straight back to you on hover/click. */
|
|
81
|
+
data?: T
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A marker after normalisation, as returned by getMarkers(). */
|
|
85
|
+
export interface ResolvedMarker<T = unknown> extends Marker<T> {
|
|
86
|
+
id: string
|
|
87
|
+
shape: MarkerShape
|
|
88
|
+
position: MarkerPosition
|
|
89
|
+
size: number
|
|
90
|
+
/** Bar index the marker resolved to, or -1 when there are no bars. */
|
|
91
|
+
index: number
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type LineStyle = 'solid' | 'dashed' | 'dotted'
|
|
95
|
+
|
|
96
|
+
/** A horizontal line at a fixed price. */
|
|
97
|
+
export interface PriceLine {
|
|
98
|
+
id?: string
|
|
99
|
+
price: number
|
|
100
|
+
/** Defaults to theme.textStrong. */
|
|
101
|
+
color?: string
|
|
102
|
+
/** Default 'dashed'. */
|
|
103
|
+
lineStyle?: LineStyle
|
|
104
|
+
/** Default 1. */
|
|
105
|
+
lineWidth?: number
|
|
106
|
+
/** Optional pill drawn at the left end. */
|
|
107
|
+
title?: string
|
|
108
|
+
titleColor?: string
|
|
109
|
+
/** Price tag on the axis. Default true. */
|
|
110
|
+
axisLabel?: boolean
|
|
111
|
+
tagTextColor?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A shaded region: supply `from`/`to` for a price band spanning the full
|
|
116
|
+
* width, or `fromTime`/`toTime` for a time band spanning the full height.
|
|
117
|
+
*/
|
|
118
|
+
export interface Zone {
|
|
119
|
+
id?: string
|
|
120
|
+
from?: number
|
|
121
|
+
to?: number
|
|
122
|
+
fromTime?: number
|
|
123
|
+
toTime?: number
|
|
124
|
+
/** Fill. Use rgba(). Default a faint theme-green wash. */
|
|
125
|
+
color?: string
|
|
126
|
+
/** Optional 1px outline. */
|
|
127
|
+
border?: string
|
|
128
|
+
label?: string
|
|
129
|
+
labelColor?: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* --------------------------------------------------------------- options -- */
|
|
133
|
+
|
|
43
134
|
export interface TimeScaleOptions {
|
|
44
135
|
/** Pixels per bar. Default 9. */
|
|
45
136
|
spacing?: number
|
|
@@ -73,6 +164,10 @@ export interface ChartOptions {
|
|
|
73
164
|
animate?: boolean
|
|
74
165
|
/** Bars requested from the feed on setFeed(). Default 1500. */
|
|
75
166
|
initialBars?: number
|
|
167
|
+
/** Initial markers; equivalent to calling setMarkers() after construction. */
|
|
168
|
+
markers?: Marker[]
|
|
169
|
+
priceLines?: PriceLine[]
|
|
170
|
+
zones?: Zone[]
|
|
76
171
|
timeScale?: TimeScaleOptions
|
|
77
172
|
priceScale?: PriceScaleOptions
|
|
78
173
|
}
|
|
@@ -84,6 +179,8 @@ export interface CrosshairPayload {
|
|
|
84
179
|
price: number
|
|
85
180
|
}
|
|
86
181
|
|
|
182
|
+
/* ------------------------------------------------------------------ feed -- */
|
|
183
|
+
|
|
87
184
|
export interface GetBarsRequest {
|
|
88
185
|
symbol: string
|
|
89
186
|
timeframe: number
|
|
@@ -147,6 +244,8 @@ export declare class RandomFeed extends DataFeed {
|
|
|
147
244
|
stop(): void
|
|
148
245
|
}
|
|
149
246
|
|
|
247
|
+
/* ---------------------------------------------------------------- scales -- */
|
|
248
|
+
|
|
150
249
|
export declare class TimeScale {
|
|
151
250
|
constructor(opts?: TimeScaleOptions)
|
|
152
251
|
visibleRange(): { from: number; to: number }
|
|
@@ -168,6 +267,8 @@ export declare class PriceScale {
|
|
|
168
267
|
readonly hi: number
|
|
169
268
|
}
|
|
170
269
|
|
|
270
|
+
/* ----------------------------------------------------------------- chart -- */
|
|
271
|
+
|
|
171
272
|
export declare class Chart {
|
|
172
273
|
constructor(container: HTMLElement, options?: ChartOptions)
|
|
173
274
|
|
|
@@ -181,6 +282,19 @@ export declare class Chart {
|
|
|
181
282
|
setFeed(feed: Feed): Promise<void>
|
|
182
283
|
detachFeed(): void
|
|
183
284
|
|
|
285
|
+
/** Replace every marker. */
|
|
286
|
+
setMarkers(markers: Marker[]): void
|
|
287
|
+
/** Current markers, normalised, each with its resolved bar index. */
|
|
288
|
+
getMarkers(): ResolvedMarker[]
|
|
289
|
+
addMarker(marker: Marker): void
|
|
290
|
+
/** Removes by id — including ids generated for you. */
|
|
291
|
+
removeMarker(id: string): void
|
|
292
|
+
clearMarkers(): void
|
|
293
|
+
setPriceLines(lines: PriceLine[]): void
|
|
294
|
+
setZones(zones: Zone[]): void
|
|
295
|
+
/** Topmost marker under a plot-relative point, else null. */
|
|
296
|
+
markerAt(x: number, y: number): ResolvedMarker | null
|
|
297
|
+
|
|
184
298
|
setTheme(theme: Partial<Theme>): void
|
|
185
299
|
setPriceMode(mode: PriceMode): void
|
|
186
300
|
setAnimate(on: boolean): void
|
|
@@ -197,12 +311,16 @@ export declare class Chart {
|
|
|
197
311
|
* NOTE: 'visibleRange' is accepted but is not currently emitted.
|
|
198
312
|
*/
|
|
199
313
|
subscribe(event: 'crosshair', fn: (payload: CrosshairPayload | null) => void): () => void
|
|
314
|
+
subscribe(event: 'markerClick', fn: (marker: ResolvedMarker) => void): () => void
|
|
315
|
+
subscribe(event: 'markerHover', fn: (marker: ResolvedMarker | null) => void): () => void
|
|
200
316
|
subscribe(event: 'visibleRange', fn: (range: { from: number; to: number }) => void): () => void
|
|
201
317
|
|
|
202
318
|
/** Removes listeners, canvases and the render loop. */
|
|
203
319
|
destroy(): void
|
|
204
320
|
|
|
205
321
|
readonly bars: Bar[]
|
|
322
|
+
readonly priceLines: PriceLine[]
|
|
323
|
+
readonly zones: Zone[]
|
|
206
324
|
readonly ts: TimeScale
|
|
207
325
|
readonly ps: PriceScale
|
|
208
326
|
}
|
|
@@ -214,6 +332,8 @@ export declare const defaultTheme: Theme
|
|
|
214
332
|
export declare const lightTheme: Theme
|
|
215
333
|
export declare const version: string
|
|
216
334
|
|
|
335
|
+
/* ------------------------------------------------------------- utilities -- */
|
|
336
|
+
|
|
217
337
|
/** Seeded PRNG used by RandomFeed. */
|
|
218
338
|
export declare function mulberry32(seed: number): () => number
|
|
219
339
|
|