wickchart 1.5.0 → 1.6.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 +188 -1
- package/package.json +3 -3
- package/src/wick-chart.js +26 -2
- package/types/wick-chart.d.ts +17 -1
package/README.md
CHANGED
|
@@ -707,6 +707,193 @@ writer wins, remote updates never touch the local undo stack. Peer
|
|
|
707
707
|
dependency: wickchart ≥ 1.4. See the live playground in the docs (Drawings
|
|
708
708
|
section — it shares a room, so open it twice and draw on either chart).
|
|
709
709
|
|
|
710
|
+
### Sessions — the `wickchart-sessions` plugin
|
|
711
|
+
|
|
712
|
+
Market session shading as opt-in bytes (~6 KB gz, own CI budget): Asia /
|
|
713
|
+
London / New York and other sessions drawn as translucent bands, with labels,
|
|
714
|
+
closed-weekend shading for equities/futures, and crosshair hover events.
|
|
715
|
+
Presets for crypto & forex use the common UTC convention; equity/futures
|
|
716
|
+
presets use IANA timezones, so 09:30 is the real 09:30 across DST changes.
|
|
717
|
+
Custom defs (`{ name, start, end, tz?, days?, color?, alpha? }`) cover
|
|
718
|
+
midnight-crossing sessions and weekday filters.
|
|
719
|
+
|
|
720
|
+
```js
|
|
721
|
+
npm install wickchart wickchart-sessions // sessions are a separate opt-in package
|
|
722
|
+
|
|
723
|
+
import { attachSessions } from 'wickchart-sessions';
|
|
724
|
+
|
|
725
|
+
const sessions = attachSessions(chart, { preset: 'crypto' });
|
|
726
|
+
sessions.setPreset('nyse'); // 'crypto' | 'forex' | 'nyse' | 'cme' | null
|
|
727
|
+
sessions.setSessions([...]); // custom defs (validated; getSessions() → JSON)
|
|
728
|
+
sessions.setWeekends(true); // shade closed Sat+Sun (default for nyse/cme)
|
|
729
|
+
chart.addEventListener('wick:sessions', (e) => status.textContent = e.detail.hover || '');
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
The hover bridge listens to the chart's own crosshair events, so shading
|
|
733
|
+
never claims a pointer gesture — pan/zoom/measure work untouched. Peer
|
|
734
|
+
dependency: wickchart ≥ 1.4.
|
|
735
|
+
|
|
736
|
+
### Replay — the `wickchart-replay` plugin
|
|
737
|
+
|
|
738
|
+
Bar replay as opt-in bytes (~3 KB gz, own CI budget): play history forward
|
|
739
|
+
bar-by-bar or at speed while the future stays hidden. The whole engine runs
|
|
740
|
+
on the public data API — a `setData` slice hides the future, `update()`
|
|
741
|
+
appends one bar per step — so the core stays replay-free. A badge layer shows
|
|
742
|
+
the mode and position at a glance.
|
|
743
|
+
|
|
744
|
+
```js
|
|
745
|
+
npm install wickchart wickchart-replay // replay is a separate opt-in package
|
|
746
|
+
|
|
747
|
+
import { attachReplay } from 'wickchart-replay';
|
|
748
|
+
|
|
749
|
+
const replay = attachReplay(chart);
|
|
750
|
+
replay.start(); // head at ~70% of the data (or pass a time/index)
|
|
751
|
+
replay.play(); // 4 bars/sec — play(15) for faster, pause() stops
|
|
752
|
+
replay.step(); // reveal one bar
|
|
753
|
+
replay.seek('2026-03-06'); // jump the head
|
|
754
|
+
replay.setLoop(true); // wrap to the anchor at the end
|
|
755
|
+
replay.stop(); // exit — the full dataset is restored
|
|
756
|
+
chart.addEventListener('wick:replay', (e) => progress.textContent =
|
|
757
|
+
e.detail.active ? `${e.detail.index + 1}/${e.detail.total}` : '');
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
Anchors accept bar indices, timestamps (ms/s) or date strings; every change
|
|
761
|
+
fires `wick:replay` with the full state. Pause live feeds while replaying —
|
|
762
|
+
an external `update()`/`setData()` aborts replay instead of corrupting the
|
|
763
|
+
chart (the demo pauses its feed automatically). Paper trading and an equity
|
|
764
|
+
curve are the planned 0.2 follow-up. Peer dependency: wickchart ≥ 1.4.
|
|
765
|
+
|
|
766
|
+
### Compare — the `wickchart-compare` plugin
|
|
767
|
+
|
|
768
|
+
Normalized multi-asset overlays as opt-in bytes (~4 KB gz, own CI budget):
|
|
769
|
+
percent-rebased compare lines (ETH against BTC, TradingView-style) plus
|
|
770
|
+
derived **ratio** and **diff** lines (`BTC/ETH`, `BTC−ETH`), drawn over the
|
|
771
|
+
main pane against their own invisible scale so the price axis is untouched.
|
|
772
|
+
A legend chip row shows each series with its live value.
|
|
773
|
+
|
|
774
|
+
```js
|
|
775
|
+
npm install wickchart wickchart-compare // compare is a separate opt-in package
|
|
776
|
+
|
|
777
|
+
import { attachCompare } from 'wickchart-compare';
|
|
778
|
+
|
|
779
|
+
const cmp = attachCompare(chart);
|
|
780
|
+
cmp.setSeries([
|
|
781
|
+
{ label: 'ETH', data: ethBars }, // OHLC or {time, value}
|
|
782
|
+
{ label: 'BTC/ETH', op: 'ratio', a: btcBars, b: ethBars }, // derived
|
|
783
|
+
]);
|
|
784
|
+
cmp.setRebase('visible'); // 0% at the window edge, re-normalized while
|
|
785
|
+
// panning; 'first' or an epoch anchor also work
|
|
786
|
+
cmp.clear(); cmp.detach();
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
Series are sampled onto the main chart's bar times, so timeframes can mix
|
|
790
|
+
and gaps break the line instead of bridging. Rebased values share one
|
|
791
|
+
invisible scale inset 8% from the pane edges; the price scale is never
|
|
792
|
+
distorted. Validated, capped at 6 series, invalid entries dropped. Peer
|
|
793
|
+
dependency: wickchart ≥ 1.4.
|
|
794
|
+
|
|
795
|
+
### Navigator — the `wickchart-navigator` plugin
|
|
796
|
+
|
|
797
|
+
The most-missed TradingView affordance: a silhouette of the whole dataset
|
|
798
|
+
docked below the chart with a draggable viewport window (~3 KB gz, own CI
|
|
799
|
+
budget). Drag the window to pan, grab an edge to resize, click outside it to
|
|
800
|
+
jump — pan/zoom and the window stay in sync live, both directions.
|
|
801
|
+
|
|
802
|
+
```js
|
|
803
|
+
npm install wickchart wickchart-navigator // navigator is a separate opt-in package
|
|
804
|
+
|
|
805
|
+
import { attachNavigator } from 'wickchart-navigator';
|
|
806
|
+
const nav = attachNavigator(chart, { height: 46 }); // strip height, 24..120
|
|
807
|
+
nav.detach(); // remove the strip again
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
The strip needs bottom space, so this plugin pairs with a small core hook:
|
|
811
|
+
a layer may declare `insetBottom` (px) — the largest declared inset reserves
|
|
812
|
+
a docked strip at the bottom of the canvas, panes and the time axis shrink
|
|
813
|
+
above it, and layers draw it as `api.layout.dock`. On charts without the
|
|
814
|
+
hook the navigator degrades silently. The silhouette is O(n) once per
|
|
815
|
+
(dataset, width) and cached. Peer dependency: wickchart ≥ 1.6.
|
|
816
|
+
|
|
817
|
+
### Alerts+ — the `wickchart-alerts-plus` plugin
|
|
818
|
+
|
|
819
|
+
The "pro" alert tier (~3 KB gz, own CI budget). Core alerts are runtime-only
|
|
820
|
+
by design; this adds what a trading tool actually needs, without the core
|
|
821
|
+
growing any of it: **persistence** (the alert list mirrors into
|
|
822
|
+
localStorage and re-arms on reload), **desktop notifications + a WebAudio
|
|
823
|
+
beep** while the tab is hidden, and an optional **webhook** that receives
|
|
824
|
+
every fire as `POST { id, price, when, time, bar, key }`.
|
|
825
|
+
|
|
826
|
+
```js
|
|
827
|
+
npm install wickchart wickchart-alerts-plus // alerts-plus is a separate opt-in package
|
|
828
|
+
|
|
829
|
+
import { attachAlertsPlus } from 'wickchart-alerts-plus';
|
|
830
|
+
const ap = attachAlertsPlus(chart, {
|
|
831
|
+
key: 'BTC:1h', // one storage key per symbol+timeframe
|
|
832
|
+
notify: true, sound: true, // hidden-tab surfacing
|
|
833
|
+
webhook: 'https://example.com/hook', // optional
|
|
834
|
+
});
|
|
835
|
+
await ap.requestNotify(); // ask for the notification permission
|
|
836
|
+
ap.add({ price: 100, direction: 'above' }); // persisted, re-armed on reload
|
|
837
|
+
ap.add({ when: 'rsi(close,14) < 30' }); // scripted alerts persist too
|
|
838
|
+
ap.list(); ap.remove(id); ap.clear(); ap.sync(); ap.detach();
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
Once-fired alerts drop out of storage automatically; alerts added directly
|
|
842
|
+
on the chart are captured at the next save point; storage/fetch are
|
|
843
|
+
injectable and every storage failure degrades to memory-only, never
|
|
844
|
+
throwing. Peer dependency: wickchart ≥ 1.4.
|
|
845
|
+
|
|
846
|
+
### Layouts — the `wickchart-layouts` plugin
|
|
847
|
+
|
|
848
|
+
Named workspace persistence (~3 KB gz, own CI budget): save and restore
|
|
849
|
+
whole chart setups by name — type, theme, log scale, toggles, indicators,
|
|
850
|
+
view range, positions, alerts — plus the drawing list when wickchart-draw
|
|
851
|
+
is attached. Everything rides the core's public `getState()`/`setState()`.
|
|
852
|
+
|
|
853
|
+
```js
|
|
854
|
+
npm install wickchart wickchart-layouts // layouts is a separate opt-in package
|
|
855
|
+
|
|
856
|
+
import { attachLayouts } from 'wickchart-layouts';
|
|
857
|
+
const layouts = attachLayouts(chart, {
|
|
858
|
+
key: 'my-desk', // storage key (default 'wickchart-layouts')
|
|
859
|
+
drawings: draw, // optional wickchart-draw handle — include drawings
|
|
860
|
+
});
|
|
861
|
+
layouts.save('swing'); // capture the current setup under a name
|
|
862
|
+
layouts.load('swing'); // apply it back
|
|
863
|
+
layouts.list(); // → [{ name, at, drawingCount }] newest first
|
|
864
|
+
layouts.export(); // → JSON string — share it, store it anywhere
|
|
865
|
+
layouts.import(json); // merge layouts back (replaces same names)
|
|
866
|
+
chart.addEventListener('wick:layouts', (e) => console.log(e.detail.action, e.detail.name));
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
Entries are capped (oldest evicted), `storage` is injectable, storage
|
|
870
|
+
failures degrade to an in-memory store for the session and never throw.
|
|
871
|
+
Pair a `load` with `wickchart-alerts-plus`'s `sync()` if you also persist
|
|
872
|
+
alerts, since a layout load replaces the chart's alert list. Peer
|
|
873
|
+
dependency: wickchart ≥ 1.4.
|
|
874
|
+
|
|
875
|
+
### Signals — the `wickchart-signals` plugin
|
|
876
|
+
|
|
877
|
+
Candlestick pattern badges (~4 KB gz, own CI budget): bullish/bearish
|
|
878
|
+
**engulfing**, **pin bars** (hammer / shooting star) and **inside bars**
|
|
879
|
+
drawn as direction-colored letter chips above/below the bar. Hover a badged
|
|
880
|
+
bar and the plugin draws the explanation ("Bullish engulfing") and fires
|
|
881
|
+
`wick:signals` — the same passive crosshair bridge as wickchart-sessions,
|
|
882
|
+
so badges never claim a pointer gesture.
|
|
883
|
+
|
|
884
|
+
```js
|
|
885
|
+
npm install wickchart wickchart-signals // signals is a separate opt-in package
|
|
886
|
+
|
|
887
|
+
import { attachSignals } from 'wickchart-signals';
|
|
888
|
+
const signals = attachSignals(chart);
|
|
889
|
+
signals.setKinds(['engulfing', 'pinbar']); // subset (default: all three)
|
|
890
|
+
signals.setLabels(false); // hover explanations off
|
|
891
|
+
chart.addEventListener('wick:signals', (e) => status.textContent = e.detail?.label || '');
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
Detection is O(n), cached per dataset and kind subset — pan/zoom are pure
|
|
895
|
+
repaints. Peer dependency: wickchart ≥ 1.4.
|
|
896
|
+
|
|
710
897
|
## Methods
|
|
711
898
|
|
|
712
899
|
| Method | Description |
|
|
@@ -721,7 +908,7 @@ section — it shares a room, so open it twice and draw on either chart).
|
|
|
721
908
|
| `getDataWindow()` | → AI-ready summary of the visible window (see below) |
|
|
722
909
|
| `getState()` | → serializable snapshot (type, indicators, view, positions, alerts) |
|
|
723
910
|
| `setState(state)` | Apply a snapshot; a pending view applies after the next `setData()` |
|
|
724
|
-
| `addLayer(layer)` / `removeLayer(idOrHandle)` | Register/detach a plugin layer (draw hook + optional pointer claim) |
|
|
911
|
+
| `addLayer(layer)` / `removeLayer(idOrHandle)` | Register/detach a plugin layer (draw hook + optional pointer claim + optional `insetBottom` dock strip) |
|
|
725
912
|
| `requestDraw()` | Repaint on the next frame (interactive layers) |
|
|
726
913
|
| `timeToX(t)` / `xToTime(x)` | Bar time ⇄ x-pixel; extrapolates into future space |
|
|
727
914
|
| `priceToY(p)` / `yToPrice(y)` | Price ⇄ y-pixel in the main pane (log-aware) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wickchart",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "<wick-chart> — a modern, dependency-free financial charting web component. Candles, line & area charts, crosshair, zoom/pan, indicators (incl. a safe expression mini-language), live streaming via <wick-feed>, theming.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/wick-chart.js",
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"dev": "npx --yes serve . -l 5173",
|
|
45
|
-
"test": "node --test \"tests/*.test.mjs\" \"plugins/draw/tests/*.test.mjs\"",
|
|
45
|
+
"test": "node --test \"tests/*.test.mjs\" \"plugins/draw/tests/*.test.mjs\" \"plugins/sessions/tests/*.test.mjs\" \"plugins/replay/tests/*.test.mjs\" \"plugins/compare/tests/*.test.mjs\" \"plugins/navigator/tests/*.test.mjs\" \"plugins/alerts-plus/tests/*.test.mjs\" \"plugins/layouts/tests/*.test.mjs\" \"plugins/signals/tests/*.test.mjs\"",
|
|
46
46
|
"build:types": "node -e \"require('fs').rmSync('types', { recursive: true, force: true });\" && tsc -p tsconfig.json",
|
|
47
47
|
"prepack": "npm run build:types",
|
|
48
|
-
"ci": "npm run build:types && npm test && node --check src/wick-chart.js && node --check src/wick-feed.js && node --check src/core.js && node --check src/react.js && node --check src/react-core.js && node --check demo/app.js && node --check plugins/draw/core.mjs && node --check plugins/draw/draw.mjs"
|
|
48
|
+
"ci": "npm run build:types && npm test && node --check src/wick-chart.js && node --check src/wick-feed.js && node --check src/core.js && node --check src/react.js && node --check src/react-core.js && node --check demo/app.js && node --check plugins/draw/core.mjs && node --check plugins/draw/draw.mjs && node --check plugins/sessions/core.mjs && node --check plugins/sessions/sessions.mjs && node --check plugins/replay/replay.mjs && node --check plugins/compare/core.mjs && node --check plugins/compare/compare.mjs && node --check plugins/navigator/core.mjs && node --check plugins/navigator/navigator.mjs && node --check plugins/alerts-plus/alerts-plus.mjs && node --check plugins/layouts/layouts.mjs && node --check plugins/signals/core.mjs && node --check plugins/signals/signals.mjs"
|
|
49
49
|
},
|
|
50
50
|
"keywords": [
|
|
51
51
|
"chart",
|
package/src/wick-chart.js
CHANGED
|
@@ -1598,8 +1598,9 @@ class WickChart extends HTMLElementBase {
|
|
|
1598
1598
|
);
|
|
1599
1599
|
}
|
|
1600
1600
|
const timeH = 26;
|
|
1601
|
+
const dock = this._dockInset();
|
|
1601
1602
|
const plotRight = Math.max(30, W - priceW);
|
|
1602
|
-
const plotBottom = H - timeH;
|
|
1603
|
+
const plotBottom = H - timeH - dock;
|
|
1603
1604
|
const paneList = this._ind.panes;
|
|
1604
1605
|
const paneArea = paneList.length
|
|
1605
1606
|
? Math.min(
|
|
@@ -1622,6 +1623,7 @@ class WickChart extends HTMLElementBase {
|
|
|
1622
1623
|
plotBottom,
|
|
1623
1624
|
main: { y0: 0, y1: mainH, h: mainH },
|
|
1624
1625
|
panes,
|
|
1626
|
+
dock: dock > 0 ? { y0: H - dock, h: dock } : null,
|
|
1625
1627
|
});
|
|
1626
1628
|
|
|
1627
1629
|
/* background */
|
|
@@ -2942,7 +2944,13 @@ class WickChart extends HTMLElementBase {
|
|
|
2942
2944
|
* layer then receives that pointer's move/up/cancel events (plus a
|
|
2943
2945
|
* 'cancel' on Escape) and the chart suppresses its own pan/measure/brush
|
|
2944
2946
|
* for the duration.
|
|
2945
|
-
*
|
|
2947
|
+
*
|
|
2948
|
+
* A layer may also declare `insetBottom` (px, 0..160): the largest
|
|
2949
|
+
* declared inset reserves a docked strip at the very bottom of the
|
|
2950
|
+
* canvas — all chart content (panes + time axis) shrinks above it and
|
|
2951
|
+
* the strip is handed to layers as `api.layout.dock = { y0, h }`
|
|
2952
|
+
* (used by the wickchart-navigator plugin).
|
|
2953
|
+
* @param {{id?: string, draw: Function, onPointer?: Function, insetBottom?: number}} layer
|
|
2946
2954
|
* @returns {object|null} the normalized layer handle (with `id`), or null
|
|
2947
2955
|
* if the layer was rejected (no draw fn, or 16 layers already added)
|
|
2948
2956
|
*/
|
|
@@ -2956,6 +2964,10 @@ class WickChart extends HTMLElementBase {
|
|
|
2956
2964
|
const entry = {
|
|
2957
2965
|
id,
|
|
2958
2966
|
draw: layer.draw,
|
|
2967
|
+
insetBottom:
|
|
2968
|
+
typeof layer.insetBottom === 'number' && Number.isFinite(layer.insetBottom)
|
|
2969
|
+
? Math.max(0, Math.min(160, Math.round(layer.insetBottom)))
|
|
2970
|
+
: 0,
|
|
2959
2971
|
onPointer: typeof layer.onPointer === 'function' ? layer.onPointer : null,
|
|
2960
2972
|
};
|
|
2961
2973
|
const at = this._layers.findIndex((l) => l.id === id);
|
|
@@ -3007,6 +3019,18 @@ class WickChart extends HTMLElementBase {
|
|
|
3007
3019
|
}
|
|
3008
3020
|
}
|
|
3009
3021
|
|
|
3022
|
+
/**
|
|
3023
|
+
* Bottom space reserved by plugin layers: the largest declared
|
|
3024
|
+
* `insetBottom` (px, clamped 0..160 at addLayer time), or 0.
|
|
3025
|
+
*/
|
|
3026
|
+
_dockInset() {
|
|
3027
|
+
let dock = 0;
|
|
3028
|
+
for (const l of this._layers) {
|
|
3029
|
+
if (l.insetBottom > dock) dock = l.insetBottom;
|
|
3030
|
+
}
|
|
3031
|
+
return dock;
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3010
3034
|
/** Ask layers, in order, whether one claims this pointerdown. */
|
|
3011
3035
|
_layerHit(e, pt) {
|
|
3012
3036
|
for (const layer of this._layers) {
|
package/types/wick-chart.d.ts
CHANGED
|
@@ -46,6 +46,10 @@ declare class WickChart extends HTMLElementBase {
|
|
|
46
46
|
y1: number;
|
|
47
47
|
h: number;
|
|
48
48
|
}[];
|
|
49
|
+
dock: {
|
|
50
|
+
y0: number;
|
|
51
|
+
h: number;
|
|
52
|
+
};
|
|
49
53
|
};
|
|
50
54
|
_cache: {
|
|
51
55
|
v: number;
|
|
@@ -791,7 +795,13 @@ declare class WickChart extends HTMLElementBase {
|
|
|
791
795
|
* layer then receives that pointer's move/up/cancel events (plus a
|
|
792
796
|
* 'cancel' on Escape) and the chart suppresses its own pan/measure/brush
|
|
793
797
|
* for the duration.
|
|
794
|
-
*
|
|
798
|
+
*
|
|
799
|
+
* A layer may also declare `insetBottom` (px, 0..160): the largest
|
|
800
|
+
* declared inset reserves a docked strip at the very bottom of the
|
|
801
|
+
* canvas — all chart content (panes + time axis) shrinks above it and
|
|
802
|
+
* the strip is handed to layers as `api.layout.dock = { y0, h }`
|
|
803
|
+
* (used by the wickchart-navigator plugin).
|
|
804
|
+
* @param {{id?: string, draw: Function, onPointer?: Function, insetBottom?: number}} layer
|
|
795
805
|
* @returns {object|null} the normalized layer handle (with `id`), or null
|
|
796
806
|
* if the layer was rejected (no draw fn, or 16 layers already added)
|
|
797
807
|
*/
|
|
@@ -799,6 +809,7 @@ declare class WickChart extends HTMLElementBase {
|
|
|
799
809
|
id?: string;
|
|
800
810
|
draw: Function;
|
|
801
811
|
onPointer?: Function;
|
|
812
|
+
insetBottom?: number;
|
|
802
813
|
}): object | null;
|
|
803
814
|
/**
|
|
804
815
|
* Remove a layer added via addLayer (pass the returned handle or its id).
|
|
@@ -810,6 +821,11 @@ declare class WickChart extends HTMLElementBase {
|
|
|
810
821
|
requestDraw(): void;
|
|
811
822
|
/** Paint every registered layer. Called from _render with live state. */
|
|
812
823
|
_drawLayers(ctx: any, pal: any, ly: any, d: any): void;
|
|
824
|
+
/**
|
|
825
|
+
* Bottom space reserved by plugin layers: the largest declared
|
|
826
|
+
* `insetBottom` (px, clamped 0..160 at addLayer time), or 0.
|
|
827
|
+
*/
|
|
828
|
+
_dockInset(): number;
|
|
813
829
|
/** Ask layers, in order, whether one claims this pointerdown. */
|
|
814
830
|
_layerHit(e: any, pt: any): any;
|
|
815
831
|
/** Deliver a pointer event to a claiming layer; never throws outward. */
|