react-native-vroom-chart 0.4.0 → 0.6.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 +265 -1
- package/cpp/VroomJsiInstaller.cpp +11 -1
- package/cpp/VroomSkiaContext.cpp +15 -7
- package/cpp/_core_include/vroom/vroom_chart.h +95 -0
- package/cpp/_core_src/bollinger.cpp +43 -0
- package/cpp/_core_src/bollinger.h +31 -0
- package/cpp/_core_src/chart.cpp +56 -0
- package/cpp/_core_src/chart.h +45 -0
- package/cpp/_core_src/chart_facade.cpp +117 -0
- package/cpp/_core_src/ma_overlay.cpp +67 -0
- package/cpp/_core_src/ma_overlay.h +21 -0
- 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/lib/index.d.mts +201 -1
- package/lib/index.d.ts +201 -1
- package/lib/index.js +122 -7
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +122 -7
- package/lib/index.mjs.map +1 -1
- package/package.json +20 -8
- package/react-native-vroom-chart.podspec +1 -1
- package/src/VroomChart.tsx +95 -8
- package/src/index.ts +3 -0
- package/src/jsi.d.ts +68 -0
- package/src/types.ts +3 -0
- package/src/useChartCore.ts +98 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#include "VroomChartHostObject.h"
|
|
2
2
|
|
|
3
3
|
#include <cstring>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
4
6
|
|
|
5
7
|
#include "chart_internal.h"
|
|
6
8
|
#include "vroom/vroom_chart.h"
|
|
@@ -27,7 +29,7 @@ ChartHostObject::~ChartHostObject() {
|
|
|
27
29
|
std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
28
30
|
jsi::Runtime& rt) {
|
|
29
31
|
std::vector<jsi::PropNameID> out;
|
|
30
|
-
out.reserve(
|
|
32
|
+
out.reserve(29);
|
|
31
33
|
out.push_back(jsi::PropNameID::forAscii(rt, "setCandles"));
|
|
32
34
|
out.push_back(jsi::PropNameID::forAscii(rt, "setSize"));
|
|
33
35
|
out.push_back(jsi::PropNameID::forAscii(rt, "setColor"));
|
|
@@ -51,10 +53,77 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
|
51
53
|
out.push_back(jsi::PropNameID::forAscii(rt, "setMACD"));
|
|
52
54
|
out.push_back(jsi::PropNameID::forAscii(rt, "setOverlays"));
|
|
53
55
|
out.push_back(jsi::PropNameID::forAscii(rt, "setVWAP"));
|
|
56
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setBollinger"));
|
|
57
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "coordAt"));
|
|
58
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setPriceLines"));
|
|
59
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "hitTestPriceLine"));
|
|
60
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setPriceLineHover"));
|
|
61
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setPriceLineDrag"));
|
|
54
62
|
out.push_back(jsi::PropNameID::forAscii(rt, "render"));
|
|
55
63
|
return out;
|
|
56
64
|
}
|
|
57
65
|
|
|
66
|
+
#if defined(__ANDROID__)
|
|
67
|
+
// Android only: RN-Skia is compiled into its own librnskia.so, separate from
|
|
68
|
+
// libvroomchart.so. Constructing a RNSkia::JsiSkPicture directly here (as iOS
|
|
69
|
+
// does, below) gives it a vtable/typeinfo from *our* .so; when RN-Skia's own
|
|
70
|
+
// compiled code later consumes it (e.g. Convertor.h's
|
|
71
|
+
// `getPropertyValue<sk_sp<SkPicture>>`, which does
|
|
72
|
+
// `value.asObject(rt).asHostObject<JsiSkPicture>(rt)` — a dynamic_pointer_cast
|
|
73
|
+
// under the hood), the cast fails cross-.so with "Object is not a HostObject
|
|
74
|
+
// of desired type", because the object's *runtime* type was never actually
|
|
75
|
+
// compiled inside librnskia.so. iOS statically links everything into one
|
|
76
|
+
// binary, so no such mismatch exists there.
|
|
77
|
+
//
|
|
78
|
+
// The fix: build the picture through RN-Skia's own public JS API instead
|
|
79
|
+
// (`Skia.Picture.MakePicture(bytes)`, the same call `require(...).png`-style
|
|
80
|
+
// static pictures use), so the resulting JsiSkPicture is genuinely
|
|
81
|
+
// constructed by librnskia.so's own code. This costs a serialize +
|
|
82
|
+
// re-parse of the picture's draw ops per call — real overhead on a gesture
|
|
83
|
+
// hot path — but is the only ABI-safe option found so far. Revisit if
|
|
84
|
+
// profiling shows this mattering (e.g. a merged-.so build, or a lighter
|
|
85
|
+
// bridge that avoids the round trip).
|
|
86
|
+
#include "include/core/SkData.h"
|
|
87
|
+
|
|
88
|
+
namespace {
|
|
89
|
+
class SkDataMutableBuffer : public facebook::jsi::MutableBuffer {
|
|
90
|
+
public:
|
|
91
|
+
explicit SkDataMutableBuffer(sk_sp<SkData> data) : data_(std::move(data)) {}
|
|
92
|
+
size_t size() const override { return data_->size(); }
|
|
93
|
+
uint8_t* data() override {
|
|
94
|
+
return const_cast<uint8_t*>(
|
|
95
|
+
static_cast<const uint8_t*>(data_->data()));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private:
|
|
99
|
+
sk_sp<SkData> data_;
|
|
100
|
+
};
|
|
101
|
+
} // namespace
|
|
102
|
+
|
|
103
|
+
static facebook::jsi::Value wrapPicture(facebook::jsi::Runtime& rt,
|
|
104
|
+
const sk_sp<SkPicture>& pic) {
|
|
105
|
+
if (!pic) return facebook::jsi::Value::null();
|
|
106
|
+
sk_sp<SkData> serialized = pic->serialize();
|
|
107
|
+
if (!serialized) return facebook::jsi::Value::null();
|
|
108
|
+
|
|
109
|
+
auto buffer = std::make_shared<SkDataMutableBuffer>(std::move(serialized));
|
|
110
|
+
jsi::ArrayBuffer arrayBuffer(rt, buffer);
|
|
111
|
+
|
|
112
|
+
auto skiaApi = rt.global().getProperty(rt, "SkiaApi");
|
|
113
|
+
if (!skiaApi.isObject()) return facebook::jsi::Value::null();
|
|
114
|
+
auto pictureFactory = skiaApi.asObject(rt).getProperty(rt, "Picture");
|
|
115
|
+
if (!pictureFactory.isObject()) return facebook::jsi::Value::null();
|
|
116
|
+
auto makePicture =
|
|
117
|
+
pictureFactory.asObject(rt).getPropertyAsFunction(rt, "MakePicture");
|
|
118
|
+
|
|
119
|
+
// Mirrors JsiSkPictureFactory::MakePicture's expected argument shape: an
|
|
120
|
+
// object with a `.buffer` property holding the ArrayBuffer (matching a
|
|
121
|
+
// Uint8Array-like value; the factory ignores everything else about it).
|
|
122
|
+
jsi::Object arg(rt);
|
|
123
|
+
arg.setProperty(rt, "buffer", arrayBuffer);
|
|
124
|
+
return makePicture.call(rt, arg);
|
|
125
|
+
}
|
|
126
|
+
#else
|
|
58
127
|
// Shared helper: wraps a fresh picture for return to JS, with memory pressure
|
|
59
128
|
// reported to Hermes so GC keeps up under gesture-rate churn.
|
|
60
129
|
static facebook::jsi::Value wrapPicture(facebook::jsi::Runtime& rt,
|
|
@@ -65,6 +134,7 @@ static facebook::jsi::Value wrapPicture(facebook::jsi::Runtime& rt,
|
|
|
65
134
|
return JSI_CREATE_HOST_OBJECT_WITH_MEMORY_PRESSURE(rt, host,
|
|
66
135
|
/*context=*/nullptr);
|
|
67
136
|
}
|
|
137
|
+
#endif
|
|
68
138
|
|
|
69
139
|
jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
70
140
|
const jsi::PropNameID& propName) {
|
|
@@ -551,6 +621,200 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
551
621
|
});
|
|
552
622
|
}
|
|
553
623
|
|
|
624
|
+
if (name == "setBollinger") {
|
|
625
|
+
// setBollinger({enabled, period, mult, source, basisKind, upperColor,
|
|
626
|
+
// upperWidth, middleColor, middleWidth, lowerColor, lowerWidth,
|
|
627
|
+
// fillEnabled, fillOpacity}) — Bollinger Bands overlay. No render; the
|
|
628
|
+
// next render() picks it up.
|
|
629
|
+
return jsi::Function::createFromHostFunction(
|
|
630
|
+
rt,
|
|
631
|
+
jsi::PropNameID::forAscii(rt, "setBollinger"),
|
|
632
|
+
1,
|
|
633
|
+
[this](jsi::Runtime& rt2,
|
|
634
|
+
const jsi::Value& /*thisVal*/,
|
|
635
|
+
const jsi::Value* args,
|
|
636
|
+
size_t count) -> jsi::Value {
|
|
637
|
+
if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
|
|
638
|
+
auto s = args[0].asObject(rt2);
|
|
639
|
+
VroomBollinger cfg{};
|
|
640
|
+
cfg.enabled = s.getProperty(rt2, "enabled").asBool() ? 1 : 0;
|
|
641
|
+
cfg.period = static_cast<int32_t>(
|
|
642
|
+
s.getProperty(rt2, "period").asNumber());
|
|
643
|
+
cfg.mult = static_cast<float>(
|
|
644
|
+
s.getProperty(rt2, "mult").asNumber());
|
|
645
|
+
cfg.source = static_cast<int32_t>(
|
|
646
|
+
s.getProperty(rt2, "source").asNumber());
|
|
647
|
+
cfg.basis_kind = static_cast<int32_t>(
|
|
648
|
+
s.getProperty(rt2, "basisKind").asNumber());
|
|
649
|
+
cfg.upper_color = static_cast<uint32_t>(
|
|
650
|
+
s.getProperty(rt2, "upperColor").asNumber());
|
|
651
|
+
cfg.upper_width = static_cast<float>(
|
|
652
|
+
s.getProperty(rt2, "upperWidth").asNumber());
|
|
653
|
+
cfg.middle_color = static_cast<uint32_t>(
|
|
654
|
+
s.getProperty(rt2, "middleColor").asNumber());
|
|
655
|
+
cfg.middle_width = static_cast<float>(
|
|
656
|
+
s.getProperty(rt2, "middleWidth").asNumber());
|
|
657
|
+
cfg.lower_color = static_cast<uint32_t>(
|
|
658
|
+
s.getProperty(rt2, "lowerColor").asNumber());
|
|
659
|
+
cfg.lower_width = static_cast<float>(
|
|
660
|
+
s.getProperty(rt2, "lowerWidth").asNumber());
|
|
661
|
+
cfg.fill_enabled =
|
|
662
|
+
s.getProperty(rt2, "fillEnabled").asBool() ? 1 : 0;
|
|
663
|
+
cfg.fill_opacity = static_cast<float>(
|
|
664
|
+
s.getProperty(rt2, "fillOpacity").asNumber());
|
|
665
|
+
vroom_chart_set_bollinger(chart_, &cfg);
|
|
666
|
+
return jsi::Value::undefined();
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (name == "coordAt") {
|
|
671
|
+
// coordAt(x, y) -> { timeMs, price } | null. The continuous data coordinate
|
|
672
|
+
// at a pixel — not snapped to a candle slot. Null when there are no candles
|
|
673
|
+
// or the viewport is degenerate. No rendering.
|
|
674
|
+
return jsi::Function::createFromHostFunction(
|
|
675
|
+
rt,
|
|
676
|
+
jsi::PropNameID::forAscii(rt, "coordAt"),
|
|
677
|
+
2,
|
|
678
|
+
[this](jsi::Runtime& rt2,
|
|
679
|
+
const jsi::Value& /*thisVal*/,
|
|
680
|
+
const jsi::Value* args,
|
|
681
|
+
size_t count) -> jsi::Value {
|
|
682
|
+
if (count < 2) return jsi::Value::null();
|
|
683
|
+
VroomCoord c{};
|
|
684
|
+
if (!vroom_chart_coord_at(chart_,
|
|
685
|
+
static_cast<float>(args[0].asNumber()),
|
|
686
|
+
static_cast<float>(args[1].asNumber()), &c)) {
|
|
687
|
+
return jsi::Value::null();
|
|
688
|
+
}
|
|
689
|
+
jsi::Object obj(rt2);
|
|
690
|
+
obj.setProperty(rt2, "timeMs", static_cast<double>(c.time_ms));
|
|
691
|
+
obj.setProperty(rt2, "price", c.price);
|
|
692
|
+
return obj;
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (name == "setPriceLines") {
|
|
697
|
+
// setPriceLines({ lines: [{ price, color, width, lineStyle, text, quantity,
|
|
698
|
+
// flags }, ...], bodyBg, fontSizePx, lineLengthFrac, align, hoverBoost }) —
|
|
699
|
+
// replaces the full set of price status lines. No render; the next render()
|
|
700
|
+
// picks it up.
|
|
701
|
+
return jsi::Function::createFromHostFunction(
|
|
702
|
+
rt,
|
|
703
|
+
jsi::PropNameID::forAscii(rt, "setPriceLines"),
|
|
704
|
+
1,
|
|
705
|
+
[this](jsi::Runtime& rt2,
|
|
706
|
+
const jsi::Value& /*thisVal*/,
|
|
707
|
+
const jsi::Value* args,
|
|
708
|
+
size_t count) -> jsi::Value {
|
|
709
|
+
if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
|
|
710
|
+
auto cfg = args[0].asObject(rt2);
|
|
711
|
+
auto lines_val = cfg.getProperty(rt2, "lines");
|
|
712
|
+
if (!lines_val.isObject()) return jsi::Value::undefined();
|
|
713
|
+
auto lines_obj = lines_val.asObject(rt2);
|
|
714
|
+
if (!lines_obj.isArray(rt2)) return jsi::Value::undefined();
|
|
715
|
+
auto arr = lines_obj.asArray(rt2);
|
|
716
|
+
const size_t len = arr.size(rt2);
|
|
717
|
+
std::vector<VroomPriceLine> lines(len);
|
|
718
|
+
// Label storage, kept alive until set_price_lines has copied it.
|
|
719
|
+
std::vector<std::string> texts(len);
|
|
720
|
+
std::vector<std::string> quantities(len);
|
|
721
|
+
for (size_t i = 0; i < len; ++i) {
|
|
722
|
+
auto l = arr.getValueAtIndex(rt2, i).asObject(rt2);
|
|
723
|
+
lines[i].price = l.getProperty(rt2, "price").asNumber();
|
|
724
|
+
lines[i].color = static_cast<uint32_t>(
|
|
725
|
+
l.getProperty(rt2, "color").asNumber());
|
|
726
|
+
lines[i].width = static_cast<float>(
|
|
727
|
+
l.getProperty(rt2, "width").asNumber());
|
|
728
|
+
lines[i].line_style = static_cast<int32_t>(
|
|
729
|
+
l.getProperty(rt2, "lineStyle").asNumber());
|
|
730
|
+
texts[i] = l.getProperty(rt2, "text").asString(rt2).utf8(rt2);
|
|
731
|
+
quantities[i] =
|
|
732
|
+
l.getProperty(rt2, "quantity").asString(rt2).utf8(rt2);
|
|
733
|
+
lines[i].text = texts[i].c_str();
|
|
734
|
+
lines[i].quantity = quantities[i].c_str();
|
|
735
|
+
lines[i].flags = static_cast<int32_t>(
|
|
736
|
+
l.getProperty(rt2, "flags").asNumber());
|
|
737
|
+
}
|
|
738
|
+
VroomPriceLineStyle style{};
|
|
739
|
+
style.body_bg = static_cast<uint32_t>(
|
|
740
|
+
cfg.getProperty(rt2, "bodyBg").asNumber());
|
|
741
|
+
style.font_size_px = static_cast<float>(
|
|
742
|
+
cfg.getProperty(rt2, "fontSizePx").asNumber());
|
|
743
|
+
style.line_length_frac = static_cast<float>(
|
|
744
|
+
cfg.getProperty(rt2, "lineLengthFrac").asNumber());
|
|
745
|
+
style.align = static_cast<int32_t>(
|
|
746
|
+
cfg.getProperty(rt2, "align").asNumber());
|
|
747
|
+
style.hover_boost = static_cast<float>(
|
|
748
|
+
cfg.getProperty(rt2, "hoverBoost").asNumber());
|
|
749
|
+
vroom_chart_set_price_lines(chart_, lines.data(), lines.size(), &style);
|
|
750
|
+
return jsi::Value::undefined();
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (name == "hitTestPriceLine") {
|
|
755
|
+
// hitTestPriceLine(x, y) -> { index, part } | null. `part` is 0 for the line
|
|
756
|
+
// or its label body (the drag target) and 1 for the close button. Cheap
|
|
757
|
+
// enough to call at gesture rate — no rendering.
|
|
758
|
+
return jsi::Function::createFromHostFunction(
|
|
759
|
+
rt,
|
|
760
|
+
jsi::PropNameID::forAscii(rt, "hitTestPriceLine"),
|
|
761
|
+
2,
|
|
762
|
+
[this](jsi::Runtime& rt2,
|
|
763
|
+
const jsi::Value& /*thisVal*/,
|
|
764
|
+
const jsi::Value* args,
|
|
765
|
+
size_t count) -> jsi::Value {
|
|
766
|
+
if (count < 2) return jsi::Value::null();
|
|
767
|
+
int32_t index = -1, part = -1;
|
|
768
|
+
if (!vroom_chart_hit_test_price_line(
|
|
769
|
+
chart_, static_cast<float>(args[0].asNumber()),
|
|
770
|
+
static_cast<float>(args[1].asNumber()), &index, &part)) {
|
|
771
|
+
return jsi::Value::null();
|
|
772
|
+
}
|
|
773
|
+
jsi::Object obj(rt2);
|
|
774
|
+
obj.setProperty(rt2, "index", index);
|
|
775
|
+
obj.setProperty(rt2, "part", part);
|
|
776
|
+
return obj;
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (name == "setPriceLineHover") {
|
|
781
|
+
// setPriceLineHover(index, part) — highlight a price line's segment; -1
|
|
782
|
+
// clears. No hover on touch, so this exists for parity/pointer devices.
|
|
783
|
+
return jsi::Function::createFromHostFunction(
|
|
784
|
+
rt,
|
|
785
|
+
jsi::PropNameID::forAscii(rt, "setPriceLineHover"),
|
|
786
|
+
2,
|
|
787
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
788
|
+
const jsi::Value& /*thisVal*/,
|
|
789
|
+
const jsi::Value* args,
|
|
790
|
+
size_t count) -> jsi::Value {
|
|
791
|
+
if (count < 2) return jsi::Value::undefined();
|
|
792
|
+
vroom_chart_set_price_line_hover(
|
|
793
|
+
chart_, static_cast<int32_t>(args[0].asNumber()),
|
|
794
|
+
static_cast<int32_t>(args[1].asNumber()));
|
|
795
|
+
return jsi::Value::undefined();
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (name == "setPriceLineDrag") {
|
|
800
|
+
// setPriceLineDrag(index, price) — live drag preview; index -1 ends it. The
|
|
801
|
+
// committed price is untouched: restate setPriceLines to apply a move.
|
|
802
|
+
return jsi::Function::createFromHostFunction(
|
|
803
|
+
rt,
|
|
804
|
+
jsi::PropNameID::forAscii(rt, "setPriceLineDrag"),
|
|
805
|
+
2,
|
|
806
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
807
|
+
const jsi::Value& /*thisVal*/,
|
|
808
|
+
const jsi::Value* args,
|
|
809
|
+
size_t count) -> jsi::Value {
|
|
810
|
+
if (count < 2) return jsi::Value::undefined();
|
|
811
|
+
vroom_chart_set_price_line_drag(
|
|
812
|
+
chart_, static_cast<int32_t>(args[0].asNumber()),
|
|
813
|
+
args[1].asNumber());
|
|
814
|
+
return jsi::Value::undefined();
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
554
818
|
if (name == "render") {
|
|
555
819
|
return jsi::Function::createFromHostFunction(
|
|
556
820
|
rt,
|
|
@@ -40,7 +40,17 @@ static void ensureAxisTypeface(jsi::Runtime& runtime) {
|
|
|
40
40
|
if (!ctx) return;
|
|
41
41
|
auto mgr = ctx->createFontMgr();
|
|
42
42
|
if (!mgr) return;
|
|
43
|
-
|
|
43
|
+
|
|
44
|
+
// A null family name is meant to request "the platform default" — CoreText
|
|
45
|
+
// (iOS) honors that, but Android's SkFontMgr_New_Android does not: it
|
|
46
|
+
// returns null unless given an actual family name, even though its
|
|
47
|
+
// underlying font set (sans-serif, arial, ...) is perfectly populated. Fall
|
|
48
|
+
// back to "sans-serif" (Android's standard generic-family alias) whenever
|
|
49
|
+
// the null-family lookup comes back empty.
|
|
50
|
+
auto tf = mgr->matchFamilyStyle(nullptr, SkFontStyle());
|
|
51
|
+
if (!tf) {
|
|
52
|
+
tf = mgr->matchFamilyStyle("sans-serif", SkFontStyle());
|
|
53
|
+
}
|
|
44
54
|
if (!tf) return;
|
|
45
55
|
vroom::set_axis_typeface(tf);
|
|
46
56
|
done = true;
|
package/cpp/VroomSkiaContext.cpp
CHANGED
|
@@ -12,13 +12,21 @@ std::shared_ptr<RNSkia::RNSkPlatformContext> getRNSkContext(
|
|
|
12
12
|
if (!obj.isHostObject(runtime)) return nullptr;
|
|
13
13
|
auto host = obj.asHostObject(runtime);
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
15
|
+
// `global.SkiaApi` is always installed by RN-Skia's own
|
|
16
|
+
// RNSkManager::installBindings() as exactly a `JsiSkApi` host object — never
|
|
17
|
+
// any other JsiSkHostObject subclass — so this cast is safe as a plain
|
|
18
|
+
// static_cast, without any RTTI check. We deliberately avoid
|
|
19
|
+
// dynamic_pointer_cast here: on Android, RN-Skia is compiled into its own
|
|
20
|
+
// librnskia.so, separate from libvroomchart.so, so a `JsiSkApi` constructed
|
|
21
|
+
// by librnskia.so's code carries typeinfo from that .so; a dynamic_cast
|
|
22
|
+
// performed here (in libvroomchart.so) sees an unmerged RTTI record for
|
|
23
|
+
// "the same" class and always fails, even though the object is perfectly
|
|
24
|
+
// valid to use. (Same underlying issue as the JsiSkPicture cross-.so cast
|
|
25
|
+
// documented in VroomChartHostObject.cpp — but there RN-Skia's own compiled
|
|
26
|
+
// code does the cast on an object *we* construct, so we can't avoid it;
|
|
27
|
+
// here *we* do the cast on an object *they* construct, so we can just skip
|
|
28
|
+
// the runtime check we don't need.)
|
|
29
|
+
auto* accessor = static_cast<HostObjectAccessor*>(host.get());
|
|
22
30
|
return accessor->getContext();
|
|
23
31
|
}
|
|
24
32
|
|
|
@@ -49,6 +49,26 @@ typedef struct VroomOverlay {
|
|
|
49
49
|
float width; // stroke width in px
|
|
50
50
|
} VroomOverlay;
|
|
51
51
|
|
|
52
|
+
// Bollinger Bands overlay drawn on the price pane: a basis MA of `source` over
|
|
53
|
+
// `period`, banded at ± `mult` × population standard deviation of the same
|
|
54
|
+
// window. Per TradingView semantics the stdev always uses the window's
|
|
55
|
+
// arithmetic mean, even when `basis_kind` selects an EMA basis line.
|
|
56
|
+
typedef struct VroomBollinger {
|
|
57
|
+
int32_t enabled; // 0/1
|
|
58
|
+
int32_t period; // lookback in candles (clamped >= 1; default 20)
|
|
59
|
+
float mult; // stdev multiplier (clamped >= 0; default 2)
|
|
60
|
+
int32_t source; // 0=close,1=open,2=high,3=low,4=hl2,5=hlc3,6=ohlc4
|
|
61
|
+
int32_t basis_kind; // 0 = SMA, 1 = EMA
|
|
62
|
+
uint32_t upper_color; // 0xAARRGGBB
|
|
63
|
+
float upper_width; // stroke px
|
|
64
|
+
uint32_t middle_color;
|
|
65
|
+
float middle_width;
|
|
66
|
+
uint32_t lower_color;
|
|
67
|
+
float lower_width;
|
|
68
|
+
int32_t fill_enabled; // 0/1: translucent fill between upper and lower
|
|
69
|
+
float fill_opacity; // 0..1, multiplied into upper_color's alpha
|
|
70
|
+
} VroomBollinger;
|
|
71
|
+
|
|
52
72
|
// A drawing anchor in data space (so a drawing tracks the candles on pan/zoom).
|
|
53
73
|
typedef struct VroomDrawPoint {
|
|
54
74
|
int64_t time_ms; // epoch milliseconds (not snapped to a candle slot)
|
|
@@ -98,6 +118,44 @@ typedef struct VroomLiquidityStyle {
|
|
|
98
118
|
float width_frac;
|
|
99
119
|
} VroomLiquidityStyle;
|
|
100
120
|
|
|
121
|
+
// ---- Price status lines ---------------------------------------------------
|
|
122
|
+
|
|
123
|
+
// Bit flags for VroomPriceLine::flags.
|
|
124
|
+
typedef enum {
|
|
125
|
+
VROOM_PRICE_LINE_DRAGGABLE = 1 << 0, // the line can be dragged vertically
|
|
126
|
+
VROOM_PRICE_LINE_CLOSABLE = 1 << 1, // render the trailing close ("x") button
|
|
127
|
+
VROOM_PRICE_LINE_AXIS_LABEL = 1 << 2, // render the price badge in the y-axis strip
|
|
128
|
+
VROOM_PRICE_LINE_EXTEND_LEFT = 1 << 3, // extend the line to the pane's left edge
|
|
129
|
+
} VroomPriceLineFlags;
|
|
130
|
+
|
|
131
|
+
// A consumer-supplied horizontal status line at a fixed price — the primitive
|
|
132
|
+
// behind resting limit orders, take-profits, liquidation levels and the like.
|
|
133
|
+
//
|
|
134
|
+
// Visually: a line across the price pane ending in a label group made of a body
|
|
135
|
+
// pill (`text`), an optional solid-filled `quantity` pill, and an optional close
|
|
136
|
+
// button, plus an optional price badge in the y-axis strip.
|
|
137
|
+
//
|
|
138
|
+
// `text` / `quantity` are UTF-8 and copied internally, so the caller may free
|
|
139
|
+
// them as soon as the call returns. A null or empty string hides that segment.
|
|
140
|
+
typedef struct VroomPriceLine {
|
|
141
|
+
double price;
|
|
142
|
+
uint32_t color; // 0xAARRGGBB — line, border, body text, close icon
|
|
143
|
+
float width; // stroke width in px
|
|
144
|
+
int32_t line_style; // 0 = solid, 1 = dotted, 2 = dashed
|
|
145
|
+
const char* text; // body label
|
|
146
|
+
const char* quantity; // trailing solid-fill segment
|
|
147
|
+
int32_t flags; // bitwise-or of VroomPriceLineFlags
|
|
148
|
+
} VroomPriceLine;
|
|
149
|
+
|
|
150
|
+
// Layout/style shared by every price line, so the per-line struct stays small.
|
|
151
|
+
typedef struct VroomPriceLineStyle {
|
|
152
|
+
uint32_t body_bg; // translucent body/close-button pill fill (0xAARRGGBB)
|
|
153
|
+
float font_size_px; // 0 = inherit VROOM_FLOAT_AXIS_FONT_SIZE_PX
|
|
154
|
+
float line_length_frac; // 0..1 of pane width: the label group's right-edge inset
|
|
155
|
+
int32_t align; // 0 = left, 1 = center, 2 = right
|
|
156
|
+
float hover_boost; // brightness multiplier for the hovered segment (1 = flat)
|
|
157
|
+
} VroomPriceLineStyle;
|
|
158
|
+
|
|
101
159
|
// A continuous data coordinate at a pixel position (no candle snapping). Used to
|
|
102
160
|
// translate a drawing-tool click into a data-space anchor.
|
|
103
161
|
typedef struct VroomCoord {
|
|
@@ -321,6 +379,11 @@ void vroom_chart_set_overlays(VroomChart* chart, const VroomOverlay* overlays,
|
|
|
321
379
|
void vroom_chart_set_vwap(VroomChart* chart, bool enabled, int reset_offset_min,
|
|
322
380
|
uint32_t color, float width);
|
|
323
381
|
|
|
382
|
+
// Configures the Bollinger Bands overlay (three price-pane lines + an optional
|
|
383
|
+
// translucent fill between the bands; no pane is reserved). Color/width/fill
|
|
384
|
+
// changes only re-render; enabled/period/mult/source/basis changes recompute.
|
|
385
|
+
void vroom_chart_set_bollinger(VroomChart* chart, const VroomBollinger* cfg);
|
|
386
|
+
|
|
324
387
|
// ---- Drawings (line annotations) ------------------------------------------
|
|
325
388
|
|
|
326
389
|
// Replaces the full set of committed line drawings (data-anchored, so they track
|
|
@@ -365,6 +428,38 @@ void vroom_chart_translate_drawing(VroomChart* chart, int32_t index,
|
|
|
365
428
|
void vroom_chart_set_liquidity(VroomChart* chart, const VroomBand* bands,
|
|
366
429
|
size_t count, const VroomLiquidityStyle* style);
|
|
367
430
|
|
|
431
|
+
// ---- Price status lines ---------------------------------------------------
|
|
432
|
+
|
|
433
|
+
// Replaces the full set of price status lines and their shared style. Lines
|
|
434
|
+
// render on the price pane above the axis labels (so their badges cover any
|
|
435
|
+
// label they overlap) and below the crosshair. A line whose price maps outside
|
|
436
|
+
// the price pane is skipped rather than clamped. Pass count 0 to clear;
|
|
437
|
+
// `style` may be null when count is 0.
|
|
438
|
+
void vroom_chart_set_price_lines(VroomChart* chart, const VroomPriceLine* lines,
|
|
439
|
+
size_t count,
|
|
440
|
+
const VroomPriceLineStyle* style);
|
|
441
|
+
|
|
442
|
+
// Hit-tests pixel (x_px, y_px) against the price lines. On a hit, fills
|
|
443
|
+
// *out_index with the line index and *out_part with 0 (the line or its label
|
|
444
|
+
// body — the drag target) or 1 (the close button), and returns true. When
|
|
445
|
+
// several lines are within tolerance the nearest in y wins. Only draggable
|
|
446
|
+
// lines report part 0 and only closable lines report part 1. Returns false on a
|
|
447
|
+
// miss (out params untouched). Either out pointer may be null.
|
|
448
|
+
bool vroom_chart_hit_test_price_line(VroomChart* chart, float x_px, float y_px,
|
|
449
|
+
int32_t* out_index, int32_t* out_part);
|
|
450
|
+
|
|
451
|
+
// Marks a price line's segment as hovered so it renders highlighted. `index` -1
|
|
452
|
+
// clears the hover. `part` matches vroom_chart_hit_test_price_line.
|
|
453
|
+
void vroom_chart_set_price_line_hover(VroomChart* chart, int32_t index,
|
|
454
|
+
int32_t part);
|
|
455
|
+
|
|
456
|
+
// Drives the live drag preview: the line, its label and its badge render at
|
|
457
|
+
// `price` instead of the committed one, and a faint ghost marks where it
|
|
458
|
+
// started. `index` -1 ends the preview. The committed price is never mutated —
|
|
459
|
+
// the host applies (or rejects) the new price by restating its lines.
|
|
460
|
+
void vroom_chart_set_price_line_drag(VroomChart* chart, int32_t index,
|
|
461
|
+
double price);
|
|
462
|
+
|
|
368
463
|
// Sets the transient in-progress "draft" the drawing tool shows while the user
|
|
369
464
|
// places points. Node A is always shown; when `has_b`, node B is shown too.
|
|
370
465
|
// `guide != 0` also draws the live preview (a guideline for a line, or a preview
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#include "bollinger.h"
|
|
2
|
+
|
|
3
|
+
#include <cmath> // std::nan, std::sqrt
|
|
4
|
+
|
|
5
|
+
#include "ma.h"
|
|
6
|
+
|
|
7
|
+
namespace vroom::bollinger {
|
|
8
|
+
|
|
9
|
+
void compute(const ::VroomCandle* candles, std::size_t n, int period,
|
|
10
|
+
double mult, int source, int basis_kind,
|
|
11
|
+
std::vector<double>& middle, std::vector<double>& upper,
|
|
12
|
+
std::vector<double>& lower) {
|
|
13
|
+
vroom::ma::compute(candles, n, basis_kind, period, source, middle);
|
|
14
|
+
upper.assign(n, std::nan(""));
|
|
15
|
+
lower.assign(n, std::nan(""));
|
|
16
|
+
if (!candles || period < 1) return;
|
|
17
|
+
const std::size_t P = static_cast<std::size_t>(period);
|
|
18
|
+
if (n < P) return;
|
|
19
|
+
|
|
20
|
+
std::vector<double> src(n);
|
|
21
|
+
for (std::size_t i = 0; i < n; ++i) src[i] = vroom::ma::source_value(candles[i], source);
|
|
22
|
+
|
|
23
|
+
// Two-pass stdev around the true window mean per bar. O(n·period), but
|
|
24
|
+
// numerically stable — the rolling Σx²−n·mean² form cancels catastrophically
|
|
25
|
+
// on large prices with small deviations (e.g. BTC-scale values).
|
|
26
|
+
for (std::size_t i = P - 1; i < n; ++i) {
|
|
27
|
+
const std::size_t s = i + 1 - P;
|
|
28
|
+
double mean = 0.0;
|
|
29
|
+
for (std::size_t j = s; j <= i; ++j) mean += src[j];
|
|
30
|
+
mean /= static_cast<double>(P);
|
|
31
|
+
double var = 0.0;
|
|
32
|
+
for (std::size_t j = s; j <= i; ++j) {
|
|
33
|
+
const double d = src[j] - mean;
|
|
34
|
+
var += d * d;
|
|
35
|
+
}
|
|
36
|
+
var /= static_cast<double>(P);
|
|
37
|
+
const double band = mult * std::sqrt(var);
|
|
38
|
+
upper[i] = middle[i] + band;
|
|
39
|
+
lower[i] = middle[i] - band;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
} // namespace vroom::bollinger
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Bollinger Bands over a candle source series — pure, no Skia, so it builds
|
|
2
|
+
// into the unit-test target. Drawn as price-pane overlay lines + a band fill.
|
|
3
|
+
|
|
4
|
+
#pragma once
|
|
5
|
+
|
|
6
|
+
#include <cstddef>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
#include "vroom/vroom_chart.h" // ::VroomCandle
|
|
10
|
+
|
|
11
|
+
namespace vroom::bollinger {
|
|
12
|
+
|
|
13
|
+
// Computes the three band series over [candles, candles+n). `period` is clamped
|
|
14
|
+
// to >= 1; `mult` is the standard-deviation multiplier. `source` is a
|
|
15
|
+
// vroom::ma::Source index and `basis_kind` a vroom::ma::Kind (SMA/EMA).
|
|
16
|
+
//
|
|
17
|
+
// middle = ma::compute(basis_kind, period, source)
|
|
18
|
+
// upper/lower = middle ± mult * population stdev of source over the trailing
|
|
19
|
+
// period window
|
|
20
|
+
//
|
|
21
|
+
// The stdev always uses the window's arithmetic mean, even when basis_kind
|
|
22
|
+
// selects an EMA basis line (TradingView semantics).
|
|
23
|
+
//
|
|
24
|
+
// Each output is resized to n; values are NaN for i < period-1 and when
|
|
25
|
+
// n < period.
|
|
26
|
+
void compute(const ::VroomCandle* candles, std::size_t n, int period,
|
|
27
|
+
double mult, int source, int basis_kind,
|
|
28
|
+
std::vector<double>& middle, std::vector<double>& upper,
|
|
29
|
+
std::vector<double>& lower);
|
|
30
|
+
|
|
31
|
+
} // namespace vroom::bollinger
|
package/cpp/_core_src/chart.cpp
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#include "include/core/SkRect.h"
|
|
19
19
|
#pragma clang diagnostic pop
|
|
20
20
|
|
|
21
|
+
#include "bollinger.h"
|
|
21
22
|
#include "candles.h"
|
|
22
23
|
#include "chart_internal.h"
|
|
23
24
|
#include "crosshair.h"
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
#include "macd.h"
|
|
30
31
|
#include "macd_pane.h"
|
|
31
32
|
#include "price_indicator.h"
|
|
33
|
+
#include "price_lines.h"
|
|
32
34
|
#include "rsi.h"
|
|
33
35
|
#include "rsi_pane.h"
|
|
34
36
|
#include "volume.h"
|
|
@@ -100,6 +102,15 @@ void VroomChart::ensure_vwap() {
|
|
|
100
102
|
vwap_dirty = false;
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
void VroomChart::ensure_bollinger() {
|
|
106
|
+
if (!bollinger.enabled || !bollinger_dirty) return;
|
|
107
|
+
vroom::bollinger::compute(candles.data(), candles.size(), bollinger.period,
|
|
108
|
+
bollinger.mult, bollinger.source,
|
|
109
|
+
bollinger.basis_kind, bb_middle_cache,
|
|
110
|
+
bb_upper_cache, bb_lower_cache);
|
|
111
|
+
bollinger_dirty = false;
|
|
112
|
+
}
|
|
113
|
+
|
|
103
114
|
void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
104
115
|
const auto lay = layout();
|
|
105
116
|
|
|
@@ -149,6 +160,23 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
149
160
|
vroom::liquidity::draw(canvas, *this, lay, bounds, candle_right,
|
|
150
161
|
candle_area_h);
|
|
151
162
|
|
|
163
|
+
// 4.7. Bollinger Band fill — the translucent region between the upper and
|
|
164
|
+
// lower bands, behind the candles so their bull/bear colors stay
|
|
165
|
+
// untinted. The band lines themselves draw above the candles (5.65).
|
|
166
|
+
if (bollinger.enabled) {
|
|
167
|
+
ensure_bollinger();
|
|
168
|
+
if (bollinger.fill_enabled &&
|
|
169
|
+
bb_upper_cache.size() == candles.size() &&
|
|
170
|
+
bb_lower_cache.size() == candles.size()) {
|
|
171
|
+
vroom::ma_overlay::fill_between(
|
|
172
|
+
canvas, lay, bounds, visible, n,
|
|
173
|
+
bb_upper_cache.data() + range.start,
|
|
174
|
+
bb_lower_cache.data() + range.start, window_ms,
|
|
175
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
176
|
+
candle_area_h, bollinger.upper_color, bollinger.fill_opacity);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
152
180
|
// 5. Price series — candles, a close-price line, or a blend of the two during
|
|
153
181
|
// the candle↔line morph. `morph_fade` crossfades candles→line and
|
|
154
182
|
// `morph_collapse` folds each candle toward its close (the line vertex).
|
|
@@ -202,6 +230,28 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
202
230
|
}
|
|
203
231
|
}
|
|
204
232
|
|
|
233
|
+
// 5.65. Bollinger Band lines — upper, lower, then the basis last so it
|
|
234
|
+
// reads on top where the bands pinch. Same price scale as the
|
|
235
|
+
// candles; the fill went down in 4.7.
|
|
236
|
+
if (bollinger.enabled) {
|
|
237
|
+
ensure_bollinger();
|
|
238
|
+
const std::size_t sz = candles.size();
|
|
239
|
+
if (bb_upper_cache.size() == sz && bb_lower_cache.size() == sz &&
|
|
240
|
+
bb_middle_cache.size() == sz) {
|
|
241
|
+
const auto stroke = [&](const std::vector<double>& cache,
|
|
242
|
+
uint32_t color, float width) {
|
|
243
|
+
vroom::ma_overlay::draw(canvas, lay, bounds, visible, n,
|
|
244
|
+
cache.data() + range.start, window_ms,
|
|
245
|
+
visible_start_ms, candle_duration_ms,
|
|
246
|
+
candle_right, candle_area_h, color,
|
|
247
|
+
width);
|
|
248
|
+
};
|
|
249
|
+
stroke(bb_upper_cache, bollinger.upper_color, bollinger.upper_width);
|
|
250
|
+
stroke(bb_lower_cache, bollinger.lower_color, bollinger.lower_width);
|
|
251
|
+
stroke(bb_middle_cache, bollinger.middle_color, bollinger.middle_width);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
205
255
|
// 5.7. Drawing annotations (committed line tools + the in-progress draft).
|
|
206
256
|
// On the price pane above the candles/overlays, below the axis labels.
|
|
207
257
|
vroom::drawings::draw(canvas, *this, lay, bounds, candle_right,
|
|
@@ -230,6 +280,12 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
230
280
|
vroom::price_indicator::draw(canvas, *this, lay, bounds,
|
|
231
281
|
candle_right, candle_area_h);
|
|
232
282
|
|
|
283
|
+
// 7.55. Consumer-supplied price status lines — same tier as the current-price
|
|
284
|
+
// indicator (their badges must cover the labels underneath), but after
|
|
285
|
+
// it so a resting order at the last close stays readable.
|
|
286
|
+
vroom::price_lines::draw(canvas, *this, lay, bounds, candle_right,
|
|
287
|
+
candle_area_h);
|
|
288
|
+
|
|
233
289
|
// 7.6. Indicator panes stacked below the candles, ordered by enable
|
|
234
290
|
// sequence (most recently enabled at the bottom). Each pane is
|
|
235
291
|
// INDICATOR_HEIGHT_FRAC of the height; the candle pane already shrank
|