README
¶
SknLineChart
Line chart with 120 horizontal, xscale, divisions displayed. The Y scale is limited to 100 divisions. Written in Go using the Fyne GUI framework.
Features
- Multiple Series of datapoints rendered as a single line
- Series should be the same color. Each point in this chart accepts a themed color name
- 120 datapoint are displayed on the x scale of chart, with 100 as the default Y value.
- More than 120 data points causes the earliest points to be rolled off the screen; each series is independently scrolls
- Data points can be added at any time, causing the series to possible scroll automatically
- The 120 x limit will throw and error on creation of the chart, or on the replacement of its active dataset.
- Data point markers are toggled with mouse button 2
- Hovering over a data point will show a popup near the mouse pointer, showing series, value, index, and timestamp of data under mouse
- Mouse button 1 will toggle the sticky datapoint popup
- Labels are available for all four corners of window, include bottom and top centered titles
- left and right middle labels can be used as scale descriptions
- Any label left empty will not be displayed.
- Horizontal and Vertical chart grid lines can also be turned off/on
SknLineChart Interface
type SknLineChart interface {
IsDataPointMarkersEnabled() bool // mouse button 2 toggles
IsHorizGridLinesEnabled() bool
IsVertGridLinesEnabled() bool
IsMousePointDisplayEnabled() bool // hoverable and mouse button one
SetDataPointMarkers(enable bool)
SetHorizGridLines(enable bool)
SetVertGridLines(enable bool)
SetMousePointDisplay(enable bool)
GetTopLeftLabel() string
GetTitle() string
GetTopRightLabel() string
GetMiddleLeftLabel() string
GetMiddleRightLabel() string
GetBottomLeftLabel() string
GetBottomCenteredLabel() string
GetBottomRightLabel() string
SetTopLeftLabel(newValue string)
SetTitle(newValue string)
SetTopRightLabel(newValue string)
SetMiddleLeftLabel(newValue string)
SetMiddleRightLabel(newValue string)
SetBottomLeftLabel(newValue string)
SetBottomCenteredLabel(newValue string)
SetBottomRightLabel(newValue string)
SetMinSize(s fyne.Size)
ApplyDataSeries(seriesName string, newSeries []SknChartDatapoint) error
ApplyDataPoint(seriesName string, newDataPoint SknChartDatapoint)
}
Fyne Custom Widget Strategy
/*
* SknLineChart
* Custom Fyne 2.0 Widget
* Strategy
* 1. Define Widget Named/Exported Struct
* 1. export fields when possible
* 2. Define Widget Renderer Named/unExported Struct
* 1. un-exportable fields when possible
* 3. Define NewWidget() *ExportedStruct method
* 1. Define state variables for this widget
* 2. Extend the BaseWidget
* 3. Define Widget required methods
* 1. CreateRenderer() fyne.WidgetRenderer, call newRenderer() below
* 4. Define any methods required by additional interfaces, like
* desktop.Mouseable for mouse button support
* 1. MouseDown(me MouseEvent)
* 2. MouseUp(me MouseEvent)
* desktop.Hoverable for mouse movement support
* 1. MouseIn(me MouseEvent)
* 2. MouseMoved(me MouseEvent) used to display data point under mouse
* 3. MouseOut()
*
* 4. Define newRenderer() *notExportedStruct method
* 1. Create canvas objects to be used in display
* 2. Initialize there content if practical; not required
* 3. Implement the required WidgetRenderer methods
* 4. Refresh() call refresh on each object
* 5. Layout(s fyne.Size) resize & move objects
* 6. MinSize() fyne.Size return the minimum size needed
* 7. Object() []fyne.Canvas return the objects to be displayed
* 8. Destroy() cleanup if needed to prevent leaks
* 5. In general state methods are the public api with or without getters/setters
* and the renderer creates the displayable objects, applies state/value to them, and
* manages their display.
*
* Critical Notes:
* - if using maps, map[string]interface{}, they will require a mutex to prevent concurrency error cause my concurrent read/writes.
* - data binding creates new var from source var, and its the new var that should be shared as it is synchronized to changes in bond data var.
*/
Example
package main
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"github.com/skoona/sknlinechart/skn/linechart"
"math/rand"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
windowClosed := false
gui := app.NewWithID("net.skoona.sknLineChart")
w := gui.NewWindow("Custom Widget Development")
w.SetOnClosed(func() {
windowClosed = true
fmt.Println("::main() Window Closed")
time.Sleep(2 * time.Second)
})
dataPoints := map[string][]linechart.LineChartDatapoint{} // legend, points
var first, second, many []linechart.LineChartDatapoint
rand.NewSource(50.0)
for x := 1; x < 125; x++ {
val := rand.Float32() * 100.0
if val > 30.0 {
val = 30.0
} else if val < 5.0 {
val = 5.0
}
first = append(first, linechart.NewLineChartDatapoint(
val,
theme.ColorOrange,
time.Now().Format(time.RFC3339)))
}
for x := 1; x < 75; x++ {
val := rand.Float32() * 40.0
if val > 60.0 {
val = 60.0
} else if val < 35.0 {
val = 35.0
}
second = append(second, linechart.NewLineChartDatapoint(
val,
theme.ColorRed,
time.Now().Format(time.RFC3339)))
}
for x := 1; x < 120; x++ {
val := rand.Float32() * 75.0
if val > 90.0 {
val = 90.0
} else if val < 65.0 {
val = 65.0
}
many = append(many, linechart.NewLineChartDatapoint(
val,
theme.ColorPurple,
time.Now().Format(time.RFC3339)))
}
dataPoints["first"] = first
dataPoints["second"] = second
lineChart, err := linechart.NewLineChart("Skoona Line Chart", "Example Time Series", &dataPoints)
if err != nil {
fmt.Println(err.Error())
}
go (func(chart linechart.LineChart) {
time.Sleep(10 * time.Second)
err = lineChart.ApplyDataSeries("many", many)
if err != nil {
fmt.Println("ApplyDataSeries", err.Error())
}
time.Sleep(time.Second)
for i := 0; i < 150; i++ {
if windowClosed {
break
}
chart.ApplyDataPoint("steady", linechart.NewLineChartDatapoint(
rand.Float32()*110.0,
theme.ColorYellow,
time.Now().Format(time.RFC3339)))
if windowClosed {
break
}
time.Sleep(time.Second)
}
})(lineChart)
w.Resize(fyne.NewSize(1024, 756))
//w.SetContent(lineChart)
w.SetContent(container.NewPadded(lineChart))
go func(w *fyne.Window) {
systemSignalChannel := make(chan os.Signal, 1)
signal.Notify(systemSignalChannel, syscall.SIGINT, syscall.SIGTERM)
sig := <-systemSignalChannel // wait on ctrl-c
windowClosed = true
fmt.Println(sig.String())
(*w).Close()
}(&w)
w.ShowAndRun()
time.Sleep(3 * time.Second)
}
Project Layout
├── LICENSE
├── README.md
├── cmd
│ └── sknlinechart
│ └── main.go
├── go.mod
├── go.sum
└── skn
└── linechart
├── datapoint.go
├── interfaces.go
├── linechart.go
└── mapsliceutils.go
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
LICENSE
The application is available as open source under the terms of the MIT License.
Click to show internal directories.
Click to hide internal directories.