Skip to content

Commit 5c59424

Browse files
committed
fix: Use reflection to handle OHLCV data and avoid import cycle
Replace direct import of chart package with reflection-based data extraction to avoid circular dependency: - Add ohlcvData struct to hold extracted candle data - Implement extractOHLCVData using reflection to extract fields - Add getInt64Field and getFloat64Field helper functions - Update calculateDataRanges to use ohlcvData type - Update createCandleDataBuffer to use ohlcvData type - Remove chart package import to break circular dependency This fixes the "Invalid candlestick data type" error by properly handling the []chart.OHLCV type without creating an import cycle between pkg/runtime and pkg/runtime/chart. Fixes #1
1 parent 9940b8a commit 5c59424

1 file changed

Lines changed: 90 additions & 31 deletions

File tree

pkg/runtime/chart_renderer.go

Lines changed: 90 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/binary"
88
"fmt"
99
"math"
10+
"reflect"
1011
"syscall/js"
1112
)
1213

@@ -16,6 +17,16 @@ var candlestickShader string
1617
//go:embed chart/shaders/line.wgsl
1718
var lineShader string
1819

20+
// ohlcvData represents extracted OHLCV data
21+
type ohlcvData struct {
22+
Timestamp int64
23+
Open float64
24+
High float64
25+
Low float64
26+
Close float64
27+
Volume float64
28+
}
29+
1930
// ChartRenderer manages rendering of charts
2031
type ChartRenderer struct {
2132
Canvas *GPUCanvas
@@ -280,16 +291,8 @@ func (cr *ChartRenderer) renderCandlestickSeries(pass js.Value, series *GPUNode)
280291
return
281292
}
282293

283-
// Try to extract OHLCV data
284-
var candles []interface{}
285-
switch d := data.(type) {
286-
case []interface{}:
287-
candles = d
288-
default:
289-
logError("[ChartRenderer] Invalid candlestick data type")
290-
return
291-
}
292-
294+
// Extract OHLCV data using reflection
295+
candles := cr.extractOHLCVData(data)
293296
if len(candles) == 0 {
294297
return
295298
}
@@ -410,6 +413,72 @@ func (cr *ChartRenderer) renderLineSeries(pass js.Value, series *GPUNode) {
410413

411414
// Helper functions
412415

416+
// extractOHLCVData uses reflection to extract OHLCV data from any slice type
417+
func (cr *ChartRenderer) extractOHLCVData(data interface{}) []ohlcvData {
418+
v := reflect.ValueOf(data)
419+
if v.Kind() != reflect.Slice {
420+
logError(fmt.Sprintf("[ChartRenderer] Data is not a slice: %T", data))
421+
return nil
422+
}
423+
424+
result := make([]ohlcvData, v.Len())
425+
for i := 0; i < v.Len(); i++ {
426+
item := v.Index(i)
427+
if item.Kind() == reflect.Struct {
428+
// Extract fields by name
429+
timestamp := getInt64Field(item, "Timestamp")
430+
open := getFloat64Field(item, "Open")
431+
high := getFloat64Field(item, "High")
432+
low := getFloat64Field(item, "Low")
433+
close := getFloat64Field(item, "Close")
434+
volume := getFloat64Field(item, "Volume")
435+
436+
result[i] = ohlcvData{
437+
Timestamp: timestamp,
438+
Open: open,
439+
High: high,
440+
Low: low,
441+
Close: close,
442+
Volume: volume,
443+
}
444+
}
445+
}
446+
447+
return result
448+
}
449+
450+
// getInt64Field extracts an int64 field from a struct
451+
func getInt64Field(v reflect.Value, fieldName string) int64 {
452+
field := v.FieldByName(fieldName)
453+
if !field.IsValid() {
454+
return 0
455+
}
456+
switch field.Kind() {
457+
case reflect.Int64:
458+
return field.Int()
459+
case reflect.Int, reflect.Int32:
460+
return field.Int()
461+
default:
462+
return 0
463+
}
464+
}
465+
466+
// getFloat64Field extracts a float64 field from a struct
467+
func getFloat64Field(v reflect.Value, fieldName string) float64 {
468+
field := v.FieldByName(fieldName)
469+
if !field.IsValid() {
470+
return 0
471+
}
472+
switch field.Kind() {
473+
case reflect.Float64, reflect.Float32:
474+
return field.Float()
475+
case reflect.Int, reflect.Int32, reflect.Int64:
476+
return float64(field.Int())
477+
default:
478+
return 0
479+
}
480+
}
481+
413482
func (cr *ChartRenderer) getPadding() map[string]float32 {
414483
padding := map[string]float32{"top": 60, "right": 20, "bottom": 40, "left": 80}
415484

@@ -424,7 +493,7 @@ func (cr *ChartRenderer) getPadding() map[string]float32 {
424493
return padding
425494
}
426495

427-
func (cr *ChartRenderer) calculateDataRanges(candles []interface{}) {
496+
func (cr *ChartRenderer) calculateDataRanges(candles []ohlcvData) {
428497
if len(candles) == 0 {
429498
return
430499
}
@@ -433,14 +502,9 @@ func (cr *ChartRenderer) calculateDataRanges(candles []interface{}) {
433502
minY, maxY := math.MaxFloat64, -math.MaxFloat64
434503

435504
for _, c := range candles {
436-
candleMap, ok := c.(map[string]interface{})
437-
if !ok {
438-
continue
439-
}
440-
441-
timestamp, _ := candleMap["Timestamp"].(float64)
442-
high, _ := candleMap["High"].(float64)
443-
low, _ := candleMap["Low"].(float64)
505+
timestamp := float64(c.Timestamp)
506+
high := c.High
507+
low := c.Low
444508

445509
if timestamp < minX {
446510
minX = timestamp
@@ -497,24 +561,19 @@ func (cr *ChartRenderer) calculateLineDataRanges(points []interface{}) {
497561
cr.DataYRange = [2]float64{minY, maxY}
498562
}
499563

500-
func (cr *ChartRenderer) createCandleDataBuffer(candles []interface{}) *GPUBuffer {
564+
func (cr *ChartRenderer) createCandleDataBuffer(candles []ohlcvData) *GPUBuffer {
501565
// Each candle: timestamp(f32), open(f32), high(f32), low(f32), close(f32), volume(f32) = 24 bytes
502566
bufferSize := len(candles) * 24
503567
data := make([]byte, bufferSize)
504568

505569
for i, c := range candles {
506-
candleMap, ok := c.(map[string]interface{})
507-
if !ok {
508-
continue
509-
}
510-
511570
offset := i * 24
512-
timestamp, _ := candleMap["Timestamp"].(float64)
513-
open, _ := candleMap["Open"].(float64)
514-
high, _ := candleMap["High"].(float64)
515-
low, _ := candleMap["Low"].(float64)
516-
close, _ := candleMap["Close"].(float64)
517-
volume, _ := candleMap["Volume"].(float64)
571+
timestamp := float64(c.Timestamp)
572+
open := c.Open
573+
high := c.High
574+
low := c.Low
575+
close := c.Close
576+
volume := c.Volume
518577

519578
binary.LittleEndian.PutUint32(data[offset:], math.Float32bits(float32(timestamp)))
520579
binary.LittleEndian.PutUint32(data[offset+4:], math.Float32bits(float32(open)))

0 commit comments

Comments
 (0)