-
Notifications
You must be signed in to change notification settings - Fork 146
Implement Pie widget #384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Implement Pie widget #384
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
a3b6126
Implement Pie widget with options and drawing functionality
VincenzoManto b54fe2d
Add Pie widget documentation with demo instructions
VincenzoManto 74c3a27
Refactor options validation and enhance ColorOption function for pie …
VincenzoManto 527a603
Refactor pie chart drawing logic to simplify radius calculations and …
VincenzoManto c895777
Add test case for drawing a two-slice pie chart with verification
VincenzoManto fb25721
Refactor pie widget code for improved readability and organization
VincenzoManto 74d9298
Remove unnecessary blank line in pie chart drawing function documenta…
VincenzoManto 4ef3ff7
Update README and pie widget documentation for clarity and accuracy
VincenzoManto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package pie | ||
|
|
||
| import ( | ||
| "github.com/mum4k/termdash/cell" | ||
| ) | ||
|
|
||
| // Option defines a function that sets a specific option for the Pie widget. | ||
| type Option interface { | ||
| // set sets the provided option. | ||
| set(*options) | ||
| } | ||
|
|
||
| // option implements Option. | ||
| type option func(*options) | ||
|
|
||
| // set implements Option.set. | ||
| func (o option) set(opts *options) { | ||
| o(opts) | ||
| } | ||
|
|
||
| // options stores the provided options. | ||
| type options struct { | ||
| colors []cell.Color | ||
| } | ||
|
|
||
| // validates the provided options | ||
| // at the moment no validation is performed cause options are not required | ||
| func (o *options) validate() error { | ||
| return nil | ||
| } | ||
|
|
||
| // ColorOption sets custom colors for the pie chart segments. | ||
| func ColorOption(colors []cell.Color) Option { | ||
| return option(func(opts *options) { | ||
| opts.colors = colors | ||
| }) | ||
| } | ||
|
|
||
| // newOptions creates a new options instance. | ||
| func newOptions() *options { | ||
| return &options{ | ||
| colors: DefaultColors, | ||
|
mum4k marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| // DefaultColors defines a default set of colors used for rendering pie chart segments. | ||
| // These colors are chosen from the predefined cell.Color constants and include a variety | ||
| // of primary and secondary colors to ensure visual distinction between segments. | ||
|
mum4k marked this conversation as resolved.
|
||
| var DefaultColors = []cell.Color{ | ||
| cell.ColorRed, | ||
| cell.ColorGreen, | ||
| cell.ColorBlue, | ||
| cell.ColorYellow, | ||
| cell.ColorMagenta, | ||
| cell.ColorCyan, | ||
| cell.ColorWhite, | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| package pie | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "github.com/mum4k/termdash/cell" | ||
| "github.com/mum4k/termdash/private/canvas" | ||
| "github.com/mum4k/termdash/private/canvas/braille" | ||
| "github.com/mum4k/termdash/private/draw" | ||
| "github.com/mum4k/termdash/terminal/terminalapi" | ||
| "github.com/mum4k/termdash/widgetapi" | ||
| "image" | ||
| "sync" | ||
| ) | ||
|
|
||
| // Pie is the widget that displays a pie chart. | ||
| type Pie struct { | ||
| mu sync.Mutex | ||
| values []int | ||
| total int | ||
| colors []cell.Color | ||
| opts *options | ||
| } | ||
|
|
||
| // New returns a new Pie widget. | ||
| func New(opts ...Option) (*Pie, error) { | ||
| opt := newOptions() | ||
| for _, o := range opts { | ||
| o.set(opt) | ||
| } | ||
| return &Pie{ | ||
| opts: opt, | ||
| }, nil | ||
| } | ||
|
|
||
| // Values must be provided before calling Draw. | ||
| func (p *Pie) Values(values []int, opts ...Option) error { | ||
| // The values must be non-negative and a color must be provided for each value. | ||
| // If not enough colors are provided, they will be reused. | ||
| p.mu.Lock() | ||
| defer p.mu.Unlock() | ||
|
|
||
| if len(values) == 0 { | ||
| return errors.New("values cannot be empty") | ||
| } | ||
|
|
||
| for _, opt := range opts { | ||
| opt.set(p.opts) | ||
| } | ||
|
|
||
| p.values = values | ||
| p.total = 0 | ||
| if len(p.colors) == 0 { | ||
| p.colors = DefaultColors | ||
|
mum4k marked this conversation as resolved.
|
||
| } | ||
| for _, v := range values { | ||
| if v < 0 { | ||
| return errors.New("all values must be non-negative") | ||
| } | ||
| p.total += v | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // it returns the center point and horizontal and vertical radii. | ||
| func pieChartMidAndRadii(ar image.Rectangle) (image.Point, int) { | ||
| width := ar.Dx() * braille.ColMult | ||
| height := ar.Dy() * braille.RowMult | ||
|
|
||
| radiusX := width/2 - 2 | ||
| if radiusX < 1 { | ||
| radiusX = 1 | ||
| } | ||
| mid := image.Point{ | ||
| X: ar.Min.X*braille.ColMult + width/2, | ||
| Y: ar.Min.Y*braille.RowMult + height/2, | ||
| } | ||
| return mid, radiusX | ||
| } | ||
|
|
||
| // Draw renders the Pie widget onto the provided canvas. It calculates the | ||
| // pie chart slices based on the values and colors defined in the Pie struct. | ||
| // Each slice is drawn as a series of radial lines from the inner radius to | ||
| // the outer radius. The method ensures thread safety by locking the Pie's | ||
| // mutex during the drawing process. | ||
| // | ||
| // The number of colors in the list is not significant. If there are more values than | ||
| // colors, the colors will be reused in a round-robin fashion. This ensures that all | ||
| // segments are assigned a color, even if the number of values exceeds the number of | ||
| // available colors in the list. | ||
| // | ||
| // Parameters: | ||
| // - cvs: The canvas onto which the pie chart will be drawn. | ||
| // - meta: Metadata about the widget's environment. | ||
| // | ||
| // Returns: | ||
| // - error: An error if the drawing process fails, or nil if successful. | ||
| func (p *Pie) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error { | ||
| p.mu.Lock() | ||
| defer p.mu.Unlock() | ||
|
|
||
| if p.total <= 0 { | ||
| return nil | ||
| } | ||
|
|
||
| bc, err := braille.New(cvs.Area()) | ||
| if err != nil { | ||
| return fmt.Errorf("braille.New => %v", err) | ||
| } | ||
|
|
||
| mid, radiusX := pieChartMidAndRadii(cvs.Area()) | ||
|
|
||
| innerRadiusX := int(float64(radiusX) * 0.6) | ||
|
|
||
| currentAngle := 0.0 | ||
| for i, value := range p.values { | ||
| endAngle := currentAngle + float64(value)/float64(p.total)*360 // Convert to degrees | ||
| color := p.colors[i%len(p.colors)] | ||
|
|
||
| for j := innerRadiusX; j <= radiusX; j++ { | ||
| // Draw the arc for the current slice. | ||
| if err := draw.BrailleCircle( | ||
| bc, | ||
| mid, | ||
| j, | ||
| draw.BrailleCircleArcOnly(int(currentAngle), int(endAngle)), | ||
| draw.BrailleCircleCellOpts(cell.FgColor(color)), | ||
| ); err != nil { | ||
| return fmt.Errorf("failed to draw pie slice arc: %v", err) | ||
| } | ||
| } | ||
|
|
||
| currentAngle = endAngle | ||
| } | ||
|
|
||
| if err := bc.CopyTo(cvs); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Keyboard input isn't supported on the Pie widget. | ||
| func (*Pie) Keyboard(k *terminalapi.Keyboard, meta *widgetapi.EventMeta) error { | ||
| return errors.New("the Pie widget doesn't support keyboard events") | ||
| } | ||
|
|
||
| // Mouse input isn't supported on the Pie widget. | ||
| func (*Pie) Mouse(m *terminalapi.Mouse, meta *widgetapi.EventMeta) error { | ||
| return errors.New("the Pie widget doesn't support mouse events") | ||
| } | ||
|
|
||
| // Options implements widgetapi.Widget.Options. | ||
| func (p *Pie) Options() widgetapi.Options { | ||
| return widgetapi.Options{ | ||
| Ratio: image.Point{braille.RowMult, braille.ColMult}, | ||
| MinimumSize: image.Point{5, 5}, | ||
| WantKeyboard: widgetapi.KeyScopeNone, | ||
| WantMouse: widgetapi.MouseScopeNone, | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.