Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
falkoschindler committed Oct 23, 2023
0 parents commit ab7c93f
Show file tree
Hide file tree
Showing 167 changed files with 4,849 additions and 0 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Publish Release

on:
workflow_dispatch:
push:
tags:
- v**

jobs:
pypi:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: set up Python
uses: actions/setup-python@v2
with:
python-version: "3.x"
- name: set up Poetry
uses: abatilo/[email protected]
with:
poetry-version: "1.3.1"
- name: get version
id: get_version
run: echo "VERSION=$(echo ${GITHUB_REF/refs\/tags\//})" >> $GITHUB_ENV
- name: set version
run: poetry version ${{ env.VERSION }}
- name: publish
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
run: poetry publish --build
- name: Create GitHub release entry
uses: softprops/action-gh-release@v1
id: create_release
with:
draft: false
prerelease: false
name: ${{ env.VERSION }}
tag_name: ${{ env.VERSION }}
env:
GITHUB_TOKEN: ${{ github.token }}
- name: verify
shell: bash
run: for i in {1..100}; do sleep 2; python -m pip install 'nicegui==${{ env.VERSION }}' && break; done; sleep 5
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
*.egg-info/
21 changes: 21 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"autopep8.args": ["--max-line-length=120"],
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"isort.args": ["--line-length", "120"],
"prettier.printWidth": 120,
"pylint.args": [
"--disable=C0103", // Invalid name (e.g., variable/function/class naming conventions)
"--disable=C0114", // Missing module docstring
"--disable=C0115", // Missing class docstring
"--disable=C0301", // Line too long (exceeds character limit)
"--disable=R0913", // Too many arguments
"--disable=W0102" // Dangerous default value as argument
],
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8",
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}
}
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# NiceGUI Highcharts

