react-native-vroom-chart 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cpp/VroomChartHostObject.cpp +39 -1
- package/cpp/_core_include/vroom/vroom_chart.h +40 -0
- package/cpp/_core_src/candles.cpp +43 -5
- package/cpp/_core_src/chart.cpp +7 -0
- package/cpp/_core_src/chart.h +10 -0
- package/cpp/_core_src/chart_facade.cpp +50 -2
- package/cpp/_core_src/liquidity.cpp +112 -0
- package/cpp/_core_src/liquidity.h +35 -0
- package/cpp/_core_src/theme.cpp +3 -0
- package/cpp/_core_src/volume.cpp +15 -3
- package/lib/index.d.mts +65 -2
- package/lib/index.d.ts +65 -2
- package/lib/index.js +32 -3
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +32 -3
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +2 -0
- package/src/jsi.d.ts +11 -0
- package/src/theme.ts +29 -6
- package/src/useChartCore.ts +20 -1
|
@@ -27,11 +27,13 @@ ChartHostObject::~ChartHostObject() {
|
|
|
27
27
|
std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
28
28
|
jsi::Runtime& rt) {
|
|
29
29
|
std::vector<jsi::PropNameID> out;
|
|
30
|
-
out.reserve(
|
|
30
|
+
out.reserve(22);
|
|
31
31
|
out.push_back(jsi::PropNameID::forAscii(rt, "setCandles"));
|
|
32
32
|
out.push_back(jsi::PropNameID::forAscii(rt, "setSize"));
|
|
33
33
|
out.push_back(jsi::PropNameID::forAscii(rt, "setColor"));
|
|
34
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setFloat"));
|
|
34
35
|
out.push_back(jsi::PropNameID::forAscii(rt, "setVisibleRange"));
|
|
36
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setDefaultCandleWidth"));
|
|
35
37
|
out.push_back(jsi::PropNameID::forAscii(rt, "pan"));
|
|
36
38
|
out.push_back(jsi::PropNameID::forAscii(rt, "translate"));
|
|
37
39
|
out.push_back(jsi::PropNameID::forAscii(rt, "zoom"));
|
|
@@ -130,6 +132,25 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
130
132
|
});
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
if (name == "setFloat") {
|
|
136
|
+
// setFloat(keyIndex, value) — keyIndex is a VroomFloatKey (e.g. wick width).
|
|
137
|
+
// The core bounds-checks the key and marks dirty.
|
|
138
|
+
return jsi::Function::createFromHostFunction(
|
|
139
|
+
rt,
|
|
140
|
+
jsi::PropNameID::forAscii(rt, "setFloat"),
|
|
141
|
+
2,
|
|
142
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
143
|
+
const jsi::Value& /*thisVal*/,
|
|
144
|
+
const jsi::Value* args,
|
|
145
|
+
size_t count) -> jsi::Value {
|
|
146
|
+
if (count < 2) return jsi::Value::undefined();
|
|
147
|
+
const int key = static_cast<int>(args[0].asNumber());
|
|
148
|
+
vroom_chart_set_float(chart_, static_cast<VroomFloatKey>(key),
|
|
149
|
+
static_cast<float>(args[1].asNumber()));
|
|
150
|
+
return jsi::Value::undefined();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
133
154
|
if (name == "setVisibleRange") {
|
|
134
155
|
// Pass 0,0 to clear (show all).
|
|
135
156
|
// Timestamps are Number on the JS side — fine for ms-since-epoch since
|
|
@@ -150,6 +171,23 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
150
171
|
});
|
|
151
172
|
}
|
|
152
173
|
|
|
174
|
+
if (name == "setDefaultCandleWidth") {
|
|
175
|
+
// setDefaultCandleWidth(px) — target candle body width for initial framing.
|
|
176
|
+
return jsi::Function::createFromHostFunction(
|
|
177
|
+
rt,
|
|
178
|
+
jsi::PropNameID::forAscii(rt, "setDefaultCandleWidth"),
|
|
179
|
+
1,
|
|
180
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
181
|
+
const jsi::Value& /*thisVal*/,
|
|
182
|
+
const jsi::Value* args,
|
|
183
|
+
size_t count) -> jsi::Value {
|
|
184
|
+
if (count < 1) return jsi::Value::undefined();
|
|
185
|
+
vroom_chart_set_default_candle_width(
|
|
186
|
+
chart_, static_cast<float>(args[0].asNumber()));
|
|
187
|
+
return jsi::Value::undefined();
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
153
191
|
if (name == "pan") {
|
|
154
192
|
// pan(dx, dy) -> JsiSkPicture
|
|
155
193
|
// Mutates the visible range and renders in one JSI call so gesture
|
|
@@ -63,6 +63,29 @@ typedef struct VroomDrawing {
|
|
|
63
63
|
float width; // stroke width in px
|
|
64
64
|
} VroomDrawing;
|
|
65
65
|
|
|
66
|
+
// A resting-liquidity band: a price interval carrying a total order size on one
|
|
67
|
+
// side of the book. Anchored in price space, so it scales with the y-axis.
|
|
68
|
+
typedef struct VroomBand {
|
|
69
|
+
double min_price; // bottom of the interval
|
|
70
|
+
double max_price; // top of the interval
|
|
71
|
+
int32_t side; // 0 = buy, 1 = sell (selects the color)
|
|
72
|
+
double volume; // total resting size; drives the band's opacity
|
|
73
|
+
} VroomBand;
|
|
74
|
+
|
|
75
|
+
// Style shared by all liquidity bands. `buy_color`/`sell_color` are 0xAARRGGBB;
|
|
76
|
+
// the alpha is replaced per band by its volume mapped into
|
|
77
|
+
// [min_opacity, max_opacity]. `max_volume <= 0` auto-scales to the largest band.
|
|
78
|
+
// The leftward reach is min(width_px, width_frac * pane_width).
|
|
79
|
+
typedef struct VroomLiquidityStyle {
|
|
80
|
+
uint32_t buy_color;
|
|
81
|
+
uint32_t sell_color;
|
|
82
|
+
double max_volume;
|
|
83
|
+
float min_opacity;
|
|
84
|
+
float max_opacity;
|
|
85
|
+
float width_px;
|
|
86
|
+
float width_frac;
|
|
87
|
+
} VroomLiquidityStyle;
|
|
88
|
+
|
|
66
89
|
// A continuous data coordinate at a pixel position (no candle snapping). Used to
|
|
67
90
|
// translate a drawing-tool click into a data-space anchor.
|
|
68
91
|
typedef struct VroomCoord {
|
|
@@ -101,6 +124,9 @@ typedef enum {
|
|
|
101
124
|
VROOM_FLOAT_X_AXIS_HEIGHT_PX, // bottom strip reserved for time labels
|
|
102
125
|
VROOM_FLOAT_VOLUME_OPACITY, // volume bar opacity (1=opaque)
|
|
103
126
|
VROOM_FLOAT_INDICATOR_HEIGHT_FRAC, // below-chart indicator pane, fraction of height
|
|
127
|
+
VROOM_FLOAT_CANDLE_RADIUS_PX, // candle body corner radius px (0 = square)
|
|
128
|
+
VROOM_FLOAT_WICK_ROUND_CAP, // 0/1: round the wick end caps
|
|
129
|
+
VROOM_FLOAT_VOLUME_RADIUS_PX, // volume bar top-corner radius px (0 = square)
|
|
104
130
|
VROOM_FLOAT_COUNT_
|
|
105
131
|
} VroomFloatKey;
|
|
106
132
|
|
|
@@ -131,6 +157,12 @@ void vroom_chart_set_size(VroomChart* chart, float width_px, float height_px, fl
|
|
|
131
157
|
|
|
132
158
|
void vroom_chart_set_visible_range(VroomChart* chart, int64_t start_ms, int64_t end_ms);
|
|
133
159
|
|
|
160
|
+
// Target candle *body* width in px for the default/reset framing. Larger = more
|
|
161
|
+
// zoomed in (fewer candles), smaller = more zoomed out. 0 restores the legacy
|
|
162
|
+
// "most recent ~80 candles" default. Affects initial framing only; an explicit
|
|
163
|
+
// set_visible_range still overrides it.
|
|
164
|
+
void vroom_chart_set_default_candle_width(VroomChart* chart, float px);
|
|
165
|
+
|
|
134
166
|
// Reads the current visible time window. Either out pointer may be null.
|
|
135
167
|
// Both are 0 when the window is still uninitialized.
|
|
136
168
|
void vroom_chart_get_visible_range(VroomChart* chart,
|
|
@@ -272,6 +304,14 @@ void vroom_chart_set_vwap(VroomChart* chart, bool enabled, int reset_offset_min,
|
|
|
272
304
|
void vroom_chart_set_drawings(VroomChart* chart, const VroomDrawing* drawings,
|
|
273
305
|
size_t count);
|
|
274
306
|
|
|
307
|
+
// ---- Liquidity bands (order-book depth overlay) ---------------------------
|
|
308
|
+
|
|
309
|
+
// Replaces the full set of resting-liquidity bands and their shared style.
|
|
310
|
+
// Bands render behind the candles, anchored at the inner edge of the price axis
|
|
311
|
+
// and fading left. Pass count 0 to clear; `style` may be null when count is 0.
|
|
312
|
+
void vroom_chart_set_liquidity(VroomChart* chart, const VroomBand* bands,
|
|
313
|
+
size_t count, const VroomLiquidityStyle* style);
|
|
314
|
+
|
|
275
315
|
// Sets the transient in-progress "draft" the drawing tool shows while the user
|
|
276
316
|
// places points. Node A is always shown; when `has_b`, node B is shown too.
|
|
277
317
|
// `guide != 0` also draws the guideline A->B (the live line preview); `guide == 0`
|
|
@@ -39,6 +39,18 @@ void draw(SkCanvas* canvas,
|
|
|
39
39
|
const uint32_t fill_bull = theme.colors[VROOM_COLOR_BULL];
|
|
40
40
|
const uint32_t fill_bear = theme.colors[VROOM_COLOR_BEAR];
|
|
41
41
|
|
|
42
|
+
const float body_r = theme.floats[VROOM_FLOAT_CANDLE_RADIUS_PX];
|
|
43
|
+
const bool wick_round = theme.floats[VROOM_FLOAT_WICK_ROUND_CAP] > 0.5f;
|
|
44
|
+
|
|
45
|
+
constexpr float kBorderWidthPx = 1.f;
|
|
46
|
+
// Draw the border only when it differs from the fill; an inherited/transparent
|
|
47
|
+
// border color (the default, and the "hide" path) renders nothing — so a hidden
|
|
48
|
+
// border costs no pixels and never affects candle width.
|
|
49
|
+
const bool draw_border_bull =
|
|
50
|
+
resolve_color(theme.colors[VROOM_COLOR_BORDER_BULL], fill_bull) != fill_bull;
|
|
51
|
+
const bool draw_border_bear =
|
|
52
|
+
resolve_color(theme.colors[VROOM_COLOR_BORDER_BEAR], fill_bear) != fill_bear;
|
|
53
|
+
|
|
42
54
|
SkPaint bull_paint;
|
|
43
55
|
bull_paint.setAntiAlias(true);
|
|
44
56
|
bull_paint.setColor(fill_bull);
|
|
@@ -53,17 +65,19 @@ void draw(SkCanvas* canvas,
|
|
|
53
65
|
resolve_color(theme.colors[VROOM_COLOR_WICK_BULL], fill_bull));
|
|
54
66
|
wick_bull.setStrokeWidth(theme.floats[VROOM_FLOAT_WICK_WIDTH_PX]);
|
|
55
67
|
wick_bull.setStyle(SkPaint::kStroke_Style);
|
|
68
|
+
wick_bull.setStrokeCap(wick_round ? SkPaint::kRound_Cap : SkPaint::kButt_Cap);
|
|
56
69
|
|
|
57
70
|
SkPaint wick_bear = wick_bull;
|
|
58
71
|
wick_bear.setColor(
|
|
59
72
|
resolve_color(theme.colors[VROOM_COLOR_WICK_BEAR], fill_bear));
|
|
60
73
|
|
|
61
|
-
// 1px body outlines
|
|
62
|
-
// border
|
|
74
|
+
// 1px body outlines, drawn *inside* the body (see the inset at draw time) so
|
|
75
|
+
// a contrasting border never widens the candle. Default sentinel resolves to
|
|
76
|
+
// the fill color; such borders are skipped entirely (draw_border_* above).
|
|
63
77
|
SkPaint border_bull;
|
|
64
78
|
border_bull.setAntiAlias(true);
|
|
65
79
|
border_bull.setStyle(SkPaint::kStroke_Style);
|
|
66
|
-
border_bull.setStrokeWidth(
|
|
80
|
+
border_bull.setStrokeWidth(kBorderWidthPx);
|
|
67
81
|
border_bull.setColor(
|
|
68
82
|
resolve_color(theme.colors[VROOM_COLOR_BORDER_BULL], fill_bull));
|
|
69
83
|
|
|
@@ -92,8 +106,32 @@ void draw(SkCanvas* canvas,
|
|
|
92
106
|
const float y_bot = std::max(y_open, y_close);
|
|
93
107
|
const float h = std::max(1.f, y_bot - y_top);
|
|
94
108
|
const SkRect body = SkRect::MakeXYWH(cx - half_body, y_top, body_w, h);
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
// Corner radius clamped so thin candles / short bodies don't over-round.
|
|
110
|
+
const float r = std::min({body_r, body_w * 0.5f, h * 0.5f});
|
|
111
|
+
const SkPaint& fill = bull ? bull_paint : bear_paint;
|
|
112
|
+
if (r > 0.f) {
|
|
113
|
+
canvas->drawRoundRect(body, r, r, fill);
|
|
114
|
+
} else {
|
|
115
|
+
canvas->drawRect(body, fill);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const bool draw_border = bull ? draw_border_bull : draw_border_bear;
|
|
119
|
+
// Inset by half the stroke so the centered stroke falls fully within the
|
|
120
|
+
// body: the border overlaps the fill's outer edge instead of straddling
|
|
121
|
+
// it, keeping the candle's footprint exactly body_w. Skip when the body is
|
|
122
|
+
// too small to hold it.
|
|
123
|
+
if (draw_border && body_w > kBorderWidthPx && h > kBorderWidthPx) {
|
|
124
|
+
const SkRect inner =
|
|
125
|
+
body.makeInset(kBorderWidthPx * 0.5f, kBorderWidthPx * 0.5f);
|
|
126
|
+
const SkPaint& bp = bull ? border_bull : border_bear;
|
|
127
|
+
// Concentric radius so the inside border follows the rounded body.
|
|
128
|
+
const float ir = std::max(0.f, r - kBorderWidthPx * 0.5f);
|
|
129
|
+
if (ir > 0.f) {
|
|
130
|
+
canvas->drawRoundRect(inner, ir, ir, bp);
|
|
131
|
+
} else {
|
|
132
|
+
canvas->drawRect(inner, bp);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
97
135
|
}
|
|
98
136
|
}
|
|
99
137
|
|
package/cpp/_core_src/chart.cpp
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
#include "crosshair.h"
|
|
24
24
|
#include "drawings.h"
|
|
25
25
|
#include "labels.h"
|
|
26
|
+
#include "liquidity.h"
|
|
26
27
|
#include "ma.h"
|
|
27
28
|
#include "ma_overlay.h"
|
|
28
29
|
#include "macd.h"
|
|
@@ -142,6 +143,12 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
142
143
|
vroom::volume::draw(canvas, visible, n, lay, theme,
|
|
143
144
|
window_ms, visible_start_ms, candle_duration_ms);
|
|
144
145
|
|
|
146
|
+
// 4.6. Liquidity bands (resting-order depth) — behind the candles so the
|
|
147
|
+
// candle bodies paint over the volume-driven tint. Anchored in price
|
|
148
|
+
// space, so they scale with the y-axis.
|
|
149
|
+
vroom::liquidity::draw(canvas, *this, lay, bounds, candle_right,
|
|
150
|
+
candle_area_h);
|
|
151
|
+
|
|
145
152
|
// 5. Candles (wicks + bodies)
|
|
146
153
|
vroom::candles::draw(canvas, visible, n, lay, theme, bounds,
|
|
147
154
|
window_ms, visible_start_ms, candle_duration_ms);
|
package/cpp/_core_src/chart.h
CHANGED
|
@@ -50,6 +50,10 @@ struct VroomChart {
|
|
|
50
50
|
int64_t visible_start_ms = 0;
|
|
51
51
|
int64_t visible_end_ms = 0;
|
|
52
52
|
|
|
53
|
+
// >0: default/reset framing targets this candle *body* width in px (drives
|
|
54
|
+
// initial zoom). 0 = legacy "last ~80 candles" behavior.
|
|
55
|
+
float default_candle_px = 0.f;
|
|
56
|
+
|
|
53
57
|
// Cached y-axis width in pixels, sized to fit the widest formatted price
|
|
54
58
|
// label. 0 = uncomputed; layout() falls back to a width ratio.
|
|
55
59
|
float axis_width_px = 0.f;
|
|
@@ -140,6 +144,12 @@ struct VroomChart {
|
|
|
140
144
|
uint32_t draft_color = 0xff2962ff;
|
|
141
145
|
float draft_width = 2.f;
|
|
142
146
|
|
|
147
|
+
// --- liquidity bands (order-book depth overlay) ------------------------
|
|
148
|
+
// Resting-order bands anchored in price space, drawn behind the candles and
|
|
149
|
+
// fading left from the price axis. Empty when the overlay is off.
|
|
150
|
+
std::vector<VroomBand> bands;
|
|
151
|
+
VroomLiquidityStyle liquidity_style{};
|
|
152
|
+
|
|
143
153
|
// --- theme --------------------------------------------------------------
|
|
144
154
|
vroom::Theme theme;
|
|
145
155
|
|
|
@@ -54,10 +54,37 @@ int64_t damp_future_delta(int64_t delta_ms, int64_t cur_future,
|
|
|
54
54
|
return static_cast<int64_t>(static_cast<double>(delta_ms) * resist);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// Frame the default view
|
|
58
|
-
//
|
|
57
|
+
// Frame the default view. When the consumer has set a target candle body width
|
|
58
|
+
// (default_candle_px), size the window so each candle renders ~that wide with the
|
|
59
|
+
// right edge pinned to the latest candle; otherwise fall back to the legacy
|
|
60
|
+
// "most recent ~80 candles". No-op when there are no candles.
|
|
59
61
|
void apply_default_framing(VroomChart* chart) {
|
|
60
62
|
if (chart->candles.empty()) return;
|
|
63
|
+
|
|
64
|
+
// Px-driven framing. Reuses the inversion from vroom_chart_scale_time_axis:
|
|
65
|
+
// body_w = usable × (dur / window) × ratio → window = usable × dur × ratio / body_w.
|
|
66
|
+
// Needs a valid layout (width_px); until size is known it falls through to
|
|
67
|
+
// the candle-count default below.
|
|
68
|
+
if (chart->default_candle_px > 0.f) {
|
|
69
|
+
const auto lay = chart->layout();
|
|
70
|
+
const double usable =
|
|
71
|
+
lay.width_px - lay.y_axis_width_px - lay.right_padding_px;
|
|
72
|
+
const double ratio = chart->theme.floats[VROOM_FLOAT_CANDLE_WIDTH_RATIO];
|
|
73
|
+
const double dur = static_cast<double>(chart->candle_duration_ms);
|
|
74
|
+
const double body = std::clamp(static_cast<double>(chart->default_candle_px),
|
|
75
|
+
kMinCandleBodyPx, kMaxCandleBodyPx);
|
|
76
|
+
if (usable > 0.0 && ratio > 0.0 && dur > 0.0 && body > 0.0) {
|
|
77
|
+
int64_t window_ms = static_cast<int64_t>((usable * dur * ratio) / body);
|
|
78
|
+
const int64_t span =
|
|
79
|
+
chart->candles.back().time_ms - chart->candles.front().time_ms;
|
|
80
|
+
window_ms = std::clamp<int64_t>(window_ms, chart->candle_duration_ms,
|
|
81
|
+
span > 0 ? span : window_ms);
|
|
82
|
+
chart->visible_end_ms = chart->candles.back().time_ms;
|
|
83
|
+
chart->visible_start_ms = chart->visible_end_ms - window_ms;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
61
88
|
constexpr size_t kDefaultVisible = 80;
|
|
62
89
|
const size_t start_idx = chart->candles.size() > kDefaultVisible
|
|
63
90
|
? chart->candles.size() - kDefaultVisible
|
|
@@ -167,6 +194,16 @@ extern "C" void vroom_chart_set_visible_range(VroomChart* chart, int64_t start_m
|
|
|
167
194
|
chart->mark_dirty();
|
|
168
195
|
}
|
|
169
196
|
|
|
197
|
+
extern "C" void vroom_chart_set_default_candle_width(VroomChart* chart, float px) {
|
|
198
|
+
if (!chart) return;
|
|
199
|
+
chart->default_candle_px = px;
|
|
200
|
+
// Re-frame so a value that arrives after set_candles still drives the initial
|
|
201
|
+
// view. Intended for initial framing only — calling it after the user has
|
|
202
|
+
// panned would snap the view back to the default frame.
|
|
203
|
+
if (!chart->candles.empty()) apply_default_framing(chart);
|
|
204
|
+
chart->mark_dirty();
|
|
205
|
+
}
|
|
206
|
+
|
|
170
207
|
extern "C" void vroom_chart_get_visible_range(VroomChart* chart,
|
|
171
208
|
int64_t* out_start_ms,
|
|
172
209
|
int64_t* out_end_ms) {
|
|
@@ -716,6 +753,17 @@ extern "C" void vroom_chart_set_drawings(VroomChart* chart,
|
|
|
716
753
|
chart->mark_dirty();
|
|
717
754
|
}
|
|
718
755
|
|
|
756
|
+
// ---- Liquidity bands (order-book depth overlay) ---------------------------
|
|
757
|
+
|
|
758
|
+
extern "C" void vroom_chart_set_liquidity(VroomChart* chart,
|
|
759
|
+
const VroomBand* bands, size_t count,
|
|
760
|
+
const VroomLiquidityStyle* style) {
|
|
761
|
+
if (!chart) return;
|
|
762
|
+
chart->bands.assign(bands, bands + count);
|
|
763
|
+
if (style) chart->liquidity_style = *style;
|
|
764
|
+
chart->mark_dirty();
|
|
765
|
+
}
|
|
766
|
+
|
|
719
767
|
extern "C" void vroom_chart_set_draft(VroomChart* chart, int64_t a_time,
|
|
720
768
|
double a_price, bool has_b, int64_t b_time,
|
|
721
769
|
double b_price, bool guide, uint32_t color,
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#include "liquidity.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
|
|
6
|
+
#pragma clang diagnostic push
|
|
7
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
8
|
+
#include "include/core/SkCanvas.h"
|
|
9
|
+
#include "include/core/SkColor.h"
|
|
10
|
+
#include "include/core/SkPaint.h"
|
|
11
|
+
#include "include/core/SkPoint.h"
|
|
12
|
+
#include "include/core/SkRect.h"
|
|
13
|
+
#include "include/core/SkShader.h"
|
|
14
|
+
#include "include/core/SkTileMode.h"
|
|
15
|
+
// Skia's linear-gradient API differs across the Skia versions we build against:
|
|
16
|
+
// the WASM build uses a newer Skia (SkGradient.h + SkShaders::LinearGradient),
|
|
17
|
+
// while react-native-skia bundles an older one (SkGradientShader::MakeLinear).
|
|
18
|
+
// Neither ships both headers, so select whichever is present.
|
|
19
|
+
#if __has_include("include/effects/SkGradient.h")
|
|
20
|
+
# include "include/core/SkSpan.h"
|
|
21
|
+
# include "include/effects/SkGradient.h"
|
|
22
|
+
# define VROOM_SK_MODERN_GRADIENT 1
|
|
23
|
+
#else
|
|
24
|
+
# include "include/effects/SkGradientShader.h"
|
|
25
|
+
# define VROOM_SK_MODERN_GRADIENT 0
|
|
26
|
+
#endif
|
|
27
|
+
#pragma clang diagnostic pop
|
|
28
|
+
|
|
29
|
+
#include "chart.h"
|
|
30
|
+
|
|
31
|
+
namespace vroom::liquidity {
|
|
32
|
+
namespace {
|
|
33
|
+
|
|
34
|
+
// A horizontal shader fading from transparent at the left of `pts` to `base` at
|
|
35
|
+
// opacity `alpha` on the right, keeping RGB constant so the faded edge doesn't
|
|
36
|
+
// darken toward black. Abstracts over the two Skia gradient APIs above.
|
|
37
|
+
sk_sp<SkShader> left_fade_shader(const SkPoint pts[2], SkColor base, float alpha) {
|
|
38
|
+
#if VROOM_SK_MODERN_GRADIENT
|
|
39
|
+
const float r = SkColorGetR(base) / 255.f;
|
|
40
|
+
const float g = SkColorGetG(base) / 255.f;
|
|
41
|
+
const float b = SkColorGetB(base) / 255.f;
|
|
42
|
+
const SkColor4f colors[2] = {SkColor4f{r, g, b, 0.f}, SkColor4f{r, g, b, alpha}};
|
|
43
|
+
const SkGradient::Colors grad_colors(
|
|
44
|
+
SkSpan<const SkColor4f>(colors, 2), SkTileMode::kClamp);
|
|
45
|
+
const SkGradient grad(grad_colors, SkGradient::Interpolation{});
|
|
46
|
+
return SkShaders::LinearGradient(pts, grad);
|
|
47
|
+
#else
|
|
48
|
+
const auto a = static_cast<U8CPU>(alpha * 255.f + 0.5f);
|
|
49
|
+
const SkColor colors[2] = {SkColorSetA(base, 0), SkColorSetA(base, a)};
|
|
50
|
+
return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
|
|
51
|
+
#endif
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
} // namespace
|
|
55
|
+
|
|
56
|
+
void draw(SkCanvas* canvas,
|
|
57
|
+
const VroomChart& chart,
|
|
58
|
+
const Layout& lay,
|
|
59
|
+
const PriceBounds& bounds,
|
|
60
|
+
float candle_right,
|
|
61
|
+
float candle_area_h) {
|
|
62
|
+
if (!canvas || candle_right <= 0.f || candle_area_h <= 0.f) return;
|
|
63
|
+
if (chart.bands.empty()) return;
|
|
64
|
+
|
|
65
|
+
const VroomLiquidityStyle& style = chart.liquidity_style;
|
|
66
|
+
|
|
67
|
+
// Leftward reach: the smaller of the px cap and the pane-fraction, clamped so
|
|
68
|
+
// the band never starts left of the pane.
|
|
69
|
+
const float frac_reach = style.width_frac > 0.f ? style.width_frac * candle_right
|
|
70
|
+
: candle_right;
|
|
71
|
+
const float px_reach = style.width_px > 0.f ? style.width_px : candle_right;
|
|
72
|
+
const float reach = std::min(px_reach, frac_reach);
|
|
73
|
+
const float left_x = std::max(0.f, candle_right - reach);
|
|
74
|
+
|
|
75
|
+
// Volume that maps to peak opacity: explicit, or the largest band's volume.
|
|
76
|
+
double max_vol = style.max_volume;
|
|
77
|
+
if (max_vol <= 0.0) {
|
|
78
|
+
for (const VroomBand& b : chart.bands) max_vol = std::max(max_vol, b.volume);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const float min_op = std::clamp(style.min_opacity, 0.f, 1.f);
|
|
82
|
+
const float max_op = std::clamp(style.max_opacity, 0.f, 1.f);
|
|
83
|
+
|
|
84
|
+
canvas->save();
|
|
85
|
+
canvas->clipRect(SkRect::MakeLTRB(0.f, 0.f, candle_right, candle_area_h));
|
|
86
|
+
|
|
87
|
+
const SkPoint pts[2] = {SkPoint{left_x, 0.f}, SkPoint{candle_right, 0.f}};
|
|
88
|
+
|
|
89
|
+
for (const VroomBand& b : chart.bands) {
|
|
90
|
+
// Higher price -> smaller y, so max_price is the top edge.
|
|
91
|
+
const float y_top = vroom::price_to_y(lay, bounds, b.max_price);
|
|
92
|
+
const float y_bot = vroom::price_to_y(lay, bounds, b.min_price);
|
|
93
|
+
if (y_bot < 0.f || y_top > candle_area_h) continue; // fully off-pane
|
|
94
|
+
|
|
95
|
+
const SkColor base =
|
|
96
|
+
static_cast<SkColor>(b.side == 0 ? style.buy_color : style.sell_color);
|
|
97
|
+
|
|
98
|
+
const double t = max_vol > 0.0
|
|
99
|
+
? std::clamp(b.volume / max_vol, 0.0, 1.0)
|
|
100
|
+
: 0.0;
|
|
101
|
+
const float alpha = min_op + (max_op - min_op) * static_cast<float>(t);
|
|
102
|
+
|
|
103
|
+
SkPaint paint;
|
|
104
|
+
paint.setAntiAlias(true);
|
|
105
|
+
paint.setShader(left_fade_shader(pts, base, alpha));
|
|
106
|
+
canvas->drawRect(SkRect::MakeLTRB(left_x, y_top, candle_right, y_bot), paint);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
canvas->restore();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
} // namespace vroom::liquidity
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Liquidity bands — resting-order / order-book depth rendered on the price pane.
|
|
2
|
+
//
|
|
3
|
+
// Each band (chart.bands) is a price interval carrying a total size on one side
|
|
4
|
+
// of the book. It is drawn as a horizontal rectangle anchored at the inner edge
|
|
5
|
+
// of the price axis (`candle_right`), stretching left by the configured reach
|
|
6
|
+
// and fading to transparent with a left->right gradient. Color comes from the
|
|
7
|
+
// band's side (buy/sell); the band's alpha comes from its volume mapped into
|
|
8
|
+
// [min_opacity, max_opacity]. Because the vertical extent is derived from price
|
|
9
|
+
// via price_to_y, bands scale with the y-axis on zoom.
|
|
10
|
+
//
|
|
11
|
+
// Like ma_overlay / drawings this is its own module so the chart orchestrator
|
|
12
|
+
// stays thin. Drawn BEHIND the candles (before candles::draw) so candle bodies
|
|
13
|
+
// paint over the bands.
|
|
14
|
+
|
|
15
|
+
#pragma once
|
|
16
|
+
|
|
17
|
+
#include "viewport.h"
|
|
18
|
+
#include "vroom/vroom_chart.h"
|
|
19
|
+
|
|
20
|
+
class SkCanvas;
|
|
21
|
+
struct VroomChart;
|
|
22
|
+
|
|
23
|
+
namespace vroom::liquidity {
|
|
24
|
+
|
|
25
|
+
// Draws the liquidity bands. `candle_right` is the x of the price-axis strip
|
|
26
|
+
// (bands' right edge); `candle_area_h` is the price-pane bottom. Geometry is
|
|
27
|
+
// clipped to that rectangle so bands never bleed into the axis strips.
|
|
28
|
+
void draw(SkCanvas* canvas,
|
|
29
|
+
const VroomChart& chart,
|
|
30
|
+
const vroom::Layout& lay,
|
|
31
|
+
const vroom::PriceBounds& bounds,
|
|
32
|
+
float candle_right,
|
|
33
|
+
float candle_area_h);
|
|
34
|
+
|
|
35
|
+
} // namespace vroom::liquidity
|
package/cpp/_core_src/theme.cpp
CHANGED
|
@@ -33,6 +33,9 @@ constexpr float kDefaultFloats[VROOM_FLOAT_COUNT_] = {
|
|
|
33
33
|
22.f, // X_AXIS_HEIGHT_PX — bottom strip for time labels
|
|
34
34
|
0.5f, // VOLUME_OPACITY — candle color at 50% transparency
|
|
35
35
|
0.20f, // INDICATOR_HEIGHT_FRAC — below-chart indicator pane = 20% of height
|
|
36
|
+
0.f, // CANDLE_RADIUS_PX — square by default
|
|
37
|
+
0.f, // WICK_ROUND_CAP — butt caps by default
|
|
38
|
+
0.f, // VOLUME_RADIUS_PX — square by default
|
|
36
39
|
};
|
|
37
40
|
|
|
38
41
|
} // namespace
|
package/cpp/_core_src/volume.cpp
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
5
5
|
#include "include/core/SkCanvas.h"
|
|
6
6
|
#include "include/core/SkPaint.h"
|
|
7
|
+
#include "include/core/SkRRect.h"
|
|
7
8
|
#include "include/core/SkRect.h"
|
|
8
9
|
#pragma clang diagnostic pop
|
|
9
10
|
|
|
@@ -41,6 +42,7 @@ void draw(SkCanvas* canvas,
|
|
|
41
42
|
const float half_body = body_w * 0.5f;
|
|
42
43
|
|
|
43
44
|
const float opacity = theme.floats[VROOM_FLOAT_VOLUME_OPACITY];
|
|
45
|
+
const float vol_r = theme.floats[VROOM_FLOAT_VOLUME_RADIUS_PX];
|
|
44
46
|
SkPaint bull_paint;
|
|
45
47
|
bull_paint.setAntiAlias(true);
|
|
46
48
|
bull_paint.setColor(theme.colors[VROOM_COLOR_ACCENT_BULL]);
|
|
@@ -61,9 +63,19 @@ void draw(SkCanvas* canvas,
|
|
|
61
63
|
visible_start_ms, window_ms);
|
|
62
64
|
const float h = std::max(
|
|
63
65
|
1.f, static_cast<float>(c.volume / max_vol) * region_h);
|
|
64
|
-
|
|
65
|
-
SkRect::MakeXYWH(cx - half_body, candle_area_h - h, body_w, h)
|
|
66
|
-
|
|
66
|
+
const SkRect rect =
|
|
67
|
+
SkRect::MakeXYWH(cx - half_body, candle_area_h - h, body_w, h);
|
|
68
|
+
const SkPaint& paint = bull ? bull_paint : bear_paint;
|
|
69
|
+
// Round only the top corners; clamp so short/narrow bars don't over-round.
|
|
70
|
+
const float r = std::min({vol_r, body_w * 0.5f, h});
|
|
71
|
+
if (r > 0.f) {
|
|
72
|
+
SkRRect rr;
|
|
73
|
+
const SkVector radii[4] = {{r, r}, {r, r}, {0, 0}, {0, 0}}; // TL, TR, BR, BL
|
|
74
|
+
rr.setRectRadii(rect, radii);
|
|
75
|
+
canvas->drawRRect(rr, paint);
|
|
76
|
+
} else {
|
|
77
|
+
canvas->drawRect(rect, paint);
|
|
78
|
+
}
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
81
|
|
package/lib/index.d.mts
CHANGED
|
@@ -68,14 +68,22 @@ type VroomTheme = {
|
|
|
68
68
|
accentBull?: VroomColor;
|
|
69
69
|
/** Generic down color for the price indicator, volume bars, and MACD histogram. Defaults to red; independent of `bear`. */
|
|
70
70
|
accentBear?: VroomColor;
|
|
71
|
-
/** Up candle body 1px
|
|
71
|
+
/** Up candle body border (1px, drawn *inside* the body so it never changes candle width). Omit or set to the bull fill color to hide it. */
|
|
72
72
|
borderBull?: VroomColor;
|
|
73
|
-
/** Down candle body 1px
|
|
73
|
+
/** Down candle body border (1px, drawn *inside* the body so it never changes candle width). Omit or set to the bear fill color to hide it. */
|
|
74
74
|
borderBear?: VroomColor;
|
|
75
75
|
/** Up candle wick color. Defaults to the bull fill color. */
|
|
76
76
|
wickBull?: VroomColor;
|
|
77
77
|
/** Down candle wick color. Defaults to the bear fill color. */
|
|
78
78
|
wickBear?: VroomColor;
|
|
79
|
+
/** Wick stroke width in px (applies to both up and down wicks). Defaults to 1. */
|
|
80
|
+
wickWidth?: number;
|
|
81
|
+
/** Corner radius (px) of candle bodies. Defaults to 0 (square). */
|
|
82
|
+
candleRadius?: number;
|
|
83
|
+
/** Round the wick end caps. Defaults to false. */
|
|
84
|
+
wickRoundCap?: boolean;
|
|
85
|
+
/** Corner radius (px) of the *top* of volume bars. Defaults to 0 (square). */
|
|
86
|
+
volumeRadius?: number;
|
|
79
87
|
/** Gridlines. */
|
|
80
88
|
grid?: VroomColor;
|
|
81
89
|
/** Axis label text (price + time). */
|
|
@@ -169,6 +177,51 @@ type VWAPConfig = {
|
|
|
169
177
|
/** Stroke width in px. Default 1.5. */
|
|
170
178
|
width?: number;
|
|
171
179
|
};
|
|
180
|
+
/**
|
|
181
|
+
* A single resting-liquidity band: a price interval carrying a total order size
|
|
182
|
+
* on one side of the book. Consolidate raw L2 levels into these buckets before
|
|
183
|
+
* passing them. The vertical extent is defined in price space, so bands scale
|
|
184
|
+
* with the y-axis on zoom.
|
|
185
|
+
*/
|
|
186
|
+
type LiquidityBand = {
|
|
187
|
+
/** Bottom of the price interval. */
|
|
188
|
+
minPrice: number;
|
|
189
|
+
/** Top of the price interval. A per-level order uses a thin interval. */
|
|
190
|
+
maxPrice: number;
|
|
191
|
+
/** Which side of the book — selects the buy vs sell color. */
|
|
192
|
+
side: 'buy' | 'sell';
|
|
193
|
+
/** Total resting size in this band; drives the band's opacity. */
|
|
194
|
+
volume: number;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Resting-order / order-book liquidity overlay. Each band is drawn as a
|
|
198
|
+
* horizontal rectangle anchored at the inner edge of the price axis, stretching
|
|
199
|
+
* left (~`widthPx`/`widthFrac` of the pane) and fading out with a gradient,
|
|
200
|
+
* colored by side and made more opaque with volume. Rendered behind the candles.
|
|
201
|
+
* Omit the prop (or pass no bands) to hide the overlay.
|
|
202
|
+
*/
|
|
203
|
+
type LiquidityConfig = {
|
|
204
|
+
/** The bands to render. */
|
|
205
|
+
bands: LiquidityBand[];
|
|
206
|
+
/** Buy-side color (hex string or packed ARGB number). Default teal-green. */
|
|
207
|
+
buyColor?: VroomColor;
|
|
208
|
+
/** Sell-side color (hex string or packed ARGB number). Default red. */
|
|
209
|
+
sellColor?: VroomColor;
|
|
210
|
+
/**
|
|
211
|
+
* Volume mapped to the peak (`maxOpacity`) band. Omit to auto-scale to the
|
|
212
|
+
* largest band's volume each render.
|
|
213
|
+
*/
|
|
214
|
+
maxVolume?: number;
|
|
215
|
+
/** Opacity floor for the smallest band. Default 0.05. */
|
|
216
|
+
minOpacity?: number;
|
|
217
|
+
/** Opacity at `maxVolume`. Default 0.8. */
|
|
218
|
+
maxOpacity?: number;
|
|
219
|
+
/** Leftward reach from the axis in px. Default 300. */
|
|
220
|
+
widthPx?: number;
|
|
221
|
+
/** Leftward reach as a fraction of pane width. Default 0.25. The smaller of
|
|
222
|
+
* `widthPx` and `widthFrac * paneWidth` is used. */
|
|
223
|
+
widthFrac?: number;
|
|
224
|
+
};
|
|
172
225
|
/** MACD indicator config. Rendered in its own pane below the candles. */
|
|
173
226
|
type MACDConfig = {
|
|
174
227
|
enabled?: boolean;
|
|
@@ -204,6 +257,14 @@ type VroomChartCoreProps = {
|
|
|
204
257
|
height?: number;
|
|
205
258
|
/** Time window to render. Omit (or both 0) to show every candle. */
|
|
206
259
|
visibleRange?: VisibleRange;
|
|
260
|
+
/**
|
|
261
|
+
* Target candle *body* width in logical px for the INITIAL framing. Larger →
|
|
262
|
+
* more zoomed in (fewer candles); smaller → more zoomed out. Only affects
|
|
263
|
+
* first load / reset; an explicit `visibleRange` takes precedence. Omit for
|
|
264
|
+
* the default (~80 candles). Useful to keep candles a consistent size across
|
|
265
|
+
* devices of different widths.
|
|
266
|
+
*/
|
|
267
|
+
defaultCandleWidth?: number;
|
|
207
268
|
theme?: VroomTheme;
|
|
208
269
|
/** RSI indicator (pane below the candles). Omit/disable to hide it. */
|
|
209
270
|
rsi?: RSIConfig;
|
|
@@ -213,6 +274,8 @@ type VroomChartCoreProps = {
|
|
|
213
274
|
movingAverages?: MovingAverageOverlay[];
|
|
214
275
|
/** VWAP overlay (session anchor, configurable reset). */
|
|
215
276
|
vwap?: VWAPConfig;
|
|
277
|
+
/** Resting-order / order-book liquidity bands drawn behind the candles. */
|
|
278
|
+
liquidity?: LiquidityConfig;
|
|
216
279
|
/**
|
|
217
280
|
* Pixels the crosshair dot / horizontal line sit *above* the touch point so
|
|
218
281
|
* they aren't hidden under the thumb. The vertical line stays centered on the
|