react-native-vroom-chart 0.13.1 → 0.15.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.
@@ -42,7 +42,7 @@ ChartHostObject::~ChartHostObject() {
42
42
  std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
43
43
  jsi::Runtime& rt) {
44
44
  std::vector<jsi::PropNameID> out;
45
- out.reserve(39);
45
+ out.reserve(42);
46
46
  out.push_back(jsi::PropNameID::forAscii(rt, "setCandles"));
47
47
  out.push_back(jsi::PropNameID::forAscii(rt, "setSize"));
48
48
  out.push_back(jsi::PropNameID::forAscii(rt, "setColor"));
@@ -82,6 +82,9 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
82
82
  out.push_back(jsi::PropNameID::forAscii(rt, "hitTestPriceLine"));
83
83
  out.push_back(jsi::PropNameID::forAscii(rt, "setPriceLineHover"));
84
84
  out.push_back(jsi::PropNameID::forAscii(rt, "setPriceLineDrag"));
85
+ out.push_back(jsi::PropNameID::forAscii(rt, "setFootprints"));
86
+ out.push_back(jsi::PropNameID::forAscii(rt, "hitTestFootprint"));
87
+ out.push_back(jsi::PropNameID::forAscii(rt, "setFootprintHover"));
85
88
  out.push_back(jsi::PropNameID::forAscii(rt, "render"));
86
89
  return out;
87
90
  }
@@ -484,17 +487,25 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
484
487
  }
485
488
 
486
489
  if (name == "beginIntervalMorph") {
487
- // beginIntervalMorph() — capture the visible candle geometry so the next
488
- // setCandles can be animated as a reshape. Call before setCandles.
490
+ // beginIntervalMorph(mode?) — capture the visible candle geometry so the
491
+ // next setCandles can animate. Optional string `'fade'` or `'transform'`
492
+ // (default). Call before setCandles.
489
493
  return jsi::Function::createFromHostFunction(
490
494
  rt,
491
495
  jsi::PropNameID::forAscii(rt, "beginIntervalMorph"),
492
- 0,
493
- [this](jsi::Runtime& /*rt2*/,
496
+ 1,
497
+ [this](jsi::Runtime& rt2,
494
498
  const jsi::Value& /*thisVal*/,
495
- const jsi::Value* /*args*/,
496
- size_t /*count*/) -> jsi::Value {
497
- vroom_chart_begin_interval_morph(chart_);
499
+ const jsi::Value* args,
500
+ size_t count) -> jsi::Value {
501
+ int32_t mode = 0;
502
+ if (count >= 1 && args[0].isString()) {
503
+ const auto s = args[0].asString(rt2).utf8(rt2);
504
+ mode = s == "fade" ? 1 : 0;
505
+ } else if (count >= 1 && args[0].isNumber()) {
506
+ mode = args[0].asNumber() != 0 ? 1 : 0;
507
+ }
508
+ vroom_chart_begin_interval_morph(chart_, mode);
498
509
  return jsi::Value::undefined();
499
510
  });
500
511
  }
@@ -1181,6 +1192,120 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
1181
1192
  });
1182
1193
  }
1183
1194
 
