react-native-vroom-chart 0.1.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/LICENSE +21 -0
- package/README.md +27 -0
- package/android/README.md +7 -0
- package/cpp/README.md +11 -0
- package/cpp/VroomChartHostObject.cpp +458 -0
- package/cpp/VroomChartHostObject.h +36 -0
- package/cpp/VroomJsiInstaller.cpp +69 -0
- package/cpp/VroomJsiInstaller.h +10 -0
- package/cpp/VroomSkiaContext.cpp +25 -0
- package/cpp/VroomSkiaContext.h +30 -0
- package/cpp/_core_include/vroom/vroom_chart.h +195 -0
- package/cpp/_core_src/candles.cpp +74 -0
- package/cpp/_core_src/candles.h +34 -0
- package/cpp/_core_src/chart.cpp +317 -0
- package/cpp/_core_src/chart.h +167 -0
- package/cpp/_core_src/chart_facade.cpp +570 -0
- package/cpp/_core_src/chart_internal.h +30 -0
- package/cpp/_core_src/crosshair.cpp +65 -0
- package/cpp/_core_src/crosshair.h +32 -0
- package/cpp/_core_src/fonts.cpp +22 -0
- package/cpp/_core_src/fonts.h +25 -0
- package/cpp/_core_src/labels.cpp +340 -0
- package/cpp/_core_src/labels.h +85 -0
- package/cpp/_core_src/ma.cpp +53 -0
- package/cpp/_core_src/ma.h +39 -0
- package/cpp/_core_src/ma_overlay.cpp +73 -0
- package/cpp/_core_src/ma_overlay.h +42 -0
- package/cpp/_core_src/macd.cpp +68 -0
- package/cpp/_core_src/macd.h +29 -0
- package/cpp/_core_src/macd_pane.cpp +199 -0
- package/cpp/_core_src/macd_pane.h +41 -0
- package/cpp/_core_src/price_indicator.cpp +106 -0
- package/cpp/_core_src/price_indicator.h +29 -0
- package/cpp/_core_src/rsi.cpp +70 -0
- package/cpp/_core_src/rsi.h +32 -0
- package/cpp/_core_src/rsi_pane.cpp +167 -0
- package/cpp/_core_src/rsi_pane.h +39 -0
- package/cpp/_core_src/theme.cpp +41 -0
- package/cpp/_core_src/theme.h +23 -0
- package/cpp/_core_src/ticks.cpp +49 -0
- package/cpp/_core_src/ticks.h +30 -0
- package/cpp/_core_src/viewport.cpp +141 -0
- package/cpp/_core_src/viewport.h +100 -0
- package/cpp/_core_src/volume.cpp +70 -0
- package/cpp/_core_src/volume.h +34 -0
- package/cpp/_core_src/vwap.cpp +51 -0
- package/cpp/_core_src/vwap.h +27 -0
- package/ios/README.md +7 -0
- package/ios/VroomChartModule.h +11 -0
- package/ios/VroomChartModule.mm +44 -0
- package/lib/index.d.mts +185 -0
- package/lib/index.d.ts +185 -0
- package/lib/index.js +429 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +396 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +75 -0
- package/react-native-vroom-chart.podspec +79 -0
- package/src/NativeVroomChart.ts +12 -0
- package/src/VroomChart.tsx +382 -0
- package/src/index.ts +14 -0
- package/src/jsi.d.ts +128 -0
- package/src/packCandles.ts +24 -0
- package/src/theme.ts +43 -0
- package/src/types.ts +27 -0
- package/src/useChartCore.ts +135 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Helpers for reaching into RN-Skia's installed JSI bindings to grab its
|
|
2
|
+
// RNSkPlatformContext — which we need so we can use JsiSkFontMgrFactory to
|
|
3
|
+
// get the system font manager cross-platform.
|
|
4
|
+
//
|
|
5
|
+
// JsiSkHostObject::getContext() is protected, so we use a "friend helper"
|
|
6
|
+
// subclass that exposes it via `using`. We never construct an instance —
|
|
7
|
+
// we just static_cast existing host objects to the accessor type.
|
|
8
|
+
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
#include <memory>
|
|
12
|
+
|
|
13
|
+
#include <jsi/jsi.h>
|
|
14
|
+
|
|
15
|
+
#include "JsiSkHostObjects.h" // RNSkia::JsiSkHostObject
|
|
16
|
+
#include "RNSkPlatformContext.h" // RNSkia::RNSkPlatformContext
|
|
17
|
+
|
|
18
|
+
namespace vroom {
|
|
19
|
+
|
|
20
|
+
class HostObjectAccessor : public RNSkia::JsiSkHostObject {
|
|
21
|
+
public:
|
|
22
|
+
using RNSkia::JsiSkHostObject::getContext;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Returns the RN-Skia platform context by reading global.SkiaApi.
|
|
26
|
+
// Returns nullptr if SkiaApi isn't on the global yet (RN-Skia not installed).
|
|
27
|
+
std::shared_ptr<RNSkia::RNSkPlatformContext> getRNSkContext(
|
|
28
|
+
facebook::jsi::Runtime& runtime);
|
|
29
|
+
|
|
30
|
+
} // namespace vroom
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// vroom_chart.h — public C facade for the vroom chart core.
|
|
2
|
+
//
|
|
3
|
+
// Threading: All calls must happen on a single thread (the render thread the
|
|
4
|
+
// host picked). The core does no internal locking.
|
|
5
|
+
//
|
|
6
|
+
// Rendering model: push. The core fires `on_redraw_requested` when something
|
|
7
|
+
// visual changed. The host marks dirty and calls `vroom_chart_draw` on the
|
|
8
|
+
// next vsync — never re-enter draw from the callback.
|
|
9
|
+
//
|
|
10
|
+
// Gestures: host-driven. The host wires its native gesture recognizers
|
|
11
|
+
// (UIPanGestureRecognizer / Android GestureDetector / RNGH) and calls
|
|
12
|
+
// `vroom_chart_pan`, `vroom_chart_zoom`, `vroom_chart_set_crosshair`,
|
|
13
|
+
// `vroom_chart_clear_crosshair`. The core has no gesture state machine.
|
|
14
|
+
|
|
15
|
+
#ifndef VROOM_CHART_H
|
|
16
|
+
#define VROOM_CHART_H
|
|
17
|
+
|
|
18
|
+
#include <stdbool.h>
|
|
19
|
+
#include <stddef.h>
|
|
20
|
+
#include <stdint.h>
|
|
21
|
+
|
|
22
|
+
#ifdef __cplusplus
|
|
23
|
+
extern "C" {
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
// Forward-declare Skia types as opaque so this header has no Skia dependency.
|
|
27
|
+
// The implementation casts back to SkCanvas* internally.
|
|
28
|
+
typedef struct SkCanvas SkCanvas;
|
|
29
|
+
|
|
30
|
+
typedef struct VroomChart VroomChart;
|
|
31
|
+
|
|
32
|
+
// ---- Data types -----------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
typedef struct VroomCandle {
|
|
35
|
+
int64_t time_ms; // epoch milliseconds, ascending, non-overlapping
|
|
36
|
+
double open;
|
|
37
|
+
double high;
|
|
38
|
+
double low;
|
|
39
|
+
double close;
|
|
40
|
+
double volume;
|
|
41
|
+
} VroomCandle;
|
|
42
|
+
|
|
43
|
+
// A moving-average overlay line drawn on the price pane.
|
|
44
|
+
typedef struct VroomOverlay {
|
|
45
|
+
int32_t kind; // 0 = SMA, 1 = EMA
|
|
46
|
+
int32_t period; // lookback in candles (>= 1)
|
|
47
|
+
int32_t source; // 0=close,1=open,2=high,3=low,4=hl2,5=hlc3,6=ohlc4
|
|
48
|
+
uint32_t color; // 0xAARRGGBB
|
|
49
|
+
float width; // stroke width in px
|
|
50
|
+
} VroomOverlay;
|
|
51
|
+
|
|
52
|
+
// ---- Styling keys ---------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
typedef enum {
|
|
55
|
+
VROOM_COLOR_BACKGROUND = 0,
|
|
56
|
+
VROOM_COLOR_BULL,
|
|
57
|
+
VROOM_COLOR_BEAR,
|
|
58
|
+
VROOM_COLOR_WICK,
|
|
59
|
+
VROOM_COLOR_GRID,
|
|
60
|
+
VROOM_COLOR_AXIS_TEXT,
|
|
61
|
+
VROOM_COLOR_CROSSHAIR,
|
|
62
|
+
VROOM_COLOR_TOOLTIP_BG,
|
|
63
|
+
VROOM_COLOR_TOOLTIP_TEXT,
|
|
64
|
+
VROOM_COLOR_CROSSHAIR_TARGET, // the hollow ring/dot at the intersection
|
|
65
|
+
VROOM_COLOR_COUNT_
|
|
66
|
+
} VroomColorKey;
|
|
67
|
+
|
|
68
|
+
typedef enum {
|
|
69
|
+
VROOM_FLOAT_CANDLE_WIDTH_RATIO = 0, // 0..1 of slot width
|
|
70
|
+
VROOM_FLOAT_WICK_WIDTH_PX,
|
|
71
|
+
VROOM_FLOAT_RIGHT_PADDING_PX, // small gutter between candles and y-axis
|
|
72
|
+
VROOM_FLOAT_AXIS_FONT_SIZE_PX,
|
|
73
|
+
VROOM_FLOAT_Y_AXIS_WIDTH_RATIO, // fallback y-axis width when no typeface
|
|
74
|
+
VROOM_FLOAT_X_AXIS_HEIGHT_PX, // bottom strip reserved for time labels
|
|
75
|
+
VROOM_FLOAT_VOLUME_OPACITY, // volume bar opacity (1=opaque)
|
|
76
|
+
VROOM_FLOAT_INDICATOR_HEIGHT_FRAC, // below-chart indicator pane, fraction of height
|
|
77
|
+
VROOM_FLOAT_COUNT_
|
|
78
|
+
} VroomFloatKey;
|
|
79
|
+
|
|
80
|
+
// ---- Callbacks ------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
typedef struct {
|
|
83
|
+
void (*on_redraw_requested)(void* ctx);
|
|
84
|
+
void (*on_viewport_changed)(void* ctx, int64_t start_ms, int64_t end_ms);
|
|
85
|
+
void (*on_crosshair_changed)(void* ctx, bool active, int64_t ms, double price);
|
|
86
|
+
} VroomCallbacks;
|
|
87
|
+
|
|
88
|
+
// ---- Lifecycle ------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
VroomChart* vroom_chart_create(const VroomCallbacks* cb, void* user_ctx);
|
|
91
|
+
void vroom_chart_destroy(VroomChart* chart);
|
|
92
|
+
|
|
93
|
+
// ---- Data (copied internally; caller may free immediately) ----------------
|
|
94
|
+
|
|
95
|
+
void vroom_chart_set_candles(VroomChart* chart, const VroomCandle* data, size_t count);
|
|
96
|
+
void vroom_chart_append_candle(VroomChart* chart, const VroomCandle* c);
|
|
97
|
+
void vroom_chart_update_last(VroomChart* chart, const VroomCandle* c);
|
|
98
|
+
|
|
99
|
+
// ---- Sizing ---------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
void vroom_chart_set_size(VroomChart* chart, float width_px, float height_px, float px_ratio);
|
|
102
|
+
|
|
103
|
+
// ---- Viewport -------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
void vroom_chart_set_visible_range(VroomChart* chart, int64_t start_ms, int64_t end_ms);
|
|
106
|
+
void vroom_chart_pan(VroomChart* chart, float dx_px, float dy_px);
|
|
107
|
+
// Directional zoom. scale_x scales the time window around focus_x_px (>1 =
|
|
108
|
+
// narrower window, wider candles); scale_y scales the price range around
|
|
109
|
+
// focus_y_px (>1 = taller candles). Pass 1.0 for an axis to leave it untouched.
|
|
110
|
+
void vroom_chart_zoom(VroomChart* chart, float scale_x, float scale_y,
|
|
111
|
+
float focus_x_px, float focus_y_px);
|
|
112
|
+
|
|
113
|
+
// Two-finger translation. Shifts the time window like pan (dx) and shifts
|
|
114
|
+
// the price bounds vertically (dy) without changing their range — so the
|
|
115
|
+
// chart slides without rescaling. dy > 0 (drag down) moves content down
|
|
116
|
+
// (price labels move down to higher numbers).
|
|
117
|
+
void vroom_chart_translate(VroomChart* chart, float dx_px, float dy_px);
|
|
118
|
+
|
|
119
|
+
// Axis-drag controls — used by JS gesture handlers that detect a drag
|
|
120
|
+
// started on the y-axis or x-axis strip. Scaling pivots around the natural
|
|
121
|
+
// anchor (price center for y, right edge for x).
|
|
122
|
+
//
|
|
123
|
+
// Sign conventions:
|
|
124
|
+
// scale_price_axis: dy > 0 (drag down) widens the price range → candles shrink
|
|
125
|
+
// scale_time_axis: dx > 0 (drag right) widens the time range → candles thin
|
|
126
|
+
void vroom_chart_scale_price_axis(VroomChart* chart, float dy_px);
|
|
127
|
+
void vroom_chart_scale_time_axis(VroomChart* chart, float dx_px);
|
|
128
|
+
|
|
129
|
+
// Reads the current y-axis width, x-axis height, and below-chart indicator pane
|
|
130
|
+
// height in pixels so callers can hit-test gestures against each region on the
|
|
131
|
+
// JS side. out_indicator_height_px is 0 when no indicator pane is shown.
|
|
132
|
+
void vroom_chart_get_axis_metrics(VroomChart* chart,
|
|
133
|
+
float* out_y_axis_width_px,
|
|
134
|
+
float* out_x_axis_height_px,
|
|
135
|
+
float* out_indicator_height_px);
|
|
136
|
+
|
|
137
|
+
// ---- Crosshair ------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
void vroom_chart_set_crosshair(VroomChart* chart, float x_px, float y_px);
|
|
140
|
+
void vroom_chart_clear_crosshair(VroomChart* chart);
|
|
141
|
+
|
|
142
|
+
// Fills *out with the OHLCV of the candle the crosshair currently snaps to and
|
|
143
|
+
// returns true. Returns false (leaving *out untouched) when the crosshair is
|
|
144
|
+
// inactive or there are no visible candles. Stateless — recomputes the snap
|
|
145
|
+
// from the current crosshair x and visible window, matching what's rendered.
|
|
146
|
+
bool vroom_chart_get_crosshair_candle(VroomChart* chart, VroomCandle* out);
|
|
147
|
+
|
|
148
|
+
// ---- Indicators -----------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
// Configures the RSI indicator (rendered in a pane below the candles). `period`
|
|
151
|
+
// is the RSI lookback in candle counts (clamped >= 2). `upper_band`/`lower_band`
|
|
152
|
+
// are the overbought/oversold reference levels (0..100; default 70/30).
|
|
153
|
+
// `ma_enabled` toggles the RSI-based moving-average trendline and `ma_period`
|
|
154
|
+
// is its length (clamped >= 1). When enabled, the candle pane shrinks by
|
|
155
|
+
// VROOM_FLOAT_INDICATOR_HEIGHT_FRAC.
|
|
156
|
+
void vroom_chart_set_rsi(VroomChart* chart, bool enabled, int period,
|
|
157
|
+
double upper_band, double lower_band,
|
|
158
|
+
bool ma_enabled, int ma_period);
|
|
159
|
+
|
|
160
|
+
// Configures the MACD indicator (its own pane below the candles). `fast`/`slow`
|
|
161
|
+
// are the EMA lengths (clamped >= 1, slow forced > fast; default 12/26) and
|
|
162
|
+
// `signal` is the signal-line EMA length (clamped >= 1; default 9). Panes stack
|
|
163
|
+
// in enable order, most recently enabled at the bottom.
|
|
164
|
+
void vroom_chart_set_macd(VroomChart* chart, bool enabled, int fast, int slow,
|
|
165
|
+
int signal);
|
|
166
|
+
|
|
167
|
+
// Replaces the full set of moving-average overlay lines (SMA/EMA) drawn on the
|
|
168
|
+
// price pane. Pass count 0 to clear them. Overlays don't reserve a pane.
|
|
169
|
+
void vroom_chart_set_overlays(VroomChart* chart, const VroomOverlay* overlays,
|
|
170
|
+
size_t count);
|
|
171
|
+
|
|
172
|
+
// Configures the session VWAP overlay (a single price-pane line). The session
|
|
173
|
+
// resets each UTC day shifted by `reset_offset_min` minutes (the configurable
|
|
174
|
+
// reset time). `color` is 0xAARRGGBB; `width` is the stroke px.
|
|
175
|
+
void vroom_chart_set_vwap(VroomChart* chart, bool enabled, int reset_offset_min,
|
|
176
|
+
uint32_t color, float width);
|
|
177
|
+
|
|
178
|
+
// ---- Rendering ------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
void vroom_chart_draw(VroomChart* chart, SkCanvas* canvas);
|
|
181
|
+
|
|
182
|
+
// True while any axis-label fade is still animating. Hosts that drive their own
|
|
183
|
+
// redraw loop (e.g. the web WASM build) poll this to know when to keep ticking.
|
|
184
|
+
bool vroom_chart_is_animating(VroomChart* chart);
|
|
185
|
+
|
|
186
|
+
// ---- Styling --------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
void vroom_chart_set_color(VroomChart* chart, VroomColorKey key, uint32_t argb);
|
|
189
|
+
void vroom_chart_set_float(VroomChart* chart, VroomFloatKey key, float value);
|
|
190
|
+
|
|
191
|
+
#ifdef __cplusplus
|
|
192
|
+
} // extern "C"
|
|
193
|
+
#endif
|
|
194
|
+
|
|
195
|
+
#endif // VROOM_CHART_H
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#include "candles.h"
|
|
2
|
+
|
|
3
|
+
#pragma clang diagnostic push
|
|
4
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
5
|
+
#include "include/core/SkCanvas.h"
|
|
6
|
+
#include "include/core/SkPaint.h"
|
|
7
|
+
#include "include/core/SkRect.h"
|
|
8
|
+
#pragma clang diagnostic pop
|
|
9
|
+
|
|
10
|
+
#include <algorithm>
|
|
11
|
+
|
|
12
|
+
#include "theme.h"
|
|
13
|
+
#include "viewport.h"
|
|
14
|
+
|
|
15
|
+
namespace vroom::candles {
|
|
16
|
+
|
|
17
|
+
void draw(SkCanvas* canvas,
|
|
18
|
+
const ::VroomCandle* visible,
|
|
19
|
+
std::size_t n,
|
|
20
|
+
const Layout& lay,
|
|
21
|
+
const Theme& theme,
|
|
22
|
+
const PriceBounds& bounds,
|
|
23
|
+
int64_t window_ms,
|
|
24
|
+
int64_t visible_start_ms,
|
|
25
|
+
int64_t candle_duration_ms) {
|
|
26
|
+
if (!canvas || n == 0) return;
|
|
27
|
+
|
|
28
|
+
const float body_w = vroom::candle_body_width(
|
|
29
|
+
lay, window_ms, candle_duration_ms);
|
|
30
|
+
|
|
31
|
+
SkPaint bull_paint;
|
|
32
|
+
bull_paint.setAntiAlias(true);
|
|
33
|
+
bull_paint.setColor(theme.colors[VROOM_COLOR_BULL]);
|
|
34
|
+
|
|
35
|
+
SkPaint bear_paint;
|
|
36
|
+
bear_paint.setAntiAlias(true);
|
|
37
|
+
bear_paint.setColor(theme.colors[VROOM_COLOR_BEAR]);
|
|
38
|
+
|
|
39
|
+
SkPaint wick_bull;
|
|
40
|
+
wick_bull.setAntiAlias(true);
|
|
41
|
+
wick_bull.setColor(theme.colors[VROOM_COLOR_BULL]);
|
|
42
|
+
wick_bull.setStrokeWidth(theme.floats[VROOM_FLOAT_WICK_WIDTH_PX]);
|
|
43
|
+
wick_bull.setStyle(SkPaint::kStroke_Style);
|
|
44
|
+
|
|
45
|
+
SkPaint wick_bear = wick_bull;
|
|
46
|
+
wick_bear.setColor(theme.colors[VROOM_COLOR_BEAR]);
|
|
47
|
+
|
|
48
|
+
const float half_body = body_w * 0.5f;
|
|
49
|
+
|
|
50
|
+
for (std::size_t i = 0; i < n; ++i) {
|
|
51
|
+
const auto& c = visible[i];
|
|
52
|
+
const bool bull = c.close >= c.open;
|
|
53
|
+
|
|
54
|
+
const float cx = vroom::candle_center_x(
|
|
55
|
+
lay, c.time_ms, candle_duration_ms,
|
|
56
|
+
visible_start_ms, window_ms);
|
|
57
|
+
const float y_high = vroom::price_to_y(lay, bounds, c.high);
|
|
58
|
+
const float y_low = vroom::price_to_y(lay, bounds, c.low);
|
|
59
|
+
const float y_open = vroom::price_to_y(lay, bounds, c.open);
|
|
60
|
+
const float y_close = vroom::price_to_y(lay, bounds, c.close);
|
|
61
|
+
|
|
62
|
+
canvas->drawLine(cx, y_high, cx, y_low,
|
|
63
|
+
bull ? wick_bull : wick_bear);
|
|
64
|
+
|
|
65
|
+
const float y_top = std::min(y_open, y_close);
|
|
66
|
+
const float y_bot = std::max(y_open, y_close);
|
|
67
|
+
const float h = std::max(1.f, y_bot - y_top);
|
|
68
|
+
canvas->drawRect(
|
|
69
|
+
SkRect::MakeXYWH(cx - half_body, y_top, body_w, h),
|
|
70
|
+
bull ? bull_paint : bear_paint);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
} // namespace vroom::candles
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Candle drawing — the wick+body loop, extracted so it can sit alongside
|
|
2
|
+
// future drawing modules (volume bars, indicator overlays, etc.) without
|
|
3
|
+
// growing the chart orchestrator.
|
|
4
|
+
|
|
5
|
+
#pragma once
|
|
6
|
+
|
|
7
|
+
#include <cstddef>
|
|
8
|
+
#include <cstdint>
|
|
9
|
+
|
|
10
|
+
#include "vroom/vroom_chart.h"
|
|
11
|
+
|
|
12
|
+
class SkCanvas;
|
|
13
|
+
|
|
14
|
+
namespace vroom {
|
|
15
|
+
struct Layout;
|
|
16
|
+
struct PriceBounds;
|
|
17
|
+
struct Theme;
|
|
18
|
+
} // namespace vroom
|
|
19
|
+
|
|
20
|
+
namespace vroom::candles {
|
|
21
|
+
|
|
22
|
+
// Draws every candle in [visible, visible + n). The visible slice should
|
|
23
|
+
// already be filtered by `visible_indices` in viewport.h.
|
|
24
|
+
void draw(SkCanvas* canvas,
|
|
25
|
+
const ::VroomCandle* visible,
|
|
26
|
+
std::size_t n,
|
|
27
|
+
const Layout& lay,
|
|
28
|
+
const Theme& theme,
|
|
29
|
+
const PriceBounds& bounds,
|
|
30
|
+
int64_t window_ms,
|
|
31
|
+
int64_t visible_start_ms,
|
|
32
|
+
int64_t candle_duration_ms);
|
|
33
|
+
|
|
34
|
+
} // namespace vroom::candles
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// VroomChart engine — orchestration only.
|
|
2
|
+
//
|
|
3
|
+
// The struct definition lives in chart.h; per-subsystem rendering and the
|
|
4
|
+
// public C facade live in candles.cpp, labels.cpp, and chart_facade.cpp.
|
|
5
|
+
// This file keeps just the constructor, the small helpers used everywhere
|
|
6
|
+
// (mark_dirty, layout, is_animating_now), the picture cache lifecycle
|
|
7
|
+
// (rebuild_chart_picture), and the top-level draw_chart that composes all
|
|
8
|
+
// the layers in z-order.
|
|
9
|
+
|
|
10
|
+
#include "chart.h"
|
|
11
|
+
|
|
12
|
+
#pragma clang diagnostic push
|
|
13
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
14
|
+
#include "include/core/SkCanvas.h"
|
|
15
|
+
#include "include/core/SkPaint.h"
|
|
16
|
+
#include "include/core/SkPicture.h"
|
|
17
|
+
#include "include/core/SkPictureRecorder.h"
|
|
18
|
+
#include "include/core/SkRect.h"
|
|
19
|
+
#pragma clang diagnostic pop
|
|
20
|
+
|
|
21
|
+
#include "candles.h"
|
|
22
|
+
#include "chart_internal.h"
|
|
23
|
+
#include "crosshair.h"
|
|
24
|
+
#include "labels.h"
|
|
25
|
+
#include "ma.h"
|
|
26
|
+
#include "ma_overlay.h"
|
|
27
|
+
#include "macd.h"
|
|
28
|
+
#include "macd_pane.h"
|
|
29
|
+
#include "price_indicator.h"
|
|
30
|
+
#include "rsi.h"
|
|
31
|
+
#include "rsi_pane.h"
|
|
32
|
+
#include "volume.h"
|
|
33
|
+
#include "vwap.h"
|
|
34
|
+
#include "theme.h"
|
|
35
|
+
#include "viewport.h"
|
|
36
|
+
|
|
37
|
+
VroomChart::VroomChart() : theme(vroom::default_theme()) {}
|
|
38
|
+
|
|
39
|
+
void VroomChart::mark_dirty() {
|
|
40
|
+
chart_dirty = true;
|
|
41
|
+
if (cb.on_redraw_requested) cb.on_redraw_requested(user_ctx);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
vroom::Layout VroomChart::layout() const {
|
|
45
|
+
const float axis_w = axis_width_px > 0.f
|
|
46
|
+
? axis_width_px
|
|
47
|
+
: width_px * theme.floats[VROOM_FLOAT_Y_AXIS_WIDTH_RATIO];
|
|
48
|
+
const int pane_count = (rsi_enabled ? 1 : 0) + (macd_enabled ? 1 : 0);
|
|
49
|
+
const float indicator_h = static_cast<float>(pane_count) * height_px *
|
|
50
|
+
theme.floats[VROOM_FLOAT_INDICATOR_HEIGHT_FRAC];
|
|
51
|
+
return vroom::Layout{
|
|
52
|
+
width_px,
|
|
53
|
+
height_px,
|
|
54
|
+
axis_w,
|
|
55
|
+
theme.floats[VROOM_FLOAT_X_AXIS_HEIGHT_PX],
|
|
56
|
+
theme.floats[VROOM_FLOAT_RIGHT_PADDING_PX],
|
|
57
|
+
theme.floats[VROOM_FLOAT_CANDLE_WIDTH_RATIO],
|
|
58
|
+
0.05f,
|
|
59
|
+
0.05f,
|
|
60
|
+
indicator_h,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void VroomChart::ensure_rsi() {
|
|
65
|
+
if (!rsi_enabled || !rsi_dirty) return;
|
|
66
|
+
vroom::rsi::compute(candles.data(), candles.size(), rsi_period, rsi_cache);
|
|
67
|
+
if (rsi_ma_enabled) {
|
|
68
|
+
vroom::rsi::compute_ma(rsi_cache, rsi_ma_period, rsi_ma_cache);
|
|
69
|
+
} else {
|
|
70
|
+
rsi_ma_cache.clear();
|
|
71
|
+
}
|
|
72
|
+
rsi_dirty = false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
void VroomChart::ensure_macd() {
|
|
76
|
+
if (!macd_enabled || !macd_dirty) return;
|
|
77
|
+
vroom::macd::compute(candles.data(), candles.size(), macd_fast, macd_slow,
|
|
78
|
+
macd_signal, macd_cache, macd_signal_cache,
|
|
79
|
+
macd_hist_cache);
|
|
80
|
+
macd_dirty = false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void VroomChart::ensure_overlays() {
|
|
84
|
+
if (!overlays_dirty) return;
|
|
85
|
+
overlay_caches.resize(overlays.size());
|
|
86
|
+
for (std::size_t i = 0; i < overlays.size(); ++i) {
|
|
87
|
+
const auto& ov = overlays[i];
|
|
88
|
+
vroom::ma::compute(candles.data(), candles.size(), ov.kind, ov.period,
|
|
89
|
+
ov.source, overlay_caches[i]);
|
|
90
|
+
}
|
|
91
|
+
overlays_dirty = false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
void VroomChart::ensure_vwap() {
|
|
95
|
+
if (!vwap_enabled || !vwap_dirty) return;
|
|
96
|
+
vroom::vwap::compute(candles.data(), candles.size(), vwap_reset_offset_min,
|
|
97
|
+
vwap_cache, vwap_breaks);
|
|
98
|
+
vwap_dirty = false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
102
|
+
const auto lay = layout();
|
|
103
|
+
|
|
104
|
+
// 1. Background
|
|
105
|
+
SkPaint bg;
|
|
106
|
+
bg.setColor(theme.colors[VROOM_COLOR_BACKGROUND]);
|
|
107
|
+
canvas->drawRect(SkRect::MakeWH(width_px, height_px), bg);
|
|
108
|
+
|
|
109
|
+
if (candles.empty()) return;
|
|
110
|
+
|
|
111
|
+
// 2. Visible slice + bounds + window_ms + geometry
|
|
112
|
+
const auto range = vroom::visible_indices(
|
|
113
|
+
candles.data(), candles.size(),
|
|
114
|
+
visible_start_ms, visible_end_ms);
|
|
115
|
+
const size_t n = range.end - range.start;
|
|
116
|
+
if (n == 0) return;
|
|
117
|
+
const ::VroomCandle* visible = candles.data() + range.start;
|
|
118
|
+
|
|
119
|
+
const auto bounds = price_bounds_initialized
|
|
120
|
+
? price_bounds
|
|
121
|
+
: vroom::price_bounds(visible, n);
|
|
122
|
+
const int64_t window_ms = visible_end_ms - visible_start_ms;
|
|
123
|
+
|
|
124
|
+
const float candle_area_h = vroom::price_pane_bottom(lay);
|
|
125
|
+
const float candle_right =
|
|
126
|
+
width_px - lay.y_axis_width_px - lay.right_padding_px;
|
|
127
|
+
|
|
128
|
+
// 3. Update label fade state ONCE per frame — both gridlines and labels
|
|
129
|
+
// share these opacities so their animations stay in lockstep.
|
|
130
|
+
vroom::labels::update_y_fades(*this, lay, bounds);
|
|
131
|
+
vroom::labels::update_x_fades(*this, lay);
|
|
132
|
+
|
|
133
|
+
// 4. Gridlines — drawn before candles so candle bodies overlay them.
|
|
134
|
+
// Vertical (time) gridlines are intentionally disabled for now —
|
|
135
|
+
// re-enable by adding `vroom::labels::draw_x_gridlines(canvas, *this,
|
|
136
|
+
// candle_right, candle_area_h);` here.
|
|
137
|
+
vroom::labels::draw_y_gridlines(canvas, *this, lay, bounds,
|
|
138
|
+
candle_right, candle_area_h);
|
|
139
|
+
|
|
140
|
+
// 4.5. Volume bars — drawn under the candles so candles z-index above.
|
|
141
|
+
vroom::volume::draw(canvas, visible, n, lay, theme,
|
|
142
|
+
window_ms, visible_start_ms, candle_duration_ms);
|
|
143
|
+
|
|
144
|
+
// 5. Candles (wicks + bodies)
|
|
145
|
+
vroom::candles::draw(canvas, visible, n, lay, theme, bounds,
|
|
146
|
+
window_ms, visible_start_ms, candle_duration_ms);
|
|
147
|
+
|
|
148
|
+
// 5.5. Moving-average overlay lines (SMA/EMA) on the price pane, over the
|
|
149
|
+
// candles. They share the candle price scale and don't reserve a pane.
|
|
150
|
+
if (!overlays.empty()) {
|
|
151
|
+
ensure_overlays();
|
|
152
|
+
for (std::size_t k = 0; k < overlays.size(); ++k) {
|
|
153
|
+
if (overlay_caches[k].size() != candles.size()) continue;
|
|
154
|
+
const double* vis = overlay_caches[k].data() + range.start;
|
|
155
|
+
vroom::ma_overlay::draw(canvas, lay, bounds, visible, n, vis,
|
|
156
|
+
window_ms, visible_start_ms,
|
|
157
|
+
candle_duration_ms, candle_right,
|
|
158
|
+
candle_area_h, overlays[k].color,
|
|
159
|
+
overlays[k].width);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 5.6. VWAP overlay (session, configurable reset) — a single price-pane line
|
|
164
|
+
// that breaks at each session reset (vwap_breaks).
|
|
165
|
+
if (vwap_enabled) {
|
|
166
|
+
ensure_vwap();
|
|
167
|
+
if (vwap_cache.size() == candles.size()) {
|
|
168
|
+
const double* vis = vwap_cache.data() + range.start;
|
|
169
|
+
const unsigned char* brk = vwap_breaks.size() == candles.size()
|
|
170
|
+
? vwap_breaks.data() + range.start
|
|
171
|
+
: nullptr;
|
|
172
|
+
vroom::ma_overlay::draw(canvas, lay, bounds, visible, n, vis,
|
|
173
|
+
window_ms, visible_start_ms,
|
|
174
|
+
candle_duration_ms, candle_right,
|
|
175
|
+
candle_area_h, vwap_color, vwap_width, brk);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 6. Axis backgrounds (mask any candle overflow). The x-axis separator
|
|
180
|
+
// line is intentionally omitted for now. The bottom strip anchors at
|
|
181
|
+
// x_axis_top (below any indicator pane) so it never paints over it.
|
|
182
|
+
SkPaint axis_bg;
|
|
183
|
+
axis_bg.setColor(theme.colors[VROOM_COLOR_BACKGROUND]);
|
|
184
|
+
canvas->drawRect(
|
|
185
|
+
SkRect::MakeXYWH(0, vroom::x_axis_top(lay), candle_right,
|
|
186
|
+
lay.x_axis_height_px),
|
|
187
|
+
axis_bg);
|
|
188
|
+
const float axis_block_w = width_px - candle_right;
|
|
189
|
+
canvas->drawRect(
|
|
190
|
+
SkRect::MakeXYWH(candle_right, 0, axis_block_w, height_px),
|
|
191
|
+
axis_bg);
|
|
192
|
+
|
|
193
|
+
// 7. Labels (read from y_fades / x_fades, no state mutation here)
|
|
194
|
+
vroom::labels::draw_y_labels(canvas, *this, lay, bounds);
|
|
195
|
+
vroom::labels::draw_x_labels(canvas, *this, lay);
|
|
196
|
+
|
|
197
|
+
// 7.5. Current-price line + box — above labels so the box covers any label
|
|
198
|
+
// it overlaps; tracks the latest close as the price scale moves.
|
|
199
|
+
vroom::price_indicator::draw(canvas, *this, lay, bounds,
|
|
200
|
+
candle_right, candle_area_h);
|
|
201
|
+
|
|
202
|
+
// 7.6. Indicator panes stacked below the candles, ordered by enable
|
|
203
|
+
// sequence (most recently enabled at the bottom). Each pane is
|
|
204
|
+
// INDICATOR_HEIGHT_FRAC of the height; the candle pane already shrank
|
|
205
|
+
// to fit them (see layout()).
|
|
206
|
+
if (lay.indicator_area_h > 0.f) {
|
|
207
|
+
struct ActivePane { int order; int type; }; // type: 0 = RSI, 1 = MACD
|
|
208
|
+
ActivePane panes[2];
|
|
209
|
+
int count = 0;
|
|
210
|
+
if (rsi_enabled) panes[count++] = {rsi_order, 0};
|
|
211
|
+
if (macd_enabled) panes[count++] = {macd_order, 1};
|
|
212
|
+
if (count == 2 && panes[0].order > panes[1].order) {
|
|
213
|
+
const ActivePane tmp = panes[0];
|
|
214
|
+
panes[0] = panes[1];
|
|
215
|
+
panes[1] = tmp;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const float pane_h =
|
|
219
|
+
height_px * theme.floats[VROOM_FLOAT_INDICATOR_HEIGHT_FRAC];
|
|
220
|
+
float pane_top = candle_area_h; // == price_pane_bottom(lay)
|
|
221
|
+
for (int i = 0; i < count; ++i) {
|
|
222
|
+
const float pane_bottom = pane_top + pane_h;
|
|
223
|
+
if (panes[i].type == 0) {
|
|
224
|
+
ensure_rsi();
|
|
225
|
+
const double* rsi_vis = rsi_cache.size() == candles.size()
|
|
226
|
+
? rsi_cache.data() + range.start : nullptr;
|
|
227
|
+
const double* rsi_ma_vis =
|
|
228
|
+
(rsi_ma_enabled && rsi_ma_cache.size() == candles.size())
|
|
229
|
+
? rsi_ma_cache.data() + range.start : nullptr;
|
|
230
|
+
vroom::rsi_pane::draw(canvas, *this, lay, visible, n, rsi_vis,
|
|
231
|
+
rsi_ma_vis, window_ms, visible_start_ms,
|
|
232
|
+
candle_duration_ms, candle_right, pane_top,
|
|
233
|
+
pane_bottom);
|
|
234
|
+
} else {
|
|
235
|
+
ensure_macd();
|
|
236
|
+
const double* macd_vis = macd_cache.size() == candles.size()
|
|
237
|
+
? macd_cache.data() + range.start : nullptr;
|
|
238
|
+
const double* sig_vis =
|
|
239
|
+
macd_signal_cache.size() == candles.size()
|
|
240
|
+
? macd_signal_cache.data() + range.start : nullptr;
|
|
241
|
+
const double* hist_vis =
|
|
242
|
+
macd_hist_cache.size() == candles.size()
|
|
243
|
+
? macd_hist_cache.data() + range.start : nullptr;
|
|
244
|
+
vroom::macd_pane::draw(canvas, *this, lay, visible, n, macd_vis,
|
|
245
|
+
sig_vis, hist_vis, window_ms,
|
|
246
|
+
visible_start_ms, candle_duration_ms,
|
|
247
|
+
candle_right, pane_top, pane_bottom);
|
|
248
|
+
}
|
|
249
|
+
pane_top = pane_bottom;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 7.7. Crosshair — drawn last so it sits on top of everything, including the
|
|
254
|
+
// indicator panes. The vertical line runs down to x_axis_top so it
|
|
255
|
+
// stays visible across the candle area and all below-chart panes; the
|
|
256
|
+
// horizontal line + ring stay in the price pane (clamped to
|
|
257
|
+
// candle_area_h).
|
|
258
|
+
if (crosshair_active) {
|
|
259
|
+
const float snap_x = vroom::snap_x_to_candle(
|
|
260
|
+
lay, visible, n, candle_duration_ms, visible_start_ms, window_ms,
|
|
261
|
+
crosshair_x_px);
|
|
262
|
+
vroom::crosshair::draw(canvas, *this, candle_right, candle_area_h,
|
|
263
|
+
vroom::x_axis_top(lay), snap_x);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 8. GC fades that have fully faded out and aren't coming back.
|
|
267
|
+
vroom::labels::gc_y_fades(*this);
|
|
268
|
+
vroom::labels::gc_x_fades(*this);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
void VroomChart::rebuild_chart_picture() {
|
|
272
|
+
// Compute dt for fade animations. We cap large gaps (resume from
|
|
273
|
+
// background, long idle) so we don't snap animations to completion.
|
|
274
|
+
auto now = std::chrono::steady_clock::now();
|
|
275
|
+
float dt = 0.f;
|
|
276
|
+
if (anim_started) {
|
|
277
|
+
dt = std::chrono::duration<float>(now - last_anim_tick).count();
|
|
278
|
+
if (dt > 0.1f) dt = 0.f;
|
|
279
|
+
}
|
|
280
|
+
last_anim_tick = now;
|
|
281
|
+
anim_started = true;
|
|
282
|
+
last_dt_seconds = dt;
|
|
283
|
+
|
|
284
|
+
SkPictureRecorder recorder;
|
|
285
|
+
SkCanvas* canvas = recorder.beginRecording(SkRect::MakeWH(width_px, height_px));
|
|
286
|
+
draw_chart(canvas);
|
|
287
|
+
chart_picture = recorder.finishRecordingAsPicture();
|
|
288
|
+
chart_dirty = false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
bool VroomChart::is_animating_now() const {
|
|
292
|
+
for (const auto& f : y_fades) {
|
|
293
|
+
if (f.opacity != f.target) return true;
|
|
294
|
+
}
|
|
295
|
+
for (const auto& f : x_fades) {
|
|
296
|
+
if (f.opacity != f.target) return true;
|
|
297
|
+
}
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// chart_internal.h exports — bridge layer (JSI HostObject) consumers.
|
|
302
|
+
|
|
303
|
+
namespace vroom {
|
|
304
|
+
|
|
305
|
+
sk_sp<SkPicture> render_chart_picture(VroomChart* chart) {
|
|
306
|
+
if (!chart) return nullptr;
|
|
307
|
+
if (chart->chart_dirty || !chart->chart_picture || chart->is_animating_now()) {
|
|
308
|
+
chart->rebuild_chart_picture();
|
|
309
|
+
}
|
|
310
|
+
return chart->chart_picture;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
bool is_animating(VroomChart* chart) {
|
|
314
|
+
return chart ? chart->is_animating_now() : false;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
} // namespace vroom
|