react-native-vroom-chart 0.14.0 → 0.15.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/cpp/VroomChartHostObject.cpp +118 -1
- package/cpp/_core_include/vroom/vroom_chart.h +84 -0
- package/cpp/_core_src/chart.cpp +16 -0
- package/cpp/_core_src/chart.h +23 -0
- package/cpp/_core_src/chart_facade.cpp +108 -0
- package/cpp/_core_src/footprints.cpp +226 -0
- package/cpp/_core_src/footprints.h +45 -0
- package/cpp/_core_src/footprints_layout.cpp +140 -0
- package/cpp/_core_src/footprints_layout.h +94 -0
- package/cpp/_core_src/tip_pulse.h +4 -4
- package/lib/index.d.mts +135 -1
- package/lib/index.d.ts +135 -1
- package/lib/index.js +83 -3
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +83 -3
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +87 -4
- package/src/index.ts +5 -0
- package/src/jsi.d.ts +39 -0
- package/src/types.ts +5 -0
- package/src/useChartCore.ts +49 -3
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Footprints — circular badges marking where a trader entered and exited, drawn
|
|
2
|
+
// above the candle each trade fell in.
|
|
3
|
+
//
|
|
4
|
+
// A buy is a "+" in the bull color, a sell a "-" in the bear color, both glyphs in
|
|
5
|
+
// VROOM_COLOR_BADGE_TEXT. Only one badge per side per candle renders however many
|
|
6
|
+
// fills went into it; the host learns the full set through
|
|
7
|
+
// vroom_chart_footprints_at and draws its own tooltip.
|
|
8
|
+
//
|
|
9
|
+
// Bucketing and badge geometry live in footprints_layout.h so the draw and
|
|
10
|
+
// hit-test passes agree by construction.
|
|
11
|
+
|
|
12
|
+
#pragma once
|
|
13
|
+
|
|
14
|
+
#include <cstdint>
|
|
15
|
+
|
|
16
|
+
#include "vroom/vroom_chart.h"
|
|
17
|
+
|
|
18
|
+
class SkCanvas;
|
|
19
|
+
struct VroomChart;
|
|
20
|
+
|
|
21
|
+
namespace vroom {
|
|
22
|
+
struct Layout;
|
|
23
|
+
struct PriceBounds;
|
|
24
|
+
} // namespace vroom
|
|
25
|
+
|
|
26
|
+
namespace vroom::footprints {
|
|
27
|
+
|
|
28
|
+
// Draws every visible footprint badge, clipped to the price pane. `window_ms` is
|
|
29
|
+
// the visible time span, used to place each badge over its candle's slot center.
|
|
30
|
+
void draw(SkCanvas* canvas,
|
|
31
|
+
const VroomChart& chart,
|
|
32
|
+
const Layout& lay,
|
|
33
|
+
const PriceBounds& bounds,
|
|
34
|
+
int64_t window_ms);
|
|
35
|
+
|
|
36
|
+
// Hit-tests pixel (x, y) against the badges. The returned hit's `side` is -1 on a
|
|
37
|
+
// miss; when two badges overlap the nearest center wins. Derives geometry from the
|
|
38
|
+
// same layout functions as draw, so what the user sees is what they can hover.
|
|
39
|
+
::VroomFootprintHit hit_test(const VroomChart& chart,
|
|
40
|
+
const Layout& lay,
|
|
41
|
+
const PriceBounds& bounds,
|
|
42
|
+
float x,
|
|
43
|
+
float y);
|
|
44
|
+
|
|
45
|
+
} // namespace vroom::footprints
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#include "footprints_layout.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
namespace vroom::footprints {
|
|
7
|
+
|
|
8
|
+
int32_t bucket_index(const ::VroomCandle* candles, size_t count,
|
|
9
|
+
int64_t duration_ms, int64_t time_ms) {
|
|
10
|
+
if (!candles || count == 0 || duration_ms <= 0) return -1;
|
|
11
|
+
|
|
12
|
+
// Last candle that opened at or before the trade.
|
|
13
|
+
const ::VroomCandle* it =
|
|
14
|
+
std::upper_bound(candles, candles + count, time_ms,
|
|
15
|
+
[](int64_t t, const ::VroomCandle& c) { return t < c.time_ms; });
|
|
16
|
+
if (it == candles) return -1; // before the first bar
|
|
17
|
+
const int32_t i = static_cast<int32_t>((it - 1) - candles);
|
|
18
|
+
|
|
19
|
+
// Inside that bar's window, or in a gap where no bar exists.
|
|
20
|
+
if (time_ms >= candles[i].time_ms + duration_ms) return -1;
|
|
21
|
+
return i;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
std::vector<Bucket> build_buckets(const ::VroomCandle* candles, size_t count,
|
|
25
|
+
int64_t duration_ms,
|
|
26
|
+
const ::VroomFootprint* prints, size_t print_count) {
|
|
27
|
+
std::vector<Bucket> out;
|
|
28
|
+
if (!candles || count == 0 || !prints || print_count == 0) return out;
|
|
29
|
+
|
|
30
|
+
// Bucket per candle index, then compact. Candle counts stay in the low
|
|
31
|
+
// thousands, so a flat scratch vector beats a map on both sides of the ledger.
|
|
32
|
+
std::vector<int32_t> slot(count, -1);
|
|
33
|
+
|
|
34
|
+
for (size_t i = 0; i < print_count; ++i) {
|
|
35
|
+
const ::VroomFootprint& p = prints[i];
|
|
36
|
+
if (p.side != VROOM_FOOTPRINT_BUY && p.side != VROOM_FOOTPRINT_SELL) continue;
|
|
37
|
+
|
|
38
|
+
const int32_t ci = bucket_index(candles, count, duration_ms, p.time_ms);
|
|
39
|
+
if (ci < 0) continue;
|
|
40
|
+
|
|
41
|
+
if (slot[static_cast<size_t>(ci)] < 0) {
|
|
42
|
+
slot[static_cast<size_t>(ci)] = static_cast<int32_t>(out.size());
|
|
43
|
+
Bucket b;
|
|
44
|
+
b.candle_time_ms = candles[ci].time_ms;
|
|
45
|
+
out.push_back(std::move(b));
|
|
46
|
+
}
|
|
47
|
+
Bucket& b = out[static_cast<size_t>(slot[static_cast<size_t>(ci)])];
|
|
48
|
+
if (p.side == VROOM_FOOTPRINT_BUY) {
|
|
49
|
+
b.buys.push_back(static_cast<int32_t>(i));
|
|
50
|
+
} else {
|
|
51
|
+
b.sells.push_back(static_cast<int32_t>(i));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The host's array can arrive in any order, so sort each side by time and read
|
|
56
|
+
// the latest off the end.
|
|
57
|
+
const auto by_time = [prints](int32_t a, int32_t b) {
|
|
58
|
+
const int64_t ta = prints[static_cast<size_t>(a)].time_ms;
|
|
59
|
+
const int64_t tb = prints[static_cast<size_t>(b)].time_ms;
|
|
60
|
+
// Fall back to the host's order so equal timestamps stay stable.
|
|
61
|
+
return ta != tb ? ta < tb : a < b;
|
|
62
|
+
};
|
|
63
|
+
for (Bucket& b : out) {
|
|
64
|
+
std::sort(b.buys.begin(), b.buys.end(), by_time);
|
|
65
|
+
std::sort(b.sells.begin(), b.sells.end(), by_time);
|
|
66
|
+
if (!b.buys.empty()) {
|
|
67
|
+
b.buy_latest_ms = prints[static_cast<size_t>(b.buys.back())].time_ms;
|
|
68
|
+
}
|
|
69
|
+
if (!b.sells.empty()) {
|
|
70
|
+
b.sell_latest_ms = prints[static_cast<size_t>(b.sells.back())].time_ms;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Buckets were created in first-seen order; callers want them by candle.
|
|
75
|
+
std::sort(out.begin(), out.end(), [](const Bucket& a, const Bucket& b) {
|
|
76
|
+
return a.candle_time_ms < b.candle_time_ms;
|
|
77
|
+
});
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Metrics metrics_from(const ::VroomFootprintStyle* style) {
|
|
82
|
+
Metrics m;
|
|
83
|
+
if (!style) return m;
|
|
84
|
+
if (style->radius_px > 0.f) m.radius = style->radius_px;
|
|
85
|
+
if (style->gap_px > 0.f) m.gap = style->gap_px;
|
|
86
|
+
if (style->margin_px > 0.f) m.margin = style->margin_px;
|
|
87
|
+
if (style->hover_boost > 0.f) m.hover_boost = style->hover_boost;
|
|
88
|
+
return m;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Stack layout_stack(const Bucket& bucket, float high_y, float pane_top,
|
|
92
|
+
float radius, float gap, float margin) {
|
|
93
|
+
Stack out;
|
|
94
|
+
if (radius <= 0.f) radius = kRadius;
|
|
95
|
+
if (gap <= 0.f) gap = kGap;
|
|
96
|
+
if (margin <= 0.f) margin = kMargin;
|
|
97
|
+
|
|
98
|
+
const bool buys = bucket.has_buys();
|
|
99
|
+
const bool sells = bucket.has_sells();
|
|
100
|
+
if (!buys && !sells) return out;
|
|
101
|
+
|
|
102
|
+
if (buys && sells) {
|
|
103
|
+
// Bottom-to-top replays the order the trades happened in. Ties go to the
|
|
104
|
+
// buy, so an entry and exit stamped the same ms still lay out predictably.
|
|
105
|
+
const bool buy_first = bucket.buy_latest_ms <= bucket.sell_latest_ms;
|
|
106
|
+
out.sides[0] = buy_first ? VROOM_FOOTPRINT_BUY : VROOM_FOOTPRINT_SELL;
|
|
107
|
+
out.sides[1] = buy_first ? VROOM_FOOTPRINT_SELL : VROOM_FOOTPRINT_BUY;
|
|
108
|
+
out.count = 2;
|
|
109
|
+
} else {
|
|
110
|
+
out.sides[0] = buys ? VROOM_FOOTPRINT_BUY : VROOM_FOOTPRINT_SELL;
|
|
111
|
+
out.count = 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Slot 0 clears the high by `margin`; each further slot clears the one below
|
|
115
|
+
// it by `gap`, which is what keeps them separately hoverable.
|
|
116
|
+
const float pitch = 2.f * radius + gap;
|
|
117
|
+
for (int32_t i = 0; i < out.count; ++i) {
|
|
118
|
+
out.y[i] = high_y - margin - radius - static_cast<float>(i) * pitch;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// A bar near the top of the view would push its stack off-screen; slide the
|
|
122
|
+
// whole stack down so the topmost badge stays inside the pane. Moving both
|
|
123
|
+
// together preserves the order the stack encodes.
|
|
124
|
+
const float top_edge = out.y[out.count - 1] - radius;
|
|
125
|
+
if (top_edge < pane_top) {
|
|
126
|
+
const float shift = pane_top - top_edge;
|
|
127
|
+
for (int32_t i = 0; i < out.count; ++i) out.y[i] += shift;
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
bool hits_badge(float cx, float cy, float radius, float x, float y) {
|
|
133
|
+
if (radius <= 0.f) return false;
|
|
134
|
+
const float dx = x - cx;
|
|
135
|
+
const float dy = y - cy;
|
|
136
|
+
const float r = radius + kHitTolerance;
|
|
137
|
+
return dx * dx + dy * dy <= r * r;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
} // namespace vroom::footprints
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Footprint bucketing + badge geometry — pure functions, no Skia, no state.
|
|
2
|
+
//
|
|
3
|
+
// Split out from footprints.cpp for the same reason as price_line_layout.h: the
|
|
4
|
+
// render pass and the hit-test pass must agree on where every badge sits, or the
|
|
5
|
+
// user ends up hovering nothing where a badge clearly is. Being Skia-free it also
|
|
6
|
+
// unit-tests without a Skia checkout.
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <cstddef>
|
|
11
|
+
#include <cstdint>
|
|
12
|
+
#include <vector>
|
|
13
|
+
|
|
14
|
+
#include "vroom/vroom_chart.h"
|
|
15
|
+
|
|
16
|
+
namespace vroom::footprints {
|
|
17
|
+
|
|
18
|
+
// Badge radius, the gap between two stacked badges, and the gap between the
|
|
19
|
+
// candle's high and the first badge. Defaults for a zero/negative style field.
|
|
20
|
+
constexpr float kRadius = 9.f;
|
|
21
|
+
constexpr float kGap = 4.f;
|
|
22
|
+
constexpr float kMargin = 8.f;
|
|
23
|
+
constexpr float kHoverBoost = 1.25f;
|
|
24
|
+
|
|
25
|
+
// Slop around the circle so a 9px badge is still a comfortable target, on a
|
|
26
|
+
// mouse and under a thumb alike.
|
|
27
|
+
constexpr float kHitTolerance = 3.f;
|
|
28
|
+
|
|
29
|
+
// One candle's footprints, split by side. Both lists hold indices into the array
|
|
30
|
+
// the host passed to vroom_chart_set_footprints — its order, so a caller can map
|
|
31
|
+
// them straight back to its own trade objects — sorted ascending by time.
|
|
32
|
+
struct Bucket {
|
|
33
|
+
int64_t candle_time_ms = 0;
|
|
34
|
+
std::vector<int32_t> buys;
|
|
35
|
+
std::vector<int32_t> sells;
|
|
36
|
+
// Time of the last trade on each side, which decides the stack order. Only
|
|
37
|
+
// meaningful when the matching list is non-empty.
|
|
38
|
+
int64_t buy_latest_ms = 0;
|
|
39
|
+
int64_t sell_latest_ms = 0;
|
|
40
|
+
|
|
41
|
+
bool has_buys() const { return !buys.empty(); }
|
|
42
|
+
bool has_sells() const { return !sells.empty(); }
|
|
43
|
+
// Every candle that made it into a bucket has at least one badge to draw.
|
|
44
|
+
int32_t badge_count() const { return (has_buys() ? 1 : 0) + (has_sells() ? 1 : 0); }
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Index of the candle whose [time_ms, time_ms + duration_ms) window contains
|
|
48
|
+
// `time_ms`, or -1 when it falls in no window — before the first candle, or in a
|
|
49
|
+
// gap (a weekend, a halt) where no bar exists. Candles must be ascending.
|
|
50
|
+
int32_t bucket_index(const ::VroomCandle* candles, size_t count,
|
|
51
|
+
int64_t duration_ms, int64_t time_ms);
|
|
52
|
+
|
|
53
|
+
// Groups `prints` onto candles. The result is ascending by candle time and holds
|
|
54
|
+
// only candles that got at least one footprint, so it is also the dedupe: however
|
|
55
|
+
// many trades a bar collected, it comes back as one bucket with at most two sides.
|
|
56
|
+
// Footprints with an unknown side, or that land in no candle's window, are dropped.
|
|
57
|
+
std::vector<Bucket> build_buckets(const ::VroomCandle* candles, size_t count,
|
|
58
|
+
int64_t duration_ms,
|
|
59
|
+
const ::VroomFootprint* prints, size_t print_count);
|
|
60
|
+
|
|
61
|
+
// The laid-out badge stack for one candle: one or two centers, going up from the
|
|
62
|
+
// candle's high. Slot 0 is the one nearest the bar.
|
|
63
|
+
struct Stack {
|
|
64
|
+
int32_t count = 0;
|
|
65
|
+
int32_t sides[2]{}; // VroomFootprintSide per slot
|
|
66
|
+
float y[2]{}; // center y per slot
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Stacks `bucket`'s badges above `high_y` (the pixel y of the candle's high).
|
|
70
|
+
//
|
|
71
|
+
// The side whose latest trade came *first* takes the lower slot, so reading the
|
|
72
|
+
// stack bottom-to-top replays the order the trades happened in. The whole stack
|
|
73
|
+
// shifts down if it would poke out the top of the pane, keeping badges on a bar
|
|
74
|
+
// near the high of the view visible and hoverable rather than clipped away.
|
|
75
|
+
//
|
|
76
|
+
// Style fields at or below zero fall back to kRadius / kGap / kMargin.
|
|
77
|
+
Stack layout_stack(const Bucket& bucket, float high_y, float pane_top,
|
|
78
|
+
float radius, float gap, float margin);
|
|
79
|
+
|
|
80
|
+
// True when (x, y) is within a badge of `radius` centered at (cx, cy), plus
|
|
81
|
+
// kHitTolerance.
|
|
82
|
+
bool hits_badge(float cx, float cy, float radius, float x, float y);
|
|
83
|
+
|
|
84
|
+
// Resolves the style struct's raw fields to the values the layout should use.
|
|
85
|
+
// A null `style`, or any field left at zero, takes the default.
|
|
86
|
+
struct Metrics {
|
|
87
|
+
float radius = kRadius;
|
|
88
|
+
float gap = kGap;
|
|
89
|
+
float margin = kMargin;
|
|
90
|
+
float hover_boost = kHoverBoost;
|
|
91
|
+
};
|
|
92
|
+
Metrics metrics_from(const ::VroomFootprintStyle* style);
|
|
93
|
+
|
|
94
|
+
} // namespace vroom::footprints
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// The pulsing ring at the line chart's tip — a phase in, a radius and two
|
|
2
2
|
// alphas out (VROOM_FLOAT_LINE_TIP_PULSE).
|
|
3
3
|
//
|
|
4
|
-
// Shape and timing follow
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Shape and timing follow the last-price animation charting tools converged on:
|
|
5
|
+
// expand while the fill washes out and the edge sharpens, keep expanding while
|
|
6
|
+
// the edge fades, then rest. That rest is nearly half the period and it is what
|
|
7
|
+
// makes the ring read as a heartbeat instead of a strobe.
|
|
8
8
|
//
|
|
9
9
|
// Radii come out as multiples of the ring's start radius rather than pixels,
|
|
10
10
|
// because the tip dot scales with the line width and the ring has to scale with
|
package/lib/index.d.mts
CHANGED
|
@@ -651,6 +651,123 @@ type PriceLinesStyle = {
|
|
|
651
651
|
*/
|
|
652
652
|
hoverBoost?: number;
|
|
653
653
|
};
|
|
654
|
+
/** Which side of a position a footprint marks. */
|
|
655
|
+
type FootprintSide = 'buy' | 'sell';
|
|
656
|
+
/**
|
|
657
|
+
* A single filled trade, drawn as a circular badge above the candle it fell in —
|
|
658
|
+
* the "footprint" a trader leaves on the chart: `buy` marks an entry (a `+`
|
|
659
|
+
* badge in the bull color), `sell` marks an exit (a `−` badge in the bear color).
|
|
660
|
+
*
|
|
661
|
+
* `timeMs` is the raw execution time, *not* a bar-open time. The chart buckets
|
|
662
|
+
* each footprint into whichever candle's window contains it, so the same array
|
|
663
|
+
* renders correctly at every interval — switch from 1m to 1h and the badges
|
|
664
|
+
* re-group onto the wider bars on their own.
|
|
665
|
+
*
|
|
666
|
+
* At most two badges render per candle: one for that bar's buys and one for its
|
|
667
|
+
* sells, however many trades went into each. Hovering (or tapping) a badge hands
|
|
668
|
+
* every footprint on that candle back through `onFootprint`, so a bar holding
|
|
669
|
+
* twenty fills still shows one badge and still reports all twenty.
|
|
670
|
+
*/
|
|
671
|
+
type Footprint = {
|
|
672
|
+
/** Stable unique id, echoed back in `onFootprint`. */
|
|
673
|
+
id: string;
|
|
674
|
+
/** Execution time as Unix epoch milliseconds, unsnapped. */
|
|
675
|
+
timeMs: number;
|
|
676
|
+
/** Entry (`'buy'`) or exit (`'sell'`) — picks the badge color and glyph. */
|
|
677
|
+
side: FootprintSide;
|
|
678
|
+
/**
|
|
679
|
+
* Execution price. Ignored by the renderer (badges sit above the bar, not at
|
|
680
|
+
* the fill), and carried through to `onFootprint` for your own UI.
|
|
681
|
+
*/
|
|
682
|
+
price?: number;
|
|
683
|
+
};
|
|
684
|
+
/** Shared layout/style for every footprint badge, passed via `footprintsStyle`. */
|
|
685
|
+
type FootprintsStyle = {
|
|
686
|
+
/** Badge radius in px. Default 9. */
|
|
687
|
+
radius?: number;
|
|
688
|
+
/**
|
|
689
|
+
* Vertical gap between the two stacked badges on a candle that has both a buy
|
|
690
|
+
* and a sell. Default 4 — wide enough that each stays independently hoverable.
|
|
691
|
+
*/
|
|
692
|
+
gap?: number;
|
|
693
|
+
/** Gap between the candle's high and the first badge, in px. Default 8. */
|
|
694
|
+
margin?: number;
|
|
695
|
+
/**
|
|
696
|
+
* How much the hovered badge brightens, as a channel multiplier. 1 disables
|
|
697
|
+
* the highlight (the halo ring still draws). Default 1.25.
|
|
698
|
+
*/
|
|
699
|
+
hoverBoost?: number;
|
|
700
|
+
};
|
|
701
|
+
/**
|
|
702
|
+
* The chart's plot area in logical px relative to the chart element's top-left:
|
|
703
|
+
* the candles and everything drawn over them, with the price and time axis
|
|
704
|
+
* strips excluded.
|
|
705
|
+
*
|
|
706
|
+
* This is the rect to test a floating UI against, and it is deliberately *not*
|
|
707
|
+
* the element's own box — the element includes the axis strips, so measuring it
|
|
708
|
+
* overstates the room beside anything near an edge.
|
|
709
|
+
*/
|
|
710
|
+
type PlotRect = {
|
|
711
|
+
left: number;
|
|
712
|
+
top: number;
|
|
713
|
+
right: number;
|
|
714
|
+
bottom: number;
|
|
715
|
+
};
|
|
716
|
+
/**
|
|
717
|
+
* Fired when the pointer enters, moves between, or leaves footprint badges (on
|
|
718
|
+
* touch platforms, when one is tapped or dismissed).
|
|
719
|
+
*
|
|
720
|
+
* The chart draws no tooltip of its own — this event is the hook for yours.
|
|
721
|
+
* Position your UI off `badge` and `pane`, both in the same coordinate space as
|
|
722
|
+
* the chart element, and fill it from `footprints`.
|
|
723
|
+
*
|
|
724
|
+
* Panning or zooming fires a `'hide'`, since the bar the badge belongs to has
|
|
725
|
+
* moved: you don't need your own gesture listener to take the tooltip down.
|
|
726
|
+
*/
|
|
727
|
+
type FootprintEvent = {
|
|
728
|
+
/** True while a badge is hovered/tapped; false when it's dismissed. */
|
|
729
|
+
active: boolean;
|
|
730
|
+
/**
|
|
731
|
+
* Why this event fired:
|
|
732
|
+
* 'show' — a badge became hovered/tapped from nothing
|
|
733
|
+
* 'move' — the pointer moved to a *different* badge without leaving in between
|
|
734
|
+
* 'hide' — the badge was dismissed: the pointer left (or a tap missed), the
|
|
735
|
+
* chart was panned or zoomed out from under it, or the crosshair
|
|
736
|
+
* took the pane over
|
|
737
|
+
*/
|
|
738
|
+
reason: 'show' | 'move' | 'hide';
|
|
739
|
+
/** Which badge — its buys or its sells. Null when inactive. */
|
|
740
|
+
side: FootprintSide | null;
|
|
741
|
+
/** Bar-open time (epoch ms) of the candle the badge sits on. Null when inactive. */
|
|
742
|
+
timeMs: number | null;
|
|
743
|
+
/**
|
|
744
|
+
* Every footprint bucketed into that candle, *both* sides, ascending by
|
|
745
|
+
* `timeMs`. Empty when inactive. Filter on `side` to show only the hovered
|
|
746
|
+
* badge's trades, or render the whole bar's activity at once.
|
|
747
|
+
*/
|
|
748
|
+
footprints: Footprint[];
|
|
749
|
+
/**
|
|
750
|
+
* The badge's center and radius in logical px relative to the chart element's
|
|
751
|
+
* top-left — anchor your tooltip to it. Null when inactive.
|
|
752
|
+
*/
|
|
753
|
+
badge: {
|
|
754
|
+
x: number;
|
|
755
|
+
y: number;
|
|
756
|
+
radius: number;
|
|
757
|
+
} | null;
|
|
758
|
+
/**
|
|
759
|
+
* The plot area the badge sits in, for choosing which side of it your tooltip
|
|
760
|
+
* fits on. Null when inactive (there is nothing to place).
|
|
761
|
+
*
|
|
762
|
+
* Only you know how big your tooltip is, so the chart reports the rect rather
|
|
763
|
+
* than picking a side:
|
|
764
|
+
*
|
|
765
|
+
* ```ts
|
|
766
|
+
* const fitsRight = badge.x + badge.radius + 8 + width <= pane.right;
|
|
767
|
+
* ```
|
|
768
|
+
*/
|
|
769
|
+
pane: PlotRect | null;
|
|
770
|
+
};
|
|
654
771
|
/**
|
|
655
772
|
* MACD indicator config. Rendered in its own pane below the candles: the gap
|
|
656
773
|
* between a fast and a slow moving average, a signal line smoothing that gap,
|
|
@@ -797,6 +914,23 @@ type VroomChartCoreProps = {
|
|
|
797
914
|
priceLines?: PriceLine[];
|
|
798
915
|
/** Shared layout/style for every entry in `priceLines`. */
|
|
799
916
|
priceLinesStyle?: PriceLinesStyle;
|
|
917
|
+
/**
|
|
918
|
+
* Executed trades to mark on the chart as circular badges above the bar they
|
|
919
|
+
* fell in — where a position was entered and exited.
|
|
920
|
+
*
|
|
921
|
+
* Pass raw fills with their real execution times; the chart groups them onto
|
|
922
|
+
* candles itself and re-groups on interval changes, so one array serves every
|
|
923
|
+
* timeframe. Order doesn't matter.
|
|
924
|
+
*/
|
|
925
|
+
footprints?: Footprint[];
|
|
926
|
+
/** Shared layout/style for every entry in `footprints`. */
|
|
927
|
+
footprintsStyle?: FootprintsStyle;
|
|
928
|
+
/**
|
|
929
|
+
* Fired when a footprint badge is hovered (tapped on touch platforms) or
|
|
930
|
+
* dismissed. The chart renders no tooltip itself — use this to place your own,
|
|
931
|
+
* anchored to `e.badge`, kept inside `e.pane`, and filled from `e.footprints`.
|
|
932
|
+
*/
|
|
933
|
+
onFootprint?: (e: FootprintEvent) => void;
|
|
800
934
|
/**
|
|
801
935
|
* Fired continuously while a draggable price line is being dragged, with the
|
|
802
936
|
* price under the pointer. Use it for a live readout (e.g. an order ticket);
|
|
@@ -991,4 +1125,4 @@ declare function classifyTransition(prev: Candle[] | null, next: Candle[], serie
|
|
|
991
1125
|
*/
|
|
992
1126
|
declare function timeframeWindow(oldWindow: VisibleRange, oldStepMs: number, oldLastMs: number, newStepMs: number, newLastMs: number): VisibleRange;
|
|
993
1127
|
|
|
994
|
-
export { type BollingerBandsConfig, type Candle, type ChartType, type CrosshairEvent, type DataTransition, type DefaultDrawingStyle, type IntervalTransition, type MACDConfig, type MAKind, type MASource, type MovingAverageOverlay, type PriceLine, type PriceLinesStyle, type RSIConfig, type TransitionEasing, type VWAPConfig, type VisibleRange, type VolumeConfig, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme, classifyTransition, inferStepMs, timeframeWindow };
|
|
1128
|
+
export { type BollingerBandsConfig, type Candle, type ChartType, type CrosshairEvent, type DataTransition, type DefaultDrawingStyle, type Footprint, type FootprintEvent, type FootprintSide, type FootprintsStyle, type IntervalTransition, type MACDConfig, type MAKind, type MASource, type MovingAverageOverlay, type PlotRect, type PriceLine, type PriceLinesStyle, type RSIConfig, type TransitionEasing, type VWAPConfig, type VisibleRange, type VolumeConfig, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme, classifyTransition, inferStepMs, timeframeWindow };
|
package/lib/index.d.ts
CHANGED
|
@@ -651,6 +651,123 @@ type PriceLinesStyle = {
|
|
|
651
651
|
*/
|
|
652
652
|
hoverBoost?: number;
|
|
653
653
|
};
|
|
654
|
+
/** Which side of a position a footprint marks. */
|
|
655
|
+
type FootprintSide = 'buy' | 'sell';
|
|
656
|
+
/**
|
|
657
|
+
* A single filled trade, drawn as a circular badge above the candle it fell in —
|
|
658
|
+
* the "footprint" a trader leaves on the chart: `buy` marks an entry (a `+`
|
|
659
|
+
* badge in the bull color), `sell` marks an exit (a `−` badge in the bear color).
|
|
660
|
+
*
|
|
661
|
+
* `timeMs` is the raw execution time, *not* a bar-open time. The chart buckets
|
|
662
|
+
* each footprint into whichever candle's window contains it, so the same array
|
|
663
|
+
* renders correctly at every interval — switch from 1m to 1h and the badges
|
|
664
|
+
* re-group onto the wider bars on their own.
|
|
665
|
+
*
|
|
666
|
+
* At most two badges render per candle: one for that bar's buys and one for its
|
|
667
|
+
* sells, however many trades went into each. Hovering (or tapping) a badge hands
|
|
668
|
+
* every footprint on that candle back through `onFootprint`, so a bar holding
|
|
669
|
+
* twenty fills still shows one badge and still reports all twenty.
|
|
670
|
+
*/
|
|
671
|
+
type Footprint = {
|
|
672
|
+
/** Stable unique id, echoed back in `onFootprint`. */
|
|
673
|
+
id: string;
|
|
674
|
+
/** Execution time as Unix epoch milliseconds, unsnapped. */
|
|
675
|
+
timeMs: number;
|
|
676
|
+
/** Entry (`'buy'`) or exit (`'sell'`) — picks the badge color and glyph. */
|
|
677
|
+
side: FootprintSide;
|
|
678
|
+
/**
|
|
679
|
+
* Execution price. Ignored by the renderer (badges sit above the bar, not at
|
|
680
|
+
* the fill), and carried through to `onFootprint` for your own UI.
|
|
681
|
+
*/
|
|
682
|
+
price?: number;
|
|
683
|
+
};
|
|
684
|
+
/** Shared layout/style for every footprint badge, passed via `footprintsStyle`. */
|
|
685
|
+
type FootprintsStyle = {
|
|
686
|
+
/** Badge radius in px. Default 9. */
|
|
687
|
+
radius?: number;
|
|
688
|
+
/**
|
|
689
|
+
* Vertical gap between the two stacked badges on a candle that has both a buy
|
|
690
|
+
* and a sell. Default 4 — wide enough that each stays independently hoverable.
|
|
691
|
+
*/
|
|
692
|
+
gap?: number;
|
|
693
|
+
/** Gap between the candle's high and the first badge, in px. Default 8. */
|
|
694
|
+
margin?: number;
|
|
695
|
+
/**
|
|
696
|
+
* How much the hovered badge brightens, as a channel multiplier. 1 disables
|
|
697
|
+
* the highlight (the halo ring still draws). Default 1.25.
|
|
698
|
+
*/
|
|
699
|
+
hoverBoost?: number;
|
|
700
|
+
};
|
|
701
|
+
/**
|
|
702
|
+
* The chart's plot area in logical px relative to the chart element's top-left:
|
|
703
|
+
* the candles and everything drawn over them, with the price and time axis
|
|
704
|
+
* strips excluded.
|
|
705
|
+
*
|
|
706
|
+
* This is the rect to test a floating UI against, and it is deliberately *not*
|
|
707
|
+
* the element's own box — the element includes the axis strips, so measuring it
|
|
708
|
+
* overstates the room beside anything near an edge.
|
|
709
|
+
*/
|
|
710
|
+
type PlotRect = {
|
|
711
|
+
left: number;
|
|
712
|
+
top: number;
|
|
713
|
+
right: number;
|
|
714
|
+
bottom: number;
|
|
715
|
+
};
|
|
716
|
+
/**
|
|
717
|
+
* Fired when the pointer enters, moves between, or leaves footprint badges (on
|
|
718
|
+
* touch platforms, when one is tapped or dismissed).
|
|
719
|
+
*
|
|
720
|
+
* The chart draws no tooltip of its own — this event is the hook for yours.
|
|
721
|
+
* Position your UI off `badge` and `pane`, both in the same coordinate space as
|
|
722
|
+
* the chart element, and fill it from `footprints`.
|
|
723
|
+
*
|
|
724
|
+
* Panning or zooming fires a `'hide'`, since the bar the badge belongs to has
|
|
725
|
+
* moved: you don't need your own gesture listener to take the tooltip down.
|
|
726
|
+
*/
|
|
727
|
+
type FootprintEvent = {
|
|
728
|
+
/** True while a badge is hovered/tapped; false when it's dismissed. */
|
|
729
|
+
active: boolean;
|
|
730
|
+
/**
|
|
731
|
+
* Why this event fired:
|
|
732
|
+
* 'show' — a badge became hovered/tapped from nothing
|
|
733
|
+
* 'move' — the pointer moved to a *different* badge without leaving in between
|
|
734
|
+
* 'hide' — the badge was dismissed: the pointer left (or a tap missed), the
|
|
735
|
+
* chart was panned or zoomed out from under it, or the crosshair
|
|
736
|
+
* took the pane over
|
|
737
|
+
*/
|
|
738
|
+
reason: 'show' | 'move' | 'hide';
|
|
739
|
+
/** Which badge — its buys or its sells. Null when inactive. */
|
|
740
|
+
side: FootprintSide | null;
|
|
741
|
+
/** Bar-open time (epoch ms) of the candle the badge sits on. Null when inactive. */
|
|
742
|
+
timeMs: number | null;
|
|
743
|
+
/**
|
|
744
|
+
* Every footprint bucketed into that candle, *both* sides, ascending by
|
|
745
|
+
* `timeMs`. Empty when inactive. Filter on `side` to show only the hovered
|
|
746
|
+
* badge's trades, or render the whole bar's activity at once.
|
|
747
|
+
*/
|
|
748
|
+
footprints: Footprint[];
|
|
749
|
+
/**
|
|
750
|
+
* The badge's center and radius in logical px relative to the chart element's
|
|
751
|
+
* top-left — anchor your tooltip to it. Null when inactive.
|
|
752
|
+
*/
|
|
753
|
+
badge: {
|
|
754
|
+
x: number;
|
|
755
|
+
y: number;
|
|
756
|
+
radius: number;
|
|
757
|
+
} | null;
|
|
758
|
+
/**
|
|
759
|
+
* The plot area the badge sits in, for choosing which side of it your tooltip
|
|
760
|
+
* fits on. Null when inactive (there is nothing to place).
|
|
761
|
+
*
|
|
762
|
+
* Only you know how big your tooltip is, so the chart reports the rect rather
|
|
763
|
+
* than picking a side:
|
|
764
|
+
*
|
|
765
|
+
* ```ts
|
|
766
|
+
* const fitsRight = badge.x + badge.radius + 8 + width <= pane.right;
|
|
767
|
+
* ```
|
|
768
|
+
*/
|
|
769
|
+
pane: PlotRect | null;
|
|
770
|
+
};
|
|
654
771
|
/**
|
|
655
772
|
* MACD indicator config. Rendered in its own pane below the candles: the gap
|
|
656
773
|
* between a fast and a slow moving average, a signal line smoothing that gap,
|
|
@@ -797,6 +914,23 @@ type VroomChartCoreProps = {
|
|
|
797
914
|
priceLines?: PriceLine[];
|
|
798
915
|
/** Shared layout/style for every entry in `priceLines`. */
|
|
799
916
|
priceLinesStyle?: PriceLinesStyle;
|
|
917
|
+
/**
|
|
918
|
+
* Executed trades to mark on the chart as circular badges above the bar they
|
|
919
|
+
* fell in — where a position was entered and exited.
|
|
920
|
+
*
|
|
921
|
+
* Pass raw fills with their real execution times; the chart groups them onto
|
|
922
|
+
* candles itself and re-groups on interval changes, so one array serves every
|
|
923
|
+
* timeframe. Order doesn't matter.
|
|
924
|
+
*/
|
|
925
|
+
footprints?: Footprint[];
|
|
926
|
+
/** Shared layout/style for every entry in `footprints`. */
|
|
927
|
+
footprintsStyle?: FootprintsStyle;
|
|
928
|
+
/**
|
|
929
|
+
* Fired when a footprint badge is hovered (tapped on touch platforms) or
|
|
930
|
+
* dismissed. The chart renders no tooltip itself — use this to place your own,
|
|
931
|
+
* anchored to `e.badge`, kept inside `e.pane`, and filled from `e.footprints`.
|
|
932
|
+
*/
|
|
933
|
+
onFootprint?: (e: FootprintEvent) => void;
|
|
800
934
|
/**
|
|
801
935
|
* Fired continuously while a draggable price line is being dragged, with the
|
|
802
936
|
* price under the pointer. Use it for a live readout (e.g. an order ticket);
|
|
@@ -991,4 +1125,4 @@ declare function classifyTransition(prev: Candle[] | null, next: Candle[], serie
|
|
|
991
1125
|
*/
|
|
992
1126
|
declare function timeframeWindow(oldWindow: VisibleRange, oldStepMs: number, oldLastMs: number, newStepMs: number, newLastMs: number): VisibleRange;
|
|
993
1127
|
|
|
994
|
-
export { type BollingerBandsConfig, type Candle, type ChartType, type CrosshairEvent, type DataTransition, type DefaultDrawingStyle, type IntervalTransition, type MACDConfig, type MAKind, type MASource, type MovingAverageOverlay, type PriceLine, type PriceLinesStyle, type RSIConfig, type TransitionEasing, type VWAPConfig, type VisibleRange, type VolumeConfig, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme, classifyTransition, inferStepMs, timeframeWindow };
|
|
1128
|
+
export { type BollingerBandsConfig, type Candle, type ChartType, type CrosshairEvent, type DataTransition, type DefaultDrawingStyle, type Footprint, type FootprintEvent, type FootprintSide, type FootprintsStyle, type IntervalTransition, type MACDConfig, type MAKind, type MASource, type MovingAverageOverlay, type PlotRect, type PriceLine, type PriceLinesStyle, type RSIConfig, type TransitionEasing, type VWAPConfig, type VisibleRange, type VolumeConfig, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme, classifyTransition, inferStepMs, timeframeWindow };
|