Skip to content
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

Add sparkline widget. #2631

Merged
merged 16 commits into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/examples/widgets/sparkline.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Sparkline {
width: 100%;
margin: 2;
}
22 changes: 22 additions & 0 deletions docs/examples/widgets/sparkline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import random
from statistics import mean

from textual.app import App, ComposeResult
from textual.widgets._sparkline import Sparkline

random.seed(73)
data = [random.expovariate(1 / 3) for _ in range(1000)]


class SparklineSummaryFunctionApp(App[None]):
CSS_PATH = "sparkline.css"

def compose(self) -> ComposeResult:
yield Sparkline(data, summary_function=max) # (1)!
yield Sparkline(data, summary_function=mean) # (2)!
yield Sparkline(data, summary_function=min) # (3)!


app = SparklineSummaryFunctionApp()
if __name__ == "__main__":
app.run()
8 changes: 8 additions & 0 deletions docs/examples/widgets/sparkline_basic.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Screen {
align: center middle;
}

Sparkline {
width: 3; /* (1)! */
margin: 2;
}
19 changes: 19 additions & 0 deletions docs/examples/widgets/sparkline_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from textual.app import App, ComposeResult
from textual.widgets._sparkline import Sparkline

data = [1, 2, 2, 1] + [1, 4, 3, 1] + [1, 8, 8, 2] # (1)!
rodrigogiraoserrao marked this conversation as resolved.
Show resolved Hide resolved


class SparklineBasicApp(App[None]):
CSS_PATH = "sparkline_basic.css"

def compose(self) -> ComposeResult:
yield Sparkline( # (2)!
data, # (3)!
summary_function=max, # (4)!
)


app = SparklineBasicApp()
if __name__ == "__main__":
app.run()
74 changes: 74 additions & 0 deletions docs/examples/widgets/sparkline_colors.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
Sparkline {
width: 100%;
margin: 1;
}

#fst > .sparkline--max-color {
color: $success;
}
#fst > .sparkline--min-color {
color: $warning;
}

#snd > .sparkline--max-color {
color: $warning;
}
#snd > .sparkline--min-color {
color: $success;
}

#trd > .sparkline--max-color {
color: $error;
}
#trd > .sparkline--min-color {
color: $warning;
}

#frt > .sparkline--max-color {
color: $warning;
}
#frt > .sparkline--min-color {
color: $error;
}

#fft > .sparkline--max-color {
color: $accent;
}
#fft > .sparkline--min-color {
color: $accent 30%;
}

#sxt > .sparkline--max-color {
color: $accent 30%;
}
#sxt > .sparkline--min-color {
color: $accent;
}

#svt > .sparkline--max-color {
color: $error;
}
#svt > .sparkline--min-color {
color: $error 30%;
}

#egt > .sparkline--max-color {
color: $error 30%;
}
#egt > .sparkline--min-color {
color: $error;
}

#nnt > .sparkline--max-color {
color: $success;
}
#nnt > .sparkline--min-color {
color: $success 30%;
}

#tnt > .sparkline--max-color {
color: $success 30%;
}
#tnt > .sparkline--min-color {
color: $success;
}
24 changes: 24 additions & 0 deletions docs/examples/widgets/sparkline_colors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from textual.app import App, ComposeResult
from textual.widgets._sparkline import Sparkline


class SparklineColorsApp(App[None]):
CSS_PATH = "sparkline_colors.css"

def compose(self) -> ComposeResult:
nums = [10, 2, 30, 60, 45, 20, 7, 8, 9, 10, 50, 13, 10, 6, 5, 4, 3, 7, 20]
yield Sparkline(nums, summary_function=max, id="fst")
yield Sparkline(nums, summary_function=max, id="snd")
yield Sparkline(nums, summary_function=max, id="trd")
yield Sparkline(nums, summary_function=max, id="frt")
yield Sparkline(nums, summary_function=max, id="fft")
yield Sparkline(nums, summary_function=max, id="sxt")
yield Sparkline(nums, summary_function=max, id="svt")
yield Sparkline(nums, summary_function=max, id="egt")
yield Sparkline(nums, summary_function=max, id="nnt")
yield Sparkline(nums, summary_function=max, id="tnt")


app = SparklineColorsApp()
if __name__ == "__main__":
app.run()
4 changes: 2 additions & 2 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Widgets are key to making user-friendly interfaces. The builtin widgets should c
* [ ] Braille
* [ ] Sixels, and other image extensions
- [x] Input
* [ ] Validation
* [x] Validation
* [ ] Error / warning states
* [ ] Template types: IP address, physical units (weight, volume), currency, credit card etc
- [X] Select control (pull-down)
Expand All @@ -72,7 +72,7 @@ Widgets are key to making user-friendly interfaces. The builtin widgets should c
- [X] Progress bars
* [ ] Style variants (solid, thin etc)
- [X] Radio boxes
- [ ] Spark-lines
- [X] Spark-lines
- [X] Switch
- [X] Tabs
- [ ] TextArea (multi-line input)
Expand Down
9 changes: 9 additions & 0 deletions docs/widget_gallery.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,15 @@ Select multiple values from a list of options.
```{.textual path="docs/examples/widgets/selection_list_selections.py" press="down,down,down"}
```