1195
+ if (name == "setFootprints") {
1196
+ // setFootprints({ prints: [{ timeMs, side }, ...], radiusPx, gapPx, marginPx,
1197
+ // hoverBoost }) — replaces the full set of executed-trade badges. The core
1198
+ // buckets them onto candles itself, so raw fill times are what to pass. No
1199
+ // render; the next render() picks it up.
1200
+ return jsi::Function::createFromHostFunction(
1201
+ rt,
1202
+ jsi::PropNameID::forAscii(rt, "setFootprints"),
1203
+ 1,
1204
+ [this](jsi::Runtime& rt2,
1205
+ const jsi::Value& /*thisVal*/,
1206
+ const jsi::Value* args,
1207
+ size_t count) -> jsi::Value {
1208
+ if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
1209
+ auto cfg = args[0].asObject(rt2);
1210
+ auto prints_val = cfg.getProperty(rt2, "prints");
1211
+ if (!prints_val.isObject()) return jsi::Value::undefined();
1212
+ auto prints_obj = prints_val.asObject(rt2);
1213
+ if (!prints_obj.isArray(rt2)) return jsi::Value::undefined();
1214
+ auto arr = prints_obj.asArray(rt2);
1215
+ const size_t len = arr.size(rt2);
1216
+ std::vector<VroomFootprint> prints(len);
1217
+ for (size_t i = 0; i < len; ++i) {
1218
+ auto f = arr.getValueAtIndex(rt2, i).asObject(rt2);
1219
+ prints[i].time_ms =
1220
+ static_cast<int64_t>(f.getProperty(rt2, "timeMs").asNumber());
1221
+ prints[i].side =
1222
+ static_cast<int32_t>(f.getProperty(rt2, "side").asNumber());
1223
+ }
1224
+ VroomFootprintStyle style{};
1225
+ style.radius_px = static_cast<float>(
1226
+ cfg.getProperty(rt2, "radiusPx").asNumber());
1227
+ style.gap_px = static_cast<float>(
1228
+ cfg.getProperty(rt2, "gapPx").asNumber());
1229
+ style.margin_px = static_cast<float>(
1230
+ cfg.getProperty(rt2, "marginPx").asNumber());
1231
+ style.hover_boost = static_cast<float>(
1232
+ cfg.getProperty(rt2, "hoverBoost").asNumber());
1233
+ vroom_chart_set_footprints(chart_, prints.data(), prints.size(), &style);
1234
+ return jsi::Value::undefined();
1235
+ });
1236
+ }
1237
+
1238
+ if (name == "hitTestFootprint") {
1239
+ // hitTestFootprint(x, y) -> { side, candleTimeMs, x, y, radius, pane,
1240
+ // indices } | null. `indices` addresses the array last passed to
1241
+ // setFootprints and covers *both* sides of that candle, so one call fills a
1242
+ // tooltip; `pane` is the plot rect, for deciding which side of the badge that
1243
+ // tooltip fits on. No rendering.
1244
+ return jsi::Function::createFromHostFunction(
1245
+ rt,
1246
+ jsi::PropNameID::forAscii(rt, "hitTestFootprint"),
1247
+ 2,
1248
+ [this](jsi::Runtime& rt2,
1249
+ const jsi::Value& /*thisVal*/,
1250
+ const jsi::Value* args,
1251
+ size_t count) -> jsi::Value {
1252
+ if (count < 2) return jsi::Value::null();
1253
+ VroomFootprintHit hit{};
1254
+ if (!vroom_chart_hit_test_footprint(
1255
+ chart_, static_cast<float>(args[0].asNumber()),
1256
+ static_cast<float>(args[1].asNumber()), &hit)) {
1257
+ return jsi::Value::null();
1258
+ }
1259
+
1260
+ const int32_t n =
1261
+ vroom_chart_footprints_at(chart_, hit.candle_time_ms, nullptr, 0);
1262
+ std::vector<int32_t> idx(static_cast<size_t>(n > 0 ? n : 0));
1263
+ if (n > 0) {
1264
+ vroom_chart_footprints_at(chart_, hit.candle_time_ms, idx.data(), n);
1265
+ }
1266
+ jsi::Array indices(rt2, idx.size());
1267
+ for (size_t i = 0; i < idx.size(); ++i) {
1268
+ indices.setValueAtIndex(rt2, i, jsi::Value(idx[i]));
1269
+ }
1270
+
1271
+ jsi::Object pane(rt2);
1272
+ pane.setProperty(rt2, "left", hit.pane_left);
1273
+ pane.setProperty(rt2, "top", hit.pane_top);
1274
+ pane.setProperty(rt2, "right", hit.pane_right);
1275
+ pane.setProperty(rt2, "bottom", hit.pane_bottom);
1276
+
1277
+ jsi::Object obj(rt2);
1278
+ obj.setProperty(rt2, "side", hit.side);
1279
+ obj.setProperty(rt2, "candleTimeMs",
1280
+ static_cast<double>(hit.candle_time_ms));
1281
+ obj.setProperty(rt2, "x", hit.center_x);
1282
+ obj.setProperty(rt2, "y", hit.center_y);
1283
+ obj.setProperty(rt2, "radius", hit.radius);
1284
+ obj.setProperty(rt2, "pane", std::move(pane));
1285
+ obj.setProperty(rt2, "indices", std::move(indices));
1286
+ return obj;
1287
+ });
1288
+ }
1289
+
1290
+ if (name == "setFootprintHover") {
1291
+ // setFootprintHover(candleTimeMs, side) — highlight one badge; side -1
1292
+ // clears. Touch has no hover, so on RN this tracks the tapped badge.
1293
+ return jsi::Function::createFromHostFunction(
1294
+ rt,
1295
+ jsi::PropNameID::forAscii(rt, "setFootprintHover"),
1296
+ 2,
1297
+ [this](jsi::Runtime& /*rt2*/,
1298
+ const jsi::Value& /*thisVal*/,
1299
+ const jsi::Value* args,
1300
+ size_t count) -> jsi::Value {
1301
+ if (count < 2) return jsi::Value::undefined();
1302
+ vroom_chart_set_footprint_hover(
1303
+ chart_, static_cast<int64_t>(args[0].asNumber()),
1304
+ static_cast<int32_t>(args[1].asNumber()));
1305
+ return jsi::Value::undefined();
1306
+ });
1307
+ }
1308
+
1184
1309
  if (name == "render") {
1185
1310
  return jsi::Function::createFromHostFunction(
1186
1311
  rt,
@@ -257,6 +257,53 @@ typedef struct VroomPriceLineStyle {
257
257
  float hover_boost; // brightness multiplier for the hovered segment (1 = flat)
258
258
  } VroomPriceLineStyle;
259
259
 
260
+ // ---- Footprints (executed-trade badges) -----------------------------------
261
+
262
+ typedef enum {
263
+ VROOM_FOOTPRINT_BUY = 0, // position entry — "+" badge in the bull color
264
+ VROOM_FOOTPRINT_SELL = 1, // position exit — "-" badge in the bear color
265
+ } VroomFootprintSide;
266
+
267
+ // A single executed trade, drawn as a circular badge above the candle it fell in.
268
+ //
269
+ // `time_ms` is the raw execution time, *not* a bar-open time: the core buckets
270
+ // each footprint into whichever candle's window contains it, so one array renders
271
+ // correctly at every interval and re-groups by itself when the candles change.
272
+ // Order is irrelevant — the core sorts within each bucket.
273
+ //
274
+ // Only the fields the renderer needs live here; a host's per-trade payload (id,
275
+ // price, size) stays on the host side and is rejoined via the indices reported by
276
+ // vroom_chart_footprints_at.
277
+ typedef struct VroomFootprint {
278
+ int64_t time_ms;
279
+ int32_t side; // VroomFootprintSide
280
+ } VroomFootprint;
281
+
282
+ // Layout/style shared by every footprint badge.
283
+ typedef struct VroomFootprintStyle {
284
+ float radius_px; // badge radius; <= 0 falls back to 9
285
+ float gap_px; // vertical gap between the two stacked badges; < 0 => 4
286
+ float margin_px; // gap between the candle's high and the first badge; < 0 => 8
287
+ float hover_boost; // brightness multiplier for the hovered badge (1 = flat)
288
+ } VroomFootprintStyle;
289
+
290
+ // The badge under a pixel, as reported by vroom_chart_hit_test_footprint.
291
+ typedef struct VroomFootprintHit {
292
+ int64_t candle_time_ms; // bar-open time of the candle the badge sits on
293
+ int32_t side; // VroomFootprintSide — which of the candle's two badges
294
+ float center_x; // badge center, in the same px space as the hit test
295
+ float center_y;
296
+ float radius;
297
+ // The plot rect the badge was clipped to — same px space, axis strips
298
+ // excluded. Reported because a host placing its own tooltip has no other way
299
+ // to know it: the surface it measures includes the price and time axes, so
300
+ // sizing against that overstates the room next to a badge near the edge.
301
+ float pane_left;
302
+ float pane_top;
303
+ float pane_right;
304
+ float pane_bottom;
305
+ } VroomFootprintHit;
306
+
260
307
  // A continuous data coordinate at a pixel position (no candle snapping). Used to
261
308
  // translate a drawing-tool click into a data-space anchor.
262
309
  typedef struct VroomCoord {
@@ -420,14 +467,13 @@ void vroom_chart_preserve_price_envelope(VroomChart* chart,
420
467
  double prev_low, double prev_high);
421
468
 
422
469
  // Captures the currently visible candle geometry so the next data swap can be
423
- // animated as a reshape rather than a jump: each candle's wick and body slide
424
- // and stretch into the shape of its counterpart in the new data.
470
+ // animated. `mode` 0 (transform) lerps each visible column into its counterpart,
471
+ // paired by slot counting back from the right edge. `mode` 1 (fade) fades the
472
+ // capture out then the new scene in on the same envelope the axes already use.
425
473
  //
426
- // Candles are paired by *slot* position counting back from the right edge of
427
- // the visible window, which a timeframe switch preserves. Call before
428
- // set_candles, then drive vroom_chart_set_interval_morph from 0 to 1.
429
- // No-op when nothing is visible.
430
- void vroom_chart_begin_interval_morph(VroomChart* chart);
474
+ // Call before set_candles, then drive vroom_chart_set_interval_morph from 0
475
+ // to 1. No-op when nothing is visible.
476
+ void vroom_chart_begin_interval_morph(VroomChart* chart, int32_t mode);
431
477
 
432
478
  // Advances the interval morph started by vroom_chart_begin_interval_morph. `t`
433
479
  // (clamped to 0..1) is the eased progress: 0 renders the captured geometry
@@ -690,6 +736,43 @@ void vroom_chart_set_price_line_hover(VroomChart* chart, int32_t index,
690
736
  void vroom_chart_set_price_line_drag(VroomChart* chart, int32_t index,
691
737
  double price);
692
738
 
739
+ // Replaces the full set of footprints and their shared style. Pass count 0 to
740
+ // clear; `style` may be null when count is 0.
741
+ //
742
+ // Footprints are grouped onto candles by timestamp, so at most two badges render
743
+ // per candle: one for that bar's buys and one for its sells, however many trades
744
+ // went into each. When a candle has both, they stack upward with `gap_px` between
745
+ // them, the side whose latest trade came first sitting nearest the bar. Badges sit
746
+ // above the candle's high, clipped to the price pane, and re-group on their own
747
+ // whenever the candles change (a new interval, more history) — the host never
748
+ // re-buckets anything.
749
+ void vroom_chart_set_footprints(VroomChart* chart, const VroomFootprint* prints,
750
+ size_t count,
751
+ const VroomFootprintStyle* style);
752
+
753
+ // Hit-tests pixel (x_px, y_px) against the footprint badges. On a hit fills *out
754
+ // with the badge's candle, side and pixel geometry and returns true; returns false
755
+ // on a miss (out untouched). `out` may be null to test without reading the hit.
756
+ // When two badges overlap the nearest center wins.
757
+ bool vroom_chart_hit_test_footprint(VroomChart* chart, float x_px, float y_px,
758
+ VroomFootprintHit* out);
759
+
760
+ // Fills `out_indices` with the indices — into the array last passed to
761
+ // vroom_chart_set_footprints — of every footprint bucketed into the candle opening
762
+ // at `candle_time_ms`, *both* sides, ascending by time. Returns the total count,
763
+ // which may exceed `max` (only the first `max` are written), or 0 when that candle
764
+ // has no footprints. Pass a null `out_indices` with max 0 to size the buffer first.
765
+ //
766
+ // This is how a host rejoins a hit badge to its own trade objects: hit-test to get
767
+ // the candle, then map these indices back through the array it supplied.
768
+ int32_t vroom_chart_footprints_at(VroomChart* chart, int64_t candle_time_ms,
769
+ int32_t* out_indices, int32_t max);
770
+
771
+ // Marks one footprint badge as hovered so it renders highlighted. `side` -1
772
+ // clears the hover; `candle_time_ms` and `side` match vroom_chart_hit_test_footprint.
773
+ void vroom_chart_set_footprint_hover(VroomChart* chart, int64_t candle_time_ms,
774
+ int32_t side);
775
+
693
776
  // Sets the transient in-progress "draft" the drawing tool shows while the user
694
777
  // places points. Node A is always shown; when `has_b`, node B is shown too.
695
778
  // `guide != 0` also draws the live preview (a guideline for a line, or a preview
@@ -28,11 +28,13 @@ namespace vroom::candles {
28
28
  // body/wick heights and width shrink to the line). `opacity` (0..1) fades the
29
29
  // whole candle layer out as the line fades in. Both default to a no-op.
30
30
  //
31
- // `from` / `from_n` is the outgoing geometry of an interval morph, indexed from
32
- // the right of the visible slice (slot 0 = newest), and `morph_t` (0..1) is the
33
- // eased progress toward `visible`. Each slot's wick and body interpolate between
34
- // the two; slots present on only one side fade in or out. `morph_t == 1` (the
35
- // default) draws `visible` alone.
31
+ // `from` / `from_n` is the outgoing geometry of an interval *transform*, indexed
32
+ // from the right of the visible slice (slot 0 = newest), and `morph_t` (0..1)
33
+ // is the eased progress toward `visible`. Each slot's wick and body interpolate
34
+ // between the two; slots present on only one side fade in or out. A host-chosen
35
+ // fade (see interval_morph_fade) skips this lerp: the chart draws the capture
36
+ // then the new scene via interval_phase instead. `morph_t == 1` (the default)
37
+ // draws `visible` alone.
36
38
  void draw(SkCanvas* canvas,
37
39
  const ::VroomCandle* visible,
38
40
  std::size_t n,