tslab-widgets 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/chart/chart.d.ts +45 -0
- package/dist/chart/chart.js +376 -0
- package/dist/chart/plugins/tooltip.d.ts +84 -0
- package/dist/chart/plugins/tooltip.js +240 -0
- package/dist/chart/plugins/trendLine.d.ts +43 -0
- package/dist/chart/plugins/trendLine.js +87 -0
- package/dist/chart/plugins/verticalLine.d.ts +47 -0
- package/dist/chart/plugins/verticalLine.js +76 -0
- package/dist/chart/plugins/volumeProfile.d.ts +60 -0
- package/dist/chart/plugins/volumeProfile.js +93 -0
- package/dist/csv/csv.d.ts +5 -0
- package/dist/csv/csv.js +83 -0
- package/dist/gauge/gauge.d.ts +9 -0
- package/dist/gauge/gauge.js +59 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +11 -0
- package/dist/json/json.d.ts +6 -0
- package/dist/json/json.js +32 -0
- package/package.json +72 -0
- package/readme.md +29 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VolumeProfileSeries = exports.VolumeProfilePaneView = exports.VolumeProfileRenderer = void 0;
|
|
4
|
+
class VolumeProfileRenderer {
|
|
5
|
+
constructor(_data) {
|
|
6
|
+
this._data = _data;
|
|
7
|
+
}
|
|
8
|
+
positionsBox(position1Media, position2Media, pixelRatio) {
|
|
9
|
+
const scaledPosition1 = Math.round(pixelRatio * position1Media);
|
|
10
|
+
const scaledPosition2 = Math.round(pixelRatio * position2Media);
|
|
11
|
+
return {
|
|
12
|
+
length: Math.abs(scaledPosition2 - scaledPosition1) + 1,
|
|
13
|
+
position: Math.min(scaledPosition1, scaledPosition2),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
draw(target) {
|
|
17
|
+
target.useBitmapCoordinateSpace(scope => {
|
|
18
|
+
if (this._data.top === null)
|
|
19
|
+
return;
|
|
20
|
+
this._data.items.forEach(row => {
|
|
21
|
+
if (row.y === null)
|
|
22
|
+
return;
|
|
23
|
+
const itemVerticalPos = this.positionsBox(row.y, row.y - this._data.columnHeight, scope.verticalPixelRatio);
|
|
24
|
+
scope.context.fillStyle = 'rgba(40, 98, 255, 0.55)';
|
|
25
|
+
const itemHorizontalPosP = this.positionsBox(this._data.position === 'left' ? 0 : scope.mediaSize.width, this._data.position === 'left' ? row.widthPositive : (scope.mediaSize.width - row.widthPositive), scope.horizontalPixelRatio);
|
|
26
|
+
scope.context.fillRect(itemHorizontalPosP.position, itemVerticalPos.position, itemHorizontalPosP.length, itemVerticalPos.length - 2);
|
|
27
|
+
scope.context.fillStyle = 'rgba(251, 191, 44, 0.55)';
|
|
28
|
+
const itemHorizontalPosN = this.positionsBox(this._data.position === 'left' ? row.widthPositive : (scope.mediaSize.width - row.widthPositive), this._data.position === 'left' ? (row.widthNegative + row.widthPositive) : (scope.mediaSize.width - (row.widthNegative + row.widthPositive)), scope.horizontalPixelRatio);
|
|
29
|
+
scope.context.fillRect(itemHorizontalPosN.position, itemVerticalPos.position, itemHorizontalPosN.length, itemVerticalPos.length - 2);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.VolumeProfileRenderer = VolumeProfileRenderer;
|
|
35
|
+
class VolumeProfilePaneView {
|
|
36
|
+
constructor(_source) {
|
|
37
|
+
this._source = _source;
|
|
38
|
+
this._columnHeight = 0;
|
|
39
|
+
this._items = [];
|
|
40
|
+
this._top = null;
|
|
41
|
+
this._widthNegative = 0;
|
|
42
|
+
this._widthPositive = 0;
|
|
43
|
+
}
|
|
44
|
+
renderer() {
|
|
45
|
+
return new VolumeProfileRenderer({
|
|
46
|
+
columnHeight: this._columnHeight,
|
|
47
|
+
items: this._items,
|
|
48
|
+
position: this._source.getContext().vpData.position,
|
|
49
|
+
top: this._top,
|
|
50
|
+
widthNegative: this._widthNegative,
|
|
51
|
+
widthPositive: this._widthPositive,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
update() {
|
|
55
|
+
var _a, _b;
|
|
56
|
+
const ctx = this._source.getContext();
|
|
57
|
+
const data = ctx.vpData;
|
|
58
|
+
const series = ctx.series;
|
|
59
|
+
const timeScale = ctx.chart.timeScale();
|
|
60
|
+
const vr = timeScale.getVisibleRange();
|
|
61
|
+
if (!vr || data.fromTime > vr.to) { // hide visible range when context initial time is after chart visible time
|
|
62
|
+
this._items = [];
|
|
63
|
+
return { widthNegative: 0, widthPositive: 0 };
|
|
64
|
+
}
|
|
65
|
+
const _width = timeScale.width() * 0.33;
|
|
66
|
+
const y1 = (_a = series.priceToCoordinate(data.profile[0].price)) !== null && _a !== void 0 ? _a : 0;
|
|
67
|
+
const y2 = (_b = series.priceToCoordinate(data.profile[1].price)) !== null && _b !== void 0 ? _b : timeScale.height();
|
|
68
|
+
this._columnHeight = Math.max(1, y1 - y2);
|
|
69
|
+
const maxVolume = data.profile.reduce((acc, item) => Math.max(acc, item.positiveVolume + item.negativeVolume), 0);
|
|
70
|
+
this._top = y1;
|
|
71
|
+
this._items = data.profile.map(row => {
|
|
72
|
+
return {
|
|
73
|
+
widthNegative: (_width * row.positiveVolume) / maxVolume,
|
|
74
|
+
widthPositive: (_width * row.negativeVolume) / maxVolume,
|
|
75
|
+
y: series.priceToCoordinate(row.price),
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.VolumeProfilePaneView = VolumeProfilePaneView;
|
|
81
|
+
class VolumeProfileSeries {
|
|
82
|
+
constructor(_chart, _series, _vpData) {
|
|
83
|
+
this._chart = _chart;
|
|
84
|
+
this._series = _series;
|
|
85
|
+
this._vpData = _vpData;
|
|
86
|
+
this._vpIndex = null;
|
|
87
|
+
this._paneViews = [new VolumeProfilePaneView(this)];
|
|
88
|
+
}
|
|
89
|
+
getContext() { return { chart: this._chart, series: this._series, vpData: this._vpData }; }
|
|
90
|
+
paneViews() { return this._paneViews; }
|
|
91
|
+
updateAllViews() { this._paneViews.forEach(pw => { pw.update(); }); }
|
|
92
|
+
}
|
|
93
|
+
exports.VolumeProfileSeries = VolumeProfileSeries;
|
package/dist/csv/csv.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.csv = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const tslab = tslib_1.__importStar(require("tslab"));
|
|
6
|
+
// https://github.com/marudhupandiyang/react-csv-to-table
|
|
7
|
+
function csv(data, options) {
|
|
8
|
+
const reactComponentId = `_csv_widget${Math.random().toString(36).substring(2, 9)}`;
|
|
9
|
+
const html = `
|
|
10
|
+
<style type="text/css">
|
|
11
|
+
table.widget-csv-table {
|
|
12
|
+
margin: auto !important;
|
|
13
|
+
margin-bottom: 10px !important;
|
|
14
|
+
> thead {
|
|
15
|
+
background-color: #000;
|
|
16
|
+
border: 1px solid #888 !important;
|
|
17
|
+
}
|
|
18
|
+
> tbody {
|
|
19
|
+
border: 1px solid #888 !important;
|
|
20
|
+
border-top: 0 !important;
|
|
21
|
+
> tr:hover {
|
|
22
|
+
background-color: #172F3F !important;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
${(options === null || options === void 0 ? void 0 : options.customCss) || ''}
|
|
27
|
+
</style>
|
|
28
|
+
<div id="${reactComponentId}" style="margin:auto;" />
|
|
29
|
+
<script type="module">
|
|
30
|
+
import React from "https://esm.sh/react@18.3.1";
|
|
31
|
+
import ReactDOM from "https://esm.sh/react-dom@18.3.1";
|
|
32
|
+
import { CsvToHtmlTable } from "https://esm.sh/react-csv-to-table@0.0.4";
|
|
33
|
+
const h = React.createElement;
|
|
34
|
+
|
|
35
|
+
const reactJupyterLabComponentId = "${reactComponentId}";
|
|
36
|
+
const jupyterLabReactComponentContainer = document.getElementById("${reactComponentId}");
|
|
37
|
+
|
|
38
|
+
const data = [${data.map((d) => JSON.stringify(d)).join(',')}];
|
|
39
|
+
const csvWidgets = groupArrayIntoPairs(data);
|
|
40
|
+
const rows = csvWidgets.map((rowDataPair, idx) => {
|
|
41
|
+
const cid1 = reactJupyterLabComponentId + '-0';
|
|
42
|
+
const cid2 = reactJupyterLabComponentId + '-1';
|
|
43
|
+
const csvTableRowStyle = {
|
|
44
|
+
display: 'flex',
|
|
45
|
+
flexDirection: 'column',
|
|
46
|
+
alignSelf: 'baseline',
|
|
47
|
+
${(options === null || options === void 0 ? void 0 : options.maxHeight) ? `maxHeight:${options.maxHeight},overflowY: "scroll"` : ''}
|
|
48
|
+
};
|
|
49
|
+
const key = 'row-' + idx;
|
|
50
|
+
return (
|
|
51
|
+
|
|
52
|
+
h('div', { key, className: 'csv-' + key, style: { display: 'flex', flexDirection: 'row', margin: 'auto' }}, [
|
|
53
|
+
h('div', { className: 'csv-col-0', style: csvTableRowStyle },
|
|
54
|
+
h(CsvToHtmlTable, { data: rowDataPair[0], csvDelimiter: ',', tableClassName: 'widget-csv-table' })
|
|
55
|
+
),
|
|
56
|
+
h('div', { className: 'csv-col-1', style: csvTableRowStyle },
|
|
57
|
+
rowDataPair[1]
|
|
58
|
+
? h(CsvToHtmlTable, { data: rowDataPair[1], csvDelimiter: ',', tableClassName: 'widget-csv-table' })
|
|
59
|
+
: null,
|
|
60
|
+
),
|
|
61
|
+
])
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
ReactDOM.render(
|
|
66
|
+
h('div', { style: { display: 'flex', flexDirection: 'column' }}, rows),
|
|
67
|
+
jupyterLabReactComponentContainer
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
function groupArrayIntoPairs(arr) {
|
|
71
|
+
const pairs = [];
|
|
72
|
+
for (let i = 0; i < arr.length; i += 2) {
|
|
73
|
+
const pair = [arr[i], arr[i + 1]];
|
|
74
|
+
pairs.push(pair);
|
|
75
|
+
}
|
|
76
|
+
return pairs;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
</script>
|
|
80
|
+
`;
|
|
81
|
+
tslab.display.html(html);
|
|
82
|
+
}
|
|
83
|
+
exports.csv = csv;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.gauge = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const tslab = tslib_1.__importStar(require("tslab"));
|
|
6
|
+
// https://www.npmjs.com/package/react-gauge-chart
|
|
7
|
+
function gauge(width = 200, props) {
|
|
8
|
+
var _a;
|
|
9
|
+
const reactComponentId = `_gauge_widget${Math.random().toString(36).substring(2, 9)}`;
|
|
10
|
+
const html = `
|
|
11
|
+
<div id="${reactComponentId}"></div>
|
|
12
|
+
<style type="text/css">
|
|
13
|
+
g.text-group {
|
|
14
|
+
text-shadow: 5px 5px 5px #000;
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
17
|
+
<script type="module">
|
|
18
|
+
import React from "https://esm.sh/react@18.3.1";
|
|
19
|
+
import ReactDOM from "https://esm.sh/react-dom@18.3.1";
|
|
20
|
+
import GaugeChart from "https://esm.sh/react-gauge-chart@0.5.1";
|
|
21
|
+
const h = React.createElement;
|
|
22
|
+
|
|
23
|
+
const reactJupyterLabComponentId = "${reactComponentId}";
|
|
24
|
+
const jupyterLabReactComponentContainer = document.getElementById("${reactComponentId}");
|
|
25
|
+
const props = JSON.parse('${JSON.stringify(props)}');
|
|
26
|
+
const styles = {
|
|
27
|
+
wrapper: {
|
|
28
|
+
width: '${width}px',
|
|
29
|
+
margin: 'auto',
|
|
30
|
+
display: 'flex',
|
|
31
|
+
flexDirection: 'column',
|
|
32
|
+
alignItems: 'center',
|
|
33
|
+
backgroundColor: '#000',
|
|
34
|
+
border: '1px solid #ccc',
|
|
35
|
+
color: '#fff',
|
|
36
|
+
},
|
|
37
|
+
title: { fontWeight: 'bold', fontSize: 13, marginBottom: '5px', paddingTop: '8px', whiteSpace: 'pre', color: '#fff' },
|
|
38
|
+
legend: { fontWeight: 'normal', fontSize: 12, marginBottom: '5px', whiteSpace: 'pre', color: '#dcdcdc' },
|
|
39
|
+
};
|
|
40
|
+
ReactDOM.render(
|
|
41
|
+
h('div', { style: styles.wrapper }, [
|
|
42
|
+
h('div', { style: styles.title }, '${(_a = props.title) !== null && _a !== void 0 ? _a : ''}'),
|
|
43
|
+
h(GaugeChart, {
|
|
44
|
+
id: 'rg-${reactComponentId}',
|
|
45
|
+
colors: ['#D10363', '#4CCD99'],
|
|
46
|
+
needleColor: '#888',
|
|
47
|
+
${props.score ? `formatTextValue: value => '${props.score}',` : ''}
|
|
48
|
+
...props,
|
|
49
|
+
}),
|
|
50
|
+
h('div', { style: styles.legend }, '${props.legend}'),
|
|
51
|
+
]),
|
|
52
|
+
jupyterLabReactComponentContainer
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
</script>
|
|
56
|
+
`;
|
|
57
|
+
tslab.display.html(html);
|
|
58
|
+
}
|
|
59
|
+
exports.gauge = gauge;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { chart } from './chart/chart';
|
|
2
|
+
export { csv } from './csv/csv';
|
|
3
|
+
export { gauge } from './gauge/gauge';
|
|
4
|
+
export { json } from './json/json';
|
|
5
|
+
export type { IPoint } from './chart/plugins/trendLine';
|
|
6
|
+
export type { IVertLineOptions } from './chart/plugins/verticalLine';
|
|
7
|
+
export type { IVolumeProfileData } from './chart/plugins/volumeProfile';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.json = exports.gauge = exports.csv = exports.chart = void 0;
|
|
4
|
+
var chart_1 = require("./chart/chart");
|
|
5
|
+
Object.defineProperty(exports, "chart", { enumerable: true, get: function () { return chart_1.chart; } });
|
|
6
|
+
var csv_1 = require("./csv/csv");
|
|
7
|
+
Object.defineProperty(exports, "csv", { enumerable: true, get: function () { return csv_1.csv; } });
|
|
8
|
+
var gauge_1 = require("./gauge/gauge");
|
|
9
|
+
Object.defineProperty(exports, "gauge", { enumerable: true, get: function () { return gauge_1.gauge; } });
|
|
10
|
+
var json_1 = require("./json/json");
|
|
11
|
+
Object.defineProperty(exports, "json", { enumerable: true, get: function () { return json_1.json; } });
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.json = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const tslab = tslib_1.__importStar(require("tslab"));
|
|
6
|
+
function json(opts) {
|
|
7
|
+
const width = opts.width || 400;
|
|
8
|
+
const height = opts.height || 200;
|
|
9
|
+
const reactComponentId = `_json_widget${Math.random().toString(36).substring(2, 9)}`;
|
|
10
|
+
const html = `
|
|
11
|
+
<div
|
|
12
|
+
id="${reactComponentId}"
|
|
13
|
+
style="width:${width + 50}px;height:${height + 50}px;margin:auto;max-height:${height + 50}px;overflow:scroll;">
|
|
14
|
+
</div>
|
|
15
|
+
<script type="module">
|
|
16
|
+
import React from "https://esm.sh/react@18.3.1";
|
|
17
|
+
import ReactDOM from "https://esm.sh/react-dom@18.3.1";
|
|
18
|
+
import ReactJsonTree from "https://esm.sh/react-json-tree@0.15.0";
|
|
19
|
+
|
|
20
|
+
const reactJupyterLabComponentId = "${reactComponentId}";
|
|
21
|
+
const jupyterLabReactComponentContainer = document.getElementById("${reactComponentId}");
|
|
22
|
+
const args = JSON.parse('${JSON.stringify(opts.data)}');
|
|
23
|
+
ReactDOM.render(React.createElement(
|
|
24
|
+
ReactJsonTree,
|
|
25
|
+
{ data: args },
|
|
26
|
+
), jupyterLabReactComponentContainer);
|
|
27
|
+
|
|
28
|
+
</script>
|
|
29
|
+
`;
|
|
30
|
+
tslab.display.html(html);
|
|
31
|
+
}
|
|
32
|
+
exports.json = json;
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tslab-widgets",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A set of widgets for ts-lab environment including financial charts",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "npx tsc",
|
|
8
|
+
"lint": "npx eslint",
|
|
9
|
+
"reset": "rm -rf node_modules && rm -rf dist && npm install"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"tslab",
|
|
17
|
+
"jupyter",
|
|
18
|
+
"widgets",
|
|
19
|
+
"chart",
|
|
20
|
+
"candlesticks",
|
|
21
|
+
"json",
|
|
22
|
+
"csv",
|
|
23
|
+
"typescript",
|
|
24
|
+
"ipynb",
|
|
25
|
+
"data science",
|
|
26
|
+
"jupyter-lab",
|
|
27
|
+
"notebook",
|
|
28
|
+
"lightweight-charts"
|
|
29
|
+
],
|
|
30
|
+
"author": "Rodrigo P. (https://github.com/rodrigopivi)",
|
|
31
|
+
"license": "Apache-2.0",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/rodrigopivi/tslab-widgets"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/rodrigopivi/tslab-widgets/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/rodrigopivi/tslab-widgets#readme",
|
|
43
|
+
"private": false,
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"tslab": "1.0.22"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@babel/core": "7.25.2",
|
|
49
|
+
"@babel/preset-react": "7.24.7",
|
|
50
|
+
"@types/node": "22.4.1",
|
|
51
|
+
"@types/react": "18.3.4",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "7.16.0",
|
|
53
|
+
"@typescript-eslint/parser": "7.16.0",
|
|
54
|
+
"cspell": "8.14.2",
|
|
55
|
+
"eslint": "8.57.0",
|
|
56
|
+
"eslint-config-prettier": "9.1.0",
|
|
57
|
+
"eslint-plugin-deprecation": "3.0.0",
|
|
58
|
+
"eslint-plugin-import": "2.29.1",
|
|
59
|
+
"eslint-plugin-perfectionist": "2.11.0",
|
|
60
|
+
"eslint-plugin-react": "7.35.0",
|
|
61
|
+
"eslint-plugin-react-hooks": "4.6.2",
|
|
62
|
+
"eslint-plugin-simple-import-sort": "12.1.1",
|
|
63
|
+
"eslint-plugin-unicorn": "54.0.0",
|
|
64
|
+
"eslint-plugin-unused-imports": "3.2.0",
|
|
65
|
+
"lightweight-charts": "4.2.0",
|
|
66
|
+
"prettier": "3.3.3",
|
|
67
|
+
"react": "18.3.1",
|
|
68
|
+
"tslib": "2.6.3",
|
|
69
|
+
"typescript": "5.5.4",
|
|
70
|
+
"webpack-cli": "5.1.4"
|
|
71
|
+
}
|
|
72
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<center>
|
|
2
|
+
<h2>⚙️📟📈 tslab-widgets 📈📟⚙️</h1>
|
|
3
|
+
</center>
|
|
4
|
+
|
|
5
|
+
<blockquote>
|
|
6
|
+
A set of widgets for tslab (the interactive programming environment on top of jupyter lab that supports typescript and javascript).
|
|
7
|
+
</blockquote>
|
|
8
|
+
<hr />
|
|
9
|
+
<br>
|
|
10
|
+
|
|
11
|
+
### Setup:
|
|
12
|
+
|
|
13
|
+
- Install [tslab](https://github.com/yunabe/tslab)
|
|
14
|
+
- `npm i tslab-widgets`
|
|
15
|
+
|
|
16
|
+
### Widget list:
|
|
17
|
+
|
|
18
|
+
- Chart (thanks to `lightweight-charts`)
|
|
19
|
+
- CSV
|
|
20
|
+
- Gauge
|
|
21
|
+
- JSON
|
|
22
|
+
|
|
23
|
+
Review the [example jupyter notebook](https://github.com/rodrigopivi/tslab-widgets/blob/main/exampmle.ipynb).
|
|
24
|
+
|
|
25
|
+
### Author
|
|
26
|
+
|
|
27
|
+
Rodrigo P.
|
|
28
|
+
|
|
29
|
+
[](https://www.buymeacoffee.com/rodrigopivi)
|