This package is an extension for [NiceGUI](https://github.com/zauberzeug/nicegui), an easy-to-use, Python-based UI framework.
It provides a `highcharts` element based on [Highcharts](https://www.highcharts.com/), the popular JavaScript charting library.
Due to Highcharts' restrictive license, this element is not part of the NiceGUI package anymore, but can be install separately.

## Installation

```bash
python3 -m pip install nicegui_highcharts
```

## Usage

Write your nice GUI in a file `main.py`:

```py
from nicegui import ui
from nicegui_highcharts import highcharts

highcharts({
'title': False,
'chart': {'type': 'bar'},
'xAxis': {'categories': ['A', 'B']},
'series': [
{'name': 'Alpha', 'data': [0.1, 0.2]},
{'name': 'Beta', 'data': [0.3, 0.4]},
],
})

ui.run()
```

Launch it with:

```bash
python3 main.py
```
16 changes: 16 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
from nicegui import ui

from nicegui_highcharts import highcharts

highcharts({
'title': False,
'chart': {'type': 'bar'},
'xAxis': {'categories': ['A', 'B']},
'series': [
{'name': 'Alpha', 'data': [0.1, 0.2]},
{'name': 'Beta', 'data': [0.3, 0.4]},
],
})

ui.run()
7 changes: 7 additions & 0 deletions nicegui_highcharts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import events
from .highcharts import Highcharts as highcharts

__all__ = [
'events',
'highcharts',
]
38 changes: 38 additions & 0 deletions nicegui_highcharts/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from dataclasses import dataclass

from nicegui.dataclasses import KWONLY_SLOTS
from nicegui.events import UiEventArguments


@dataclass(**KWONLY_SLOTS)
class ChartEventArguments(UiEventArguments):
event_type: str


@dataclass(**KWONLY_SLOTS)
class ChartPointClickEventArguments(ChartEventArguments):
series_index: int
point_index: int
point_x: float
point_y: float


@dataclass(**KWONLY_SLOTS)
class ChartPointDragStartEventArguments(ChartEventArguments):
pass


@dataclass(**KWONLY_SLOTS)
class ChartPointDragEventArguments(ChartEventArguments):
series_index: int
point_index: int
point_x: float
point_y: float


@dataclass(**KWONLY_SLOTS)
class ChartPointDropEventArguments(ChartEventArguments):
series_index: int
point_index: int
point_x: float
point_y: float
64 changes: 64 additions & 0 deletions nicegui_highcharts/highcharts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
export default {
template: "<div></div>",
mounted() {
setTimeout(() => {
const imports = this.extras.map((extra) => import(window.path_prefix + extra));
Promise.allSettled(imports).then(() => {
this.seriesCount = this.options.series ? this.options.series.length : 0;
this.options.plotOptions = this.options.plotOptions ?? {};
this.options.plotOptions.series = this.options.plotOptions.series ?? {};
this.options.plotOptions.series.point = this.options.plotOptions.series.point ?? {};
this.options.plotOptions.series.point.events = this.options.plotOptions.series.point.events ?? {};
function uncycle(e) {
// Highcharts events are cyclic, so we need to uncycle them
let { point, target, ...rest } = e;
point = point ?? target;
return {
...rest,
point_index: point?.index,
point_x: point?.x,
point_y: point?.y,
series_index: point?.series?.index,
};
}
this.options.plotOptions.series.point.events.click = (e) => this.$emit("pointClick", uncycle(e));
this.options.plotOptions.series.point.events.dragStart = (e) => this.$emit("pointDragStart", uncycle(e));
this.options.plotOptions.series.point.events.drag = (e) => this.$emit("pointDrag", uncycle(e));
this.options.plotOptions.series.point.events.drop = (e) => this.$emit("pointDrop", uncycle(e));
this.chart = Highcharts[this.type](this.$el, this.options);
this.chart.reflow();
});
}, 0); // NOTE: wait for window.path_prefix to be set in app.mounted()
},
beforeDestroy() {
this.destroyChart();
},
beforeUnmount() {
this.destroyChart();
},
methods: {
update_chart() {
if (this.chart) {
while (this.seriesCount > this.options.series.length) {
this.chart.series[0].remove();
this.seriesCount--;
}
while (this.seriesCount < this.options.series.length) {
this.chart.addSeries({}, false);
this.seriesCount++;
}
this.chart.update(this.options);
}
},
destroyChart() {
if (this.chart) {
this.chart.destroy();
}
},
},
props: {
type: String,
options: Object,
extras: Array,
},
};
99 changes: 99 additions & 0 deletions nicegui_highcharts/highcharts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from typing import Callable, Dict, List, Optional

from nicegui import events, ui

from .events import (ChartPointClickEventArguments, ChartPointDragEventArguments, ChartPointDragStartEventArguments,
ChartPointDropEventArguments)


class Highcharts(ui.element,
component='highcharts.js',
libraries=['lib/highcharts/*.js'],
extra_libraries=['lib/highcharts/modules/*.js']):

def __init__(self, options: Dict, *,
type: str = 'chart', extras: List[str] = [], # pylint: disable=redefined-builtin
on_point_click: Optional[Callable] = None,
on_point_drag_start: Optional[Callable] = None,
on_point_drag: Optional[Callable] = None,
on_point_drop: Optional[Callable] = None,
) -> None:
"""Chart
An element to create a chart using `Highcharts <https://www.highcharts.com/>`_.
Updates can be pushed to the chart by changing the `options` property.
After data has changed, call the `update` method to refresh the chart.
By default, a `Highcharts.chart` is created.
To use, e.g., `Highcharts.stockChart` instead, set the `type` property to "stockChart".
:param options: dictionary of Highcharts options
:param type: chart type (e.g. "chart", "stockChart", "mapChart", ...; default: "chart")
:param extras: list of extra dependencies to include (e.g. "annotations", "arc-diagram", "solid-gauge", ...)
:param on_point_click: callback function that is called when a point is clicked
:param on_point_drag_start: callback function that is called when a point drag starts
:param on_point_drag: callback function that is called when a point is dragged
:param on_point_drop: callback function that is called when a point is dropped
"""
super().__init__()
self._props['type'] = type
self._props['options'] = options
self._props['extras'] = extras
self.libraries.extend(library for library in self.extra_libraries if library.path.stem in extras)

if on_point_click:
def handle_point_click(e: events.GenericEventArguments) -> None:
events.handle_event(on_point_click, ChartPointClickEventArguments(
sender=self,
client=self.client,
event_type='point_click',
point_index=e.args['point_index'],
point_x=e.args['point_x'],
point_y=e.args['point_y'],
series_index=e.args['series_index'],
))
self.on('pointClick', handle_point_click, ['point_index', 'point_x', 'point_y', 'series_index'])

if on_point_drag_start:
def handle_point_dragStart(_: events.GenericEventArguments) -> None:
events.handle_event(on_point_drag_start, ChartPointDragStartEventArguments(
sender=self,
client=self.client,
event_type='point_drag_start',
))
self.on('pointDragStart', handle_point_dragStart, [])

if on_point_drag:
def handle_point_drag(e: events.GenericEventArguments) -> None:
events.handle_event(on_point_drag, ChartPointDragEventArguments(
sender=self,
client=self.client,
event_type='point_drag',
point_index=e.args['point_index'],
point_x=e.args['point_x'],
point_y=e.args['point_y'],
series_index=e.args['series_index'],
))
self.on('pointDrag', handle_point_drag, ['point_index', 'point_x', 'point_y', 'series_index'])

if on_point_drop:
def handle_point_drop(e: events.GenericEventArguments) -> None:
events.handle_event(on_point_drop, ChartPointDropEventArguments(
sender=self,
client=self.client,
event_type='point_drop',
point_index=e.args['point_index'],
point_x=e.args['point_x'],
point_y=e.args['point_y'],
series_index=e.args['series_index'],
))
self.on('pointDrop', handle_point_drop, ['point_index', 'point_x', 'point_y', 'series_index'])

@property
def options(self) -> Dict:
"""The options dictionary."""
return self._props['options']

def update(self) -> None:
super().update()
self.run_method('update_chart')
Loading

0 comments on commit ab7c93f

Please sign in to comment.