## Sparkline

Display numerical data.

[Sparkline reference](./widgets/sparkline.md){ .md-button .md-button--primary }

```{.textual path="docs/examples/widgets/sparkline.py" lines="11"}
```

## Static

Displays simple static content. Typically used as a base class.
Expand Down
112 changes: 112 additions & 0 deletions docs/widgets/sparkline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Sparkline

!!! tip "Added in version 0.27.0"

A widget that is used to visually represent numerical data.

- [ ] Focusable
- [ ] Container

## Examples

### Basic example

The example below illustrates the relationship between the data, its length, the width of the sparkline, and the number of buckets.

!!! tip

The sparkline data is split into equally-sized chunks called buckets.
The number of buckets matches the width of the sparkline.

=== "Output"

```{.textual path="docs/examples/widgets/sparkline_basic.py" lines="5" columns="30"}
```

=== "sparkline_basic.py"

```python hl_lines="4 11 12 13"
--8<-- "docs/examples/widgets/sparkline_basic.py"
```

1. We have 12 data points.
2. This sparkline will have its width set to 3 via CSS.
3. The data (12 numbers) will be split across 3 buckets, so 4 data points for each bucket.
4. Each bar will show the largest value of that bucket.
The largest value of each bucket is 2, 4, and 8, respectively.
That explains why the first bar is half the height of the second and the second bar is half the height of the third.

=== "sparkline_basic.css"

```sass
--8<-- "docs/examples/widgets/sparkline_basic.css"
```

1. By setting the width to 3 we get three buckets.

### Different summary functions

The example below shows a sparkline widget with different summary functions.
The summary function is applied to each bucket to reduce its data into a single number that represents the bucket.

=== "Output"

```{.textual path="docs/examples/widgets/sparkline.py" lines="11"}
```

=== "sparkline.py"

```python hl_lines="15-17"
--8<-- "docs/examples/widgets/sparkline.py"
```

1. Each bar will show the largest value of that bucket.
2. Each bar will show the mean value of that bucket.
3. Each bar will show the smaller value of that bucket.

=== "sparkline.css"

```sass
--8<-- "docs/examples/widgets/sparkline.css"
```

### Changing the colors

The example below shows how to use component classes to change the colors of the sparkline.

=== "Output"

```{.textual path="docs/examples/widgets/sparkline_colors.py" lines=22}
```

=== "sparkline_colors.py"

```python
--8<-- "docs/examples/widgets/sparkline_colors.py"
```

=== "sparkline_colors.css"

```sass
--8<-- "docs/examples/widgets/sparkline_colors.css"
```


## Reactive Attributes

| Name | Type | Default | Description |
| --------- | ----- | ----------- | -------------------------------------------------- |
| `data` | `Sequence[int | float] | None` | `None` | The data represented by the sparkline. |
rodrigogiraoserrao marked this conversation as resolved.
Show resolved Hide resolved
| `summary_function` | `Callable[[Sequence[int | float]], float]` | `max` | The function used to summarise each of the bins. |
rodrigogiraoserrao marked this conversation as resolved.
Show resolved Hide resolved


## Messages

This widget sends no messages.

---


::: textual.widgets.Sparkline
options:
heading_level: 2
1 change: 1 addition & 0 deletions mkdocs-nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ nav:
- "widgets/radioset.md"
- "widgets/select.md"
- "widgets/selection_list.md"
- "widgets/sparkline.md"
- "widgets/static.md"
- "widgets/switch.md"
- "widgets/tabbed_content.md"
Expand Down
4 changes: 3 additions & 1 deletion src/textual/renderables/sparkline.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ def last(l: Sequence[T]) -> T:
console.print(f"data = {nums}\n")
for f in funcs:
console.print(
f"{f.__name__}:\t", Sparkline(nums, width=12, summary_function=f), end=""
f"{f.__name__}:\t",
Sparkline(nums, width=12, summary_function=f),
end="",
)
console.print("\n")
2 changes: 2 additions & 0 deletions src/textual/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from ._radio_set import RadioSet
from ._select import Select
from ._selection_list import SelectionList
from ._sparkline import Sparkline
from ._static import Static
from ._switch import Switch
from ._tabbed_content import TabbedContent, TabPane
Expand Down Expand Up @@ -63,6 +64,7 @@
"RadioSet",
"Select",
"SelectionList",
"Sparkline",
"Static",
"Switch",
"Tab",
Expand Down
1 change: 1 addition & 0 deletions src/textual/widgets/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ from ._radio_button import RadioButton as RadioButton
from ._radio_set import RadioSet as RadioSet
from ._select import Select as Select
from ._selection_list import SelectionList as SelectionList
from ._sparkline import Sparkline as Sparkline
from ._static import Static as Static
from ._switch import Switch as Switch
from ._tabbed_content import TabbedContent as TabbedContent
Expand Down
Loading