react-native-vroom-chart 0.5.0 → 0.7.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/android/CMakeLists.txt +143 -0
- package/android/README.md +60 -5
- package/android/build.gradle +115 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/VroomChartJni.cpp +24 -0
- package/android/src/main/java/com/vroom/chart/VroomChartModule.kt +31 -0
- package/android/src/main/java/com/vroom/chart/VroomChartPackage.kt +31 -0
- package/cpp/VroomChartHostObject.cpp +368 -33
- package/cpp/VroomJsiInstaller.cpp +11 -1
- package/cpp/VroomSkiaContext.cpp +15 -7
- package/cpp/_core_include/vroom/vroom_chart.h +250 -22
- package/cpp/_core_src/bollinger.h +1 -1
- package/cpp/_core_src/candles.cpp +138 -41
- package/cpp/_core_src/candles.h +11 -1
- package/cpp/_core_src/chart.cpp +98 -40
- package/cpp/_core_src/chart.h +82 -20
- package/cpp/_core_src/chart_facade.cpp +297 -66
- package/cpp/_core_src/gradient.cpp +46 -0
- package/cpp/_core_src/gradient.h +31 -0
- package/cpp/_core_src/labels.cpp +49 -16
- package/cpp/_core_src/labels.h +33 -4
- package/cpp/_core_src/liquidity.cpp +3 -37
- package/cpp/_core_src/ma_overlay.cpp +174 -12
- package/cpp/_core_src/ma_overlay.h +55 -3
- package/cpp/_core_src/macd.cpp +13 -42
- package/cpp/_core_src/macd.h +11 -8
- package/cpp/_core_src/macd_pane.cpp +57 -27
- package/cpp/_core_src/price_line_layout.cpp +87 -0
- package/cpp/_core_src/price_line_layout.h +80 -0
- package/cpp/_core_src/price_lines.cpp +428 -0
- package/cpp/_core_src/price_lines.h +56 -0
- package/cpp/_core_src/rsi.cpp +4 -18
- package/cpp/_core_src/rsi.h +6 -6
- package/cpp/_core_src/rsi_pane.cpp +34 -19
- package/cpp/_core_src/series_ma.cpp +64 -0
- package/cpp/_core_src/series_ma.h +30 -0
- package/cpp/_core_src/style_inherit.h +35 -0
- package/cpp/_core_src/theme.cpp +2 -1
- package/cpp/_core_src/viewport.cpp +36 -12
- package/cpp/_core_src/viewport.h +50 -0
- package/cpp/_core_src/volume.cpp +33 -8
- package/cpp/_core_src/volume.h +12 -1
- package/cpp/_core_src/volume_anim.cpp +32 -0
- package/cpp/_core_src/volume_anim.h +41 -0
- package/lib/index.d.mts +261 -25
- package/lib/index.d.ts +261 -25
- package/lib/index.js +252 -36
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +252 -36
- package/lib/index.mjs.map +1 -1
- package/package.json +5 -4
- package/src/VroomChart.tsx +162 -11
- package/src/easing.ts +40 -0
- package/src/index.ts +5 -0
- package/src/jsi.d.ts +124 -20
- package/src/theme.ts +1 -0
- package/src/types.ts +5 -0
- package/src/useChartCore.ts +166 -28
|
@@ -20,17 +20,29 @@
|
|
|
20
20
|
|
|
21
21
|
#include "chart.h"
|
|
22
22
|
#include "fonts.h"
|
|
23
|
+
#include "style_inherit.h"
|
|
23
24
|
#include "theme.h"
|
|
24
25
|
#include "viewport.h"
|
|
25
26
|
|
|
26
27
|
namespace vroom::macd_pane {
|
|
27
28
|
|
|
28
29
|
namespace {
|
|
30
|
+
using vroom::style::color_or;
|
|
31
|
+
using vroom::style::width_or;
|
|
32
|
+
|
|
29
33
|
constexpr SkColor kMacdLine = 0xff2962ff; // blue MACD line
|
|
30
34
|
constexpr SkColor kSignalLine = 0xffff6d00; // orange signal line
|
|
31
35
|
constexpr SkColor kZeroLine = 0xff484f58; // zero reference
|
|
32
36
|
constexpr SkColor kDivider = 0xff21262d; // pane separator
|
|
33
37
|
constexpr float kPadFrac = 0.85f; // keep curves off the band edges
|
|
38
|
+
constexpr float kLineWidth = 1.5f; // default stroke for both lines
|
|
39
|
+
constexpr float kFadedAlpha = 0.5f; // easing histogram bars
|
|
40
|
+
|
|
41
|
+
// The easing histogram bars default to their own base color dimmed, which is
|
|
42
|
+
// how the pane read before the colors were configurable.
|
|
43
|
+
SkColor faded_or(uint32_t configured, SkColor base) {
|
|
44
|
+
return vroom::style::faded_or(configured, base, kFadedAlpha);
|
|
45
|
+
}
|
|
34
46
|
} // namespace
|
|
35
47
|
|
|
36
48
|
void draw(SkCanvas* canvas,
|
|
@@ -51,23 +63,25 @@ void draw(SkCanvas* canvas,
|
|
|
51
63
|
const float band_h = pane_bottom - pane_top;
|
|
52
64
|
if (band_h <= 0.f) return;
|
|
53
65
|
|
|
66
|
+
const VroomMACD& cfg = chart.macd;
|
|
54
67
|
const float mid = (pane_top + pane_bottom) * 0.5f;
|
|
55
68
|
// User y-zoom scales the amplitude about the zero line (mid). 1.0 = the
|
|
56
69
|
// default auto-fit; >1 zooms in, <1 zooms out.
|
|
57
70
|
const float half =
|
|
58
71
|
band_h * 0.5f * kPadFrac * static_cast<float>(chart.macd_y_scale);
|
|
59
72
|
|
|
60
|
-
// Auto-scale symmetric about zero across
|
|
73
|
+
// Auto-scale symmetric about zero across the finite values on show, so
|
|
74
|
+
// hiding a series lets the rest fill the pane.
|
|
61
75
|
double scale = 0.0;
|
|
62
|
-
auto track = [&](const double* s) {
|
|
63
|
-
if (!s) return;
|
|
76
|
+
auto track = [&](const double* s, bool shown) {
|
|
77
|
+
if (!s || !shown) return;
|
|
64
78
|
for (std::size_t i = 0; i < n; ++i) {
|
|
65
79
|
if (std::isfinite(s[i])) scale = std::max(scale, std::abs(s[i]));
|
|
66
80
|
}
|
|
67
81
|
};
|
|
68
|
-
track(macd_visible);
|
|
69
|
-
track(signal_visible);
|
|
70
|
-
track(hist_visible);
|
|
82
|
+
track(macd_visible, cfg.line_visible != 0);
|
|
83
|
+
track(signal_visible, cfg.signal_visible != 0);
|
|
84
|
+
track(hist_visible, cfg.hist_visible != 0);
|
|
71
85
|
|
|
72
86
|
auto y_for = [&](double v) -> float {
|
|
73
87
|
if (scale <= 0.0) return mid;
|
|
@@ -90,34 +104,44 @@ void draw(SkCanvas* canvas,
|
|
|
90
104
|
canvas->clipRect(SkRect::MakeLTRB(0.f, pane_top, candle_right, pane_bottom));
|
|
91
105
|
|
|
92
106
|
// Zero line.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
if (cfg.zero_visible) {
|
|
108
|
+
SkPaint zero;
|
|
109
|
+
zero.setAntiAlias(true);
|
|
110
|
+
zero.setColor(color_or(cfg.zero_color, kZeroLine));
|
|
111
|
+
zero.setStrokeWidth(1.f);
|
|
112
|
+
canvas->drawLine(0.f, mid, candle_right, mid, zero);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Histogram bars: from the zero line to y_for(hist), 4-color — above or
|
|
116
|
+
// below zero, each in a building and an easing shade. A bar is building
|
|
117
|
+
// while it grows away from zero and easing while it falls back toward it.
|
|
118
|
+
if (hist_visible && cfg.hist_visible) {
|
|
102
119
|
const float body_w =
|
|
103
120
|
vroom::candle_body_width(lay, window_ms, candle_duration_ms);
|
|
104
121
|
const float half_body = body_w * 0.5f;
|
|
105
122
|
const float zero_y = y_for(0.0);
|
|
106
|
-
const SkColor
|
|
107
|
-
|
|
123
|
+
const SkColor up =
|
|
124
|
+
color_or(cfg.hist_up_color, chart.theme.colors[VROOM_COLOR_ACCENT_BULL]);
|
|
125
|
+
const SkColor down =
|
|
126
|
+
color_or(cfg.hist_down_color, chart.theme.colors[VROOM_COLOR_ACCENT_BEAR]);
|
|
127
|
+
const SkColor up_easing = faded_or(cfg.hist_up_fading_color, up);
|
|
128
|
+
const SkColor down_easing = faded_or(cfg.hist_down_fading_color, down);
|
|
108
129
|
for (std::size_t i = 0; i < n; ++i) {
|
|
109
130
|
const double h = hist_visible[i];
|
|
110
131
|
if (!std::isfinite(h)) continue;
|
|
111
132
|
const bool have_prev = i > 0 && std::isfinite(hist_visible[i - 1]);
|
|
112
133
|
const double prev = have_prev ? hist_visible[i - 1] : h;
|
|
113
|
-
bool
|
|
134
|
+
bool building = true;
|
|
114
135
|
if (have_prev) {
|
|
115
|
-
|
|
136
|
+
building = h >= 0.0 ? h >= prev : h <= prev;
|
|
116
137
|
}
|
|
117
138
|
SkPaint bar;
|
|
118
139
|
bar.setAntiAlias(true);
|
|
119
|
-
|
|
120
|
-
|
|
140
|
+
if (h >= 0.0) {
|
|
141
|
+
bar.setColor(building ? up : up_easing);
|
|
142
|
+
} else {
|
|
143
|
+
bar.setColor(building ? down : down_easing);
|
|
144
|
+
}
|
|
121
145
|
const float cx = vroom::candle_center_x(
|
|
122
146
|
lay, visible[i].time_ms, candle_duration_ms, visible_start_ms,
|
|
123
147
|
window_ms);
|
|
@@ -130,7 +154,7 @@ void draw(SkCanvas* canvas,
|
|
|
130
154
|
}
|
|
131
155
|
|
|
132
156
|
// MACD + signal lines.
|
|
133
|
-
auto stroke_series = [&](const double* series, SkColor color) {
|
|
157
|
+
auto stroke_series = [&](const double* series, SkColor color, float width) {
|
|
134
158
|
if (!series) return;
|
|
135
159
|
SkPathBuilder path;
|
|
136
160
|
bool pen_down = false;
|
|
@@ -155,11 +179,17 @@ void draw(SkCanvas* canvas,
|
|
|
155
179
|
line.setAntiAlias(true);
|
|
156
180
|
line.setColor(color);
|
|
157
181
|
line.setStyle(SkPaint::kStroke_Style);
|
|
158
|
-
line.setStrokeWidth(
|
|
182
|
+
line.setStrokeWidth(width);
|
|
159
183
|
canvas->drawPath(path.detach(), line);
|
|
160
184
|
};
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
if (cfg.signal_visible) {
|
|
186
|
+
stroke_series(signal_visible, color_or(cfg.signal_color, kSignalLine),
|
|
187
|
+
width_or(cfg.signal_width, kLineWidth));
|
|
188
|
+
}
|
|
189
|
+
if (cfg.line_visible) { // MACD over signal
|
|
190
|
+
stroke_series(macd_visible, color_or(cfg.line_color, kMacdLine),
|
|
191
|
+
width_or(cfg.line_width, kLineWidth));
|
|
192
|
+
}
|
|
163
193
|
|
|
164
194
|
// Caption, top-left of the pane.
|
|
165
195
|
auto tf = vroom::axis_typeface();
|
|
@@ -168,8 +198,8 @@ void draw(SkCanvas* canvas,
|
|
|
168
198
|
font.setSubpixel(true);
|
|
169
199
|
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
|
170
200
|
char caption[40];
|
|
171
|
-
std::snprintf(caption, sizeof(caption), "MACD %d %d %d",
|
|
172
|
-
|
|
201
|
+
std::snprintf(caption, sizeof(caption), "MACD %d %d %d", cfg.fast,
|
|
202
|
+
cfg.slow, cfg.signal);
|
|
173
203
|
SkRect cb;
|
|
174
204
|
font.measureText(caption, std::strlen(caption), SkTextEncoding::kUTF8,
|
|
175
205
|
&cb);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#include "price_line_layout.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
namespace vroom::price_lines {
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
// A pill's width is its text plus symmetric padding. Zero-width text means the
|
|
10
|
+
// segment is absent, not a padding-only sliver.
|
|
11
|
+
float pill_width(float text_w) {
|
|
12
|
+
return text_w > 0.f ? text_w + 2.f * kPadH : 0.f;
|
|
13
|
+
}
|
|
14
|
+
} // namespace
|
|
15
|
+
|
|
16
|
+
bool contains(const Rect& r, float x, float y) {
|
|
17
|
+
if (r.empty()) return false;
|
|
18
|
+
return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
GroupLayout layout_group(const LabelMetrics& metrics,
|
|
22
|
+
float y,
|
|
23
|
+
float pane_right,
|
|
24
|
+
const ::VroomPriceLineStyle& style) {
|
|
25
|
+
GroupLayout out;
|
|
26
|
+
|
|
27
|
+
const float body_w = pill_width(metrics.text_w);
|
|
28
|
+
const float qty_w = pill_width(metrics.quantity_w);
|
|
29
|
+
// The close button is a square cell, so its icon stays centered whatever the
|
|
30
|
+
// font size works out to.
|
|
31
|
+
const float close_w = metrics.closable ? metrics.label_h : 0.f;
|
|
32
|
+
const float group_w = body_w + qty_w + close_w;
|
|
33
|
+
|
|
34
|
+
if (group_w <= 0.f || metrics.label_h <= 0.f || pane_right <= 0.f) {
|
|
35
|
+
// A bare line: no group, but still report a degenerate span at the
|
|
36
|
+
// anchor so callers can treat left/right uniformly.
|
|
37
|
+
out.left = out.right = pane_right;
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
float left = 0.f;
|
|
42
|
+
switch (style.align) {
|
|
43
|
+
case 0: // left
|
|
44
|
+
left = 0.f;
|
|
45
|
+
break;
|
|
46
|
+
case 1: // center
|
|
47
|
+
left = (pane_right - group_w) * 0.5f;
|
|
48
|
+
break;
|
|
49
|
+
default: { // right
|
|
50
|
+
const float frac = std::clamp(style.line_length_frac, 0.f, 1.f);
|
|
51
|
+
const float right = pane_right - frac * pane_right - kAxisGutter;
|
|
52
|
+
left = right - group_w;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Keep the group on-screen: never past the axis, never off the left edge.
|
|
57
|
+
left = std::min(left, pane_right - group_w);
|
|
58
|
+
left = std::max(left, 0.f);
|
|
59
|
+
|
|
60
|
+
const float top = y - metrics.label_h * 0.5f;
|
|
61
|
+
const float bottom = top + metrics.label_h;
|
|
62
|
+
|
|
63
|
+
float x = left;
|
|
64
|
+
if (body_w > 0.f) {
|
|
65
|
+
out.body = Rect{x, top, x + body_w, bottom};
|
|
66
|
+
x += body_w;
|
|
67
|
+
}
|
|
68
|
+
if (qty_w > 0.f) {
|
|
69
|
+
out.quantity = Rect{x, top, x + qty_w, bottom};
|
|
70
|
+
x += qty_w;
|
|
71
|
+
}
|
|
72
|
+
if (close_w > 0.f) {
|
|
73
|
+
out.close = Rect{x, top, x + close_w, bottom};
|
|
74
|
+
x += close_w;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
out.left = left;
|
|
78
|
+
out.right = x;
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
bool hits_line(float line_y, float stroke_width, float y) {
|
|
83
|
+
const float band = std::max(stroke_width, 1.f) + kLineHitTolerance;
|
|
84
|
+
return std::fabs(y - line_y) <= band;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} // namespace vroom::price_lines
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Price-status-line geometry — pure functions, no Skia, no state.
|
|
2
|
+
//
|
|
3
|
+
// Split out from price_lines.cpp so the rendering pass and the hit-test pass
|
|
4
|
+
// derive their rectangles from the same arithmetic: a label the user can see but
|
|
5
|
+
// not click (or vice versa) is the classic failure mode for this widget. Being
|
|
6
|
+
// Skia-free it also unit-tests without a Skia checkout.
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "vroom/vroom_chart.h"
|
|
11
|
+
namespace vroom::price_lines {
|
|
12
|
+
|
|
13
|
+
// Pill padding / corner radius, matching the current-price indicator so a price
|
|
14
|
+
// line and the price badge read as the same family of chrome.
|
|
15
|
+
constexpr float kPadH = 8.f; // left/right of the text
|
|
16
|
+
constexpr float kPadV = 4.f; // above/below the text
|
|
17
|
+
constexpr float kCorner = 6.f; // rounded-pill corner radius
|
|
18
|
+
|
|
19
|
+
// Extra grab band above and below the stroke, so a 1px line is still an easy
|
|
20
|
+
// target. Matches the tolerance mainstream chart libraries settled on.
|
|
21
|
+
constexpr float kLineHitTolerance = 7.f;
|
|
22
|
+
|
|
23
|
+
// Gap between the label group and the price axis when right-aligned. Wide enough
|
|
24
|
+
// that the line still shows through it — a couple of dashes reading as "this
|
|
25
|
+
// continues to the badge" rather than one orphaned dot.
|
|
26
|
+
constexpr float kAxisGutter = 12.f;
|
|
27
|
+
|
|
28
|
+
struct Rect {
|
|
29
|
+
float left = 0.f;
|
|
30
|
+
float top = 0.f;
|
|
31
|
+
float right = 0.f;
|
|
32
|
+
float bottom = 0.f;
|
|
33
|
+
|
|
34
|
+
// Empty rects stand in for absent segments (no quantity, no close button).
|
|
35
|
+
bool empty() const { return right <= left || bottom <= top; }
|
|
36
|
+
float width() const { return right - left; }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// True when (x, y) falls inside `r`. Always false for an empty rect.
|
|
40
|
+
bool contains(const Rect& r, float x, float y);
|
|
41
|
+
|
|
42
|
+
// The text extents a caller measured with the real font, plus which optional
|
|
43
|
+
// segments to reserve room for. `label_h` is the pill height (already including
|
|
44
|
+
// kPadV on both sides).
|
|
45
|
+
struct LabelMetrics {
|
|
46
|
+
float text_w = 0.f; // body text width; 0 hides the body pill
|
|
47
|
+
float quantity_w = 0.f; // quantity text width; 0 hides the quantity pill
|
|
48
|
+
float label_h = 0.f;
|
|
49
|
+
bool closable = false; // reserve the trailing square close-button cell
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// The laid-out label group, vertically centered on the line. Any of the three
|
|
53
|
+
// segment rects may be empty. `left`/`right` bound whichever segments exist and
|
|
54
|
+
// are equal when the group is empty (a bare line).
|
|
55
|
+
struct GroupLayout {
|
|
56
|
+
Rect body;
|
|
57
|
+
Rect quantity;
|
|
58
|
+
Rect close;
|
|
59
|
+
float left = 0.f;
|
|
60
|
+
float right = 0.f;
|
|
61
|
+
|
|
62
|
+
bool empty() const { return right <= left; }
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Lays the group out on the line at `y`. `pane_right` is the inner edge of the
|
|
66
|
+
// price axis (the line's right end).
|
|
67
|
+
//
|
|
68
|
+
// `style.align` picks the anchor: 2 (right, the default) parks the group's right
|
|
69
|
+
// edge `style.line_length_frac` of the pane width in from the axis; 1 centers
|
|
70
|
+
// it; 0 pins it to the left edge. The group is clamped into [0, pane_right], so
|
|
71
|
+
// a label wider than the pane starts flush left rather than overflowing.
|
|
72
|
+
GroupLayout layout_group(const LabelMetrics& metrics,
|
|
73
|
+
float y,
|
|
74
|
+
float pane_right,
|
|
75
|
+
const ::VroomPriceLineStyle& style);
|
|
76
|
+
|
|
77
|
+
// True when `y` is within the grab band of a line drawn at `line_y`.
|
|
78
|
+
bool hits_line(float line_y, float stroke_width, float y);
|
|
79
|
+
|
|
80
|
+
} // namespace vroom::price_lines
|