react-native-vroom-chart 0.7.0 → 0.8.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/cpp/VroomChartHostObject.cpp +131 -1
- package/cpp/_core_include/vroom/vroom_chart.h +51 -14
- package/cpp/_core_src/chart.h +14 -9
- package/cpp/_core_src/chart_facade.cpp +41 -3
- package/cpp/_core_src/drawings.cpp +147 -4
- package/cpp/_core_src/drawings.h +10 -5
- package/lib/index.d.mts +46 -4
- package/lib/index.d.ts +46 -4
- package/lib/index.js +196 -41
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +201 -49
- package/lib/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/VroomChart.tsx +33 -16
- package/src/dataTransitions.ts +148 -0
- package/src/index.ts +6 -0
- package/src/jsi.d.ts +51 -0
- package/src/useChartCore.ts +171 -4
|
@@ -29,7 +29,7 @@ ChartHostObject::~ChartHostObject() {
|
|
|
29
29
|
std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
30
30
|
jsi::Runtime& rt) {
|
|
31
31
|
std::vector<jsi::PropNameID> out;
|
|
32
|
-
out.reserve(
|
|
32
|
+
out.reserve(39);
|
|
33
33
|
out.push_back(jsi::PropNameID::forAscii(rt, "setCandles"));
|
|
34
34
|
out.push_back(jsi::PropNameID::forAscii(rt, "setSize"));
|
|
35
35
|
out.push_back(jsi::PropNameID::forAscii(rt, "setColor"));
|
|
@@ -38,6 +38,13 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
|
38
38
|
out.push_back(jsi::PropNameID::forAscii(rt, "setDefaultCandleWidth"));
|
|
39
39
|
out.push_back(jsi::PropNameID::forAscii(rt, "setChartType"));
|
|
40
40
|
out.push_back(jsi::PropNameID::forAscii(rt, "setMorph"));
|
|
41
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "getVisibleRange"));
|
|
42
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "resetView"));
|
|
43
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "resetPriceScale"));
|
|
44
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "getVisiblePriceEnvelope"));
|
|
45
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "preservePriceEnvelope"));
|
|
46
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "beginIntervalMorph"));
|
|
47
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setIntervalMorph"));
|
|
41
48
|
out.push_back(jsi::PropNameID::forAscii(rt, "pan"));
|
|
42
49
|
out.push_back(jsi::PropNameID::forAscii(rt, "translate"));
|
|
43
50
|
out.push_back(jsi::PropNameID::forAscii(rt, "zoom"));
|
|
@@ -297,6 +304,129 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
297
304
|
});
|
|
298
305
|
}
|
|
299
306
|
|
|
307
|
+
if (name == "getVisibleRange") {
|
|
308
|
+
// getVisibleRange() -> { startMs, endMs }. Both 0 while uninitialized.
|
|
309
|
+
return jsi::Function::createFromHostFunction(
|
|
310
|
+
rt,
|
|
311
|
+
jsi::PropNameID::forAscii(rt, "getVisibleRange"),
|
|
312
|
+
0,
|
|
313
|
+
[this](jsi::Runtime& rt2,
|
|
314
|
+
const jsi::Value& /*thisVal*/,
|
|
315
|
+
const jsi::Value* /*args*/,
|
|
316
|
+
size_t /*count*/) -> jsi::Value {
|
|
317
|
+
int64_t s = 0, e = 0;
|
|
318
|
+
vroom_chart_get_visible_range(chart_, &s, &e);
|
|
319
|
+
jsi::Object obj(rt2);
|
|
320
|
+
obj.setProperty(rt2, "startMs", static_cast<double>(s));
|
|
321
|
+
obj.setProperty(rt2, "endMs", static_cast<double>(e));
|
|
322
|
+
return obj;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (name == "resetView") {
|
|
327
|
+
// resetView() — reframe the most recent candles and re-enable y auto-fit.
|
|
328
|
+
return jsi::Function::createFromHostFunction(
|
|
329
|
+
rt,
|
|
330
|
+
jsi::PropNameID::forAscii(rt, "resetView"),
|
|
331
|
+
0,
|
|
332
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
333
|
+
const jsi::Value& /*thisVal*/,
|
|
334
|
+
const jsi::Value* /*args*/,
|
|
335
|
+
size_t /*count*/) -> jsi::Value {
|
|
336
|
+
vroom_chart_reset_view(chart_);
|
|
337
|
+
return jsi::Value::undefined();
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (name == "resetPriceScale") {
|
|
342
|
+
// resetPriceScale() — re-enable y auto-fit, leaving the time window alone.
|
|
343
|
+
return jsi::Function::createFromHostFunction(
|
|
344
|
+
rt,
|
|
345
|
+
jsi::PropNameID::forAscii(rt, "resetPriceScale"),
|
|
346
|
+
0,
|
|
347
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
348
|
+
const jsi::Value& /*thisVal*/,
|
|
349
|
+
const jsi::Value* /*args*/,
|
|
350
|
+
size_t /*count*/) -> jsi::Value {
|
|
351
|
+
vroom_chart_reset_price_scale(chart_);
|
|
352
|
+
return jsi::Value::undefined();
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (name == "getVisiblePriceEnvelope") {
|
|
357
|
+
// getVisiblePriceEnvelope() -> { low, high } | null. The extent the visible
|
|
358
|
+
// candles occupy, not the (wider) axis range. No rendering.
|
|
359
|
+
return jsi::Function::createFromHostFunction(
|
|
360
|
+
rt,
|
|
361
|
+
jsi::PropNameID::forAscii(rt, "getVisiblePriceEnvelope"),
|
|
362
|
+
0,
|
|
363
|
+
[this](jsi::Runtime& rt2,
|
|
364
|
+
const jsi::Value& /*thisVal*/,
|
|
365
|
+
const jsi::Value* /*args*/,
|
|
366
|
+
size_t /*count*/) -> jsi::Value {
|
|
367
|
+
double low = 0.0, high = 0.0;
|
|
368
|
+
if (!vroom_chart_get_visible_price_envelope(chart_, &low, &high)) {
|
|
369
|
+
return jsi::Value::null();
|
|
370
|
+
}
|
|
371
|
+
jsi::Object obj(rt2);
|
|
372
|
+
obj.setProperty(rt2, "low", low);
|
|
373
|
+
obj.setProperty(rt2, "high", high);
|
|
374
|
+
return obj;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (name == "preservePriceEnvelope") {
|
|
379
|
+
// preservePriceEnvelope(prevLow, prevHigh) — scale-lock a manual price
|
|
380
|
+
// range across a timeframe switch. No-op in auto-y mode.
|
|
381
|
+
return jsi::Function::createFromHostFunction(
|
|
382
|
+
rt,
|
|
383
|
+
jsi::PropNameID::forAscii(rt, "preservePriceEnvelope"),
|
|
384
|
+
2,
|
|
385
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
386
|
+
const jsi::Value& /*thisVal*/,
|
|
387
|
+
const jsi::Value* args,
|
|
388
|
+
size_t count) -> jsi::Value {
|
|
389
|
+
if (count < 2) return jsi::Value::undefined();
|
|
390
|
+
vroom_chart_preserve_price_envelope(chart_, args[0].asNumber(),
|
|
391
|
+
args[1].asNumber());
|
|
392
|
+
return jsi::Value::undefined();
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (name == "beginIntervalMorph") {
|
|
397
|
+
// beginIntervalMorph() — capture the visible candle geometry so the next
|
|
398
|
+
// setCandles can be animated as a reshape. Call before setCandles.
|
|
399
|
+
return jsi::Function::createFromHostFunction(
|
|
400
|
+
rt,
|
|
401
|
+
jsi::PropNameID::forAscii(rt, "beginIntervalMorph"),
|
|
402
|
+
0,
|
|
403
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
404
|
+
const jsi::Value& /*thisVal*/,
|
|
405
|
+
const jsi::Value* /*args*/,
|
|
406
|
+
size_t /*count*/) -> jsi::Value {
|
|
407
|
+
vroom_chart_begin_interval_morph(chart_);
|
|
408
|
+
return jsi::Value::undefined();
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (name == "setIntervalMorph") {
|
|
413
|
+
// setIntervalMorph(t) — advance the capture toward the new candles. `t` is
|
|
414
|
+
// pre-eased progress: 0 = the captured frame, 1 = settled (capture freed).
|
|
415
|
+
return jsi::Function::createFromHostFunction(
|
|
416
|
+
rt,
|
|
417
|
+
jsi::PropNameID::forAscii(rt, "setIntervalMorph"),
|
|
418
|
+
1,
|
|
419
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
420
|
+
const jsi::Value& /*thisVal*/,
|
|
421
|
+
const jsi::Value* args,
|
|
422
|
+
size_t count) -> jsi::Value {
|
|
423
|
+
if (count < 1) return jsi::Value::undefined();
|
|
424
|
+
vroom_chart_set_interval_morph(
|
|
425
|
+
chart_, static_cast<float>(args[0].asNumber()));
|
|
426
|
+
return jsi::Value::undefined();
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
300
430
|
if (name == "pan") {
|
|
301
431
|
// pan(dx, dy) -> JsiSkPicture
|
|
302
432
|
// Mutates the visible range and renders in one JSI call so gesture
|
|
@@ -172,17 +172,20 @@ typedef struct VroomDrawPoint {
|
|
|
172
172
|
// and `b`; the other two corners (a.x,b.y) and (b.x,a.y) derive.
|
|
173
173
|
// 2 = pencil — a freehand path through `points` (in draw order). `a`/`b` mirror
|
|
174
174
|
// the first/last point so bounds and handle code stay uniform.
|
|
175
|
+
// 3 = path — straight segments through `points` (in draw order), ending in
|
|
176
|
+
// an arrowhead on the last one. Same storage as a pencil, but
|
|
177
|
+
// rendered unsmoothed and with every vertex a grab handle.
|
|
175
178
|
//
|
|
176
|
-
// `points`/`point_count` are only read for
|
|
177
|
-
// null/0. Like the rest of this API the points are copied internally, so
|
|
178
|
-
// caller may free them as soon as the call returns.
|
|
179
|
+
// `points`/`point_count` are only read for kinds 2 and 3; line and box leave
|
|
180
|
+
// them null/0. Like the rest of this API the points are copied internally, so
|
|
181
|
+
// the caller may free them as soon as the call returns.
|
|
179
182
|
typedef struct VroomDrawing {
|
|
180
183
|
VroomDrawPoint a;
|
|
181
184
|
VroomDrawPoint b;
|
|
182
185
|
uint32_t color; // 0xAARRGGBB
|
|
183
186
|
float width; // stroke width in px
|
|
184
|
-
int32_t kind; // 0 = line, 1 = box, 2 = pencil
|
|
185
|
-
const VroomDrawPoint* points; // pencil
|
|
187
|
+
int32_t kind; // 0 = line, 1 = box, 2 = pencil, 3 = path
|
|
188
|
+
const VroomDrawPoint* points; // pencil/path points (kind 2/3), else null
|
|
186
189
|
int32_t point_count; // number of `points`, else 0
|
|
187
190
|
} VroomDrawing;
|
|
188
191
|
|
|
@@ -551,18 +554,31 @@ void vroom_chart_set_drawings(VroomChart* chart, const VroomDrawing* drawings,
|
|
|
551
554
|
size_t count);
|
|
552
555
|
|
|
553
556
|
// Hit-tests pixel (x_px, y_px) against the committed drawings. On a hit, fills
|
|
554
|
-
// *out_index with the drawing index, *out_part with
|
|
555
|
-
//
|
|
556
|
-
//
|
|
557
|
-
// reported for the currently selected drawing (whose handles are
|
|
558
|
-
// Returns false on a miss (out params untouched). Any out pointer may
|
|
557
|
+
// *out_index with the drawing index, *out_part with the sub-part that was hit,
|
|
558
|
+
// *out_t with the 0..1 grab position along the segment (A→B; 0/1 for handle
|
|
559
|
+
// hits, 0 for the shapes that have no meaningful t), and returns true. Handle
|
|
560
|
+
// hits are only reported for the currently selected drawing (whose handles are
|
|
561
|
+
// visible). Returns false on a miss (out params untouched). Any out pointer may
|
|
562
|
+
// be null.
|
|
563
|
+
//
|
|
564
|
+
// `out_part` is per-kind (see VroomDrawing.kind):
|
|
565
|
+
// line — 0 (endpoint A), 1 (endpoint B), 2 (body).
|
|
566
|
+
// box — 0..3 (corners, in the order a, (b.x,a.y), b, (a.x,b.y)), 4 (body).
|
|
567
|
+
// pencil — 5 (body; a stroke has no handles).
|
|
568
|
+
// path — 6 (body), or VROOM_DRAW_PART_VERTEX + i for vertex i. The offset
|
|
569
|
+
// keeps a vertex index from colliding with the small part numbers
|
|
570
|
+
// above, and is why a path is capped at VROOM_PATH_MAX_POINTS.
|
|
571
|
+
#define VROOM_DRAW_PART_VERTEX 100
|
|
572
|
+
#define VROOM_PATH_MAX_POINTS 64
|
|
559
573
|
bool vroom_chart_hit_test_drawing(VroomChart* chart, float x_px, float y_px,
|
|
560
574
|
int32_t* out_index, int32_t* out_part,
|
|
561
575
|
float* out_t);
|
|
562
576
|
|
|
563
|
-
// Selects a committed drawing (renders its
|
|
564
|
-
//
|
|
565
|
-
//
|
|
577
|
+
// Selects a committed drawing (renders its handles). `index` -1 clears the
|
|
578
|
+
// selection. `grabbed_endpoint` renders that handle 50% larger while it's being
|
|
579
|
+
// dragged (0/1 for a line, 0 for the box corner the host normalized to, the
|
|
580
|
+
// vertex index for a path); -1 for none. An out-of-range index clears the
|
|
581
|
+
// selection.
|
|
566
582
|
void vroom_chart_set_selected_drawing(VroomChart* chart, int32_t index,
|
|
567
583
|
int32_t grabbed_endpoint);
|
|
568
584
|
|
|
@@ -572,8 +588,16 @@ void vroom_chart_move_drawing_endpoint(VroomChart* chart, int32_t index,
|
|
|
572
588
|
int32_t endpoint, int64_t time_ms,
|
|
573
589
|
double price);
|
|
574
590
|
|
|
591
|
+
// Moves one vertex of a committed path (kind 3) to a new data-space anchor, for
|
|
592
|
+
// live handle dragging. `vertex` indexes `points`; a/b are re-mirrored onto the
|
|
593
|
+
// first/last point afterwards. No-op for an out-of-range drawing/vertex index or
|
|
594
|
+
// a drawing that isn't a path.
|
|
595
|
+
void vroom_chart_move_drawing_vertex(VroomChart* chart, int32_t index,
|
|
596
|
+
int32_t vertex, int64_t time_ms,
|
|
597
|
+
double price);
|
|
598
|
+
|
|
575
599
|
// Shifts a whole committed drawing by a *relative* data-space delta — `a`, `b`,
|
|
576
|
-
// and (for a pencil) every
|
|
600
|
+
// and (for a pencil or path) every point. Used for live body dragging of shapes
|
|
577
601
|
// whose points can't be restated cheaply. No-op for an out-of-range index.
|
|
578
602
|
void vroom_chart_translate_drawing(VroomChart* chart, int32_t index,
|
|
579
603
|
int64_t d_time_ms, double d_price);
|
|
@@ -642,6 +666,19 @@ void vroom_chart_start_draft_stroke(VroomChart* chart, uint32_t color,
|
|
|
642
666
|
void vroom_chart_append_draft_point(VroomChart* chart, int64_t time_ms,
|
|
643
667
|
double price);
|
|
644
668
|
|
|
669
|
+
// Restates the whole in-progress path (kind 3) draft: `count` vertices placed so
|
|
670
|
+
// far, plus — when `has_cursor` — a rubber-band segment from the last vertex to
|
|
671
|
+
// (cursor_time, cursor_price) carrying the arrow tip. Each placed vertex renders
|
|
672
|
+
// a node dot. A path is placed one deliberate click at a time (so `count` stays
|
|
673
|
+
// small, see VROOM_PATH_MAX_POINTS), which is why this restates rather than
|
|
674
|
+
// appending: the host stays the sole owner of the vertex list, and undoing a
|
|
675
|
+
// point is just a shorter array. Pass count 0 with has_cursor false to show
|
|
676
|
+
// nothing; the points are copied, so the caller may free them on return.
|
|
677
|
+
void vroom_chart_set_draft_path(VroomChart* chart, const VroomDrawPoint* points,
|
|
678
|
+
int32_t count, bool has_cursor,
|
|
679
|
+
int64_t cursor_time, double cursor_price,
|
|
680
|
+
uint32_t color, float width);
|
|
681
|
+
|
|
645
682
|
// Clears the draft (hides the in-progress node dots / guideline / stroke).
|
|
646
683
|
void vroom_chart_clear_draft(VroomChart* chart);
|
|
647
684
|
|
package/cpp/_core_src/chart.h
CHANGED
|
@@ -182,9 +182,10 @@ struct VroomChart {
|
|
|
182
182
|
// Committed drawings, anchored in data space so they track the candles on
|
|
183
183
|
// pan/zoom. Drawn on the price pane above candles/overlays.
|
|
184
184
|
//
|
|
185
|
-
// Mirrors the public VroomDrawing but *owns* its points, so a pencil
|
|
186
|
-
// (kind 2) can carry a variable number of them. For
|
|
187
|
-
// empty and only a/b are used; for pencil a/b
|
|
185
|
+
// Mirrors the public VroomDrawing but *owns* its points, so a pencil stroke
|
|
186
|
+
// (kind 2) or path (kind 3) can carry a variable number of them. For
|
|
187
|
+
// line/box `points` is empty and only a/b are used; for pencil/path a/b
|
|
188
|
+
// mirror the first/last point.
|
|
188
189
|
struct StoredDrawing {
|
|
189
190
|
VroomDrawPoint a{};
|
|
190
191
|
VroomDrawPoint b{};
|
|
@@ -199,7 +200,9 @@ struct VroomChart {
|
|
|
199
200
|
// draft_a is always drawn (node dot); draft_b is drawn when draft_has_b.
|
|
200
201
|
// draft_guide draws the live guideline A->B; when false only node dots show
|
|
201
202
|
// (the committed segment renders via `drawings`). draft_color/draft_width
|
|
202
|
-
// style the guideline to match the eventual line.
|
|
203
|
+
// style the guideline to match the eventual line. For a path (draft_kind 3)
|
|
204
|
+
// draft_points holds the vertices placed so far and draft_b is the cursor
|
|
205
|
+
// the rubber-band segment runs to (when draft_has_b).
|
|
203
206
|
bool draft_active = false;
|
|
204
207
|
VroomDrawPoint draft_a{};
|
|
205
208
|
bool draft_has_b = false;
|
|
@@ -207,14 +210,16 @@ struct VroomChart {
|
|
|
207
210
|
bool draft_guide = false;
|
|
208
211
|
uint32_t draft_color = 0xff2962ff;
|
|
209
212
|
float draft_width = 2.f;
|
|
210
|
-
int32_t draft_kind = 0; // 0 = line, 1 = box, 2 = pencil
|
|
211
|
-
//
|
|
213
|
+
int32_t draft_kind = 0; // 0 = line, 1 = box, 2 = pencil, 3 = path
|
|
214
|
+
// Points of the in-progress stroke (draft_kind 2, grown one at a time) or
|
|
215
|
+
// path (draft_kind 3, restated per click/move).
|
|
212
216
|
std::vector<VroomDrawPoint> draft_points;
|
|
213
217
|
|
|
214
218
|
// Selection/editing state for committed drawings. selected_drawing indexes
|
|
215
|
-
// `drawings` (or -1); its
|
|
216
|
-
// 0 (A) or 1 (B)
|
|
217
|
-
//
|
|
219
|
+
// `drawings` (or -1); its handles render on top. grabbed_endpoint is the
|
|
220
|
+
// handle being dragged — 0 (A) or 1 (B) for a line, 0 for a box (the host
|
|
221
|
+
// normalizes the grabbed corner to endpoint 0), the vertex index for a path
|
|
222
|
+
// — rendered 50% larger; -1 when none.
|
|
218
223
|
int32_t selected_drawing = -1;
|
|
219
224
|
int32_t grabbed_endpoint = -1;
|
|
220
225
|
|
|
@@ -883,8 +883,9 @@ extern "C" void vroom_chart_set_drawings(VroomChart* chart,
|
|
|
883
883
|
const VroomDrawing* drawings,
|
|
884
884
|
size_t count) {
|
|
885
885
|
if (!chart) return;
|
|
886
|
-
// Deep-copy into the owning form: a pencil's path
|
|
887
|
-
// caller's `points` buffer may be freed as soon as this
|
|
886
|
+
// Deep-copy into the owning form: a pencil stroke's or path's points live in
|
|
887
|
+
// the chart, so the caller's `points` buffer may be freed as soon as this
|
|
888
|
+
// returns.
|
|
888
889
|
chart->drawings.clear();
|
|
889
890
|
chart->drawings.reserve(count);
|
|
890
891
|
for (size_t i = 0; i < count; ++i) {
|
|
@@ -895,7 +896,7 @@ extern "C" void vroom_chart_set_drawings(VroomChart* chart,
|
|
|
895
896
|
d.color = src.color;
|
|
896
897
|
d.width = src.width;
|
|
897
898
|
d.kind = src.kind;
|
|
898
|
-
if (src.kind == 2 && src.points && src.point_count > 0) {
|
|
899
|
+
if ((src.kind == 2 || src.kind == 3) && src.points && src.point_count > 0) {
|
|
899
900
|
d.points.assign(src.points, src.points + src.point_count);
|
|
900
901
|
// Keep a/b mirroring the path ends so bounds/handle code is uniform.
|
|
901
902
|
d.a = d.points.front();
|
|
@@ -1025,6 +1026,27 @@ extern "C" void vroom_chart_append_draft_point(VroomChart* chart, int64_t time_m
|
|
|
1025
1026
|
chart->mark_dirty();
|
|
1026
1027
|
}
|
|
1027
1028
|
|
|
1029
|
+
extern "C" void vroom_chart_set_draft_path(VroomChart* chart,
|
|
1030
|
+
const VroomDrawPoint* points,
|
|
1031
|
+
int32_t count, bool has_cursor,
|
|
1032
|
+
int64_t cursor_time,
|
|
1033
|
+
double cursor_price, uint32_t color,
|
|
1034
|
+
float width) {
|
|
1035
|
+
if (!chart) return;
|
|
1036
|
+
chart->draft_active = true;
|
|
1037
|
+
chart->draft_kind = 3;
|
|
1038
|
+
chart->draft_guide = true;
|
|
1039
|
+
chart->draft_color = color;
|
|
1040
|
+
chart->draft_width = width;
|
|
1041
|
+
chart->draft_points.clear();
|
|
1042
|
+
if (points && count > 0) chart->draft_points.assign(points, points + count);
|
|
1043
|
+
chart->draft_has_b = has_cursor;
|
|
1044
|
+
chart->draft_b = VroomDrawPoint{cursor_time, cursor_price};
|
|
1045
|
+
// draft_a doubles as the first placed vertex for the shared node-dot path.
|
|
1046
|
+
if (!chart->draft_points.empty()) chart->draft_a = chart->draft_points.front();
|
|
1047
|
+
chart->mark_dirty();
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1028
1050
|
extern "C" void vroom_chart_clear_draft(VroomChart* chart) {
|
|
1029
1051
|
if (!chart) return;
|
|
1030
1052
|
if (!chart->draft_active) return;
|
|
@@ -1151,6 +1173,21 @@ extern "C" void vroom_chart_move_drawing_endpoint(VroomChart* chart, int32_t ind
|
|
|
1151
1173
|
chart->mark_dirty();
|
|
1152
1174
|
}
|
|
1153
1175
|
|
|
1176
|
+
extern "C" void vroom_chart_move_drawing_vertex(VroomChart* chart, int32_t index,
|
|
1177
|
+
int32_t vertex, int64_t time_ms,
|
|
1178
|
+
double price) {
|
|
1179
|
+
if (!chart) return;
|
|
1180
|
+
if (index < 0 || index >= static_cast<int32_t>(chart->drawings.size())) return;
|
|
1181
|
+
auto& d = chart->drawings[index];
|
|
1182
|
+
if (d.kind != 3) return;
|
|
1183
|
+
if (vertex < 0 || vertex >= static_cast<int32_t>(d.points.size())) return;
|
|
1184
|
+
d.points[vertex] = VroomDrawPoint{time_ms, price};
|
|
1185
|
+
// a/b mirror the path ends, so moving a terminal vertex has to restate them.
|
|
1186
|
+
d.a = d.points.front();
|
|
1187
|
+
d.b = d.points.back();
|
|
1188
|
+
chart->mark_dirty();
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1154
1191
|
extern "C" void vroom_chart_translate_drawing(VroomChart* chart, int32_t index,
|
|
1155
1192
|
int64_t d_time_ms, double d_price) {
|
|
1156
1193
|
if (!chart) return;
|
|
@@ -1160,6 +1197,7 @@ extern "C" void vroom_chart_translate_drawing(VroomChart* chart, int32_t index,
|
|
|
1160
1197
|
p.time_ms += d_time_ms;
|
|
1161
1198
|
p.price += d_price;
|
|
1162
1199
|
};
|
|
1200
|
+
// a/b mirror the path/stroke ends, so shifting everything keeps them in sync.
|
|
1163
1201
|
shift(d.a);
|
|
1164
1202
|
shift(d.b);
|
|
1165
1203
|
for (VroomDrawPoint& p : d.points) shift(p);
|
|
@@ -115,6 +115,99 @@ void draw_path(SkCanvas* canvas, const VroomChart& chart, const Layout& lay,
|
|
|
115
115
|
canvas->drawPath(builder.detach(), paint);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// Strokes straight segments through `pts` (already in pixels). Unlike a pencil
|
|
119
|
+
// stroke a path's vertices were each placed deliberately, so the corners are the
|
|
120
|
+
// point — no smoothing. Round joins/caps keep the corners from spiking at the
|
|
121
|
+
// sharp angles a wave count tends to produce.
|
|
122
|
+
void draw_polyline(SkCanvas* canvas, const std::vector<SkPoint>& pts,
|
|
123
|
+
SkColor color, float width) {
|
|
124
|
+
if (pts.size() < 2) return;
|
|
125
|
+
SkPathBuilder builder;
|
|
126
|
+
builder.moveTo(pts[0]);
|
|
127
|
+
for (size_t i = 1; i < pts.size(); ++i) builder.lineTo(pts[i]);
|
|
128
|
+
|
|
129
|
+
SkPaint paint;
|
|
130
|
+
paint.setAntiAlias(true);
|
|
131
|
+
paint.setColor(color);
|
|
132
|
+
paint.setStyle(SkPaint::kStroke_Style);
|
|
133
|
+
paint.setStrokeWidth(width > 0.f ? width : 2.f);
|
|
134
|
+
paint.setStrokeCap(SkPaint::kRound_Cap);
|
|
135
|
+
paint.setStrokeJoin(SkPaint::kRound_Join);
|
|
136
|
+
canvas->drawPath(builder.detach(), paint);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Apex-to-base length of the arrowhead. Scales with the stroke so a thicker
|
|
140
|
+
// path gets a proportionally bigger head.
|
|
141
|
+
float arrow_head_len(float width) {
|
|
142
|
+
return 5.f + 2.5f * (width > 0.f ? width : 2.f);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fills a small triangular arrowhead with its apex at `tip`, pointing away from
|
|
146
|
+
// `from`. Skipped when the segment is too short to seat one (otherwise the head
|
|
147
|
+
// would swallow the whole segment and flip direction on tiny jitters).
|
|
148
|
+
void draw_arrow_head(SkCanvas* canvas, SkPoint tip, SkPoint from, SkColor color,
|
|
149
|
+
float width) {
|
|
150
|
+
const float len = arrow_head_len(width);
|
|
151
|
+
const float half = len * 0.45f; // base half-width
|
|
152
|
+
const float dx = tip.fX - from.fX;
|
|
153
|
+
const float dy = tip.fY - from.fY;
|
|
154
|
+
const float dist = std::sqrt(dx * dx + dy * dy);
|
|
155
|
+
if (dist < len) return;
|
|
156
|
+
|
|
157
|
+
const float ux = dx / dist; // unit vector pointing at the tip
|
|
158
|
+
const float uy = dy / dist;
|
|
159
|
+
const SkPoint base{tip.fX - ux * len, tip.fY - uy * len};
|
|
160
|
+
|
|
161
|
+
SkPathBuilder head;
|
|
162
|
+
head.moveTo(tip);
|
|
163
|
+
head.lineTo(base.fX - uy * half, base.fY + ux * half); // base, one side
|
|
164
|
+
head.lineTo(base.fX + uy * half, base.fY - ux * half); // base, the other
|
|
165
|
+
head.close();
|
|
166
|
+
|
|
167
|
+
SkPaint paint;
|
|
168
|
+
paint.setAntiAlias(true);
|
|
169
|
+
paint.setColor(color);
|
|
170
|
+
paint.setStyle(SkPaint::kFill_Style);
|
|
171
|
+
canvas->drawPath(head.detach(), paint);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Projects every point of a path/stroke to pixels in one pass, so the render and
|
|
175
|
+
// arrowhead code can index them without re-projecting.
|
|
176
|
+
std::vector<SkPoint> project_all(const VroomChart& chart, const Layout& lay,
|
|
177
|
+
const PriceBounds& bounds, int64_t window_ms,
|
|
178
|
+
const std::vector<VroomDrawPoint>& pts) {
|
|
179
|
+
std::vector<SkPoint> out;
|
|
180
|
+
out.reserve(pts.size());
|
|
181
|
+
for (const auto& p : pts) out.push_back(to_px(chart, lay, bounds, window_ms, p));
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Draws a whole arrow-tipped path from its already-projected points: the
|
|
186
|
+
// segments, then the head on the last one.
|
|
187
|
+
void draw_arrow_path(SkCanvas* canvas, const std::vector<SkPoint>& pts,
|
|
188
|
+
SkColor color, float width) {
|
|
189
|
+
if (pts.size() < 2) return;
|
|
190
|
+
const SkPoint tip = pts.back();
|
|
191
|
+
const SkPoint prev = pts[pts.size() - 2];
|
|
192
|
+
const float len = arrow_head_len(width);
|
|
193
|
+
const float dx = tip.fX - prev.fX;
|
|
194
|
+
const float dy = tip.fY - prev.fY;
|
|
195
|
+
const float dist = std::sqrt(dx * dx + dy * dy);
|
|
196
|
+
if (dist < len) { // no room for a head — just the bare segments
|
|
197
|
+
draw_polyline(canvas, pts, color, width);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Stop the stroke at the head's base rather than running it to the apex:
|
|
202
|
+
// its round cap is a half-disc of radius width/2 centred on the endpoint, so
|
|
203
|
+
// at the apex it bulges past the point and blunts it. The base is wider than
|
|
204
|
+
// the stroke, so the cap ends up hidden inside the triangle.
|
|
205
|
+
std::vector<SkPoint> body(pts);
|
|
206
|
+
body.back() = SkPoint{tip.fX - dx / dist * len, tip.fY - dy / dist * len};
|
|
207
|
+
draw_polyline(canvas, body, color, width);
|
|
208
|
+
draw_arrow_head(canvas, tip, prev, color, width);
|
|
209
|
+
}
|
|
210
|
+
|
|
118
211
|
void draw_node(SkCanvas* canvas, SkPoint pt, float scale = 1.f) {
|
|
119
212
|
SkPaint fill;
|
|
120
213
|
fill.setAntiAlias(true);
|
|
@@ -232,6 +325,12 @@ void draw(SkCanvas* canvas,
|
|
|
232
325
|
static_cast<SkColor>(d.color), d.width);
|
|
233
326
|
continue;
|
|
234
327
|
}
|
|
328
|
+
if (d.kind == 3) {
|
|
329
|
+
draw_arrow_path(
|
|
330
|
+
canvas, project_all(chart, lay, bounds, window_ms, d.points),
|
|
331
|
+
static_cast<SkColor>(d.color), d.width);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
235
334
|
const SkPoint a = to_px(chart, lay, bounds, window_ms, d.a);
|
|
236
335
|
const SkPoint b = to_px(chart, lay, bounds, window_ms, d.b);
|
|
237
336
|
if (d.kind == 1) {
|
|
@@ -255,7 +354,16 @@ void draw(SkCanvas* canvas,
|
|
|
255
354
|
const auto& d = chart.drawings[chart.selected_drawing];
|
|
256
355
|
const SkPoint sa = to_px(chart, lay, bounds, window_ms, d.a);
|
|
257
356
|
const SkPoint sb = to_px(chart, lay, bounds, window_ms, d.b);
|
|
258
|
-
if (d.kind ==
|
|
357
|
+
if (d.kind == 3) {
|
|
358
|
+
// Path: every vertex was placed by hand, so every one is a grab
|
|
359
|
+
// handle. The one being dragged renders 50% larger, like a line's.
|
|
360
|
+
const auto pts = project_all(chart, lay, bounds, window_ms, d.points);
|
|
361
|
+
for (size_t i = 0; i < pts.size(); ++i) {
|
|
362
|
+
draw_node(canvas, pts[i],
|
|
363
|
+
chart.grabbed_endpoint == static_cast<int32_t>(i) ? 1.5f
|
|
364
|
+
: 1.f);
|
|
365
|
+
}
|
|
366
|
+
} else if (d.kind == 2) {
|
|
259
367
|
// Pencil: anchors on the first/last point are a visual cue that the
|
|
260
368
|
// stroke is movable — they aren't grab handles, so never enlarged.
|
|
261
369
|
draw_node(canvas, sa);
|
|
@@ -289,6 +397,28 @@ void draw(SkCanvas* canvas,
|
|
|
289
397
|
return;
|
|
290
398
|
}
|
|
291
399
|
|
|
400
|
+
// 1d. Path in progress: the vertices placed so far, plus a rubber-band
|
|
401
|
+
// segment to the cursor. The arrow rides the leading edge — on the
|
|
402
|
+
// cursor while drawing, and (once committed) on the final vertex — so
|
|
403
|
+
// the preview reads as the arrow it's about to become.
|
|
404
|
+
if (chart.draft_kind == 3) {
|
|
405
|
+
if (chart.draft_points.empty()) return;
|
|
406
|
+
auto pts = project_all(chart, lay, bounds, window_ms, chart.draft_points);
|
|
407
|
+
if (chart.draft_has_b) {
|
|
408
|
+
pts.push_back(to_px(chart, lay, bounds, window_ms, chart.draft_b));
|
|
409
|
+
}
|
|
410
|
+
canvas->save();
|
|
411
|
+
canvas->clipRect(clip);
|
|
412
|
+
draw_arrow_path(canvas, pts, static_cast<SkColor>(chart.draft_color),
|
|
413
|
+
chart.draft_width);
|
|
414
|
+
canvas->restore();
|
|
415
|
+
// Node dots on the placed vertices only (not the cursor) — unclipped, so
|
|
416
|
+
// a vertex on the very edge still renders whole.
|
|
417
|
+
const size_t placed = chart.draft_points.size();
|
|
418
|
+
for (size_t i = 0; i < placed; ++i) draw_node(canvas, pts[i]);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
292
422
|
const SkPoint a = to_px(chart, lay, bounds, window_ms, chart.draft_a);
|
|
293
423
|
const bool has_b = chart.draft_has_b;
|
|
294
424
|
const SkPoint b =
|
|
@@ -339,7 +469,18 @@ HitResult hit_test(const VroomChart& chart,
|
|
|
339
469
|
const auto& d = chart.drawings[chart.selected_drawing];
|
|
340
470
|
const SkPoint a = to_px(chart, lay, bounds, window_ms, d.a);
|
|
341
471
|
const SkPoint b = to_px(chart, lay, bounds, window_ms, d.b);
|
|
342
|
-
if (d.kind ==
|
|
472
|
+
if (d.kind == 3) {
|
|
473
|
+
// Path: every vertex is a handle. Later vertices win an overlap, so
|
|
474
|
+
// stacked points stay individually reachable by peeling them off.
|
|
475
|
+
for (size_t i = d.points.size(); i-- > 0;) {
|
|
476
|
+
const SkPoint p = to_px(chart, lay, bounds, window_ms, d.points[i]);
|
|
477
|
+
if (std::hypot(x - p.fX, y - p.fY) <= kHandleHit) {
|
|
478
|
+
return HitResult{chart.selected_drawing,
|
|
479
|
+
VROOM_DRAW_PART_VERTEX + static_cast<int32_t>(i),
|
|
480
|
+
0.f};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
} else if (d.kind == 2) {
|
|
343
484
|
// Pencil has no grab handles — its anchors translate like any other
|
|
344
485
|
// part of the stroke, so fall through to the body pass.
|
|
345
486
|
} else if (d.kind == 1) {
|
|
@@ -367,13 +508,15 @@ HitResult hit_test(const VroomChart& chart,
|
|
|
367
508
|
float best_t = 0.f;
|
|
368
509
|
for (size_t i = 0; i < chart.drawings.size(); ++i) {
|
|
369
510
|
const auto& d = chart.drawings[i];
|
|
370
|
-
|
|
511
|
+
// Pencil and path share the polyline distance — a path's straight
|
|
512
|
+
// segments are exactly what dist_to_path already measures against.
|
|
513
|
+
if (d.kind == 2 || d.kind == 3) {
|
|
371
514
|
const float dist = dist_to_path(x, y, chart, lay, bounds, window_ms,
|
|
372
515
|
d.points, kBodyHit);
|
|
373
516
|
if (dist <= best) { // <= so later (topmost) drawings win ties
|
|
374
517
|
best = dist;
|
|
375
518
|
best_i = static_cast<int32_t>(i);
|
|
376
|
-
best_part = 5;
|
|
519
|
+
best_part = d.kind == 3 ? 6 : 5;
|
|
377
520
|
best_t = 0.f;
|
|
378
521
|
}
|
|
379
522
|
continue;
|
package/cpp/_core_src/drawings.h
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Drawing annotations — user-placed line tools rendered on the price pane.
|
|
2
2
|
//
|
|
3
3
|
// Two kinds of geometry are drawn here:
|
|
4
|
-
// * Committed
|
|
5
|
-
//
|
|
4
|
+
// * Committed shapes (chart.drawings) — lines, boxes, freehand strokes and
|
|
5
|
+
// arrow-tipped paths, anchored in data space so they stay glued to the
|
|
6
|
+
// candles as the user pans/zooms.
|
|
6
7
|
// * The transient "draft" (chart.draft_*) — the in-progress node dots and the
|
|
7
|
-
// live guideline shown while the user is placing a
|
|
8
|
+
// live guideline shown while the user is placing a shape.
|
|
8
9
|
//
|
|
9
10
|
// Like ma_overlay / crosshair this is its own module so the chart orchestrator
|
|
10
11
|
// stays thin. Drawn after the overlays and before the axis backgrounds.
|
|
@@ -39,8 +40,12 @@ void draw(SkCanvas* canvas,
|
|
|
39
40
|
// * pencil — always 5 (stroke body). A freehand stroke has no grab handles:
|
|
40
41
|
// its end anchors are a visual cue only and translate the whole
|
|
41
42
|
// path like any other part of it.
|
|
42
|
-
//
|
|
43
|
-
//
|
|
43
|
+
// * path — 6 (path body) or VROOM_DRAW_PART_VERTEX + i for vertex i. Unlike
|
|
44
|
+
// a pencil every vertex was placed deliberately, so every one is a
|
|
45
|
+
// grab handle; the offset keeps those indices clear of the small
|
|
46
|
+
// part numbers above.
|
|
47
|
+
// Corner/endpoint/vertex hits are only reported for the currently selected
|
|
48
|
+
// drawing (whose handles are visible).
|
|
44
49
|
struct HitResult {
|
|
45
50
|
int32_t index;
|
|
46
51
|
int32_t part;
|