From 64bcbfbade01f18b1d39e2397414f6cc3ec0a57e Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sun, 10 Dec 2023 14:47:42 -0800 Subject: [PATCH] Report with multiple charts. --- helper/README.md | 26 ++++++++++++++++++-------- helper/report.go | 26 ++++++++++++++++++++++++-- helper/report.tmpl | 37 +++++++++++++++++++++++++++++-------- helper/report_test.go | 6 ++++-- strategy/apo_strategy.go | 11 +++++++---- 5 files changed, 82 insertions(+), 24 deletions(-) diff --git a/helper/README.md b/helper/README.md index 2df98be..1398526 100644 --- a/helper/README.md +++ b/helper/README.md @@ -79,7 +79,8 @@ The information provided on this project is strictly for informational purposes - [type Number](<#Number>) - [type Report](<#Report>) - [func NewReport\(title string, date \<\-chan time.Time\) \*Report](<#NewReport>) - - [func \(r \*Report\) AddColumn\(column ReportColumn\)](<#Report.AddColumn>) + - [func \(r \*Report\) AddChart\(\) int](<#Report.AddChart>) + - [func \(r \*Report\) AddColumn\(column ReportColumn, charts ...int\)](<#Report.AddColumn>) - [func \(r \*Report\) WriteToFile\(fileName string\) error](<#Report.WriteToFile>) - [func \(r \*Report\) WriteToWriter\(writer io.Writer\) error](<#Report.WriteToWriter>) - [type ReportColumn](<#ReportColumn>) @@ -852,7 +853,7 @@ type Number interface { ``` -## type [Report]() +## type [Report]() Report generates an HTML file containing an interactive chart that visually represents the provided data and annotations. @@ -865,7 +866,7 @@ type Report struct { ``` -### func [NewReport]() +### func [NewReport]() ```go func NewReport(title string, date <-chan time.Time) *Report @@ -873,17 +874,26 @@ func NewReport(title string, date <-chan time.Time) *Report NewReport takes a channel of time as the time axis and returns a new instance of the Report struct. This instance can later be used to add data and annotations and subsequently generate a report. + +### func \(\*Report\) [AddChart]() + +```go +func (r *Report) AddChart() int +``` + +AddChart adds a new chart to the report and returns its unique identifier. This identifier can be used later to refer to the chart and add columns to it. + -### func \(\*Report\) [AddColumn]() +### func \(\*Report\) [AddColumn]() ```go -func (r *Report) AddColumn(column ReportColumn) +func (r *Report) AddColumn(column ReportColumn, charts ...int) ``` -AddColumn adds a new data column to the report. +AddColumn adds a new data column to the specified charts. If no chart is specified, it will be added to the main chart. -### func \(\*Report\) [WriteToFile]() +### func \(\*Report\) [WriteToFile]() ```go func (r *Report) WriteToFile(fileName string) error @@ -892,7 +902,7 @@ func (r *Report) WriteToFile(fileName string) error WriteToFile writes the generated report content to a file with the specified name. This allows users to conveniently save the report for later viewing or analysis. -### func \(\*Report\) [WriteToWriter]() +### func \(\*Report\) [WriteToWriter]() ```go func (r *Report) WriteToWriter(writer io.Writer) error diff --git a/helper/report.go b/helper/report.go index 0bd6935..24833a4 100644 --- a/helper/report.go +++ b/helper/report.go @@ -41,6 +41,7 @@ type reportModel struct { Title string Date <-chan time.Time Columns []ReportColumn + Views [][]int } // Report generates an HTML file containing an interactive chart that @@ -62,13 +63,34 @@ func NewReport(title string, date <-chan time.Time) *Report { Title: title, Date: date, Columns: []ReportColumn{}, + Views: [][]int{ + {}, + }, }, } } -// AddColumn adds a new data column to the report. -func (r *Report) AddColumn(column ReportColumn) { +// AddChart adds a new chart to the report and returns its unique +// identifier. This identifier can be used later to refer to the +// chart and add columns to it. +func (r *Report) AddChart() int { + r.model.Views = append(r.model.Views, []int{}) + return len(r.model.Views) - 1 +} + +// AddColumn adds a new data column to the specified charts. If no +// chart is specified, it will be added to the main chart. +func (r *Report) AddColumn(column ReportColumn, charts ...int) { r.model.Columns = append(r.model.Columns, column) + columnID := len(r.model.Columns) + + if len(charts) == 0 { + charts = append(charts, 0) + } + + for _, chartID := range charts { + r.model.Views[chartID] = append(r.model.Views[chartID], columnID) + } } // WriteToWriter writes the report content to the provided io.Writer. diff --git a/helper/report.tmpl b/helper/report.tmpl index 6616588..ea3669e 100644 --- a/helper/report.tmpl +++ b/helper/report.tmpl @@ -16,8 +16,12 @@
-
+ {{ range $i, $view := .Views }} +
+ {{ if eq $i 0 }}
+ {{ end }} + {{ end }}
@@ -36,18 +40,28 @@ function drawDashboard() { var dashboard = new google.visualization.Dashboard(document.getElementById("dashboard")); - var chart = new google.visualization.ChartWrapper({ + {{ range $i, $view := .Views }} + var chart{{ $i }} = new google.visualization.ChartWrapper({ "chartType": "LineChart", - "containerId": "chart", + "containerId": "chart{{ $i }}", "options": { - "title": "{{ .Title }}", "curveType": "function", "legend": { "position": "right", }, - "height": 400, + "height": + {{ if eq $i 0 }}400{{ else }}200{{ end }}, + }, + "view": { + "columns": [ + 0, + {{ range $view }} + {{ . }}, + {{ end }} + ] }, }); + {{ end }} var rangeFilter = new google.visualization.ControlWrapper({ "controlType": "ChartRangeFilter", @@ -58,6 +72,9 @@ "chartOptions": { "height": 50, }, + "chartView": { + "columns": [0, 1], + } }, }, }); @@ -65,7 +82,7 @@ // Create the data table. var data = new google.visualization.DataTable(); data.addColumn("date", "Date"); - {{ range.Columns }} + {{ range .Columns }} data.addColumn({ "type": "{{ .Type }}", "label": "{{ .Name }}", @@ -73,7 +90,7 @@ }); {{ end }} - {{ range.Date }} + {{ range .Date }} data.addRow([ new Date({{ .Format "2006, 1, 2" }}), {{ range $.Columns }} @@ -82,7 +99,11 @@ ]); {{ end }} - dashboard.bind(rangeFilter, chart); + dashboard.bind(rangeFilter, [ + {{ range $i, $id := .Views }} + chart{{ $i }}, + {{ end }} + ]); dashboard.draw(data); } diff --git a/helper/report_test.go b/helper/report_test.go index 6be6b2c..474ab08 100644 --- a/helper/report_test.go +++ b/helper/report_test.go @@ -34,10 +34,12 @@ func TestReportWriteToFile(t *testing.T) { annotations := helper.Map(inputs[4], func(row *Row) string { return row.Annotation }) report := helper.NewReport("Test Report", dates) + report.AddChart() + report.AddColumn(helper.NewNumericReportColumn("High", highs)) report.AddColumn(helper.NewNumericReportColumn("Low", lows)) - report.AddColumn(helper.NewNumericReportColumn("Close", closes)) - report.AddColumn(helper.NewAnnotationReportColumn(annotations)) + report.AddColumn(helper.NewNumericReportColumn("Close", closes), 0, 1) + report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1) fileName := "report.html" defer os.Remove(fileName) diff --git a/strategy/apo_strategy.go b/strategy/apo_strategy.go index f13f3f9..330229f 100644 --- a/strategy/apo_strategy.go +++ b/strategy/apo_strategy.go @@ -57,16 +57,19 @@ func (a *ApoStrategy[T]) Compute(c <-chan T) <-chan Action { // Report takes input channels containing dates and values, computes the recommended actions // based on the data, and generates a report annotated with those actions. func (a *ApoStrategy[T]) Report(dates <-chan time.Time, c <-chan T) *helper.Report { - cs := helper.Duplicate(c, 2) + cs := helper.Duplicate(c, 3) dates = helper.Skip(dates, a.Apo.SlowPeriod-1) cs[0] = helper.Skip(cs[0], a.Apo.SlowPeriod-1) - - annotations := ActionsToAnnotations(a.Compute(cs[1])) + apo := a.Apo.Compute(cs[1]) + annotations := ActionsToAnnotations(a.Compute(cs[2])) report := helper.NewReport("APO Strategy", dates) + report.AddChart() + report.AddColumn(helper.NewNumericReportColumn("Close", cs[0])) - report.AddColumn(helper.NewAnnotationReportColumn(annotations)) + report.AddColumn(helper.NewNumericReportColumn("APO", apo), 1) + report.AddColumn(helper.NewAnnotationReportColumn(annotations), 0, 1) return report }