@trdrs/quickcharts 1.2.0 → 2.0.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/CHANGELOG.md +120 -0
- package/README.md +742 -180
- package/dist/adapters/rest.d.ts +3 -3
- package/dist/adapters/rest.js +2 -2
- package/dist/adapters/rest.js.map +1 -1
- package/dist/{ar-G7MVXRE4.js → ar-KSXQ2USS.js} +20 -20
- package/dist/ar-KSXQ2USS.js.map +1 -0
- package/dist/{assets-ySndlUS3.d.ts → assets-DOuBBsOp.d.ts} +50 -50
- package/dist/{ca_ES-EUSRU4VG.js → ca_ES-JG4FM55E.js} +20 -20
- package/dist/ca_ES-JG4FM55E.js.map +1 -0
- package/dist/{chunk-RDAK5MUM.js → chunk-6S6TO2N6.js} +41 -37
- package/dist/chunk-6S6TO2N6.js.map +1 -0
- package/dist/{chunk-KFBQSPNA.js → chunk-D3S26ARP.js} +2 -2
- package/dist/chunk-D3S26ARP.js.map +1 -0
- package/dist/{chunk-AKXIFHQN.js → chunk-KLPTCWA2.js} +1 -1
- package/dist/{chunk-AKXIFHQN.js.map → chunk-KLPTCWA2.js.map} +1 -1
- package/dist/{de-2SVTG25S.js → de-VSW2LHNF.js} +20 -20
- package/dist/de-VSW2LHNF.js.map +1 -0
- package/dist/drawings.d.ts +16 -16
- package/dist/drawings.js +12 -12
- package/dist/{es-UDHZM4W7.js → es-5ZXIPVOB.js} +20 -20
- package/dist/es-5ZXIPVOB.js.map +1 -0
- package/dist/feature-manifest.json +51 -3
- package/dist/{fr-35VN4G4S.js → fr-4F6DAWGD.js} +20 -20
- package/dist/fr-4F6DAWGD.js.map +1 -0
- package/dist/{he_IL-JPZMCURP.js → he_IL-B7PBQUEV.js} +20 -20
- package/dist/he_IL-B7PBQUEV.js.map +1 -0
- package/dist/{id_ID-XSTFHHJQ.js → id_ID-CYJRMXPC.js} +20 -20
- package/dist/id_ID-CYJRMXPC.js.map +1 -0
- package/dist/index.d.ts +599 -225
- package/dist/index.js +10110 -9106
- package/dist/index.js.map +1 -1
- package/dist/{it-EIY6JESQ.js → it-KTX4SG5P.js} +20 -20
- package/dist/it-KTX4SG5P.js.map +1 -0
- package/dist/{ja-42N4XRCW.js → ja-V7OURU5D.js} +20 -20
- package/dist/ja-V7OURU5D.js.map +1 -0
- package/dist/{ko-3VRPTIP3.js → ko-2RNEBYVE.js} +20 -20
- package/dist/ko-2RNEBYVE.js.map +1 -0
- package/dist/{ms_MY-SKN7O6M5.js → ms_MY-EY235E7Q.js} +20 -20
- package/dist/ms_MY-EY235E7Q.js.map +1 -0
- package/dist/{pl-TYARHCLT.js → pl-ADNVRP55.js} +20 -20
- package/dist/pl-ADNVRP55.js.map +1 -0
- package/dist/{pt-72IIFLL4.js → pt-OL2DR3DO.js} +20 -20
- package/dist/pt-OL2DR3DO.js.map +1 -0
- package/dist/quickcharts.css +218 -205
- package/dist/{resources-Ch8eA1Mw.d.ts → resources-Le6flcEn.d.ts} +3 -3
- package/dist/rest-openapi.json +11 -11
- package/dist/{ru-TPH4F7IB.js → ru-V6YFEPAF.js} +20 -20
- package/dist/ru-V6YFEPAF.js.map +1 -0
- package/dist/{sv-DC5RS67Z.js → sv-TORXQVE7.js} +20 -20
- package/dist/sv-TORXQVE7.js.map +1 -0
- package/dist/{th-I6H6AOZ3.js → th-K6SJSYSY.js} +20 -20
- package/dist/th-K6SJSYSY.js.map +1 -0
- package/dist/theme-manifest.json +99 -11
- package/dist/{tr-7FSUVCNW.js → tr-4AEM66KW.js} +20 -20
- package/dist/tr-4AEM66KW.js.map +1 -0
- package/dist/{vi-DLRPHLLH.js → vi-YU5XCEUU.js} +20 -20
- package/dist/vi-YU5XCEUU.js.map +1 -0
- package/dist/{zh-ZXIQBBEP.js → zh-QXKRYHU5.js} +20 -20
- package/dist/zh-QXKRYHU5.js.map +1 -0
- package/dist/{zh_TW-TYNUJU7U.js → zh_TW-AYPDKG4Q.js} +20 -20
- package/dist/zh_TW-AYPDKG4Q.js.map +1 -0
- package/package.json +1 -1
- package/dist/ar-G7MVXRE4.js.map +0 -1
- package/dist/ca_ES-EUSRU4VG.js.map +0 -1
- package/dist/chunk-KFBQSPNA.js.map +0 -1
- package/dist/chunk-RDAK5MUM.js.map +0 -1
- package/dist/de-2SVTG25S.js.map +0 -1
- package/dist/es-UDHZM4W7.js.map +0 -1
- package/dist/fr-35VN4G4S.js.map +0 -1
- package/dist/he_IL-JPZMCURP.js.map +0 -1
- package/dist/id_ID-XSTFHHJQ.js.map +0 -1
- package/dist/it-EIY6JESQ.js.map +0 -1
- package/dist/ja-42N4XRCW.js.map +0 -1
- package/dist/ko-3VRPTIP3.js.map +0 -1
- package/dist/ms_MY-SKN7O6M5.js.map +0 -1
- package/dist/pl-TYARHCLT.js.map +0 -1
- package/dist/pt-72IIFLL4.js.map +0 -1
- package/dist/ru-TPH4F7IB.js.map +0 -1
- package/dist/sv-DC5RS67Z.js.map +0 -1
- package/dist/th-I6H6AOZ3.js.map +0 -1
- package/dist/tr-7FSUVCNW.js.map +0 -1
- package/dist/vi-DLRPHLLH.js.map +0 -1
- package/dist/zh-ZXIQBBEP.js.map +0 -1
- package/dist/zh_TW-TYNUJU7U.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# @trdrs/quickcharts
|
|
2
2
|
|
|
3
|
-
Quick Charts is a charting library
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
or hosting: the host supplies data and storage, and the chart draws.
|
|
3
|
+
Quick Charts is a datafeed-driven charting library with drawings, indicators, layouts and replay.
|
|
4
|
+
It draws over the datafeed and the storage you supply: the chart consumes the `ChartDatafeed`
|
|
5
|
+
interface, so your feed drives it without a change to the chart.
|
|
7
6
|
|
|
8
7
|
## Install
|
|
9
8
|
|
|
@@ -21,8 +20,8 @@ runtime. Use default peer resolution; do not suppress peer checks.
|
|
|
21
20
|
Quick Charts is licensed under the Apache License 2.0: see `LICENSE` and `NOTICE`. Because the
|
|
22
21
|
renderer is *your* dependency, its own Apache-2.0 NOTICE
|
|
23
22
|
obligations attach to **your** bundle: `THIRD-PARTY-NOTICES.md` in this package spells out exactly
|
|
24
|
-
what to carry and how.
|
|
25
|
-
|
|
23
|
+
what to carry and how. An application composes its own features on the chart through the extension
|
|
24
|
+
seam below.
|
|
26
25
|
|
|
27
26
|
Quickstart, the smallest working chart (see [The widget](#the-widget) for the full options). The
|
|
28
27
|
stylesheet import is not optional: it carries the chart's layout as well as its look, and without it
|
|
@@ -44,9 +43,8 @@ await widget.ready()
|
|
|
44
43
|
Implement `ChartDatafeed` (see `datafeed.ts`). Required methods: `search`, `resolve`, `history`,
|
|
45
44
|
`subscribeBars`. Optional: `serverTime` (countdown skew correction) and `config` (a feed-level
|
|
46
45
|
capability declaration, described [below](#capability-declaration-config-optional)). The datafeed serves
|
|
47
|
-
symbol metadata, bars and bar updates
|
|
48
|
-
|
|
49
|
-
source.
|
|
46
|
+
symbol metadata, bars and bar updates; your host serves quotes (last, change, volume, the top of
|
|
47
|
+
book) to its own consumers from its own source.
|
|
50
48
|
|
|
51
49
|
When `appearance.countdown` is enabled, a live streaming time bar replaces the native last-value
|
|
52
50
|
label with one price-and-time label. The price uses the resolved symbol formatter. Replay, stale,
|
|
@@ -176,8 +174,8 @@ export const feed: ChartDatafeed = {
|
|
|
176
174
|
```
|
|
177
175
|
|
|
178
176
|
Declare only what is **true**. Absent method / absent field / empty list = unconstrained, so a feed that
|
|
179
|
-
serves any
|
|
180
|
-
The UDF adapter declares automatically from the server's own `/config` (and only ever declares
|
|
177
|
+
serves any timeframe must not declare a finite `resolutions` list, because the widget then enforces
|
|
178
|
+
it. The UDF adapter declares automatically from the server's own `/config` (and only ever declares
|
|
181
179
|
timeframes it would actually serve).
|
|
182
180
|
|
|
183
181
|
## The UDF on-ramp
|
|
@@ -213,10 +211,11 @@ What the adapter honors of the protocol:
|
|
|
213
211
|
|
|
214
212
|
Symbology is the set of display facts that decide how a market's prices are written. Your datafeed
|
|
215
213
|
owns them (`resolve` answers with `SymbolInfo`), and one package formatter uses them everywhere a
|
|
216
|
-
price appears: the price scale, the crosshair and last-price labels, the legend, the
|
|
217
|
-
every drawing label,
|
|
218
|
-
from the size of the price, so the same market reads at the same width on every surface.
|
|
219
|
-
that declares its own precision keeps it; every other value writes through the symbol
|
|
214
|
+
price appears: the price scale, the crosshair and last-price labels, the legend, the context menu,
|
|
215
|
+
every drawing label, indicator scales, and the extension seam. Precision comes from the symbol,
|
|
216
|
+
never from the size of the price, so the same market reads at the same width on every surface. An
|
|
217
|
+
indicator that declares its own precision keeps it; every other value writes through the symbol
|
|
218
|
+
formatter.
|
|
220
219
|
|
|
221
220
|
`PriceFormat` expresses every supported form in five facts:
|
|
222
221
|
|
|
@@ -303,8 +302,8 @@ symbolNames({ symbol: 'NASDAQ:AAPL', name: 'Apple Inc', exchange: 'NASDAQ', type
|
|
|
303
302
|
symbolNames('HYPERLIQUID:ETH').mark // 'ETH', before the symbol resolves
|
|
304
303
|
```
|
|
305
304
|
|
|
306
|
-
Symbology is display truth
|
|
307
|
-
|
|
305
|
+
Symbology is display truth: how the chart writes a market's prices. Execution facts, such as lot
|
|
306
|
+
size and pip value, stay with your trading integration, and its execution grid can differ from the
|
|
308
307
|
chart's display grid.
|
|
309
308
|
|
|
310
309
|
If your data comes from a UDF server, map its `/symbols` answer without collapsing the facts:
|
|
@@ -322,14 +321,14 @@ info?.dataStatus // 'streaming'
|
|
|
322
321
|
|
|
323
322
|
Quick Charts persists two different kinds of thing, and the difference decides where each one lives.
|
|
324
323
|
|
|
325
|
-
Saved charts, layouts, drawing documents and templates are **entities**: a
|
|
324
|
+
Saved charts, layouts, drawing documents and templates are **entities**: a viewer names them, opens
|
|
326
325
|
them in two tabs, and can lose them. They have identity and versions, and they live on the
|
|
327
326
|
save/load adapter you supply. Viewer preferences (the last symbol and timeframe, the scale mode, the
|
|
328
327
|
replay speed) are **flat settings**: they need no identity, and they live on the small `ChartStorage`
|
|
329
328
|
port beside it.
|
|
330
329
|
|
|
331
330
|
The chart never writes on its own. It tells you when a save would be worth making, through the
|
|
332
|
-
`saveNeeded` event, and it runs a write only when a
|
|
331
|
+
`saveNeeded` event, and it runs a write only when a viewer asks for one through the built-in UI or
|
|
333
332
|
when you call a save method yourself.
|
|
334
333
|
|
|
335
334
|
### What a saved entity carries
|
|
@@ -367,7 +366,7 @@ changes, and a menu with Save, an Autosave switch, Make a copy, Rename, Download
|
|
|
367
366
|
new layout, the layouts used most recently, and Open layout: the Layouts dialog, with search, a sort
|
|
368
367
|
by name or by date modified, a star that keeps a layout at the top, and delete behind a
|
|
369
368
|
confirmation. The sort and the stars are the viewer's, kept in the widget's `ChartStorage`. Each
|
|
370
|
-
saved layout is listed by the market and
|
|
369
|
+
saved layout is listed by the market and timeframe its active chart showed when
|
|
371
370
|
it was saved, or by its age where the store kept neither.
|
|
372
371
|
|
|
373
372
|
Every row runs a command (`widget.layout.save`, `rename`, `load`, `open`, `delete`, `create`, `autosave`,
|
|
@@ -417,7 +416,7 @@ const adapter = memorySaveLoadAdapter()
|
|
|
417
416
|
const controller = new AbortController()
|
|
418
417
|
|
|
419
418
|
await adapter.layouts.list(controller.signal)
|
|
420
|
-
await adapter.templates('
|
|
419
|
+
await adapter.templates('indicator').list()
|
|
421
420
|
const context = { version: 1, kind: 'symbol-global', symbol: 'ESZ2026' } as const
|
|
422
421
|
await adapter.drawings(context).create(emptyDrawingDocument(context))
|
|
423
422
|
|
|
@@ -428,8 +427,9 @@ outcome.kind // 'not-found'
|
|
|
428
427
|
|
|
429
428
|
An aborted call rejects with an error named `AbortError` and changes nothing.
|
|
430
429
|
|
|
431
|
-
`memorySaveLoadAdapter`
|
|
432
|
-
ephemeral embed, and implement the same contract over your
|
|
430
|
+
`memorySaveLoadAdapter` keeps everything in memory for the life of the page. Use it for tests,
|
|
431
|
+
server rendering and an intentionally ephemeral embed, and implement the same contract over your
|
|
432
|
+
own backend for durable storage.
|
|
433
433
|
|
|
434
434
|
### The REST adapter and its wire contract
|
|
435
435
|
|
|
@@ -447,10 +447,11 @@ const restSaves = createRestSaveLoadAdapter({
|
|
|
447
447
|
await restSaves.layouts.list()
|
|
448
448
|
```
|
|
449
449
|
|
|
450
|
-
You supply two values
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
450
|
+
You supply two values. `baseUrl` is where you mounted the routes, and `request` is how a request is
|
|
451
|
+
made. Authorization, cookies, CORS, retries, timeouts and tenancy stay in your request function,
|
|
452
|
+
because their consequences are yours: the adapter builds each request from `baseUrl` and hands it to
|
|
453
|
+
your function, which adds the credentials and headers your service needs, and it makes a request
|
|
454
|
+
only when you call a verb.
|
|
454
455
|
|
|
455
456
|
Your service serves four collections under that base URL. Every path below is relative to it.
|
|
456
457
|
|
|
@@ -532,8 +533,8 @@ remain in step while the menu is open; transient tile maximize does not change t
|
|
|
532
533
|
names.
|
|
533
534
|
|
|
534
535
|
Growing a layout keeps every existing chart instance and initializes each new tile from pane 0's
|
|
535
|
-
safe presentation: symbol, timeframe, style,
|
|
536
|
-
is copied per tile; replay, session state and saved-chart identity are not copied.
|
|
536
|
+
safe presentation: symbol, timeframe, style, indicators and comparisons. Mutable indicator
|
|
537
|
+
configuration is copied per tile; replay, session state and saved-chart identity are not copied.
|
|
537
538
|
|
|
538
539
|
`load` answers rather than rejecting. Five kinds, told apart without reading a message:
|
|
539
540
|
|
|
@@ -587,13 +588,13 @@ switch (opened.kind) {
|
|
|
587
588
|
A load that a later load supersedes is `cancelled`, with no signal of your own involved, and only
|
|
588
589
|
the later load binds. This includes a later load begun synchronously by a chart callback during
|
|
589
590
|
application. A widget disposed while the store is still answering answers the same way. That is
|
|
590
|
-
what keeps a slow answer for the chart a
|
|
591
|
+
what keeps a slow answer for the chart a viewer has left from landing on the chart they are looking
|
|
591
592
|
at.
|
|
592
593
|
|
|
593
594
|
Where a body fails after part of it has been applied, the chart puts back the content it held. If
|
|
594
595
|
that fails too the chart is holding neither, and it stops saving: `saveLoad.notSaving()` reports it,
|
|
595
596
|
`save` answers `not-saving` and writes nothing, the built-in layouts menu says so on the toolbar and
|
|
596
|
-
stands its Save and its autosave down, and the sentence for the
|
|
597
|
+
stands its Save and its autosave down, and the sentence for the viewer arrives on `saveConflict`.
|
|
597
598
|
|
|
598
599
|
Saving starts again when a load lands, and only then: a whole content out of the store, applied in
|
|
599
600
|
full, under the resource it came from. `save(name, { asNew: true })` stays available throughout, and
|
|
@@ -623,7 +624,7 @@ Complete recovery requires every typed appearance leaf with a valid concrete col
|
|
|
623
624
|
in combined mode, a drawings array. Separate-mode chart content does not need that array.
|
|
624
625
|
|
|
625
626
|
Hydration and rollback do not emit `saveNeeded` for their own storage writes. An event already
|
|
626
|
-
queued by a
|
|
627
|
+
queued by a viewer's edit is preserved. A late save result does not replace a newer load's binding;
|
|
627
628
|
the built-in commands announce a saved layout only while that result is still current.
|
|
628
629
|
|
|
629
630
|
### Drawings: combined or separate
|
|
@@ -676,18 +677,18 @@ tile.
|
|
|
676
677
|
|
|
677
678
|
### Templates
|
|
678
679
|
|
|
679
|
-
`adapter.templates(kind)` is a store per kind: `'
|
|
680
|
-
drawing tool's look, and `'palette'` for chart appearance. A drawing template carries the `tool`
|
|
681
|
-
belongs to, because a trend-line template means nothing on a rectangle. Their content is opaque,
|
|
680
|
+
`adapter.templates(kind)` is a store per kind: `'indicator'` for indicator settings, `'drawing'` for
|
|
681
|
+
a drawing tool's look, and `'palette'` for chart appearance. A drawing template carries the `tool`
|
|
682
|
+
it belongs to, because a trend-line template means nothing on a rectangle. Their content is opaque,
|
|
682
683
|
like a chart's.
|
|
683
684
|
|
|
684
|
-
###
|
|
685
|
+
### Viewer settings
|
|
685
686
|
|
|
686
687
|
The widget keeps the viewer's flat preferences in a `ChartStorage`. Every key is an opaque string in
|
|
687
688
|
the `quickcharts.` namespace, and every value is an opaque string; a store routes or scopes them and
|
|
688
|
-
|
|
689
|
-
per device or per
|
|
690
|
-
|
|
689
|
+
treats both as opaque. The default is an in-memory store that lasts the page; supply your own to
|
|
690
|
+
keep them per device or per viewer. A browser store is a few lines you write and own, so where
|
|
691
|
+
preferences live stays your decision:
|
|
691
692
|
|
|
692
693
|
```ts
|
|
693
694
|
import { memoryChartStorage, type ChartStorage } from '@trdrs/quickcharts'
|
|
@@ -710,7 +711,7 @@ live on the saved-resource adapter above, never here.
|
|
|
710
711
|
A conflict means someone else wrote the same entity after the copy on screen was read. The chart
|
|
711
712
|
never resolves that by overwriting. It reports the case with a sentence from its own catalog and the
|
|
712
713
|
ref that stands now, and leaves the decision to you: reload and lose the local edit, save a copy
|
|
713
|
-
under a new name, or show the
|
|
714
|
+
under a new name, or show the viewer both.
|
|
714
715
|
|
|
715
716
|
Reach the report in two places. A verb you called answers a typed `conflict` outcome. A write the
|
|
716
717
|
widget made for the viewer, through the built-in UI or its own drawing sync, arrives on the
|
|
@@ -863,7 +864,7 @@ widget.dispose() // teardown; every handle and subscription is inert afterwards
|
|
|
863
864
|
The widget paints the main series and volume, applies live updates by the bar rules above, pages
|
|
864
865
|
older history in as the viewer scrolls left (stopping at the feed's `noData`), persists the viewer's
|
|
865
866
|
preferences through `ChartStorage`, runs configured indicator instances through the manifest
|
|
866
|
-
pipeline, and mounts the drawing layer, the legend and the
|
|
867
|
+
pipeline, and mounts the drawing layer, the legend and the context menu.
|
|
867
868
|
|
|
868
869
|
When older main or comparison history extends the renderer's shared timeline, the chart retains
|
|
869
870
|
the latest fractional logical window, spacing and permitted off-data padding. Its compensating
|
|
@@ -875,7 +876,7 @@ the maintenance span or either endpoint. Public range methods and built-in navig
|
|
|
875
876
|
enter that same epoch, so every supported navigation door has the same precedence.
|
|
876
877
|
|
|
877
878
|
`toolbarContainer` optionally supplies a separate element for the top toolbar, such as a space
|
|
878
|
-
between a host's navigation and
|
|
879
|
+
between a host's navigation and its other controls. The widget appends one package-owned, themed
|
|
879
880
|
child there; it never clears or styles the supplied element. The toolbar follows the widget's
|
|
880
881
|
theme, language and active chart, and its child is removed at disposal. The toolbar's menus and
|
|
881
882
|
dialogs open in the widget's layer on the document body, at viewport coordinates, so each menu
|
|
@@ -885,12 +886,13 @@ toolbar available in fullscreen. Without `toolbarContainer`, the toolbar sits ab
|
|
|
885
886
|
part of the default composition. When horizontal space is limited, the toolbar scrolls without
|
|
886
887
|
dropping controls from the keyboard order.
|
|
887
888
|
|
|
888
|
-
`drawingToolbarContainer` supplies separate host space for a single drawing
|
|
889
|
+
`drawingToolbarContainer` supplies separate host space for a single drawing toolbar. The package
|
|
889
890
|
fills that space's height with its own themed child, follows the active chart when a layout
|
|
890
|
-
changes, and removes the child on disposal. An external
|
|
891
|
-
canvas; without the option, each chart keeps its internal
|
|
892
|
-
Drawing flyouts stay beside the external
|
|
893
|
-
pane. Switching charts closes an open flyout before the
|
|
891
|
+
changes, and removes the child on disposal. An external drawing toolbar leaves the full chart width
|
|
892
|
+
for the canvas; without the option, each chart keeps its internal drawing toolbar and reserved
|
|
893
|
+
leading column. Drawing flyouts stay beside the external drawing toolbar within the widget's bounds
|
|
894
|
+
regardless of the active pane. Switching charts closes an open flyout before the drawing toolbar
|
|
895
|
+
targets the newly active chart.
|
|
894
896
|
Include this container in the custom fullscreen target alongside the chart and top toolbar.
|
|
895
897
|
|
|
896
898
|
### Widget and chart
|
|
@@ -918,14 +920,277 @@ function drive(widget: ChartWidget): void {
|
|
|
918
920
|
}
|
|
919
921
|
```
|
|
920
922
|
|
|
921
|
-
###
|
|
923
|
+
### Chart styles
|
|
922
924
|
|
|
923
|
-
`candles`, `hollow`, `bars`, `line`, `area`, `baseline`, `stepline`, listed
|
|
924
|
-
|
|
925
|
-
|
|
925
|
+
`candles`, `hollow`, `bars`, `line`, `area`, `baseline`, `stepline`, listed as `CHART_STYLES`. A
|
|
926
|
+
style switch is presentation: nothing refetches, and the loaded bars, indicators, drawings,
|
|
927
|
+
comparisons, scale and visible range all survive it. Four of the seven are value-shaped
|
|
926
928
|
(`valueShaped(style)`), which is the one predicate a host branches on when it renders open, high and
|
|
927
929
|
low values of its own.
|
|
928
930
|
|
|
931
|
+
By default a chart offers all seven styles, and the style picker groups them by family. `styles`
|
|
932
|
+
names the styles you offer, in the order the picker lists them, and `style` must be one of them:
|
|
933
|
+
|
|
934
|
+
```ts
|
|
935
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
936
|
+
|
|
937
|
+
declare const datafeed: ChartDatafeed
|
|
938
|
+
|
|
939
|
+
// Candles, line and area, in this order, opening on the line.
|
|
940
|
+
createChart({ container, datafeed, styles: ['candles', 'line', 'area'], style: 'line' })
|
|
941
|
+
|
|
942
|
+
// Every style and no picker: a control of your own runs the chart.style.<id> commands.
|
|
943
|
+
createChart({ container, datafeed, ui: { topBar: { styles: false } } })
|
|
944
|
+
|
|
945
|
+
// Candles and line, with no picker.
|
|
946
|
+
createChart({ container, datafeed, styles: ['candles', 'line'], ui: { topBar: { styles: false } } })
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
A style you leave out has no `chart.style.<id>` command, so no menu, shortcut or host control
|
|
950
|
+
reaches it, and `setStyle` ignores it. With one style offered the picker is not shown, since there
|
|
951
|
+
is nothing to choose. `ui.topBar.styles: false` hides the picker on its own terms, so you can
|
|
952
|
+
restrict the set, hide the picker, do both, or do neither. A saved layout, saved chart or stored
|
|
953
|
+
preference that names a style you left out opens on the first style in `styles`, and the rest of it
|
|
954
|
+
restores. An empty list, an id outside `CHART_STYLES`, a repeated id and a `style` outside the list
|
|
955
|
+
are setup errors that `createChart` throws.
|
|
956
|
+
|
|
957
|
+
### Offered timeframes
|
|
958
|
+
|
|
959
|
+
By default a chart offers the 26 presets (`TIMEFRAME_PRESETS`) and lets the viewer compose custom
|
|
960
|
+
timeframes. `timeframes` names the timeframe tokens you offer, presets or any other token the
|
|
961
|
+
grammar reads, and `timeframe` must be one of them. The order you give does not matter: the picker
|
|
962
|
+
lists each unit's group smallest first. `customTimeframes: false` keeps the presets and removes
|
|
963
|
+
custom timeframes:
|
|
964
|
+
|
|
965
|
+
```ts
|
|
966
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
967
|
+
|
|
968
|
+
declare const datafeed: ChartDatafeed
|
|
969
|
+
|
|
970
|
+
// Four timeframes, one of them beyond the presets, opening on five minutes.
|
|
971
|
+
createChart({ container, datafeed, timeframes: ['2m', '5m', '1h', '1d'], timeframe: '5m' })
|
|
972
|
+
|
|
973
|
+
// Every preset and no custom timeframes.
|
|
974
|
+
createChart({ container, datafeed, customTimeframes: false })
|
|
975
|
+
|
|
976
|
+
// Two timeframes and no picker: a control of your own runs the chart.timeframe.<token> commands.
|
|
977
|
+
createChart({ container, datafeed, timeframes: ['1m', '5m'], ui: { topBar: { timeframes: false } } })
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
A preset you leave out has no `chart.timeframe.<token>` command, and `chart.timeframe.set` and
|
|
981
|
+
`setTimeframe` ignore any token you leave out. The picker shows only what you list: a group with
|
|
982
|
+
no listed token is not drawn, a listed token beyond the presets sits in its unit's group without a
|
|
983
|
+
delete, and there is no custom-timeframe composer. The viewer's saved chips show where you list
|
|
984
|
+
them. When you list none of them, the chips are your five smallest timeframes, and the viewer's
|
|
985
|
+
stored chips stay stored for a chart that offers them. With one timeframe offered the picker is not
|
|
986
|
+
shown. `ui.topBar.timeframes: false` hides the picker on its own terms. A range preset whose
|
|
987
|
+
timeframe you leave out reads its span at your nearest coarser timeframe, or at your largest when
|
|
988
|
+
none is coarser. Within what you offer, the feed's `resolutions` and the symbol's
|
|
989
|
+
`supportedResolutions` still apply: a chip the feed or the symbol does not serve is disabled, and
|
|
990
|
+
the picker's list leaves its row out.
|
|
991
|
+
|
|
992
|
+
A saved layout, saved chart or stored preference that names a timeframe you left out opens on your
|
|
993
|
+
smallest timeframe, and the rest of it restores. With `customTimeframes: false` a stored custom
|
|
994
|
+
token opens on `1m`. An empty list, a token the grammar cannot read, a repeated token, a
|
|
995
|
+
`timeframe` or `layout.charts[].timeframe` outside what you offer, and `customTimeframes: true`
|
|
996
|
+
beside a list are setup errors that `createChart` throws. `customTimeframes` is the switch, and
|
|
997
|
+
`preferences.customTimeframes` is the list of custom tokens a first-run viewer starts with.
|
|
998
|
+
|
|
999
|
+
### Offered layouts
|
|
1000
|
+
|
|
1001
|
+
By default a chart offers all 55 arrangements (`ARRANGEMENTS`) and lets the viewer change every
|
|
1002
|
+
sync switch. `layouts` names the arrangement codes you offer, and the layout opens on
|
|
1003
|
+
`layout.arrangement` or else on the first code you list. `layoutSync` names the sync switches the
|
|
1004
|
+
viewer may change:
|
|
1005
|
+
|
|
1006
|
+
```ts
|
|
1007
|
+
import { createChart, type ChartDatafeed, type ChartSaveLoadAdapter } from '@trdrs/quickcharts'
|
|
1008
|
+
|
|
1009
|
+
declare const datafeed: ChartDatafeed
|
|
1010
|
+
declare const saveLoad: ChartSaveLoadAdapter
|
|
1011
|
+
|
|
1012
|
+
// A single chart: no layout setup menu, and no re-tile from any door.
|
|
1013
|
+
createChart({ container, datafeed, layouts: ['s'] })
|
|
1014
|
+
|
|
1015
|
+
// One chart, two side by side or a 2x2 grid, with the crosshair always synced and symbol sync the
|
|
1016
|
+
// only switch the viewer changes.
|
|
1017
|
+
createChart({ container, datafeed, layouts: ['s', '2h', '4'], layoutSync: ['symbol'], layout: { sync: { crosshair: true } } })
|
|
1018
|
+
|
|
1019
|
+
// Every arrangement, with the layout setup menu kept and the saved-layouts menu hidden.
|
|
1020
|
+
createChart({ container, datafeed, saveLoad, ui: { topBar: { savedLayouts: false } } })
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
The layout setup menu shows only the arrangements you list, in its own rows by chart count, and
|
|
1024
|
+
leaves out a row with none of them. An arrangement you leave out has no tile, and
|
|
1025
|
+
`widget.layout.setArrangement` and its command ignore it. With one arrangement offered there is
|
|
1026
|
+
nothing to choose, so the menu is not shown, unless that arrangement holds several charts and a
|
|
1027
|
+
sync switch is offered, in which case the menu holds the switches alone. A switch you leave out holds
|
|
1028
|
+
the value `layout.sync` gives it, or off: the menu does not show it, `widget.layout.setSync` and its
|
|
1029
|
+
command leave it as it is, and a saved layout cannot change it. An empty `layoutSync` fixes every
|
|
1030
|
+
switch.
|
|
1031
|
+
|
|
1032
|
+
A saved layout whose arrangement you left out opens on the offered arrangement with the most charts
|
|
1033
|
+
not above the saved count, the first one listed winning a tie. When every offered arrangement holds
|
|
1034
|
+
more charts, it opens on the one with the fewest, and the extra panes clone the first chart as a
|
|
1035
|
+
re-tile does. The saved charts past the ones shown are not dropped. The layout carries them with the
|
|
1036
|
+
saved arrangement and its divider positions, and a save writes them back unchanged beside the shown
|
|
1037
|
+
charts as they are now: a host offering only `['s']` opens a saved 4-chart layout as one chart, and
|
|
1038
|
+
after it saves, a host offering every arrangement still opens all four. A re-tile is a new
|
|
1039
|
+
arrangement and ends the carry, as it tears down the charts it has no pane for. A saved active chart
|
|
1040
|
+
among the hidden ones leaves the first chart active, and the sync switches restore as saved, apart
|
|
1041
|
+
from those `layoutSync` leaves out. An empty `layouts`, an unknown or repeated code, a
|
|
1042
|
+
`layout.arrangement` outside the list, and an unknown or repeated sync switch are setup errors that
|
|
1043
|
+
`createChart` throws.
|
|
1044
|
+
|
|
1045
|
+
`ui.topBar.layouts: false` hides the layout setup menu and the saved-layouts menu together;
|
|
1046
|
+
`ui.topBar.layoutSetup: false` and `ui.topBar.savedLayouts: false` hide one of them, and neither
|
|
1047
|
+
shows a menu `layouts: false` hid. The saved-layouts menu saves, opens and lists layouts, so it is
|
|
1048
|
+
shown only with a layouts store (`saveLoad.layouts`). Without one, its Download chart data row joins
|
|
1049
|
+
the image menu beside Download image and Copy image, the way an export menu offers a chart as a
|
|
1050
|
+
picture or as data.
|
|
1051
|
+
|
|
1052
|
+
### Offered drawing tools
|
|
1053
|
+
|
|
1054
|
+
By default a chart offers every drawing tool. `drawingTools` names the tools you offer, by the type
|
|
1055
|
+
`access.drawingTool` and the `tool.<type>` icon ids use: a type `drawingTools.all()` lists (from
|
|
1056
|
+
`@trdrs/quickcharts/drawings`), `zoom` or `eraser`. The order you give does not matter, because the
|
|
1057
|
+
drawing toolbar keeps its own groups and sections:
|
|
1058
|
+
|
|
1059
|
+
```ts
|
|
1060
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1061
|
+
|
|
1062
|
+
declare const datafeed: ChartDatafeed
|
|
1063
|
+
|
|
1064
|
+
// Lines, levels and a ruler: no shapes, patterns, notes or glyphs.
|
|
1065
|
+
createChart({ container, datafeed, drawingTools: ['trend_line', 'horizontal_line', 'fib_retracement', 'measure'] })
|
|
1066
|
+
|
|
1067
|
+
// A chart whose viewers keep and remove the drawings on it and make nothing new.
|
|
1068
|
+
createChart({ container, datafeed, drawingTools: ['eraser'] })
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
A tool you leave out is not drawn anywhere a tool is chosen: not in its group's flyout, not as a
|
|
1072
|
+
group's face, which wears the first tool the group offers, not on the favorites bar, and not on the
|
|
1073
|
+
drawing toolbar for `measure` and `zoom`. A section or group it empties goes with it. The glyph
|
|
1074
|
+
picker's kinds are the `emoji`, `sticker` and `icon` tools, so a kind you leave out has no tab, and
|
|
1075
|
+
leaving out all three removes the glyph group. Every door that would arm it refuses:
|
|
1076
|
+
`chart.drawings.arm` answers `denied`, and `armTool`, `placeImage` and an image pasted over the
|
|
1077
|
+
chart place nothing. A copy is a new drawing, so a drawing of a tool you leave out is not copied
|
|
1078
|
+
into another: `chart.drawings.clone` and `chart.drawings.paste` are unavailable for it, `clone` and
|
|
1079
|
+
`paste` make nothing, and a Control- or Command-drag moves the drawing instead of duplicating it.
|
|
1080
|
+
Copying it to the clipboard is not refused, so it pastes on a chart that offers its tool.
|
|
1081
|
+
|
|
1082
|
+
Drawings of a tool you leave out that are already on the chart, from a saved chart or layout, a
|
|
1083
|
+
drawings document or another host, render and stay fully editable. They select, restyle through the
|
|
1084
|
+
selection's settings bar and the settings dialog, lock, hide and delete exactly as any drawing does:
|
|
1085
|
+
from the settings bar, the eraser, the remove menu and the Delete key. The eraser is always offered,
|
|
1086
|
+
listed or not, because it removes drawings rather than making them, which is why `['eraser']` is the
|
|
1087
|
+
way to offer no tool that creates one.
|
|
1088
|
+
|
|
1089
|
+
The list composes with your access policy: a tool is offered when you list it and the policy permits
|
|
1090
|
+
it, and a listed tool the policy refuses is drawn as `access.refused` says (see Refused controls).
|
|
1091
|
+
Nothing stored is rewritten: a starred tool you leave out keeps its star and returns to the favorites
|
|
1092
|
+
bar on a chart that offers it, and a group's remembered face is kept the same way.
|
|
1093
|
+
`features.drawings: false` removes drawing altogether, whatever the list says. An empty list, a type
|
|
1094
|
+
that names no tool and a repeated type are setup errors that `createChart` throws.
|
|
1095
|
+
|
|
1096
|
+
### Offered indicators
|
|
1097
|
+
|
|
1098
|
+
By default a chart offers every built-in indicator. `builtInIndicators` names the built-ins you
|
|
1099
|
+
offer, by the definition id `access.indicator` receives: an id `BUILT_IN_INDICATORS` lists, such as
|
|
1100
|
+
`sma`. The order you give does not matter, because the indicator picker keeps its own:
|
|
1101
|
+
|
|
1102
|
+
```ts
|
|
1103
|
+
import { BUILT_IN_INDICATORS, createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1104
|
+
|
|
1105
|
+
declare const datafeed: ChartDatafeed
|
|
1106
|
+
|
|
1107
|
+
// Moving averages and RSI only.
|
|
1108
|
+
createChart({ container, datafeed, builtInIndicators: ['sma', 'ema', 'rsi'] })
|
|
1109
|
+
|
|
1110
|
+
// A chart that opens with an indicator it offers.
|
|
1111
|
+
const sma = BUILT_IN_INDICATORS.find((definition) => definition.id === 'sma')!
|
|
1112
|
+
createChart({ container, datafeed, builtInIndicators: ['sma'], indicators: [{ id: 'sma-1', definition: sma }] })
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
A built-in you leave out is not listed in the indicator picker: not among the built-ins, not
|
|
1116
|
+
among the favorites, and not in search results. Every door that would add one refuses:
|
|
1117
|
+
`chart.indicators.add` (from the picker, the keyboard or your own control) answers `denied`,
|
|
1118
|
+
`indicators.add` adds nothing, `indicators.set` leaves out an instance of it the chart does not
|
|
1119
|
+
already hold, and a new pane that a re-tile adds copies the first chart's indicators without it. The
|
|
1120
|
+
chart has no verb that duplicates an indicator, so a second instance is an add like any other.
|
|
1121
|
+
|
|
1122
|
+
Instances of a built-in you leave out that are already on the chart, from a saved chart or layout,
|
|
1123
|
+
an undo step or another host sharing the store, render and stay fully editable and removable. The
|
|
1124
|
+
settings dialog, `chart.indicators.update`, the legend's eye and remove control, remove all and
|
|
1125
|
+
`indicators.remove` work on them exactly as on any instance, and an edit that would move one onto
|
|
1126
|
+
another built-in you leave out keeps it as it stands. Nothing saved is rewritten because of the
|
|
1127
|
+
list: a starred built-in you leave out keeps its star and returns to the favorites on a chart that
|
|
1128
|
+
offers it.
|
|
1129
|
+
|
|
1130
|
+
The list filters the built-ins alone. A definition of your own, whose `manifest.id` names no
|
|
1131
|
+
built-in, is never filtered, and neither is what your `indicatorPicker` source lists: its
|
|
1132
|
+
collections and rows are your content. The source is handed only the offered ids as `builtInIds`.
|
|
1133
|
+
The list composes with your access policy: a built-in is offered when you list it and the policy
|
|
1134
|
+
permits it, and a listed one the policy refuses is drawn as `access.refused` says (see Refused
|
|
1135
|
+
controls). A non-list, an empty list, an id that names no built-in, a repeated id and an
|
|
1136
|
+
`indicators` instance whose built-in you do not list are setup errors that `createChart` throws.
|
|
1137
|
+
|
|
1138
|
+
### Offered ranges
|
|
1139
|
+
|
|
1140
|
+
By default the bottom bar offers every range preset in `RANGE_PRESETS`, from `1D` to `All`.
|
|
1141
|
+
`ranges` names the presets you offer by their keys, in the order the bottom bar draws them:
|
|
1142
|
+
|
|
1143
|
+
```ts
|
|
1144
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1145
|
+
|
|
1146
|
+
declare const datafeed: ChartDatafeed
|
|
1147
|
+
|
|
1148
|
+
// A day, a week's worth of sessions and a year, in this order.
|
|
1149
|
+
createChart({ container, datafeed, ranges: ['1D', '5D', '1Y'] })
|
|
1150
|
+
|
|
1151
|
+
// No range buttons: the bottom bar keeps its clock, timezone picker and session view.
|
|
1152
|
+
createChart({ container, datafeed, ranges: [] })
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
A preset you leave out has no `chart.range.<key>` command, so no menu, shortcut or host control
|
|
1156
|
+
reaches it, `chart.range.set` ignores its key, and the bottom bar draws no button for it. An empty
|
|
1157
|
+
list offers none, which is how you keep the rest of the bottom bar without range buttons;
|
|
1158
|
+
`ui.bottomBar: false` removes the whole bar. `chart.range.set` still takes an explicit
|
|
1159
|
+
`{ from, to }` window either way. A preset whose timeframe your `timeframes` leave out reads its
|
|
1160
|
+
span at your nearest coarser timeframe, as it does without a list. A non-list, a key that names no
|
|
1161
|
+
preset and a repeated key are setup errors that `createChart` throws.
|
|
1162
|
+
|
|
1163
|
+
### Offered timezones
|
|
1164
|
+
|
|
1165
|
+
By default the timezone picker offers every zone in `TIMEZONES` and the exchange choice.
|
|
1166
|
+
`timezones` names the choices you offer: zone ids, and `exchange` (`EXCHANGE_TIMEZONE`) for the
|
|
1167
|
+
charted symbol's own zone. The picker keeps its own order (UTC, the exchange choice, then every
|
|
1168
|
+
zone by its current offset); the order you give decides only which choice comes first:
|
|
1169
|
+
|
|
1170
|
+
```ts
|
|
1171
|
+
import { createChart, EXCHANGE_TIMEZONE, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1172
|
+
|
|
1173
|
+
declare const datafeed: ChartDatafeed
|
|
1174
|
+
|
|
1175
|
+
// The exchange's own time, New York or London, opening on the exchange's time for a first-run viewer.
|
|
1176
|
+
createChart({ container, datafeed, timezones: [EXCHANGE_TIMEZONE, 'America/New_York', 'Europe/London'] })
|
|
1177
|
+
|
|
1178
|
+
// Always Tokyo time: no picker, and the clock reads Tokyo.
|
|
1179
|
+
createChart({ container, datafeed, timezones: ['Asia/Tokyo'] })
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
A choice you leave out has no `chart.timezone.<id>` command (`chart.timezone.exchange` for the
|
|
1183
|
+
exchange choice), `chart.timezone.set` and `setTimezone` ignore it, and the picker lists no row for
|
|
1184
|
+
it. With one choice offered the picker is not shown, since there is nothing to choose, and the
|
|
1185
|
+
bottom bar's clock still reads the time in that zone.
|
|
1186
|
+
|
|
1187
|
+
A chart opens on the viewer's stored choice, else on `preferences.timezone`, when you list it, and
|
|
1188
|
+
otherwise on the first choice you list. A stored choice you leave out is not rewritten: it stays
|
|
1189
|
+
stored until the viewer picks another zone, so a chart that offers it again opens on it. The
|
|
1190
|
+
exported registry is not filtered: `TIMEZONES`, `isTimezoneChoice` and `timezoneListing` answer
|
|
1191
|
+
the full set for code of your own. A non-list, an empty list, an id that names no zone and a
|
|
1192
|
+
repeated id are setup errors that `createChart` throws.
|
|
1193
|
+
|
|
929
1194
|
### Events
|
|
930
1195
|
|
|
931
1196
|
Typed maps, one per scope. Every subscription returns its unsubscribe and is inert after
|
|
@@ -981,7 +1246,9 @@ function toolbar(widget: ChartWidget): void {
|
|
|
981
1246
|
|
|
982
1247
|
A command whose one id spans many subjects declares `refuses(arg)` beside `available()`: an
|
|
983
1248
|
argument your access policy turns away answers `denied` before availability is asked, which is how
|
|
984
|
-
`chart.drawings.arm` refuses a tool.
|
|
1249
|
+
`chart.drawings.arm` refuses a tool. `onChange` fires when the registered set or a shortcut changes,
|
|
1250
|
+
and when `widget.refreshAccess()` says your policy may answer differently. A refusal is a value,
|
|
1251
|
+
never a throw: `ok`, `unavailable`,
|
|
985
1252
|
`denied`, `unknown`, or `failed` with the
|
|
986
1253
|
error. Your own commands register through the same door: a chart extension's `contributeCommands`
|
|
987
1254
|
puts them in this list with `scope: 'chart'` and its own label text.
|
|
@@ -1005,10 +1272,10 @@ over it is absent. A control you hide in `ui` is the only thing that changes. Th
|
|
|
1005
1272
|
Two presentation flags also close the one command whose only job is opening their dialog.
|
|
1006
1273
|
`ui.symbolSearch: false` removes the search dialog and `chart.symbol.search`, while
|
|
1007
1274
|
`chart.symbol.set` still changes the symbol. `ui.indicatorPicker: false` removes the indicator
|
|
1008
|
-
|
|
1275
|
+
picker and `chart.indicators.open`, while `chart.indicators.add` still adds an indicator.
|
|
1009
1276
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1277
|
+
Authorization belongs to `access`: a hidden control's command still answers to it, and a flag
|
|
1278
|
+
reaches only what the datafeed can do, so a feed with no search keeps every search door closed.
|
|
1012
1279
|
|
|
1013
1280
|
```ts
|
|
1014
1281
|
import { createChart, createUdfDatafeed } from '@trdrs/quickcharts'
|
|
@@ -1028,6 +1295,99 @@ The library reads `features` and `ui` once, when the widget is created. A key or
|
|
|
1028
1295
|
plane does not take throws a `TypeError` naming its path before anything mounts. The package's
|
|
1029
1296
|
`dist/feature-manifest.json` lists every flag in its `features` and `ui` blocks.
|
|
1030
1297
|
|
|
1298
|
+
### Refused controls
|
|
1299
|
+
|
|
1300
|
+
By default the chart's own controls draw what your access policy refuses disabled, which suits an
|
|
1301
|
+
offer the viewer can unlock. `access.refused: 'hide'` leaves it out instead, for a chart that simply
|
|
1302
|
+
does not offer it:
|
|
1303
|
+
|
|
1304
|
+
```ts
|
|
1305
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1306
|
+
|
|
1307
|
+
declare const datafeed: ChartDatafeed
|
|
1308
|
+
declare const session: { may(feature: string): boolean }
|
|
1309
|
+
|
|
1310
|
+
createChart({
|
|
1311
|
+
container,
|
|
1312
|
+
datafeed,
|
|
1313
|
+
access: {
|
|
1314
|
+
refused: 'hide',
|
|
1315
|
+
command: (id) => id !== 'chart.replay.start' || session.may('replay'),
|
|
1316
|
+
drawingTool: (tool) => tool !== 'brush' && tool !== 'highlighter',
|
|
1317
|
+
indicator: (id) => id !== 'vwap',
|
|
1318
|
+
},
|
|
1319
|
+
})
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
With `'hide'`, a refused drawing tool is not in its group's flyout, on the favorites bar or in the
|
|
1323
|
+
glyph picker; a section, a group or a favorites bar it leaves empty goes with it, and a group's face
|
|
1324
|
+
that would wear a refused tool wears the first one the group still offers. A refused indicator is
|
|
1325
|
+
not in the indicator picker. A control or menu row whose command the policy refuses is not drawn
|
|
1326
|
+
(top bar, bottom bar, menus, the drawing toolbar and the selection's bar, the navigation cluster,
|
|
1327
|
+
the replay transport, legend row controls and the context menu), and a rule left with nothing beside
|
|
1328
|
+
it goes too. A picker always keeps its current choice: the chosen style, timeframe, timezone or
|
|
1329
|
+
scale is listed even when its command is refused.
|
|
1330
|
+
|
|
1331
|
+
Only a refusal hides. A permitted command that cannot run now (nothing to undo, no bars loaded,
|
|
1332
|
+
nothing selected) is drawn disabled as before. The policy is asked whenever the chrome syncs (a
|
|
1333
|
+
change on the active chart, a change to the command registry) and whenever a menu or flyout opens,
|
|
1334
|
+
and `widget.refreshAccess()` asks it again at any moment (see A policy that changes), so a policy
|
|
1335
|
+
that follows your session moves the controls with it. Nothing stored is rewritten: a
|
|
1336
|
+
viewer's favorite keeps its star and returns to the favorites bar once you permit the tool again,
|
|
1337
|
+
and drawings and indicators already on the chart render exactly as they do under a refusal by
|
|
1338
|
+
default. The keyboard, `widget.commands` and the chart handles refuse exactly as they do with
|
|
1339
|
+
`'disable'`. A value other than `'disable'` or `'hide'` is a setup error that `createChart` throws.
|
|
1340
|
+
|
|
1341
|
+
Under either value the policy refuses creating content, never what is already on the chart or in
|
|
1342
|
+
saved content: it refuses adding, never restoring. A saved chart, a layout load, an undo or a redo,
|
|
1343
|
+
a drawings document and shared drawing storage put back every indicator and drawing they carry,
|
|
1344
|
+
those the policy refuses included, so a load followed by a save never rewrites a viewer's content
|
|
1345
|
+
without them. An indicator whose definition `access.indicator` refuses, once on the chart (added
|
|
1346
|
+
before the policy changed, or put back by a restore), renders and stays fully editable and
|
|
1347
|
+
removable:
|
|
1348
|
+
`indicators.set`, `chart.indicators.update` and the settings dialog edit it, an edit of any other
|
|
1349
|
+
indicator keeps it, and an edit that would move it, or another indicator, onto a refused definition
|
|
1350
|
+
leaves that indicator as it stands. A new instance of a refused definition is still left out by
|
|
1351
|
+
every door. A drawing whose tool `access.drawingTool` refuses selects, restyles, locks, hides and
|
|
1352
|
+
deletes as any drawing does; a copy is a new drawing, so it is not cloned, pasted or duplicated by a
|
|
1353
|
+
Control- or Command-drag, while copying it to the clipboard is not refused.
|
|
1354
|
+
|
|
1355
|
+
### A policy that changes
|
|
1356
|
+
|
|
1357
|
+
The predicates are asked live: every door that runs a command, arms a tool or adds an indicator asks
|
|
1358
|
+
at that moment, so a refusal holds from the instant your policy answers it. The chart's own controls
|
|
1359
|
+
read the policy when they sync and when a menu opens. When its answers change and nothing on the
|
|
1360
|
+
chart does (a viewer's plan changed mid-session), call `widget.refreshAccess()`:
|
|
1361
|
+
|
|
1362
|
+
```ts
|
|
1363
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
1364
|
+
|
|
1365
|
+
declare const datafeed: ChartDatafeed
|
|
1366
|
+
declare const session: { may(feature: string): boolean; onChange(listener: () => void): void }
|
|
1367
|
+
|
|
1368
|
+
const widget = createChart({
|
|
1369
|
+
container,
|
|
1370
|
+
datafeed,
|
|
1371
|
+
access: {
|
|
1372
|
+
refused: 'hide',
|
|
1373
|
+
command: (id) => id !== 'chart.replay.start' || session.may('replay'),
|
|
1374
|
+
indicator: (id) => id !== 'vwap' || session.may('vwap'),
|
|
1375
|
+
},
|
|
1376
|
+
})
|
|
1377
|
+
session.onChange(() => widget.refreshAccess())
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
Every surface that reads the policy reads it again at once: the top and bottom bars and their
|
|
1381
|
+
controls, the drawing toolbar's tools and groups, the favorites bar, the selection's bar, the
|
|
1382
|
+
navigation cluster, the replay transport and the legend's row controls, shown or left out as
|
|
1383
|
+
`access.refused` says and enabled or disabled. A menu, flyout or dialog that is open reads it too:
|
|
1384
|
+
the bars' menus, the context menu, the indicator picker and the saved layouts dialog rebuild their
|
|
1385
|
+
rows in place, and a drawing toolbar flyout (the glyph picker among them) or a selection bar panel
|
|
1386
|
+
opens again from its control, or closes when that control is now left out or disabled. Listeners of `widget.commands.onChange`
|
|
1387
|
+
hear it as well, so a control of your own can read `commands.available` again. Nothing stored
|
|
1388
|
+
changes, nothing on the chart changes, and no `saveNeeded` is raised: a refresh is a reading, never
|
|
1389
|
+
a write.
|
|
1390
|
+
|
|
1031
1391
|
### Your own interface
|
|
1032
1392
|
|
|
1033
1393
|
The widget supports three ways to present a chart, over the same behavior and the same commands.
|
|
@@ -1072,14 +1432,15 @@ The chart's own dialogs still open from their commands while its bars are hidden
|
|
|
1072
1432
|
them in the widget's layer on the document, and `widget.layout.save` asks a layout that was never
|
|
1073
1433
|
saved for its name. Hide a dialog in `ui` when your interface provides its own.
|
|
1074
1434
|
|
|
1075
|
-
The library
|
|
1076
|
-
|
|
1435
|
+
The library draws its own controls over its own renderer and keeps its internal components to
|
|
1436
|
+
itself. The class names under the widget's root are private, apart from the styling hooks listed
|
|
1077
1437
|
under Theme.
|
|
1078
1438
|
|
|
1079
1439
|
### Fullscreen and image
|
|
1080
1440
|
|
|
1081
|
-
Chart-root fullscreen fills the screen with the widget's own element
|
|
1082
|
-
|
|
1441
|
+
Chart-root fullscreen fills the screen with the widget's own element, or with the
|
|
1442
|
+
`fullscreen.target` you name; your application shell and any fit-to-container layout stay in your
|
|
1443
|
+
CSS.
|
|
1083
1444
|
|
|
1084
1445
|
```ts
|
|
1085
1446
|
import { type ChartWidget } from '@trdrs/quickcharts'
|
|
@@ -1098,24 +1459,24 @@ async function shareable(widget: ChartWidget): Promise<void> {
|
|
|
1098
1459
|
identity and the attribution you configured through `image`. Nothing is uploaded, shared or stored:
|
|
1099
1460
|
you receive a blob and decide.
|
|
1100
1461
|
|
|
1101
|
-
### Indicator
|
|
1462
|
+
### Indicator picker content
|
|
1102
1463
|
|
|
1103
|
-
The Indicators button and `commands.execute('chart.indicators.open')` open the same
|
|
1464
|
+
The Indicators button and `commands.execute('chart.indicators.open')` open the same picker.
|
|
1104
1465
|
Pass `{ collection: 'saved' }` as the command argument to open a declared host collection directly.
|
|
1105
|
-
An unknown collection falls back to the shipped catalog. Reopening an already open
|
|
1466
|
+
An unknown collection falls back to the shipped catalog. Reopening an already open picker keeps its current selection and query.
|
|
1106
1467
|
It lists the 23 shipped definitions, with search and Favorites. Add creates a new instance on the
|
|
1107
|
-
currently active chart and keeps the
|
|
1468
|
+
currently active chart and keeps the picker open. Favorites use your `ChartStorage` port.
|
|
1108
1469
|
|
|
1109
1470
|
Supply `ChartWidgetOptions.indicatorPicker` to include additional localized collections and rows
|
|
1110
|
-
in that
|
|
1471
|
+
in that picker. You supply data and opaque actions, not DOM, indicator definitions, or a renderer.
|
|
1111
1472
|
Built-in annotations can add favorite state, counts and actions; they cannot replace a definition,
|
|
1112
|
-
its name, or Add. Without a source, the
|
|
1473
|
+
its name, or Add. Without a source, the picker has only shipped built-ins and Favorites.
|
|
1113
1474
|
|
|
1114
1475
|
```ts
|
|
1115
1476
|
import type { IndicatorPickerSource } from '@trdrs/quickcharts'
|
|
1116
1477
|
|
|
1117
1478
|
const indicatorPicker: IndicatorPickerSource = {
|
|
1118
|
-
collections: [{ id: 'saved', label: 'Saved
|
|
1479
|
+
collections: [{ id: 'saved', label: 'Saved indicators' }],
|
|
1119
1480
|
async list({ collection, query, builtInIds }, signal) {
|
|
1120
1481
|
if (signal.aborted) return { kind: 'unavailable', message: 'Content unavailable.' }
|
|
1121
1482
|
return {
|
|
@@ -1134,8 +1495,10 @@ const indicatorPicker: IndicatorPickerSource = {
|
|
|
1134
1495
|
}
|
|
1135
1496
|
```
|
|
1136
1497
|
|
|
1137
|
-
The source receives the collection, query and shipped definition ids
|
|
1138
|
-
|
|
1498
|
+
The source receives the collection, query and the shipped definition ids the picker lists (only
|
|
1499
|
+
those `builtInIndicators` offers, when you name a list). Its own collections and rows are never
|
|
1500
|
+
filtered by that list. It owns filtering, paging and any
|
|
1501
|
+
service limits; the picker lists what it answers.
|
|
1139
1502
|
Collection ids `builtin` and `favorites`, and action ids `add` and `favorite`, are reserved.
|
|
1140
1503
|
Item ids cannot collide with shipped definition ids or these reserved ids. Duplicate ids,
|
|
1141
1504
|
malformed rows and missing primary actions refuse the response; the shipped catalog remains usable.
|
|
@@ -1144,7 +1507,7 @@ name a localized navigation group and use `layout: 'list'` for name/secondary-ac
|
|
|
1144
1507
|
An absent count stays blank. Omit `favorite` to omit that action on a contributed item.
|
|
1145
1508
|
|
|
1146
1509
|
A favorite action receives the requested next boolean. Successful actions refresh the current
|
|
1147
|
-
collection unless they return `close: true`. Refusals and exceptions keep the
|
|
1510
|
+
collection unless they return `close: true`. Refusals and exceptions keep the picker open with
|
|
1148
1511
|
plain text, never rendered markup. Icons reuse `ChartExtensionIcon`; invalid icons draw no glyph.
|
|
1149
1512
|
The host localizes contributed labels and messages; the chart localizes its own controls.
|
|
1150
1513
|
|
|
@@ -1159,8 +1522,9 @@ chrome is painted from it and renders nothing without it.
|
|
|
1159
1522
|
|
|
1160
1523
|
- **The top bar.** The symbol pill opens the symbol search for the active chart; the compare door
|
|
1161
1524
|
opens it in compare mode. The timeframe picker shows the saved timeframes as chips and a list of
|
|
1162
|
-
the 26 presets in five groups, each row savable as a
|
|
1163
|
-
|
|
1525
|
+
the timeframes the chart offers (by default the 26 presets in five groups), each row savable as a
|
|
1526
|
+
chip, with a composer for a custom timeframe under the unit's ceiling while custom timeframes are
|
|
1527
|
+
offered. The style picker lists the styles the chart offers. Indicators opens the picker
|
|
1164
1528
|
over the 23 built-in definitions, and the legend's gear opens the settings dialog for an instance
|
|
1165
1529
|
(inputs, style, visibility). Bar replay enters and leaves replay for the active chart; entering
|
|
1166
1530
|
asks where to begin rather than choosing a starting bar. Its starting-point menu answers with a
|
|
@@ -1168,19 +1532,20 @@ chrome is painted from it and renders nothing without it.
|
|
|
1168
1532
|
date runs `chart.replay.startFirst`: the chart pages the feed's history back to its oldest bar,
|
|
1169
1533
|
within the 20,000 bars one session holds, and starts there. Undo and
|
|
1170
1534
|
redo step back and forward through the active chart's own content, and each names the change it
|
|
1171
|
-
would move. Layout setup offers the
|
|
1172
|
-
arrangements and the five sync switches; the saved-layouts menu
|
|
1173
|
-
deletes layouts through `saveLoad.layouts` and marks
|
|
1535
|
+
would move. Layout setup offers the arrangements and sync switches the chart offers (by default
|
|
1536
|
+
the 55 arrangements and the five sync switches); the saved-layouts menu, shown with a layouts
|
|
1537
|
+
store, saves, copies, renames, opens and deletes layouts through `saveLoad.layouts` and marks
|
|
1538
|
+
unsaved changes, with an autosave switch and Download chart data.
|
|
1174
1539
|
Chart settings edits appearance, grid and session shading, the price-scale mode and the theme
|
|
1175
1540
|
mode, and its Reset defaults row runs `chart.appearance.reset`, which drops the viewer's own
|
|
1176
1541
|
appearance edits and returns the price scale to normal so the chart reads as the theme and your
|
|
1177
1542
|
constructor options paint it. `ui: { topBar: { settings: { theme: false } } }` removes its Theme
|
|
1178
1543
|
section and leaves the rest of the menu, for a host that offers the theme choice in its own
|
|
1179
|
-
settings; the widget's theme API and theme commands are untouched. Fullscreen and the image menu (Download image,
|
|
1180
|
-
the bar.
|
|
1181
|
-
- **The bottom bar.** The range presets, the clock in the display zone with the
|
|
1182
|
-
(UTC and the exchange choice first), and the session view for a
|
|
1183
|
-
regular hours.
|
|
1544
|
+
settings; the widget's theme API and theme commands are untouched. Fullscreen and the image menu (Download image, Copy image where the browser can, and
|
|
1545
|
+
Download chart data when there is no layouts store) close the bar.
|
|
1546
|
+
- **The bottom bar.** The range presets the chart offers, the clock in the display zone with the
|
|
1547
|
+
list of the timezones it offers (UTC and the exchange choice first), and the session view for a
|
|
1548
|
+
symbol that trades outside regular hours.
|
|
1184
1549
|
- **Around the charts.** A navigation cluster (zoom, scroll, reset) sits at the bottom of each
|
|
1185
1550
|
pane, and past one tile it carries a sixth control that fills the layout with the active tile or
|
|
1186
1551
|
gives the layout back, over `widget.layout.toggleMaximize`, the same verb the Alt press on a tile
|
|
@@ -1193,7 +1558,7 @@ chrome is painted from it and renders nothing without it.
|
|
|
1193
1558
|
replay entry can claim the row. Chart notices report a feed that cannot serve the symbol, an
|
|
1194
1559
|
image that could not be copied, or a save the store refused.
|
|
1195
1560
|
|
|
1196
|
-
Undo and redo step through the chart's own content: the symbol, the
|
|
1561
|
+
Undo and redo step through the chart's own content: the symbol, the timeframe, the style, the price
|
|
1197
1562
|
scale and whether it frames itself, the appearance a viewer authored, the comparisons, the
|
|
1198
1563
|
indicators and the drawings. A step is one reading of that content, so a step back puts the whole
|
|
1199
1564
|
reading back rather than reversing a single verb, and the two controls name the change they would
|
|
@@ -1273,7 +1638,8 @@ null when `ui: { topBar: false }` removes the bar, which is what to check before
|
|
|
1273
1638
|
you would have nowhere to put.
|
|
1274
1639
|
|
|
1275
1640
|
Every control acts through the command registry and reflects `commands.available`, so a command
|
|
1276
|
-
your access policy refuses renders disabled and does nothing
|
|
1641
|
+
your access policy refuses renders disabled and does nothing, or is not drawn with
|
|
1642
|
+
`access.refused: 'hide'`. The saved-layouts menu runs the
|
|
1277
1643
|
layout verbs `widget.layout.save`, `rename`, `load`, `open`, `delete`, `create` and `autosave`; what a
|
|
1278
1644
|
verb did reports through the `layout` event and what it refused through `saveConflict`. Copy image
|
|
1279
1645
|
reports through the `image` event (a refused copy falls back to a download). Each control has a flag
|
|
@@ -1298,7 +1664,9 @@ trimmed.on('saveNeeded', () => note('the layout has unsaved changes'))
|
|
|
1298
1664
|
```
|
|
1299
1665
|
|
|
1300
1666
|
The search dialog's class chips come from your datafeed's `config().classes`, named through
|
|
1301
|
-
`search.classNames`; a class you do not name wears its token. `search.
|
|
1667
|
+
`search.classNames`; a class you do not name wears its token. `search.spreads`,
|
|
1668
|
+
`search.allClasses` and `search.classSelection` choose how the classes and the spread operators are
|
|
1669
|
+
offered, as "Classes and spread operators" under Search describes. `search.scope` names what your
|
|
1302
1670
|
search is limited to at the far edge of that strip, with the mark its `mark` paints; your datafeed
|
|
1303
1671
|
decides what is found. The range presets read your
|
|
1304
1672
|
datafeed's optional `earliestBar(symbol)`: a preset deeper than the history you serve is disabled.
|
|
@@ -1329,8 +1697,8 @@ is a factory that returns a fresh `<svg>` element. An id you leave out keeps the
|
|
|
1329
1697
|
as a `TypeError` when the widget is created.
|
|
1330
1698
|
|
|
1331
1699
|
An id names what a glyph means, not one control. `settings` is the top bar's settings button, a
|
|
1332
|
-
|
|
1333
|
-
Drawing tools take `tool.<type>`, chart styles `style.<style>` and layout arrangements
|
|
1700
|
+
indicator's gear in the legend and the drawing settings bar's gear, so one drawing stands in all
|
|
1701
|
+
three. Drawing tools take `tool.<type>`, chart styles `style.<style>` and layout arrangements
|
|
1334
1702
|
`layout.<code>`, from the registries the rest of the API uses.
|
|
1335
1703
|
|
|
1336
1704
|
```ts
|
|
@@ -1380,15 +1748,16 @@ before costs that one glyph its artwork. The control draws the chart's own glyph
|
|
|
1380
1748
|
|
|
1381
1749
|
`icons` covers the chart's own controls. The emoji, sticker and icon glyphs a drawing places come
|
|
1382
1750
|
through the asset port, and a chart extension draws its rows and layers from the
|
|
1383
|
-
`ChartExtensionIcon` descriptors it contributes. The product's mark in the plot's corner is
|
|
1384
|
-
|
|
1385
|
-
|
|
1751
|
+
`ChartExtensionIcon` descriptors it contributes. The product's mark in the plot's corner is the
|
|
1752
|
+
chart's own artwork, outside `icons`. `mountContextMenu`, `openSymbolSearch`, `mountSymbolSearch`,
|
|
1753
|
+
`openTimeframePicker` and `mountTimeframePicker` take the same `icons` for a menu, search or picker
|
|
1754
|
+
you mount without a widget.
|
|
1386
1755
|
|
|
1387
1756
|
### Neutral marks
|
|
1388
1757
|
|
|
1389
1758
|
Serve `marks` and `timescaleMarks` from your datafeed and the chart draws them. A mark is a note
|
|
1390
1759
|
about a moment: its color is a semantic theme role rather than a literal, its words are yours, and
|
|
1391
|
-
the chart
|
|
1760
|
+
the chart draws it as given.
|
|
1392
1761
|
|
|
1393
1762
|
```ts
|
|
1394
1763
|
import { type BarMark, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
@@ -1426,13 +1795,13 @@ void withMarks
|
|
|
1426
1795
|
display name, venue, timeframe, OHLC and change against the previous painted close. Prices use
|
|
1427
1796
|
the symbol's formatter. Hover selects a bar; leaving restores the latest painted reading. Replay
|
|
1428
1797
|
does not expose bars beyond its cursor. Missing metadata leaves the supplied symbol unchanged.
|
|
1429
|
-
Legend and search
|
|
1430
|
-
|
|
1431
|
-
focus during value updates.
|
|
1432
|
-
symbol formatter; Volume reads bar volume using resolved volume precision. The row-list
|
|
1433
|
-
|
|
1434
|
-
across pane removal. The status control opens session details, retains an unknown-session
|
|
1435
|
-
and is hidden during replay. Price-scale chips use the same commands as `setScaleMode`.
|
|
1798
|
+
Legend and search rows wear your `symbolMark`, or a decorative monogram without one.
|
|
1799
|
+
Indicator and separate-pane comparison rows follow their renderer panes. Stable row controls
|
|
1800
|
+
retain focus during value updates. Indicator values read the first plot, using declared precision
|
|
1801
|
+
or the symbol formatter; Volume reads bar volume using resolved volume precision. The row-list
|
|
1802
|
+
toggle, indicator eye and pane collapse are independent controls. Pane restore remembers indicator
|
|
1803
|
+
identity across pane removal. The status control opens session details, retains an unknown-session
|
|
1804
|
+
state and is hidden during replay. Price-scale chips use the same commands as `setScaleMode`.
|
|
1436
1805
|
- **An interface language** (`locale`, English by default), one of the 21 the package ships.
|
|
1437
1806
|
`BUILT_IN_LOCALES` lists them for a picker: each carries its stable code, its canonical BCP 47
|
|
1438
1807
|
`tag`, its reading direction (`ar` and `he_IL` are `rtl`), and its endonym. The chart's own chrome
|
|
@@ -1524,9 +1893,9 @@ theme.resetCustom()
|
|
|
1524
1893
|
stop()
|
|
1525
1894
|
```
|
|
1526
1895
|
|
|
1527
|
-
Switching modes keeps the symbol, timeframe, visible range, drawings and
|
|
1528
|
-
has. A palette you supply is a tint rather than a replacement: a role you do not name keeps
|
|
1529
|
-
built-in value for that mode.
|
|
1896
|
+
Switching modes keeps the symbol, timeframe, visible range, drawings and indicators the chart
|
|
1897
|
+
already has. A palette you supply is a tint rather than a replacement: a role you do not name keeps
|
|
1898
|
+
its built-in value for that mode.
|
|
1530
1899
|
|
|
1531
1900
|
### Roles
|
|
1532
1901
|
|
|
@@ -1535,26 +1904,75 @@ inventory, and the built-in palettes and the generated stylesheet are built from
|
|
|
1535
1904
|
property names are private, so a color, a size or a radius is always set through a role. The
|
|
1536
1905
|
class names inside the stylesheet are private too, except the supported hooks below.
|
|
1537
1906
|
|
|
1907
|
+
### Motion
|
|
1908
|
+
|
|
1909
|
+
Every duration, timing function and motion scale the chart uses is a role in the `motion` family,
|
|
1910
|
+
so you retune the chart's motion the way you retune its colors. Both built-in palettes carry the
|
|
1911
|
+
same values:
|
|
1912
|
+
|
|
1913
|
+
| Role | Default | What it times |
|
|
1914
|
+
|---|---|---|
|
|
1915
|
+
| `motion.durationFast` | `90ms` | A state change under the pointer, such as a hover fill. |
|
|
1916
|
+
| `motion.durationBase` | `150ms` | A modal dialog and its backdrop opening and closing, and the replay row entering and leaving. |
|
|
1917
|
+
| `motion.durationModerate` | `250ms` | A control moving to its new state, such as a switch knob sliding. |
|
|
1918
|
+
| `motion.durationSlow` | `350ms` | A small mark settling, such as a disclosure caret turning or a checkbox filling. |
|
|
1919
|
+
| `motion.durationSlower` | `500ms` | A larger mark turning, such as the drawing toolbar's chevron. |
|
|
1920
|
+
| `motion.easingStandard` | `ease` | A modal dialog box, the replay row, a caret dip, a checkbox fill. |
|
|
1921
|
+
| `motion.easingOut` | `ease-out` | A modal backdrop fading, a switch knob sliding. |
|
|
1922
|
+
| `motion.easingLinear` | `linear` | A color or opacity change under the pointer. |
|
|
1923
|
+
| `motion.easingSpring` | `cubic-bezier(0.175, 0.885, 0.32, 1.275)` | A caret or chevron turning with a slight overshoot. |
|
|
1924
|
+
| `motion.scaleEnter` | `0.97` | The scale a modal dialog box grows from as it opens. |
|
|
1925
|
+
|
|
1926
|
+
Every modal dialog the chart opens (the symbol search, chart settings, the indicator browser and
|
|
1927
|
+
indicator settings, the saved-layouts browser, the name and confirm prompts, go to date, and the
|
|
1928
|
+
drawing settings, image picker and template prompts) opens with its backdrop fading in over
|
|
1929
|
+
`motion.durationBase` on `motion.easingOut`, and its box fading in and growing from
|
|
1930
|
+
`motion.scaleEnter` to full size over `motion.durationBase` on `motion.easingStandard`. It closes
|
|
1931
|
+
with the same motion reversed. A closing dialog stops taking input and returns focus at once, and the
|
|
1932
|
+
chart removes it when the same `motion.durationBase` has elapsed, so a duration you set times both
|
|
1933
|
+
the transition and the removal. A dialog opened over another moves on its own and closes first, and
|
|
1934
|
+
a dialog the chart replaces or tears down goes at once.
|
|
1935
|
+
|
|
1936
|
+
A duration takes a CSS duration such as `200ms` or `0.2s`; an easing takes any CSS timing function,
|
|
1937
|
+
such as `ease-in-out`, `steps(4)` or `cubic-bezier(0.2, 0, 0, 1)`; a scale takes a unitless factor
|
|
1938
|
+
such as `0.95`.
|
|
1939
|
+
|
|
1940
|
+
```ts
|
|
1941
|
+
import { createThemeController, type CustomThemes } from '@trdrs/quickcharts'
|
|
1942
|
+
|
|
1943
|
+
const calm: CustomThemes = {
|
|
1944
|
+
light: { 'motion.durationBase': '200ms', 'motion.scaleEnter': '0.95' },
|
|
1945
|
+
dark: { 'motion.durationBase': '200ms', 'motion.scaleEnter': '0.95' },
|
|
1946
|
+
}
|
|
1947
|
+
const theme = createThemeController({ mode: 'dark', custom: calm })
|
|
1948
|
+
note(theme.get()['motion.durationBase'])
|
|
1949
|
+
```
|
|
1950
|
+
|
|
1951
|
+
When the reader's system asks for reduced motion (`prefers-reduced-motion: reduce`), the
|
|
1952
|
+
stylesheet resolves every duration role to `0ms` and marks those declarations `!important`, so a
|
|
1953
|
+
palette you supply cannot bring motion back: dialogs, menus and the replay row open and close at
|
|
1954
|
+
once.
|
|
1955
|
+
|
|
1538
1956
|
### Cascade layers
|
|
1539
1957
|
|
|
1540
1958
|
The stylesheet declares two cascade layers and puts everything it contains in them: the built-in
|
|
1541
|
-
palettes in `
|
|
1542
|
-
declaration `!important` only where a later layer must not undo it:
|
|
1543
|
-
attribute, the
|
|
1544
|
-
search field, which shows its focus by its caret. Your first stylesheet
|
|
1545
|
-
order before any product stylesheet loads, because a layer's position is
|
|
1546
|
-
statement that names it:
|
|
1959
|
+
palettes in `quickcharts.tokens`, every recipe in `quickcharts.chart`. Nothing in it is unlayered.
|
|
1960
|
+
It marks a declaration `!important` only where a later layer or an inline palette must not undo it:
|
|
1961
|
+
an element's `hidden` attribute, the zero motion durations under a reduced-motion preference, and
|
|
1962
|
+
the missing focus ring of a search field, which shows its focus by its caret. Your first stylesheet
|
|
1963
|
+
must declare the complete order before any product stylesheet loads, because a layer's position is
|
|
1964
|
+
fixed by the first statement that names it:
|
|
1547
1965
|
|
|
1548
1966
|
```css
|
|
1549
|
-
@layer reset,
|
|
1967
|
+
@layer reset, quickcharts.tokens, quickcharts.chart, host;
|
|
1550
1968
|
```
|
|
1551
1969
|
|
|
1552
1970
|
Put your reset in `reset`. A reset left unlayered outranks every layered rule and strips the
|
|
1553
1971
|
chart's controls of their borders and grounds. Put an intentional override of a supported hook in
|
|
1554
|
-
`host`, or leave it unlayered; either wins over the chart's recipe.
|
|
1555
|
-
`
|
|
1556
|
-
|
|
1557
|
-
|
|
1972
|
+
`host`, or leave it unlayered; either wins over the chart's recipe. Layers of your own product go
|
|
1973
|
+
between `quickcharts.chart` and `host`. Inline styles the chart writes for measured geometry
|
|
1974
|
+
outrank any stylesheet rule, and a rule marked `!important` reverses layer order, so neither is a
|
|
1975
|
+
way to restyle the chart.
|
|
1558
1976
|
|
|
1559
1977
|
### Supported styling hooks
|
|
1560
1978
|
|
|
@@ -1566,9 +1984,9 @@ applying in any release. Removing a hook, a state or a customization is a breaki
|
|
|
1566
1984
|
| Hook | Purpose | States | Customization |
|
|
1567
1985
|
|---|---|---|---|
|
|
1568
1986
|
| `.qc-topbar` | The top toolbar: symbol search, timeframe, chart style, indicators, layouts, replay and the widget menus. | | background-color, border, padding, gap, box-shadow |
|
|
1569
|
-
| `.qc-drawing-toolbar` | The drawing
|
|
1987
|
+
| `.qc-drawing-toolbar` | The drawing toolbar beside the plot. | `aria-orientation`: vertical beside the plot, horizontal in a host row. | background-color, border, padding, gap, box-shadow |
|
|
1570
1988
|
| `.qc-bottombar` | The bottom bar: range shortcuts, the session clock and the timezone. | | background-color, border, padding, gap, box-shadow |
|
|
1571
|
-
| `.qc-legend` | The legend over the plot: the symbol, its reading and each
|
|
1989
|
+
| `.qc-legend` | The legend over the plot: the symbol, its reading and each indicator row. | | background-color, border, border-radius, padding, box-shadow, inset |
|
|
1572
1990
|
| `.qc-menu-panel` | A floating menu opened from a toolbar control. | `hidden`: present while the menu is closed. | background-color, border, border-radius, padding, box-shadow |
|
|
1573
1991
|
| `.qc-dialog` | A modal dialog: settings, search, layouts and the drawing editors. | `data-role`: which dialog this is, in the chart's own vocabulary. | background-color, border, border-radius, padding, box-shadow, max-width |
|
|
1574
1992
|
|
|
@@ -1600,8 +2018,8 @@ problem. Configuration errors never reach a render.
|
|
|
1600
2018
|
### Theme and appearance are two ladders
|
|
1601
2019
|
|
|
1602
2020
|
The theme palette is the broad brand surface. Chart appearance is the specific one: series colors,
|
|
1603
|
-
candle anatomy, grid visibility and
|
|
1604
|
-
affect the same pixel, appearance wins.
|
|
2021
|
+
candle anatomy, grid visibility and indicator visuals in `ChartOverrides.appearance`. Where both
|
|
2022
|
+
could affect the same pixel, appearance wins.
|
|
1605
2023
|
|
|
1606
2024
|
Theme palette precedence, lowest first:
|
|
1607
2025
|
|
|
@@ -1612,7 +2030,7 @@ Chart appearance precedence, lowest first:
|
|
|
1612
2030
|
|
|
1613
2031
|
1. the built-in appearance for the selected mode;
|
|
1614
2032
|
2. the constructor's `appearance` partial;
|
|
1615
|
-
3. restored
|
|
2033
|
+
3. restored viewer appearance;
|
|
1616
2034
|
4. runtime `chart.applyAppearance` patches.
|
|
1617
2035
|
|
|
1618
2036
|
Resetting custom palettes returns the chart to the built-in mode and leaves saved chart appearance
|
|
@@ -1624,23 +2042,24 @@ opens in those colors on any theme.
|
|
|
1624
2042
|
|
|
1625
2043
|
## Compare
|
|
1626
2044
|
|
|
1627
|
-
|
|
1628
|
-
placements, persisted in the chart content blob. `same-percent`
|
|
1629
|
-
main price scale and flips it to percent while any such
|
|
1630
|
-
back when the last one leaves); `new-scale` binds the LEFT scale with absolute
|
|
1631
|
-
axis exists only while such a
|
|
1632
|
-
clip to the main series window, so a
|
|
1633
|
-
a curated quick-add list for the compare dialog;
|
|
2045
|
+
Compare draws OTHER symbols beside a chart's own. Each comparison is managed like an indicator:
|
|
2046
|
+
from the legend, at one of three placements, persisted in the chart content blob. `same-percent`
|
|
2047
|
+
shares the main price scale and flips it to percent while any such comparison lives (the prior
|
|
2048
|
+
scale mode comes back when the last one leaves); `new-scale` binds the LEFT scale with absolute
|
|
2049
|
+
prices (the left axis exists only while such a comparison does); `new-pane` takes a pane of its
|
|
2050
|
+
own. Compared bars clip to the main series window, so a comparison never extends the time axis.
|
|
2051
|
+
`features.compareSymbols` supplies a curated quick-add list for the compare dialog;
|
|
2052
|
+
`compare.symbols()` reads it back.
|
|
1634
2053
|
|
|
1635
2054
|
The widget's standard Compare door lives in the top bar, and `ui: { topBar: { compare: false } }`
|
|
1636
2055
|
hides it. The legend header does not duplicate that door. Commands and comparison-row actions remain
|
|
1637
|
-
available when the top-bar door is hidden, and `features: { compare: false }` removes
|
|
1638
|
-
altogether. The built-in dialog's search rows add at any of the three placements, curated
|
|
1639
|
-
above results, and the ADDED section removes. Each
|
|
1640
|
-
the dialog in change-symbol mode (the pick re-keys the
|
|
1641
|
-
beside the value (% under `same-percent`, the last close
|
|
1642
|
-
each chart
|
|
1643
|
-
that chart's content.
|
|
2056
|
+
available when the top-bar door is hidden, and `features: { compare: false }` removes compare
|
|
2057
|
+
altogether. The built-in dialog's search rows add at any of the three placements, curated
|
|
2058
|
+
`compareSymbols` rows sit above results, and the ADDED section removes. Each comparison takes a
|
|
2059
|
+
legend row whose title reopens the dialog in change-symbol mode (the pick re-keys the comparison in
|
|
2060
|
+
place), with an eye and a remove beside the value (% under `same-percent`, the last close
|
|
2061
|
+
otherwise). In a layout, each chart holds its own comparisons (`widget.activeChart().compare`), and
|
|
2062
|
+
they ride the layout blob with the rest of that chart's content.
|
|
1644
2063
|
|
|
1645
2064
|
```ts
|
|
1646
2065
|
import { createChart, createUdfDatafeed } from '@trdrs/quickcharts'
|
|
@@ -1656,15 +2075,15 @@ compare.add('CL', { placement: 'new-pane' })
|
|
|
1656
2075
|
compare.setVisible('CL', false)
|
|
1657
2076
|
const active = compare.list() // [{ symbol, placement, color, visible }]
|
|
1658
2077
|
note(`NQ last: ${compare.latest('NQ') ?? '-'}`)
|
|
1659
|
-
compare.remove('NQ') // the scale mode the
|
|
2078
|
+
compare.remove('NQ') // the scale mode the viewer held comes back
|
|
1660
2079
|
```
|
|
1661
2080
|
|
|
1662
2081
|
## Timeframes
|
|
1663
2082
|
|
|
1664
|
-
A timeframe token names
|
|
1665
|
-
units are ticks (`t`), seconds (`s`), minutes (`m`), hours (`h`), days (`d`), weeks (`w`) and
|
|
1666
|
-
months (`mo`), and each unit has a ceiling (`TIMEFRAME_MAX`). A token outside the grammar parses
|
|
1667
|
-
|
|
2083
|
+
A timeframe token names how much one bar spans: a count and a unit, `1m`, `4h`, `1d`, `3mo`, `500t`.
|
|
2084
|
+
The units are ticks (`t`), seconds (`s`), minutes (`m`), hours (`h`), days (`d`), weeks (`w`) and
|
|
2085
|
+
months (`mo`), and each unit has a ceiling (`TIMEFRAME_MAX`). A token outside the grammar parses as
|
|
2086
|
+
null, and the chart never asks a feed for it.
|
|
1668
2087
|
|
|
1669
2088
|
```ts
|
|
1670
2089
|
import { formatTimeframe, isIntradayTimeframe, parseTimeframe, timeframeSeconds } from '@trdrs/quickcharts'
|
|
@@ -1772,7 +2191,7 @@ if (equity) {
|
|
|
1772
2191
|
|
|
1773
2192
|
## Ranges
|
|
1774
2193
|
|
|
1775
|
-
`RANGE_PRESETS` lists the nine range presets, each a visible span and the
|
|
2194
|
+
`RANGE_PRESETS` lists the nine range presets, each a visible span and the timeframe it reads best
|
|
1776
2195
|
at: `1D` over one-minute bars through `All` over monthly bars. `rangeAvailable` withholds a preset
|
|
1777
2196
|
deeper than the history a symbol has, and `frameRange` sets a pane's visible window for a span,
|
|
1778
2197
|
anchored on the last real bar so a future whitespace horizon never frames as empty space. The
|
|
@@ -1800,7 +2219,7 @@ host's own search surface subscribes to the same state.
|
|
|
1800
2219
|
The widget prefetches the default catalog and reuses completed pages across search, compare and
|
|
1801
2220
|
change-symbol dialogs. Each opening owns its query, class filter and pending work. Closing it
|
|
1802
2221
|
discards late results without clearing completed pages. Locale changes and widget disposal clear
|
|
1803
|
-
the widget cache. A replacement widget
|
|
2222
|
+
the widget cache. A replacement widget starts with a cache of its own.
|
|
1804
2223
|
|
|
1805
2224
|
Completed reuse holds at most 32 queries and 5,000 rows, evicting least-recent entries. Retained
|
|
1806
2225
|
multi-page results reopen without a first-page refetch. Eviction does not truncate an active
|
|
@@ -1838,6 +2257,87 @@ isSymbolPair('BTC/USD') // true
|
|
|
1838
2257
|
spreadExpression(' es - nq ') // 'ES-NQ'
|
|
1839
2258
|
```
|
|
1840
2259
|
|
|
2260
|
+
### Classes and spread operators
|
|
2261
|
+
|
|
2262
|
+
The dialog offers your datafeed's `config().classes` as a strip of chips, an All chip first, one
|
|
2263
|
+
class selected at a time and sent to your feed as `cls`. Its field offers six spread operators
|
|
2264
|
+
behind a toggle, and a query that reads as an expression leads the list with the expression row.
|
|
2265
|
+
Three `search` options change that, and a chart that sets none of them searches exactly as above.
|
|
2266
|
+
|
|
2267
|
+
- `spreads`: `false` offers no operator toggle, no operator and no expression row, and your feed
|
|
2268
|
+
receives the query exactly as typed. `{ operators }` keeps spreads on and offers only the listed
|
|
2269
|
+
operators (`SpreadOperatorId`: `division`, `subtraction`, `addition`, `multiplication`,
|
|
2270
|
+
`exponentiation`, `reciprocal`), in the listed order. Default `true`. The compare dialog never
|
|
2271
|
+
offers the operators.
|
|
2272
|
+
- `allClasses`: `false` offers no All chip. With one class at a time, the first class you declare
|
|
2273
|
+
starts selected; with several, no selection means every class. `{ label }` writes the All chip
|
|
2274
|
+
with your label. Default: the chip, with the catalog's label.
|
|
2275
|
+
- `classSelection`: `'multiple'` makes every chip a toggle, and the All chip clears the selection.
|
|
2276
|
+
Your feed receives `classes` with every selected class in declared order, and `cls` too while
|
|
2277
|
+
exactly one is selected, so a feed that reads only `cls` still narrows a single chip. Default
|
|
2278
|
+
`'single'`.
|
|
2279
|
+
|
|
2280
|
+
A `classes` entry is a class token, or a `SearchClassNode`, `{ id, children }`, whose children are
|
|
2281
|
+
narrower classes. Selecting such a class opens a second row of chips beneath the strip: its own
|
|
2282
|
+
all chip, written like the All chip, and one chip per child, named through `search.classNames`.
|
|
2283
|
+
Your feed always receives the most specific class selected: the child when one is picked, the
|
|
2284
|
+
class when none is. The dialog renders two levels; a child's own `children` are not offered, and
|
|
2285
|
+
the child narrows by its `id`. The second row is a labelled group of pressed buttons, reached by
|
|
2286
|
+
Tab after the strip, as the strip is.
|
|
2287
|
+
|
|
2288
|
+
```ts
|
|
2289
|
+
import { createChart, type ChartDatafeed, type DatafeedConfig } from '@trdrs/quickcharts'
|
|
2290
|
+
|
|
2291
|
+
declare const cryptoBase: ChartDatafeed
|
|
2292
|
+
|
|
2293
|
+
// Spot markets by quote currency, beside perpetuals.
|
|
2294
|
+
const cryptoFeed: ChartDatafeed = {
|
|
2295
|
+
...cryptoBase,
|
|
2296
|
+
async config(): Promise<DatafeedConfig> {
|
|
2297
|
+
return { classes: [{ id: 'spot', children: ['usdc', 'usdt'] }, 'perp'] }
|
|
2298
|
+
},
|
|
2299
|
+
// `cls` is 'spot' while the whole class is selected, and 'usdc' once USDC is picked under it.
|
|
2300
|
+
search: (query, options) => cryptoBase.search(query, options),
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
const crypto = createChart({
|
|
2304
|
+
container,
|
|
2305
|
+
datafeed: cryptoFeed,
|
|
2306
|
+
search: {
|
|
2307
|
+
classNames: { spot: 'Spot', usdc: 'USDC', usdt: 'USDT', perp: 'Perpetuals' },
|
|
2308
|
+
allClasses: { label: 'Every market' },
|
|
2309
|
+
spreads: { operators: ['division', 'subtraction'] },
|
|
2310
|
+
},
|
|
2311
|
+
})
|
|
2312
|
+
void crypto
|
|
2313
|
+
```
|
|
2314
|
+
|
|
2315
|
+
With several classes at once, read `classes` and fall back to `cls`:
|
|
2316
|
+
|
|
2317
|
+
```ts
|
|
2318
|
+
import { createChart, type ChartDatafeed } from '@trdrs/quickcharts'
|
|
2319
|
+
|
|
2320
|
+
declare const catalogFeed: ChartDatafeed
|
|
2321
|
+
|
|
2322
|
+
const multiFeed: ChartDatafeed = {
|
|
2323
|
+
...catalogFeed,
|
|
2324
|
+
async search(query, options = {}) {
|
|
2325
|
+
// ['future', 'option'] with two chips selected; absent with none, which is every class.
|
|
2326
|
+
const classes = options.classes ?? (options.cls ? [options.cls] : [])
|
|
2327
|
+
return myBackend.search(query, classes, options.limit, options.offset)
|
|
2328
|
+
},
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
const multi = createChart({
|
|
2332
|
+
container,
|
|
2333
|
+
datafeed: multiFeed,
|
|
2334
|
+
search: { classSelection: 'multiple', allClasses: false, spreads: false },
|
|
2335
|
+
})
|
|
2336
|
+
void multi
|
|
2337
|
+
```
|
|
2338
|
+
|
|
2339
|
+
`openSymbolSearch` and `mountSymbolSearch` take the same three options beside their `classes`.
|
|
2340
|
+
|
|
1841
2341
|
### The picker, away from a chart
|
|
1842
2342
|
|
|
1843
2343
|
A page that picks a market where no chart is mounted opens the same dialog the chart's symbol pill
|
|
@@ -1877,9 +2377,63 @@ card.dispose()
|
|
|
1877
2377
|
catalog.dispose()
|
|
1878
2378
|
```
|
|
1879
2379
|
|
|
2380
|
+
### The timeframe picker, away from a chart
|
|
2381
|
+
|
|
2382
|
+
A page that asks for a timeframe where no chart is mounted (a backtest's timeframe, an alert's
|
|
2383
|
+
timeframe) opens the list the chart's timeframe caret drops: the same unit groups, rows, labels and
|
|
2384
|
+
custom composer, built by the same code, so a timeframe reads there exactly as it reads on a chart.
|
|
2385
|
+
`openTimeframePicker` drops it from a control you own, which gets `aria-expanded` while it is open
|
|
2386
|
+
and focus back when it closes; `mountTimeframePicker` builds it bare into a box you own and
|
|
2387
|
+
position, as a card on a phone. Picking a row, or composing a custom timeframe, hands the token to
|
|
2388
|
+
`onPick`, because there is no chart here to set. The drop-down then closes; the card calls
|
|
2389
|
+
`onClose`, your cue to close its box.
|
|
2390
|
+
|
|
2391
|
+
It takes what you already give a chart. `timeframe` is the value it opens on, checked and focused,
|
|
2392
|
+
and a custom value sits in its unit's group. `timeframes` and `customTimeframes` offer exactly what
|
|
2393
|
+
they offer a chart (see Offered timeframes), with the same setup errors, thrown before anything
|
|
2394
|
+
mounts, plus one for a `timeframe` the grammar cannot read. `resolutions` and
|
|
2395
|
+
`supportedResolutions` say what your feed and the symbol serve: as in the chart's list, a row they
|
|
2396
|
+
do not serve is left out, and the composer does not offer one. `theme`, `locale` (or a prepared
|
|
2397
|
+
`i18n`) and `icons` paint it as they paint a chart, on a theme root of its own, so the stylesheet
|
|
2398
|
+
reaches it outside any chart in either mode.
|
|
2399
|
+
|
|
2400
|
+
It carries no saved chips and no stars, and keeps nothing: the chips are a chart's quick-select row,
|
|
2401
|
+
stored with the chart's preferences, and a field that asks for one timeframe has no row to put them
|
|
2402
|
+
in. A composed timeframe is handed back and not added to any list. The drop-down takes the keyboard
|
|
2403
|
+
as the chart's does (arrow keys, Home and End rove the rows; Escape and a press outside close
|
|
2404
|
+
it) and moves as every menu does.
|
|
2405
|
+
|
|
2406
|
+
```ts
|
|
2407
|
+
import { mountTimeframePicker, openTimeframePicker } from '@trdrs/quickcharts'
|
|
2408
|
+
|
|
2409
|
+
declare const timeframeField: HTMLButtonElement
|
|
2410
|
+
declare const timeframeBox: HTMLElement
|
|
2411
|
+
declare const backtest: { timeframe: string; setTimeframe(timeframe: string): void }
|
|
2412
|
+
|
|
2413
|
+
timeframeField.addEventListener('click', () => {
|
|
2414
|
+
const picker = openTimeframePicker({
|
|
2415
|
+
anchor: timeframeField,
|
|
2416
|
+
timeframe: backtest.timeframe,
|
|
2417
|
+
resolutions: ['1m', '5m', '15m', '1h', '4h', '1d'], // what the feed serves
|
|
2418
|
+
theme: { mode: 'light' },
|
|
2419
|
+
onPick: (timeframe) => backtest.setTimeframe(timeframe),
|
|
2420
|
+
})
|
|
2421
|
+
void picker // picker.close() closes it from outside; Escape and a press outside close it too
|
|
2422
|
+
})
|
|
2423
|
+
|
|
2424
|
+
const card = mountTimeframePicker({
|
|
2425
|
+
container: timeframeBox,
|
|
2426
|
+
timeframes: ['5m', '15m', '1h', '4h', '1d'],
|
|
2427
|
+
timeframe: '1h',
|
|
2428
|
+
onPick: (timeframe) => backtest.setTimeframe(timeframe),
|
|
2429
|
+
onClose: () => timeframeBox.remove(),
|
|
2430
|
+
})
|
|
2431
|
+
card.focus()
|
|
2432
|
+
```
|
|
2433
|
+
|
|
1880
2434
|
### The legend
|
|
1881
2435
|
|
|
1882
|
-
Every chart carries its own legend over its plot: an identity row naming the market, the
|
|
2436
|
+
Every chart carries its own legend over its plot: an identity row naming the market, the timeframe
|
|
1883
2437
|
and the venue, and a values row carrying the hovered bar's O H L C and its move against the previous
|
|
1884
2438
|
close. The numbers are written through that chart's own formatter, so a level in the legend is the
|
|
1885
2439
|
level its price axis writes, and they follow the pointer while the crosshair is on the plot and the
|
|
@@ -1895,10 +2449,10 @@ The `size` is the chart's, not a suggestion: the box is already laid out, and pa
|
|
|
1895
2449
|
you were given is what lines the marks up down a list. The chart ships no artwork and fetches none,
|
|
1896
2450
|
so without this every mark is the neutral monogram it draws itself.
|
|
1897
2451
|
|
|
1898
|
-
`venueMark` and `
|
|
1899
|
-
the same way: the venue a market lists on, or the data
|
|
1900
|
-
receives the name the row writes (`exchange` or `
|
|
1901
|
-
a disposer. Without them a source wears its initial on a neutral disc.
|
|
2452
|
+
`venueMark` and `dataSourceMark` paint the source at the end of every symbol search and compare
|
|
2453
|
+
row the same way: the venue a market lists on, or the data source where a row names no venue. Each
|
|
2454
|
+
receives the name the row writes (`exchange` or `dataSource`), the element and the size, and
|
|
2455
|
+
returns a disposer. Without them a source wears its initial on a neutral disc.
|
|
1902
2456
|
|
|
1903
2457
|
```ts
|
|
1904
2458
|
import { createChart, createUdfDatafeed } from '@trdrs/quickcharts'
|
|
@@ -1942,7 +2496,7 @@ re-tile, a restore. Pointing your own surface at a chart moves no chart's symbol
|
|
|
1942
2496
|
concepts stay separate: each chart keeps charting what it charts, and one of them is the one you are
|
|
1943
2497
|
looking at.
|
|
1944
2498
|
|
|
1945
|
-
Five sync toggles fan changes across the charts: `symbol`, `
|
|
2499
|
+
Five sync toggles fan changes across the charts: `symbol`, `timeframe` and `dateRange` replay a
|
|
1946
2500
|
change onto every chart, `crosshair` mirrors continuously by time, and `time` centers every chart on
|
|
1947
2501
|
a clicked moment. The whole layout serializes as ONE opaque content blob (arrangement, sync flags,
|
|
1948
2502
|
durable chart-entity identities, active chart, every chart's own content), so a saved multi-chart
|
|
@@ -2019,19 +2573,21 @@ const saved = drawings?.export() // the persistence wire format (SerializedDrawi
|
|
|
2019
2573
|
note(`${selected?.type ?? 'nothing'} selected, ${saved?.length ?? 0} drawings`)
|
|
2020
2574
|
```
|
|
2021
2575
|
|
|
2022
|
-
### The toolbar
|
|
2576
|
+
### The drawing toolbar
|
|
2023
2577
|
|
|
2024
|
-
The toolbar
|
|
2578
|
+
The drawing toolbar runs down the chart's leading edge. Its buttons are the cursor with its three
|
|
2025
2579
|
pointer modes and the eraser; seven tool groups, each opening a flyout of the group's sections
|
|
2026
2580
|
with a star on every row that lands the tool on the favorites bar; Measure and Zoom; the magnet
|
|
2027
2581
|
with its weak and strong strengths; stay in drawing mode; lock all; the eye that hides drawings,
|
|
2028
2582
|
indicators, or both; drawing sync, shown only in a layout of more than one chart; the remove menu,
|
|
2029
2583
|
which names what each row takes and carries the locked-item policy; and the favorites star. Each
|
|
2030
2584
|
group button wears the tool it last armed, and every action is a `chart.drawings.*` command
|
|
2031
|
-
through the registry: a control renders disabled
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2585
|
+
through the registry: a control renders disabled while the registry would not run its command (a
|
|
2586
|
+
tool or verb your access policy refuses, a selection verb with nothing selected, the Image tool
|
|
2587
|
+
without an asset port), and with `access.refused: 'hide'` a tool or verb the policy refuses is left
|
|
2588
|
+
out instead (see Refused controls), and a command the policy refuses answers `denied` from the
|
|
2589
|
+
toolbar as from anywhere else, including `chart.drawings.arm` for a refused tool. A tool your
|
|
2590
|
+
`drawingTools` list leaves out is not on the toolbar at all (see Offered drawing tools). An image is
|
|
2035
2591
|
placed through `chart.drawings.placeImage`, from the picker or a system-clipboard paste over the
|
|
2036
2592
|
chart. Every flyout, palette and dialog a surface opens sits inside the chart root and closes with
|
|
2037
2593
|
it, and the eye and lock all announce their state through a live region. Lock all also makes Paste
|
|
@@ -2048,7 +2604,7 @@ chosen.
|
|
|
2048
2604
|
Selecting a drawing shows the settings bar: templates, the stroke color with its opacity, the
|
|
2049
2605
|
background for tools that have one, the text color and font size for text tools, thickness and
|
|
2050
2606
|
line style, the settings gear, lock, delete, and a More menu with the stacking moves, the
|
|
2051
|
-
per-
|
|
2607
|
+
per-timeframe visibility presets, clone, copy and hide. Every edit persists at once and becomes the
|
|
2052
2608
|
tool's default for the next drawing of that type. The settings dialog opens from the gear with
|
|
2053
2609
|
Inputs, Style, Text, Table, Coordinates and Visibility pages as the tool has them; its edits apply
|
|
2054
2610
|
live, Cancel restores the drawing, and Ok commits the session as one edit.
|
|
@@ -2057,7 +2613,7 @@ Text-bearing tools open an inline editor where the text sits, in the drawing's o
|
|
|
2057
2613
|
placement committed empty is removed; an existing note committed empty is blanked. Ctrl or Cmd
|
|
2058
2614
|
with Enter commits, Escape cancels, and a press on the chart commits.
|
|
2059
2615
|
|
|
2060
|
-
Templates are named setups a
|
|
2616
|
+
Templates are named setups a viewer saves from either surface and applies on demand. They and
|
|
2061
2617
|
the remembered defaults ride `ChartSaveLoadAdapter.templates('drawing')`, so a host that keeps
|
|
2062
2618
|
saved charts on a server keeps these there too. Same-tool default changes are written in issue
|
|
2063
2619
|
order, and a delayed read or failed write does not replace the newest local choice. Store work is
|
|
@@ -2085,20 +2641,25 @@ layer.armTool('rectangle')
|
|
|
2085
2641
|
layer.destroy()
|
|
2086
2642
|
```
|
|
2087
2643
|
|
|
2644
|
+
A layer you mount yourself arms whatever you arm. Its `copies` option answers whether a new drawing
|
|
2645
|
+
may be made as a copy of a drawing of a type: return false for a tool you do not offer, and clone,
|
|
2646
|
+
paste and a Control- or Command-drag duplicate make nothing for its drawings, `canPaste` answers
|
|
2647
|
+
false, and the drag moves the drawing itself.
|
|
2648
|
+
|
|
2088
2649
|
### `@trdrs/quickcharts/drawings`
|
|
2089
2650
|
|
|
2090
2651
|
Everything about drawings a host builds its own UI from is one subpath. A host that never draws
|
|
2091
2652
|
never imports any of it.
|
|
2092
2653
|
|
|
2093
2654
|
```ts
|
|
2094
|
-
import {
|
|
2655
|
+
import { buildDrawingToolbarGroups, drawingTools, parseDrawingsStore, restoreDrawings, serializeDrawingsStore } from '@trdrs/quickcharts/drawings'
|
|
2095
2656
|
|
|
2096
2657
|
// The catalog: 90 tools in 14 categories, read-only.
|
|
2097
2658
|
const trendLine = drawingTools.get('trend_line')
|
|
2098
2659
|
const fibs = drawingTools.byCategory('fibonacci')
|
|
2099
2660
|
|
|
2100
|
-
// The
|
|
2101
|
-
for (const group of
|
|
2661
|
+
// The drawing toolbar as data: seven groups, their sections, and a catalog key per heading.
|
|
2662
|
+
for (const group of buildDrawingToolbarGroups()) {
|
|
2102
2663
|
for (const section of group.sections) note(`${group.id}/${section.label}: ${section.tools.length}`)
|
|
2103
2664
|
}
|
|
2104
2665
|
|
|
@@ -2108,10 +2669,10 @@ const drawings = restoreDrawings(store['ES'] ?? [])
|
|
|
2108
2669
|
localStorage.setItem('acme.chart.drawings', serializeDrawingsStore(store))
|
|
2109
2670
|
```
|
|
2110
2671
|
|
|
2111
|
-
The subpath carries the workflow models too: what the
|
|
2112
|
-
chart-owned drawings and indicators and nothing else), the cursor modes and the two
|
|
2113
|
-
tools, the magnet policy over `magnetSnap`, the remove menu, favorites over a
|
|
2114
|
-
standing preference record, and per-tool defaults and named templates over
|
|
2672
|
+
The subpath carries the workflow models too: what the drawing toolbar's eye blanks (`HideMode`,
|
|
2673
|
+
which reaches chart-owned drawings and indicators and nothing else), the cursor modes and the two
|
|
2674
|
+
transient tools, the magnet policy over `magnetSnap`, the remove menu, favorites over a
|
|
2675
|
+
`FavoritesPort`, the standing preference record, and per-tool defaults and named templates over
|
|
2115
2676
|
`ChartSaveLoadAdapter.templates('drawing')`. Each is a pure function or a plain record, so a host
|
|
2116
2677
|
builds its own controls without reimplementing the decisions behind them.
|
|
2117
2678
|
|
|
@@ -2119,7 +2680,7 @@ What a lock refuses is the library's own. A host locks and unlocks through the d
|
|
|
2119
2680
|
reads what they answer: `chart.drawings.lock` holds one drawing where it stands,
|
|
2120
2681
|
`chart.drawings.lockAll` suspends the whole chart, and every editing command answers `ok` or
|
|
2121
2682
|
`unavailable` for the state the chart is actually in, so a control built on those outcomes says
|
|
2122
|
-
exactly what the chart will accept. The gesture list below is what a
|
|
2683
|
+
exactly what the chart will accept. The gesture list below is what a viewer meets.
|
|
2123
2684
|
|
|
2124
2685
|
```ts
|
|
2125
2686
|
import { blanks, chooseHideMode, DEFAULT_HIDE_STATE, DrawingTemplates } from '@trdrs/quickcharts/drawings'
|
|
@@ -2154,9 +2715,9 @@ note(IMAGE_ACCEPT)
|
|
|
2154
2715
|
|
|
2155
2716
|
What to know:
|
|
2156
2717
|
|
|
2157
|
-
- **The subpath is a subset
|
|
2158
|
-
mutable registry stay inside the library.
|
|
2159
|
-
|
|
2718
|
+
- **The subpath is a deliberate subset.** The drawing classes, the model store and the
|
|
2719
|
+
mutable registry stay inside the library. A host chooses among the catalog's tools through
|
|
2720
|
+
`drawingTools` and the access policy.
|
|
2160
2721
|
- **Every tool places.** Fixed-anchor tools place by press-drag-release or click then click; an
|
|
2161
2722
|
instant tool (the position tools) lands whole from one press; a multipoint tool adds a point per
|
|
2162
2723
|
click until a double-click ends the run; a freehand tool captures the drag as a stroke; a
|
|
@@ -2177,13 +2738,13 @@ What to know:
|
|
|
2177
2738
|
so anchors never drift apart); grab an anchor handle to reshape; hold Shift to constrain a
|
|
2178
2739
|
two-point placement or an anchor drag to 45 degree rays; Ctrl-drag duplicates; the magnet pulls
|
|
2179
2740
|
a placed or dragged anchor onto the bar's own open, high, low or close; a locked drawing
|
|
2180
|
-
selects, takes the Delete and Clone a
|
|
2741
|
+
selects, takes the Delete and Clone a viewer asks for by name, and refuses a move, a resize, a
|
|
2181
2742
|
text edit, the eraser and a Ctrl-drag copy; lock all suspends every edit, Delete and Clone
|
|
2182
2743
|
included, until it is released.
|
|
2183
2744
|
|
|
2184
2745
|
## Extensions
|
|
2185
2746
|
|
|
2186
|
-
An extension is host code that draws on the chart, adds rows to its
|
|
2747
|
+
An extension is host code that draws on the chart, adds rows to its context menu, offers commands,
|
|
2187
2748
|
and stores viewer state in the chart's own save blob. The chart attaches it at mount, pushes its
|
|
2188
2749
|
changes at it, and takes it down at teardown, along with everything it drew.
|
|
2189
2750
|
|
|
@@ -2237,9 +2798,9 @@ What to know:
|
|
|
2237
2798
|
layer on the document body, painted with the widget's theme: a menu or an editor mounted there at
|
|
2238
2799
|
viewport coordinates stands over every pane and over whatever the page stacks around the widget,
|
|
2239
2800
|
which the pane's own overlay cannot promise. `ctx.symbolTitle()` is the name symbology gives the
|
|
2240
|
-
charted market, the name the legend and the
|
|
2801
|
+
charted market, the name the legend and the context menu print; `ctx.chart.symbol()` stays the
|
|
2241
2802
|
ticker an action is sent for. `ctx.painters` carries the mark painters you supplied as `.symbol`,
|
|
2242
|
-
`.venue` and `.
|
|
2803
|
+
`.venue` and `.dataSource`, so a surface that names a market paints it with the same mark the
|
|
2243
2804
|
legend and the search rows wear rather than shipping artwork of its own; a painter you did not
|
|
2244
2805
|
supply is `null`, and the surface writes the name alone.
|
|
2245
2806
|
- **A contributed row may bring its own glyph.** `ChartExtensionMenuItem.icon` is a
|
|
@@ -2254,19 +2815,20 @@ What to know:
|
|
|
2254
2815
|
default), an action on the price the pointer landed on, placed under Copy price and Paste; or
|
|
2255
2816
|
`view`, a switch over what the chart shows, placed under the remove rows. Rows keep the order
|
|
2256
2817
|
they were contributed in within their group.
|
|
2257
|
-
- **A drawn layer can join the
|
|
2258
|
-
lists the layer in the eye's menu after the
|
|
2259
|
-
"Hide all", which blanks it with them. `label`
|
|
2260
|
-
the two marks the eye wears while the layer is
|
|
2261
|
-
the current state at contribution and on every
|
|
2262
|
-
`setHidden()` through the same eye the
|
|
2818
|
+
- **A drawn layer can join the drawing toolbar's eye.**
|
|
2819
|
+
`ctx.contributeHideLayer({ id, label, icon, apply })` lists the layer in the eye's menu after the
|
|
2820
|
+
chart's own drawings and indicators and before "Hide all", which blanks it with them. `label`
|
|
2821
|
+
carries the row's wording in both states, `icon` the two marks the eye wears while the layer is
|
|
2822
|
+
its subject, and `apply(hidden)` is called with the current state at contribution and on every
|
|
2823
|
+
change. The handle reads `hidden()`, flips `setHidden()` through the same eye the drawing toolbar
|
|
2824
|
+
drives, and `remove()` withdraws the layer.
|
|
2263
2825
|
- **A printed chord is a real binding.** `ChartExtensionMenuItem.shortcut` prints on the row and
|
|
2264
2826
|
answers to the key. The chart's dispatcher offers a press no built-in verb claims to the rows your
|
|
2265
|
-
|
|
2266
|
-
the click makes. The press acts on the tile the pointer is inside, active or
|
|
2267
|
-
tile, on a point with no readable level, while the viewer is typing, or while
|
|
2268
|
-
keyboard, the key is left to your page. A row your
|
|
2269
|
-
carries one chord.
|
|
2827
|
+
`contributeContextMenu` callback returns for the level under the pointer and runs that row's own
|
|
2828
|
+
`run`, the same call the click makes. The press acts on the tile the pointer is inside, active or
|
|
2829
|
+
not; outside every tile, on a point with no readable level, while the viewer is typing, or while
|
|
2830
|
+
a modal holds the keyboard, the key is left to your page. A row your callback withholds claims no
|
|
2831
|
+
key, and a row carries one chord.
|
|
2270
2832
|
- **A glyph is bounded, and a refusal is free.** Up to 8 shapes, each up to 2048 characters of path
|
|
2271
2833
|
data with every number in it finite, and an `outline` stroke width above 0 and up to 8. A shape
|
|
2272
2834
|
outside that is dropped and the rest of the glyph still draws; a descriptor outside it draws
|
|
@@ -2289,7 +2851,7 @@ What to know:
|
|
|
2289
2851
|
re-attaches it on every switch, so a market-scoped overlay cannot carry one market's drawing onto
|
|
2290
2852
|
another's bars.
|
|
2291
2853
|
- **A failing extension is its own problem.** A throw in `attach` drops that extension and the
|
|
2292
|
-
chart still mounts; a throw in a subscriber, a menu
|
|
2854
|
+
chart still mounts; a throw in a subscriber, a menu builder, a command or a teardown is
|
|
2293
2855
|
contained.
|
|
2294
2856
|
- **Layouts attach per chart.** A widget hands its shared options to every chart it tiles, so each
|
|
2295
2857
|
chart gets its own attachment, its own context and its own state slot.
|
|
@@ -2317,7 +2879,7 @@ Every claim in this document maps to a test or a generated artifact in the packa
|
|
|
2317
2879
|
and the tarball, and the third-party notices are held by the boundary tests and
|
|
2318
2880
|
`THIRD-PARTY-NOTICES.md`.
|
|
2319
2881
|
|
|
2320
|
-
## Versioning
|
|
2882
|
+
## Versioning
|
|
2321
2883
|
|
|
2322
2884
|
- **SemVer, enforced at the gate.** The public surface is pinned by an API-surface test (every
|
|
2323
2885
|
exported name and its runtime kind), the shipped type declarations are compiled against by a
|
|
@@ -2329,8 +2891,8 @@ Every claim in this document maps to a test or a generated artifact in the packa
|
|
|
2329
2891
|
and fields (`config` and `serverTime` are the pattern): an existing
|
|
2330
2892
|
implementation keeps compiling, and the widget treats absence as "unconstrained / not supported".
|
|
2331
2893
|
Your integration never breaks by standing still within a major.
|
|
2332
|
-
- **
|
|
2333
|
-
|
|
2334
|
-
|
|
2894
|
+
- **A major version may rename or remove public names.** Its upgrading guide in
|
|
2895
|
+
[CHANGELOG.md](CHANGELOG.md) lists each one, old to new, with how saved state carries over: what
|
|
2896
|
+
the previous major saved still opens, and the next save writes the current names.
|
|
2335
2897
|
- **The wire timeframe grammar is stable vocabulary.** `<N><unit>` with units `t s m h d w mo`.
|
|
2336
2898
|
Extensions may add units; an existing token never changes meaning.
|