Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions nicegui/elements/uplot/dist/index.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions nicegui/elements/uplot/dist/index.js.map

Large diffs are not rendered by default.

3,149 changes: 3,149 additions & 0 deletions nicegui/elements/uplot/package-lock.json

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions nicegui/elements/uplot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "nicegui-uplot-bundle",
"version": "1.0.0",
"description": "uPlot bundling for NiceGUI",
"private": true,
"type": "module",
"scripts": {
"build": "rollup -c rollup.config.mjs --validate",
"clean": "rimraf ./dist/*"
},
"dependencies": {
"uplot": "^1.6.32"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^29.0.2",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-terser": "^1.0.0",
"patch-package": "^8.0.1",
"rimraf": "^6.1.3",
"rollup": "^4.60.0",
"rollup-plugin-postcss": "^4.0.2"
}
}
21 changes: 21 additions & 0 deletions nicegui/elements/uplot/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import nodeResolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import terser from "@rollup/plugin-terser";
import postcss from 'rollup-plugin-postcss';

export default {
input: "./src/index.mjs",
output: {
dir: './dist/',
format: 'es',
sourcemap: true,
},
plugins: [
nodeResolve(),
commonjs(),
postcss(),
terser({
mangle: true,
}),
],
};
4 changes: 4 additions & 0 deletions nicegui/elements/uplot/src/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import 'uplot/dist/uPlot.min.css';
import uPlot from "uplot";

export { uPlot };
114 changes: 114 additions & 0 deletions nicegui/elements/uplot/uplot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Polyfill for Object.is
if (!Object.is) {
Object.defineProperty(Object, "is", {
value: function(x, y) {
return (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y);
},
configurable: true,
enumerable: false,
writable: true
});
}

// Compares uPlot options for update/create/keep
function optionsUpdateState(_lhs, _rhs) {
const {width: lhsWidth, height: lhsHeight, ...lhs} = _lhs;
const {width: rhsWidth, height: rhsHeight, ...rhs} = _rhs;

let state = 'keep';
if (lhsHeight !== rhsHeight || lhsWidth !== rhsWidth) {
state = 'update';
}
if (Object.keys(lhs).length !== Object.keys(rhs).length) {
return 'create';
}
for (const k of Object.keys(lhs)) {
if (!Object.is(lhs[k], rhs[k])) {
state = 'create';
break;
}
}
return state;
}

// Compares uPlot data arrays
function dataMatch(lhs, rhs) {
if (lhs.length !== rhs.length) {
return false;
}
return lhs.every(function(lhsOneSeries, seriesIdx) {
const rhsOneSeries = rhs[seriesIdx];
if (lhsOneSeries.length !== rhsOneSeries.length) {
return false;
}
return lhsOneSeries.every(function(value, valueIdx) {
return value === rhsOneSeries[valueIdx];
});
});
}

// Based on uplot-vue by @skalinichev (https://github.com/skalinichev/uplot-wrappers)
export default {
template: '<div></div>',
props: {
options: { type: Object, required: true },
data: { type: Array, required: true },
resetScales: { type: Boolean, required: false, default: true },
class: { type: String, required: false },
},
data() {
return { _chart: null, _uPlot: null };
},
async mounted() {
const { uPlot } = await import('nicegui-uplot');
this._uPlot = uPlot;
this._create();
},
unmounted() {
this._destroy();
},
watch: {
options: {
handler(options, prevOptions) {
const optionsState = optionsUpdateState(prevOptions, options);
if (!this._chart || optionsState === 'create') {
this._destroy();
this._create();
} else if (optionsState === 'update') {
this._chart.setSize({ width: options.width, height: options.height });
}
},
deep: true
},
data: {
handler(data, prevData) {
if (!this._chart) {
this._create();
} else if (!dataMatch(prevData, data)) {
if (this.$props.resetScales) {
this._chart.setData(data);
} else {
this._chart.setData(data, false);
this._chart.redraw();
}
}
},
deep: true
}
},
methods: {
_destroy() {
if (this._chart) {
this._chart.destroy();
this._chart = null;
}
},
_create() {
if (!this._uPlot) return;
if (this._chart) {
this._destroy();
}
this._chart = new this._uPlot(this.$props.options, this.$props.data, this.$el);
},
},
};
48 changes: 48 additions & 0 deletions nicegui/elements/uplot/uplot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from __future__ import annotations

from typing import Any

import numpy as np

from ...element import Element


class UPlot(Element, component='uplot.js', esm={'nicegui-uplot': 'dist'}):
def __init__(
self,
options: dict,
data: list[list[Any] | np.ndarray] | np.ndarray,
reset_scales: bool = True,
) -> None:
"""
uPlot Element

Renders a uPlot chart.

:param options: uPlot options dictionary (see https://github.com/leeoniya/uPlot/tree/master/docs#basics)
:param data: uPlot data array (see https://github.com/leeoniya/uPlot/tree/master/docs#data-format)
:param reset_scales: Whether to reset scales when updating data (default: True)
"""
super().__init__()
self._props['options'] = options
self._props['data'] = data
self._props['resetScales'] = reset_scales

def update_data(
self,
data: list[list[Any] | np.ndarray] | np.ndarray,
) -> None:
"""
Update the data array and optionally reset scales.
"""
with self._props.suspend_updates():
self._props['data'] = data
super().update()

def update_options(self, options: dict) -> None:
"""
Update the uPlot options.
"""
with self._props.suspend_updates():
self._props['options'] = options
super().update()