react-native-vroom-chart 0.13.0 → 0.13.1
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 +8 -0
- package/android/README.md +11 -10
- package/cpp/VroomChartHostObject.cpp +140 -47
- package/cpp/VroomChartHostObject.h +1 -1
- package/cpp/VroomJsiInstaller.cpp +1 -1
- package/lib/index.js +62 -32
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +71 -34
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +92 -43
- package/src/jsi.d.ts +12 -9
- package/src/useChartCore.ts +4 -5
package/android/CMakeLists.txt
CHANGED
|
@@ -124,6 +124,14 @@ target_compile_options(vroomchart PRIVATE -Wno-mismatched-tags)
|
|
|
124
124
|
|
|
125
125
|
find_library(VROOM_LOG_LIB log)
|
|
126
126
|
|
|
127
|
+
# Android 15+ devices can use 16KB pages. NDK r27 and older default to 4KB
|
|
128
|
+
# ELF alignment (align=2**12); Play/SDK 35 then rejects libvroomchart.so.
|
|
129
|
+
# r28+ already aligns to 16KB, so these flags are redundant there but harmless.
|
|
130
|
+
target_link_options(vroomchart PRIVATE
|
|
131
|
+
"-Wl,-z,max-page-size=16384"
|
|
132
|
+
"-Wl,-z,common-page-size=16384"
|
|
133
|
+
)
|
|
134
|
+
|
|
127
135
|
target_link_libraries(vroomchart
|
|
128
136
|
${VROOM_LOG_LIB}
|
|
129
137
|
ReactAndroid::jsi
|
package/android/README.md
CHANGED
|
@@ -52,13 +52,14 @@ different, unmerged RTTI record for "the same" class and fails with
|
|
|
52
52
|
`Expected a Skia object of a different type`.
|
|
53
53
|
|
|
54
54
|
`VroomChartHostObject.cpp`'s `wrapPicture()` works around this only on
|
|
55
|
-
Android
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
Android. Two copies of Skia cannot share an `sk_sp<SkPicture>` pointer, and
|
|
56
|
+
serializing the chart picture (the original workaround) embedded the Android
|
|
57
|
+
system typeface on every pan/zoom frame — an OOM that killed the process after
|
|
58
|
+
a few seconds of gesturing.
|
|
59
|
+
|
|
60
|
+
The hot path now rasterizes the picture in vroom's Skia and hands RN-Skia raw
|
|
61
|
+
pixels via `Skia.Image.MakeImage`, so `<Image>` paints a framebuffer whose size
|
|
62
|
+
is bounded by the view. If rasterization fails, we still serialize, but with
|
|
63
|
+
`SkTypeface::SerializeBehavior::kDontIncludeData` so the font file is not
|
|
64
|
+
copied; `MakePicture` re-resolves `sans-serif` inside `librnskia.so`. iOS is
|
|
65
|
+
unchanged (one binary, direct `JsiSkPicture` wrap).
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include <string>
|
|
5
5
|
#include <vector>
|
|
6
6
|
|
|
7
|
+
#include "chart.h"
|
|
7
8
|
#include "chart_internal.h"
|
|
8
9
|
#include "vroom/vroom_chart.h"
|
|
9
10
|
|
|
@@ -11,6 +12,18 @@
|
|
|
11
12
|
#include "JsiSkNativeObjects.h"
|
|
12
13
|
#include "JsiSkPicture.h"
|
|
13
14
|
|
|
15
|
+
#if defined(__ANDROID__)
|
|
16
|
+
#include <algorithm>
|
|
17
|
+
#include <cmath>
|
|
18
|
+
|
|
19
|
+
#include "include/core/SkBitmap.h"
|
|
20
|
+
#include "include/core/SkCanvas.h"
|
|
21
|
+
#include "include/core/SkData.h"
|
|
22
|
+
#include "include/core/SkImageInfo.h"
|
|
23
|
+
#include "include/core/SkSerialProcs.h"
|
|
24
|
+
#include "include/core/SkTypeface.h"
|
|
25
|
+
#endif
|
|
26
|
+
|
|
14
27
|
namespace vroom {
|
|
15
28
|
|
|
16
29
|
namespace jsi = facebook::jsi;
|
|
@@ -74,27 +87,21 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
|
74
87
|
}
|
|
75
88
|
|
|
76
89
|
#if defined(__ANDROID__)
|
|
77
|
-
// Android only: RN-Skia
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// `getJsiObject<JsiSkPicture>(rt, value)` — a dynamic_pointer_cast
|
|
83
|
-
// under the hood), the cast fails cross-.so with "Expected a Skia object of
|
|
84
|
-
// a different type", because the object's *runtime* type was never actually
|
|
85
|
-
// compiled inside librnskia.so. iOS statically links everything into one
|
|
86
|
-
// binary, so no such mismatch exists there.
|
|
90
|
+
// Android only: RN-Skia lives in librnskia.so, vroom in libvroomchart.so, and
|
|
91
|
+
// each statically links its own libskia.a. A JsiSkPicture constructed here has
|
|
92
|
+
// our .so's RTTI, so RN-Skia's dynamic_pointer_cast fails with "Expected a
|
|
93
|
+
// Skia object of a different type". iOS statically links one binary, so the
|
|
94
|
+
// iOS wrap below can hand the sk_sp<SkPicture> through directly.
|
|
87
95
|
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
// Crossing that boundary used to serialize the chart picture and call
|
|
97
|
+
// Skia.Picture.MakePicture(bytes). Default SkPicture::serialize() embeds the
|
|
98
|
+
// Android system typeface (Noto-scale, megabytes) on every pan/zoom frame;
|
|
99
|
+
// Hermes never kept up and the process was OOM-killed (~3 GB RSS in ~10s).
|
|
100
|
+
//
|
|
101
|
+
// Preferred path: rasterize in *our* Skia and hand RN-Skia raw pixels via
|
|
102
|
+
// Skia.Image.MakeImage — memory is one framebuffer, labels stay correct.
|
|
103
|
+
// Fallback: still serialize, but with kDontIncludeData so the font file is
|
|
104
|
+
// not copied; MakePicture re-resolves sans-serif on RN-Skia's side.
|
|
98
105
|
namespace {
|
|
99
106
|
class SkDataMutableBuffer : public facebook::jsi::MutableBuffer {
|
|
100
107
|
public:
|
|
@@ -108,41 +115,119 @@ class SkDataMutableBuffer : public facebook::jsi::MutableBuffer {
|
|
|
108
115
|
private:
|
|
109
116
|
sk_sp<SkData> data_;
|
|
110
117
|
};
|
|
111
|
-
} // namespace
|
|
112
118
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
jsi::Value skiaApiObject(jsi::Runtime& rt, const char* name) {
|
|
120
|
+
auto skiaApi = rt.global().getProperty(rt, "SkiaApi");
|
|
121
|
+
if (!skiaApi.isObject()) return jsi::Value::undefined();
|
|
122
|
+
auto prop = skiaApi.asObject(rt).getProperty(rt, name);
|
|
123
|
+
return prop.isObject() ? std::move(prop) : jsi::Value::undefined();
|
|
124
|
+
}
|
|
118
125
|
|
|
119
|
-
|
|
126
|
+
// Uint8Array-shaped `{ buffer }` — MakePicture and Data.fromBytes both
|
|
127
|
+
// read the ArrayBuffer off that property.
|
|
128
|
+
jsi::Object bytesLike(jsi::Runtime& rt, sk_sp<SkData> data) {
|
|
129
|
+
auto buffer = std::make_shared<SkDataMutableBuffer>(std::move(data));
|
|
120
130
|
jsi::ArrayBuffer arrayBuffer(rt, buffer);
|
|
131
|
+
jsi::Object arg(rt);
|
|
132
|
+
arg.setProperty(rt, "buffer", arrayBuffer);
|
|
133
|
+
return arg;
|
|
134
|
+
}
|
|
121
135
|
|
|
122
|
-
|
|
123
|
-
if (!
|
|
124
|
-
|
|
125
|
-
|
|
136
|
+
sk_sp<const SkData> serializeTypefaceNoEmbed(SkTypeface* tf, void*) {
|
|
137
|
+
if (!tf) return nullptr;
|
|
138
|
+
return tf->serialize(SkTypeface::SerializeBehavior::kDontIncludeData);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
jsi::Value wrapSerializedPicture(jsi::Runtime& rt, const sk_sp<SkPicture>& pic) {
|
|
142
|
+
SkSerialProcs procs;
|
|
143
|
+
procs.fTypefaceProc = serializeTypefaceNoEmbed;
|
|
144
|
+
sk_sp<SkData> serialized = pic->serialize(&procs);
|
|
145
|
+
if (!serialized) return jsi::Value::null();
|
|
146
|
+
|
|
147
|
+
auto pictureFactory = skiaApiObject(rt, "Picture");
|
|
148
|
+
if (pictureFactory.isUndefined()) return jsi::Value::null();
|
|
126
149
|
auto pictureFactoryObj = pictureFactory.asObject(rt);
|
|
127
150
|
auto makePicture =
|
|
128
151
|
pictureFactoryObj.getPropertyAsFunction(rt, "MakePicture");
|
|
129
152
|
|
|
130
|
-
// Mirrors JsiSkPictureFactory::MakePicture's expected argument shape: an
|
|
131
|
-
// object with a `.buffer` property holding the ArrayBuffer (matching a
|
|
132
|
-
// Uint8Array-like value; the factory ignores everything else about it).
|
|
133
|
-
//
|
|
134
153
|
// Skia 2.11's NativeState host methods recover the factory via `this`
|
|
135
154
|
// (`fromThis` → "Expected PictureFactory but got non-object" if unbound).
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
return makePicture.callWithThis(rt, pictureFactoryObj,
|
|
156
|
+
bytesLike(rt, std::move(serialized)));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
jsi::Value wrapRasterImage(jsi::Runtime& rt, const sk_sp<SkPicture>& pic,
|
|
160
|
+
float dpr) {
|
|
161
|
+
const SkRect cull = pic->cullRect();
|
|
162
|
+
const float scale = dpr > 0.f ? dpr : 1.f;
|
|
163
|
+
const int w =
|
|
164
|
+
std::max(1, static_cast<int>(std::ceil(cull.width() * scale)));
|
|
165
|
+
const int h =
|
|
166
|
+
std::max(1, static_cast<int>(std::ceil(cull.height() * scale)));
|
|
167
|
+
|
|
168
|
+
// Reused across frames on this JS thread so pan/zoom doesn't heap-allocate
|
|
169
|
+
// a new bitmap every time. Multiple charts share it; a size change reallocs.
|
|
170
|
+
thread_local SkBitmap raster;
|
|
171
|
+
const auto info = SkImageInfo::MakeN32Premul(w, h);
|
|
172
|
+
if ((raster.width() != w || raster.height() != h) &&
|
|
173
|
+
!raster.tryAllocPixels(info)) {
|
|
174
|
+
return jsi::Value::null();
|
|
175
|
+
}
|
|
176
|
+
raster.eraseColor(SK_ColorTRANSPARENT);
|
|
177
|
+
SkCanvas canvas(raster);
|
|
178
|
+
canvas.scale(scale, scale);
|
|
179
|
+
canvas.translate(-cull.left(), -cull.top());
|
|
180
|
+
canvas.drawPicture(pic);
|
|
181
|
+
|
|
182
|
+
SkPixmap pixmap;
|
|
183
|
+
if (!raster.peekPixels(&pixmap) || pixmap.addr() == nullptr) {
|
|
184
|
+
return jsi::Value::null();
|
|
185
|
+
}
|
|
186
|
+
sk_sp<SkData> pixels =
|
|
187
|
+
SkData::MakeWithCopy(pixmap.addr(), pixmap.computeByteSize());
|
|
188
|
+
if (!pixels) return jsi::Value::null();
|
|
189
|
+
|
|
190
|
+
auto dataFactory = skiaApiObject(rt, "Data");
|
|
191
|
+
auto imageFactory = skiaApiObject(rt, "Image");
|
|
192
|
+
if (dataFactory.isUndefined() || imageFactory.isUndefined()) {
|
|
193
|
+
return jsi::Value::null();
|
|
194
|
+
}
|
|
195
|
+
auto dataFactoryObj = dataFactory.asObject(rt);
|
|
196
|
+
auto imageFactoryObj = imageFactory.asObject(rt);
|
|
197
|
+
auto fromBytes = dataFactoryObj.getPropertyAsFunction(rt, "fromBytes");
|
|
198
|
+
auto makeImage = imageFactoryObj.getPropertyAsFunction(rt, "MakeImage");
|
|
199
|
+
|
|
200
|
+
auto skData =
|
|
201
|
+
fromBytes.callWithThis(rt, dataFactoryObj, bytesLike(rt, std::move(pixels)));
|
|
202
|
+
if (!skData.isObject()) return jsi::Value::null();
|
|
203
|
+
|
|
204
|
+
jsi::Object imageInfo(rt);
|
|
205
|
+
imageInfo.setProperty(rt, "width", static_cast<double>(w));
|
|
206
|
+
imageInfo.setProperty(rt, "height", static_cast<double>(h));
|
|
207
|
+
imageInfo.setProperty(rt, "colorType",
|
|
208
|
+
static_cast<double>(pixmap.colorType()));
|
|
209
|
+
imageInfo.setProperty(rt, "alphaType",
|
|
210
|
+
static_cast<double>(pixmap.alphaType()));
|
|
211
|
+
return makeImage.callWithThis(rt, imageFactoryObj, imageInfo, skData,
|
|
212
|
+
static_cast<double>(pixmap.rowBytes()));
|
|
213
|
+
}
|
|
214
|
+
} // namespace
|
|
215
|
+
|
|
216
|
+
static facebook::jsi::Value wrapPicture(facebook::jsi::Runtime& rt,
|
|
217
|
+
const sk_sp<SkPicture>& pic,
|
|
218
|
+
float dpr = 1.f) {
|
|
219
|
+
if (!pic) return facebook::jsi::Value::null();
|
|
220
|
+
auto image = wrapRasterImage(rt, pic, dpr);
|
|
221
|
+
if (!image.isNull()) return image;
|
|
222
|
+
return wrapSerializedPicture(rt, pic);
|
|
139
223
|
}
|
|
140
224
|
#else
|
|
141
225
|
// Shared helper: wraps a fresh picture for return to JS. Memory pressure is
|
|
142
226
|
// applied inside JsiSkPicture::create (NativeObject::create reads
|
|
143
227
|
// getMemoryPressure() → picture->approximateBytesUsed()).
|
|
144
228
|
static facebook::jsi::Value wrapPicture(facebook::jsi::Runtime& rt,
|
|
145
|
-
const sk_sp<SkPicture>& pic
|
|
229
|
+
const sk_sp<SkPicture>& pic,
|
|
230
|
+
float /*dpr*/ = 1.f) {
|
|
146
231
|
if (!pic) return facebook::jsi::Value::null();
|
|
147
232
|
auto host =
|
|
148
233
|
std::make_shared<RNSkia::JsiSkPicture>(/*context=*/nullptr, pic);
|
|
@@ -448,7 +533,8 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
448
533
|
const float dx = static_cast<float>(args[0].asNumber());
|
|
449
534
|
const float dy = static_cast<float>(args[1].asNumber());
|
|
450
535
|
vroom_chart_pan(chart_, dx, dy);
|
|
451
|
-
return wrapPicture(rt2, render_chart_picture(chart_)
|
|
536
|
+
return wrapPicture(rt2, render_chart_picture(chart_),
|
|
537
|
+
chart_->px_ratio);
|
|
452
538
|
});
|
|
453
539
|
}
|
|
454
540
|
|
|
@@ -467,7 +553,8 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
467
553
|
const float dx = static_cast<float>(args[0].asNumber());
|
|
468
554
|
const float dy = static_cast<float>(args[1].asNumber());
|
|
469
555
|
vroom_chart_translate(chart_, dx, dy);
|
|
470
|
-
return wrapPicture(rt2, render_chart_picture(chart_)
|
|
556
|
+
return wrapPicture(rt2, render_chart_picture(chart_),
|
|
557
|
+
chart_->px_ratio);
|
|
471
558
|
});
|
|
472
559
|
}
|
|
473
560
|
|
|
@@ -489,7 +576,8 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
489
576
|
const float fx = static_cast<float>(args[2].asNumber());
|
|
490
577
|
const float fy = static_cast<float>(args[3].asNumber());
|
|
491
578
|
vroom_chart_zoom(chart_, sx, sy, fx, fy);
|
|
492
|
-
return wrapPicture(rt2, render_chart_picture(chart_)
|
|
579
|
+
return wrapPicture(rt2, render_chart_picture(chart_),
|
|
580
|
+
chart_->px_ratio);
|
|
493
581
|
});
|
|
494
582
|
}
|
|
495
583
|
|
|
@@ -505,7 +593,8 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
505
593
|
if (count < 1) return jsi::Value::null();
|
|
506
594
|
const float dy = static_cast<float>(args[0].asNumber());
|
|
507
595
|
vroom_chart_scale_price_axis(chart_, dy);
|
|
508
|
-
return wrapPicture(rt2, render_chart_picture(chart_)
|
|
596
|
+
return wrapPicture(rt2, render_chart_picture(chart_),
|
|
597
|
+
chart_->px_ratio);
|
|
509
598
|
});
|
|
510
599
|
}
|
|
511
600
|
|
|
@@ -521,7 +610,8 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
521
610
|
if (count < 1) return jsi::Value::null();
|
|
522
611
|
const float dx = static_cast<float>(args[0].asNumber());
|
|
523
612
|
vroom_chart_scale_time_axis(chart_, dx);
|
|
524
|
-
return wrapPicture(rt2, render_chart_picture(chart_)
|
|
613
|
+
return wrapPicture(rt2, render_chart_picture(chart_),
|
|
614
|
+
chart_->px_ratio);
|
|
525
615
|
});
|
|
526
616
|
}
|
|
527
617
|
|
|
@@ -572,7 +662,8 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
572
662
|
const float x = static_cast<float>(args[0].asNumber());
|
|
573
663
|
const float y = static_cast<float>(args[1].asNumber());
|
|
574
664
|
vroom_chart_set_crosshair(chart_, x, y);
|
|
575
|
-
return wrapPicture(rt2, render_chart_picture(chart_)
|
|
665
|
+
return wrapPicture(rt2, render_chart_picture(chart_),
|
|
666
|
+
chart_->px_ratio);
|
|
576
667
|
});
|
|
577
668
|
}
|
|
578
669
|
|
|
@@ -587,7 +678,8 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
587
678
|
const jsi::Value* /*args*/,
|
|
588
679
|
size_t /*count*/) -> jsi::Value {
|
|
589
680
|
vroom_chart_clear_crosshair(chart_);
|
|
590
|
-
return wrapPicture(rt2, render_chart_picture(chart_)
|
|
681
|
+
return wrapPicture(rt2, render_chart_picture(chart_),
|
|
682
|
+
chart_->px_ratio);
|
|
591
683
|
});
|
|
592
684
|
}
|
|
593
685
|
|
|
@@ -1098,7 +1190,8 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
1098
1190
|
const jsi::Value& /*thisVal*/,
|
|
1099
1191
|
const jsi::Value* /*args*/,
|
|
1100
1192
|
size_t /*count*/) -> jsi::Value {
|
|
1101
|
-
return wrapPicture(rt2, render_chart_picture(chart_)
|
|
1193
|
+
return wrapPicture(rt2, render_chart_picture(chart_),
|
|
1194
|
+
chart_->px_ratio);
|
|
1102
1195
|
});
|
|
1103
1196
|
}
|
|
1104
1197
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// Methods exposed to JS:
|
|
7
7
|
// setCandles(arrayBuffer) — copies packed VroomCandle[] into the core
|
|
8
8
|
// setSize(width, height, pxRatio)
|
|
9
|
-
// render() ->
|
|
9
|
+
// render() -> SkPicture (iOS) / SkImage (Android)
|
|
10
10
|
|
|
11
11
|
#pragma once
|
|
12
12
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// methods on the returned object:
|
|
6
6
|
// setCandles(arrayBuffer)
|
|
7
7
|
// setSize(w, h, pxRatio)
|
|
8
|
-
// render() ->
|
|
8
|
+
// render() -> SkPicture (iOS) / SkImage (Android)
|
|
9
9
|
//
|
|
10
10
|
// The host object's lifetime is managed by JS GC — when the React component
|
|
11
11
|
// drops its reference, the underlying VroomChart is destroyed.
|
package/lib/index.js
CHANGED
|
@@ -514,6 +514,9 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
514
514
|
}
|
|
515
515
|
|
|
516
516
|
// src/VroomChart.tsx
|
|
517
|
+
function isSkImage(frame) {
|
|
518
|
+
return typeof frame.getImageInfo === "function";
|
|
519
|
+
}
|
|
517
520
|
function VroomChart(props) {
|
|
518
521
|
const {
|
|
519
522
|
candles,
|
|
@@ -565,17 +568,38 @@ function VroomChart(props) {
|
|
|
565
568
|
rec.beginRecording(import_react_native_skia.Skia.XYWHRect(0, 0, 1, 1));
|
|
566
569
|
return rec.finishRecordingAsPicture();
|
|
567
570
|
}, []);
|
|
571
|
+
const emptyImage = (0, import_react2.useMemo)(() => {
|
|
572
|
+
const data = import_react_native_skia.Skia.Data.fromBytes(new Uint8Array(4));
|
|
573
|
+
return import_react_native_skia.Skia.Image.MakeImage(
|
|
574
|
+
{
|
|
575
|
+
width: 1,
|
|
576
|
+
height: 1,
|
|
577
|
+
colorType: import_react_native_skia.ColorType.RGBA_8888,
|
|
578
|
+
alphaType: import_react_native_skia.AlphaType.Premul
|
|
579
|
+
},
|
|
580
|
+
data,
|
|
581
|
+
4
|
|
582
|
+
);
|
|
583
|
+
}, []);
|
|
568
584
|
const pictureSV = (0, import_react_native_reanimated.useSharedValue)(emptyPicture);
|
|
585
|
+
const imageSV = (0, import_react_native_reanimated.useSharedValue)(emptyImage);
|
|
586
|
+
const applyFrame = (0, import_react2.useCallback)(
|
|
587
|
+
(frame) => {
|
|
588
|
+
if (isSkImage(frame)) imageSV.value = frame;
|
|
589
|
+
else pictureSV.value = frame;
|
|
590
|
+
},
|
|
591
|
+
[imageSV, pictureSV]
|
|
592
|
+
);
|
|
569
593
|
const reduceMotion = (0, import_react_native_reanimated.useReducedMotion)();
|
|
570
594
|
const onFrame = (0, import_react2.useCallback)(
|
|
571
595
|
(p) => {
|
|
572
|
-
|
|
596
|
+
applyFrame(p);
|
|
573
597
|
},
|
|
574
|
-
[
|
|
598
|
+
[applyFrame]
|
|
575
599
|
);
|
|
576
600
|
const { handle, picture, volumeCollapseRef } = useChartCore(
|
|
577
601
|
candles,
|
|
578
|
-
{ width, height },
|
|
602
|
+
{ width, height, pxRatio: import_react_native2.PixelRatio.get() },
|
|
579
603
|
visibleRange,
|
|
580
604
|
defaultCandleWidth,
|
|
581
605
|
chartType,
|
|
@@ -604,11 +628,11 @@ function VroomChart(props) {
|
|
|
604
628
|
animRaf.current = null;
|
|
605
629
|
if (!handle) return;
|
|
606
630
|
const next = handle.render();
|
|
607
|
-
if (next)
|
|
631
|
+
if (next) applyFrame(next);
|
|
608
632
|
if (handle.isAnimating()) {
|
|
609
633
|
animRaf.current = requestAnimationFrame(animTick);
|
|
610
634
|
}
|
|
611
|
-
}, [handle,
|
|
635
|
+
}, [handle, applyFrame]);
|
|
612
636
|
const maybeStartAnim = (0, import_react2.useCallback)(() => {
|
|
613
637
|
if (animRaf.current != null) return;
|
|
614
638
|
if (!handle?.isAnimating()) return;
|
|
@@ -623,9 +647,9 @@ function VroomChart(props) {
|
|
|
623
647
|
};
|
|
624
648
|
}, []);
|
|
625
649
|
(0, import_react2.useEffect)(() => {
|
|
626
|
-
if (picture)
|
|
650
|
+
if (picture) applyFrame(picture);
|
|
627
651
|
maybeStartAnim();
|
|
628
|
-
}, [picture,
|
|
652
|
+
}, [picture, applyFrame, maybeStartAnim]);
|
|
629
653
|
const morphRaf = (0, import_react2.useRef)(null);
|
|
630
654
|
const morphFade = (0, import_react2.useRef)(null);
|
|
631
655
|
const morphHandle = (0, import_react2.useRef)(null);
|
|
@@ -639,7 +663,7 @@ function VroomChart(props) {
|
|
|
639
663
|
morphFade.current = target;
|
|
640
664
|
handle.setChartType(target);
|
|
641
665
|
const p = handle.render();
|
|
642
|
-
if (p)
|
|
666
|
+
if (p) applyFrame(p);
|
|
643
667
|
maybeStartAnim();
|
|
644
668
|
return void 0;
|
|
645
669
|
}
|
|
@@ -656,7 +680,7 @@ function VroomChart(props) {
|
|
|
656
680
|
morphFade.current = target;
|
|
657
681
|
handle.setChartType(target);
|
|
658
682
|
const p = handle.render();
|
|
659
|
-
if (p)
|
|
683
|
+
if (p) applyFrame(p);
|
|
660
684
|
maybeStartAnim();
|
|
661
685
|
return void 0;
|
|
662
686
|
}
|
|
@@ -669,7 +693,7 @@ function VroomChart(props) {
|
|
|
669
693
|
morphFade.current = fade;
|
|
670
694
|
handle.setMorph(reduceMotion ? 0 : fade, fade);
|
|
671
695
|
const p = handle.render();
|
|
672
|
-
if (p)
|
|
696
|
+
if (p) applyFrame(p);
|
|
673
697
|
if (prog < 1) {
|
|
674
698
|
morphRaf.current = requestAnimationFrame(step);
|
|
675
699
|
} else {
|
|
@@ -677,7 +701,7 @@ function VroomChart(props) {
|
|
|
677
701
|
morphFade.current = target;
|
|
678
702
|
handle.setChartType(target);
|
|
679
703
|
const q = handle.render();
|
|
680
|
-
if (q)
|
|
704
|
+
if (q) applyFrame(q);
|
|
681
705
|
maybeStartAnim();
|
|
682
706
|
}
|
|
683
707
|
};
|
|
@@ -688,7 +712,7 @@ function VroomChart(props) {
|
|
|
688
712
|
morphRaf.current = null;
|
|
689
713
|
}
|
|
690
714
|
};
|
|
691
|
-
}, [handle, chartType, transitionMs, reduceMotion,
|
|
715
|
+
}, [handle, chartType, transitionMs, reduceMotion, applyFrame, maybeStartAnim]);
|
|
692
716
|
const volumeRaf = (0, import_react2.useRef)(null);
|
|
693
717
|
const volumeHandle = (0, import_react2.useRef)(null);
|
|
694
718
|
(0, import_react2.useEffect)(() => {
|
|
@@ -714,7 +738,7 @@ function VroomChart(props) {
|
|
|
714
738
|
volumeCollapseRef.current = { t: target, easing };
|
|
715
739
|
handle.setVolumeCollapse(target, easing);
|
|
716
740
|
const p = handle.render();
|
|
717
|
-
if (p)
|
|
741
|
+
if (p) applyFrame(p);
|
|
718
742
|
maybeStartAnim();
|
|
719
743
|
return void 0;
|
|
720
744
|
}
|
|
@@ -728,7 +752,7 @@ function VroomChart(props) {
|
|
|
728
752
|
volumeCollapseRef.current = { t, easing: kind };
|
|
729
753
|
handle.setVolumeCollapse(t, kind);
|
|
730
754
|
const p = handle.render();
|
|
731
|
-
if (p)
|
|
755
|
+
if (p) applyFrame(p);
|
|
732
756
|
if (prog < 1) {
|
|
733
757
|
volumeRaf.current = requestAnimationFrame(step);
|
|
734
758
|
} else {
|
|
@@ -748,7 +772,7 @@ function VroomChart(props) {
|
|
|
748
772
|
volume?.enabled,
|
|
749
773
|
transitionMs,
|
|
750
774
|
reduceMotion,
|
|
751
|
-
|
|
775
|
+
applyFrame,
|
|
752
776
|
volumeCollapseRef,
|
|
753
777
|
maybeStartAnim
|
|
754
778
|
]);
|
|
@@ -766,7 +790,7 @@ function VroomChart(props) {
|
|
|
766
790
|
axisCollapse.current = { y: targetY, x: targetX };
|
|
767
791
|
handle.setAxisCollapse(targetY, targetX);
|
|
768
792
|
const p = handle.render();
|
|
769
|
-
if (p)
|
|
793
|
+
if (p) applyFrame(p);
|
|
770
794
|
maybeStartAnim();
|
|
771
795
|
return void 0;
|
|
772
796
|
}
|
|
@@ -785,7 +809,7 @@ function VroomChart(props) {
|
|
|
785
809
|
axisCollapse.current = { y: targetY, x: targetX };
|
|
786
810
|
handle.setAxisCollapse(targetY, targetX);
|
|
787
811
|
const p = handle.render();
|
|
788
|
-
if (p)
|
|
812
|
+
if (p) applyFrame(p);
|
|
789
813
|
maybeStartAnim();
|
|
790
814
|
return void 0;
|
|
791
815
|
}
|
|
@@ -799,7 +823,7 @@ function VroomChart(props) {
|
|
|
799
823
|
axisCollapse.current = { y, x };
|
|
800
824
|
handle.setAxisCollapse(y, x);
|
|
801
825
|
const p = handle.render();
|
|
802
|
-
if (p)
|
|
826
|
+
if (p) applyFrame(p);
|
|
803
827
|
if (prog < 1) {
|
|
804
828
|
axisRaf.current = requestAnimationFrame(step);
|
|
805
829
|
} else {
|
|
@@ -820,7 +844,7 @@ function VroomChart(props) {
|
|
|
820
844
|
showXAxis,
|
|
821
845
|
transitionMs,
|
|
822
846
|
reduceMotion,
|
|
823
|
-
|
|
847
|
+
applyFrame,
|
|
824
848
|
maybeStartAnim
|
|
825
849
|
]);
|
|
826
850
|
const hitAxis = (0, import_react2.useCallback)(
|
|
@@ -860,7 +884,7 @@ function VroomChart(props) {
|
|
|
860
884
|
priceDrag.current = { index: pl.index, id: pl.line.id, price: pl.line.price };
|
|
861
885
|
handle.setPriceLineDrag(pl.index, pl.line.price);
|
|
862
886
|
const p = handle.render();
|
|
863
|
-
if (p)
|
|
887
|
+
if (p) applyFrame(p);
|
|
864
888
|
}
|
|
865
889
|
}
|
|
866
890
|
}).onChange((e) => {
|
|
@@ -883,7 +907,7 @@ function VroomChart(props) {
|
|
|
883
907
|
next = handle.render();
|
|
884
908
|
} else if (crosshairActive.current) {
|
|
885
909
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
886
|
-
if (ch)
|
|
910
|
+
if (ch) applyFrame(ch);
|
|
887
911
|
const info = handle.getCrosshairInfo();
|
|
888
912
|
const t = info?.timeMs ?? null;
|
|
889
913
|
if (t !== lastCrosshairTime.current) {
|
|
@@ -894,7 +918,7 @@ function VroomChart(props) {
|
|
|
894
918
|
} else {
|
|
895
919
|
next = handle.translate(e.changeX, e.changeY);
|
|
896
920
|
}
|
|
897
|
-
if (next)
|
|
921
|
+
if (next) applyFrame(next);
|
|
898
922
|
maybeStartAnim();
|
|
899
923
|
}).onEnd((e) => {
|
|
900
924
|
if (!handle) return;
|
|
@@ -903,7 +927,7 @@ function VroomChart(props) {
|
|
|
903
927
|
priceDrag.current = null;
|
|
904
928
|
handle.setPriceLineDrag(-1, 0);
|
|
905
929
|
const p = handle.render();
|
|
906
|
-
if (p)
|
|
930
|
+
if (p) applyFrame(p);
|
|
907
931
|
if (g) onPriceLineDragEnd?.(g.id, g.price);
|
|
908
932
|
return;
|
|
909
933
|
}
|
|
@@ -923,7 +947,7 @@ function VroomChart(props) {
|
|
|
923
947
|
velocity *= Math.pow(0.5, dt / HALF_LIFE_S);
|
|
924
948
|
const dx = velocity * dt;
|
|
925
949
|
const next = handle.pan(dx, 0);
|
|
926
|
-
if (next)
|
|
950
|
+
if (next) applyFrame(next);
|
|
927
951
|
maybeStartAnim();
|
|
928
952
|
if (Math.abs(velocity) > MIN_STOP) {
|
|
929
953
|
decayRaf.current = requestAnimationFrame(tick);
|
|
@@ -977,7 +1001,7 @@ function VroomChart(props) {
|
|
|
977
1001
|
}
|
|
978
1002
|
if (frameX === 1 && frameY === 1) return;
|
|
979
1003
|
const next = handle.zoom(frameX, frameY, focalX, focalY);
|
|
980
|
-
if (next)
|
|
1004
|
+
if (next) applyFrame(next);
|
|
981
1005
|
maybeStartAnim();
|
|
982
1006
|
});
|
|
983
1007
|
const longPress = import_react_native_gesture_handler.Gesture.LongPress().runOnJS(true).onStart((e) => {
|
|
@@ -987,7 +1011,7 @@ function VroomChart(props) {
|
|
|
987
1011
|
cancelDecay();
|
|
988
1012
|
crosshairActive.current = true;
|
|
989
1013
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
990
|
-
if (ch)
|
|
1014
|
+
if (ch) applyFrame(ch);
|
|
991
1015
|
const info = handle.getCrosshairInfo();
|
|
992
1016
|
lastCrosshairTime.current = info?.timeMs ?? null;
|
|
993
1017
|
onCrosshair?.({
|
|
@@ -1010,7 +1034,7 @@ function VroomChart(props) {
|
|
|
1010
1034
|
if (hitAxis(e.x, e.y) !== "chart") return;
|
|
1011
1035
|
crosshairActive.current = false;
|
|
1012
1036
|
const ch = handle.clearCrosshair();
|
|
1013
|
-
if (ch)
|
|
1037
|
+
if (ch) applyFrame(ch);
|
|
1014
1038
|
lastCrosshairTime.current = null;
|
|
1015
1039
|
onCrosshair?.({ active: false, candle: null, timeMs: null, price: null, reason: "hide" });
|
|
1016
1040
|
});
|
|
@@ -1025,11 +1049,17 @@ function VroomChart(props) {
|
|
|
1025
1049
|
style
|
|
1026
1050
|
]
|
|
1027
1051
|
},
|
|
1028
|
-
/* @__PURE__ */ import_react2.default.createElement(import_react_native_gesture_handler.GestureDetector, { gesture }, /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, { style: { flex: 1 } }, /* @__PURE__ */ import_react2.default.createElement(import_react_native_skia.Canvas, { style: { flex: 1 } }, width > 0 && height > 0 ? (
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1052
|
+
/* @__PURE__ */ import_react2.default.createElement(import_react_native_gesture_handler.GestureDetector, { gesture }, /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, { style: { flex: 1 } }, /* @__PURE__ */ import_react2.default.createElement(import_react_native_skia.Canvas, { style: { flex: 1 } }, width > 0 && height > 0 ? /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement(import_react_native_skia.Picture, { picture: pictureSV }), /* @__PURE__ */ import_react2.default.createElement(
|
|
1053
|
+
import_react_native_skia.Image,
|
|
1054
|
+
{
|
|
1055
|
+
image: imageSV,
|
|
1056
|
+
x: 0,
|
|
1057
|
+
y: 0,
|
|
1058
|
+
width,
|
|
1059
|
+
height,
|
|
1060
|
+
fit: "fill"
|
|
1061
|
+
}
|
|
1062
|
+
)) : null)))
|
|
1033
1063
|
);
|
|
1034
1064
|
}
|
|
1035
1065
|
// Annotate the CommonJS export names for ESM import in node:
|