-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
userplot.go
183 lines (147 loc) · 4.84 KB
/
userplot.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package statsviz
import (
"errors"
"fmt"
"github.com/arl/statsviz/internal/plot"
)
// TimeSeriesType describes the type of a time series plot.
type TimeSeriesType string
const (
// Scatter is a time series plot made of lines.
Scatter TimeSeriesType = "scatter"
// Bar is a time series plot made of bars.
Bar TimeSeriesType = "bar"
)
// BarMode determines how bars at the same location are displayed on a bar plot.
type BarMode string
const (
// Stack indicates that bars are stacked on top of one another.
Stack BarMode = "stack"
// Ggroup indicates that bars are plotted next to one another, centered
// around the shared location.
Group BarMode = "group"
// Relative indicates that bars are stacked on top of one another, with
// negative values below the axis and positive values above.
Relative BarMode = "relative"
// Overlay indicates that bars are plotted over one another.
Overlay BarMode = "overlay"
)
var (
// ErrNoTimeSeries is returned when a user plot has no time series.
ErrNoTimeSeries = errors.New("user plot must have at least one time series")
// ErrEmptyPlotName is returned when a user plot has an empty name.
ErrEmptyPlotName = errors.New("user plot name can't be empty")
)
// ErrReservedPlotName is returned when a reserved plot name is used for a user plot.
type ErrReservedPlotName string
func (e ErrReservedPlotName) Error() string {
return fmt.Sprintf("%q is a reserved plot name", string(e))
}
// HoverOnType describes the type of hover effect on a time series plot.
type HoverOnType string
const (
// HoverOnPoints specifies that the hover effects highlights individual
// points.
HoverOnPoints HoverOnType = "points"
// HoverOnPoints specifies that the hover effects highlights filled regions.
HoverOnFills HoverOnType = "fills"
// HoverOnPointsAndFills specifies that the hover effects highlights both
// points and filled regions.
HoverOnPointsAndFills HoverOnType = "points+fills"
)
// A TimeSeries describes a single time series of a plot.
type TimeSeries struct {
// Name is the name identifying this time series in the user interface.
Name string
// UnitFmt is the d3-format string used to format the numbers of this time
// series in the user interface. See https://github.com/d3/d3-format.
Unitfmt string
// HoverOn configures whether the hover effect highlights individual points
// or do they highlight filled regions, or both. Defaults to HoverOnFills.
HoverOn HoverOnType
// Type is the time series type, either [Scatter] or [Bar]. default: [Scatter].
Type TimeSeriesType
// GetValue specifies the function called to get the value of this time
// series.
GetValue func() float64
}
// TimeSeriesPlotConfig describes the configuration of a time series plot.
type TimeSeriesPlotConfig struct {
// Name is the plot name, it must be unique.
Name string
// Title is the plot title, shown above the plot.
Title string
// Type is either [Scatter] or [Bar]. default: [Scatter].
Type TimeSeriesType
// BarMode is either [Stack], [Group], [Relative] or [Overlay].
// default: [Group].
BarMode BarMode
// Tooltip is the html-aware text shown when the user clicks on the plot
// Info icon.
InfoText string
// YAxisTitle is the title of Y axis.
YAxisTitle string
// YAxisTickSuffix is the suffix added to tick values.
YAxisTickSuffix string
// Series contains the time series shown on this plot, there must be at
// least one.
Series []TimeSeries
}
// Build validates the configuration and builds a time series plot for it
func (p TimeSeriesPlotConfig) Build() (TimeSeriesPlot, error) {
var zero TimeSeriesPlot
if p.Name == "" {
return zero, ErrEmptyPlotName
}
if plot.IsReservedPlotName(p.Name) {
return zero, ErrReservedPlotName(p.Name)
}
if len(p.Series) == 0 {
return zero, ErrNoTimeSeries
}
var (
subplots []plot.Subplot
funcs []func() float64
)
for _, ts := range p.Series {
switch ts.HoverOn {
case "":
ts.HoverOn = HoverOnFills
case HoverOnPoints, HoverOnFills, HoverOnPointsAndFills:
// ok
default:
return zero, fmt.Errorf("invalid HoverOn value %s", ts.HoverOn)
}
subplots = append(subplots, plot.Subplot{
Name: ts.Name,
Unitfmt: ts.Unitfmt,
HoverOn: string(ts.HoverOn),
Type: string(ts.Type),
})
funcs = append(funcs, ts.GetValue)
}
return TimeSeriesPlot{
timeseries: &plot.ScatterUserPlot{
Plot: plot.Scatter{
Name: p.Name,
Title: p.Title,
Type: string(p.Type),
InfoText: p.InfoText,
Layout: plot.ScatterLayout{
BarMode: string(p.BarMode),
Yaxis: plot.ScatterYAxis{
Title: p.YAxisTitle,
TickSuffix: p.YAxisTickSuffix,
},
},
Subplots: subplots,
},
Funcs: funcs,
},
}, nil
}
// TimeSeriesPlot is an opaque type representing a timeseries plot.
// A plot can be created with [TimeSeriesPlotConfig.Build].
type TimeSeriesPlot struct {
timeseries *plot.ScatterUserPlot
}