@cehlers88/chart 1.2.0 → 1.2.1
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/dist/ChartEngine.d.ts +18 -0
- package/dist/Factory/DataGroupFactory.d.ts +20 -0
- package/dist/Utils/ChartStyle.d.ts +69 -0
- package/dist/customElement/CanvasDiagram.d.ts +14 -0
- package/dist/index.js +19 -8
- package/dist/interface/ICanvasDiagram.d.ts +1 -0
- package/dist/interface/IChartEngine.d.ts +1 -0
- package/dist/interface/IChartPlugin.d.ts +2 -0
- package/dist/plugin/AbstractPlugin.d.ts +30 -12
- package/dist/plugin/DiagramLine.d.ts +71 -8
- package/dist/plugin/LegendsPlugin.d.ts +17 -1
- package/dist/type/TData.d.ts +1 -0
- package/dist/type/TDataGroup.d.ts +2 -0
- package/package.json +3 -2
package/dist/ChartEngine.d.ts
CHANGED
|
@@ -24,6 +24,17 @@ export default class ChartEngine implements IChartEngine {
|
|
|
24
24
|
plugins?: IChartPlugin[];
|
|
25
25
|
});
|
|
26
26
|
addPlugin(plugin: IChartPlugin): ChartEngine;
|
|
27
|
+
/**
|
|
28
|
+
* Changes the size of the canvas (in canvas pixels). The plugins rebuild
|
|
29
|
+
* their buffers, so the chart is drawn sharp in the new size instead of
|
|
30
|
+
* being stretched by css.
|
|
31
|
+
*/
|
|
32
|
+
resize(width: number, height: number): ChartEngine;
|
|
33
|
+
/**
|
|
34
|
+
* Converts a pointer position (css pixels) into canvas pixels - both differ
|
|
35
|
+
* as soon as the canvas is scaled by css, e.g. by `width: 100%`.
|
|
36
|
+
*/
|
|
37
|
+
private toCanvasPosition;
|
|
27
38
|
getDrawAreaRect(): TRect;
|
|
28
39
|
getPlugin(pluginName: string): IChartPlugin | undefined;
|
|
29
40
|
getPlugins(): IChartPlugin[];
|
|
@@ -58,6 +69,13 @@ export default class ChartEngine implements IChartEngine {
|
|
|
58
69
|
* Removes all data groups.
|
|
59
70
|
*/
|
|
60
71
|
clearData(): IChartEngine;
|
|
72
|
+
/**
|
|
73
|
+
* Appends a value - e.g. a new measurement that arrived after the page was
|
|
74
|
+
* loaded - to a data group. The value may be given as `{value, timestamp}`
|
|
75
|
+
* or `{value, label}`. A group with a `maxLength` drops its oldest values,
|
|
76
|
+
* so a live chart keeps its window. Without a group name the value goes to
|
|
77
|
+
* the only group of the chart; an unknown group is created.
|
|
78
|
+
*/
|
|
61
79
|
addValue(value: any, groupName?: string): IChartEngine;
|
|
62
80
|
/**
|
|
63
81
|
* Replaces a single value within a data group.
|
|
@@ -10,6 +10,11 @@ export default class DataGroupFactory {
|
|
|
10
10
|
* objects like {value, valueStart, label}.
|
|
11
11
|
*/
|
|
12
12
|
static createDataValue(valueDef: any): TData;
|
|
13
|
+
/**
|
|
14
|
+
* Unix timestamps in seconds or milliseconds (see `toMilliseconds`),
|
|
15
|
+
* stored as milliseconds; everything else means "no timestamp".
|
|
16
|
+
*/
|
|
17
|
+
static normalizeTimestamp(timestamp: any): number | undefined;
|
|
13
18
|
static createDataGroup(values: any[], groupLabel?: string, color?: string): TDataGroup;
|
|
14
19
|
/**
|
|
15
20
|
* Parses a data group definition. Beside the JSON notation used by the
|
|
@@ -17,6 +22,21 @@ export default class DataGroupFactory {
|
|
|
17
22
|
* may be passed, so data can be handed over programmatically at runtime.
|
|
18
23
|
*/
|
|
19
24
|
static parseDataGroupsDefinition(value: string | any[] | object): TDataGroup[];
|
|
25
|
+
/**
|
|
26
|
+
* Applies the optional parts of an object definition to a data group:
|
|
27
|
+
* `unit` (shown in tooltips and axis labels), `labels` (one label per value),
|
|
28
|
+
* `timestamps` (one unix timestamp per value, in seconds or milliseconds -
|
|
29
|
+
* shown as time labels) and `maxLength` (values added later push the oldest
|
|
30
|
+
* ones out, so a live chart keeps its time window). Labels and timestamps of
|
|
31
|
+
* single values given as `{value, label, timestamp}` win.
|
|
32
|
+
*/
|
|
33
|
+
static applyGroupOptions(dataGroup: TDataGroup, definition: {
|
|
34
|
+
unit?: string;
|
|
35
|
+
labels?: any[];
|
|
36
|
+
maxLength?: number;
|
|
37
|
+
timestamps?: any[];
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
}): TDataGroup;
|
|
20
40
|
static getNextColor(): string;
|
|
21
41
|
static setColor(color: string, index: number): void;
|
|
22
42
|
static setColors(colors: string[]): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library ChartStyle
|
|
3
|
+
* Created 29.09.26
|
|
4
|
+
*
|
|
5
|
+
* Shared helpers for the look of the charts: theme colours, colour alpha,
|
|
6
|
+
* number formatting and "nice" axis ticks.
|
|
7
|
+
*
|
|
8
|
+
* @author Christoph Ehlers
|
|
9
|
+
*/
|
|
10
|
+
export type TChartTheme = {
|
|
11
|
+
textColor: string;
|
|
12
|
+
gridColor: string;
|
|
13
|
+
tooltipBackground: string;
|
|
14
|
+
tooltipBorder: string;
|
|
15
|
+
tooltipTextColor: string;
|
|
16
|
+
font: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The defaults are mid-tone colours, so a chart stays readable on a dark as
|
|
20
|
+
* well as on a light background when the host page defines nothing.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_THEME: TChartTheme;
|
|
23
|
+
/**
|
|
24
|
+
* Reads the theme from the CSS custom properties `--chart-*` of the given
|
|
25
|
+
* element, so the host page can adapt the charts to its own theme without
|
|
26
|
+
* touching any plugin options.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readTheme(element: any): TChartTheme;
|
|
29
|
+
/**
|
|
30
|
+
* Returns the given colour with the given opacity. Hex colours (#rgb, #rrggbb,
|
|
31
|
+
* #rrggbbaa) and rgb()/rgba() are supported; any other notation is returned
|
|
32
|
+
* unchanged, which only costs the transparency.
|
|
33
|
+
*/
|
|
34
|
+
export declare function withAlpha(color: string, alpha: number): string;
|
|
35
|
+
/**
|
|
36
|
+
* The language of the page (`<html lang>`), so numbers and times are written
|
|
37
|
+
* the way the rest of the page writes them.
|
|
38
|
+
*/
|
|
39
|
+
export declare function pageLocale(): string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Formats a value for axis labels and tooltips: at most `maxDecimals`
|
|
42
|
+
* decimals, grouped by the locale of the page.
|
|
43
|
+
*/
|
|
44
|
+
export declare function formatValue(value: any, maxDecimals?: number, locale?: string | undefined): string;
|
|
45
|
+
/**
|
|
46
|
+
* Calculates evenly spaced, human friendly axis ticks (1, 2, 2.5, 5 × 10^n)
|
|
47
|
+
* which enclose the given range.
|
|
48
|
+
*/
|
|
49
|
+
export declare function niceTicks(min: number, max: number, maxTicks?: number): {
|
|
50
|
+
min: number;
|
|
51
|
+
max: number;
|
|
52
|
+
step: number;
|
|
53
|
+
ticks: number[];
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Returns the number of decimals needed to show the given step exactly.
|
|
57
|
+
*/
|
|
58
|
+
export declare function decimalsOf(step: number): number;
|
|
59
|
+
/**
|
|
60
|
+
* Normalizes a unix timestamp in seconds or milliseconds (see
|
|
61
|
+
* SECONDS_MILLISECONDS_BOUNDARY) to milliseconds. 0, negative values, null
|
|
62
|
+
* and anything unparsable mean "no timestamp".
|
|
63
|
+
*/
|
|
64
|
+
export declare function toMilliseconds(timestamp: any): number | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Turns unix timestamps (seconds or milliseconds) into short labels. The date
|
|
67
|
+
* is only part of the label when the timestamps span more than one day.
|
|
68
|
+
*/
|
|
69
|
+
export declare function formatTimestamps(timestamps: any[], locale?: string | undefined): (string | undefined)[];
|
|
@@ -14,12 +14,20 @@ declare class CanvasDiagram extends HTMLElement implements ICanvasDiagram {
|
|
|
14
14
|
private _engine;
|
|
15
15
|
private _ignoreValueAttribute;
|
|
16
16
|
private _pendingActions;
|
|
17
|
+
private _resizeObserver;
|
|
18
|
+
private _responsive;
|
|
19
|
+
private _runningBeforeDisconnect;
|
|
17
20
|
static get observedAttributes(): string[];
|
|
18
21
|
connectedCallback(): void;
|
|
19
22
|
disconnectedCallback(): void;
|
|
20
23
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
21
24
|
addPlugin(plugin: IChartPlugin): ICanvasDiagram;
|
|
22
25
|
getPlugin(pluginName: string): IChartPlugin | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Changes the size of the diagram (in pixels) and redraws it sharply in
|
|
28
|
+
* the new size.
|
|
29
|
+
*/
|
|
30
|
+
resize(width: number, height: number): ICanvasDiagram;
|
|
23
31
|
getEngine(): IChartEngine | undefined;
|
|
24
32
|
addDataGroup(dataGroup: TDataGroup): ICanvasDiagram;
|
|
25
33
|
addValue(value: any, groupName?: string): ICanvasDiagram;
|
|
@@ -44,7 +52,13 @@ declare class CanvasDiagram extends HTMLElement implements ICanvasDiagram {
|
|
|
44
52
|
*/
|
|
45
53
|
private withEngine;
|
|
46
54
|
private flushPendingActions;
|
|
55
|
+
/**
|
|
56
|
+
* A width given in percent (`width="100%"`) or as `auto` makes the diagram
|
|
57
|
+
* responsive: it takes the width of its container and is redrawn in the
|
|
58
|
+
* new size whenever that changes.
|
|
59
|
+
*/
|
|
47
60
|
private createCanvas;
|
|
61
|
+
private observeSize;
|
|
48
62
|
private evalArguments;
|
|
49
63
|
}
|
|
50
64
|
export default CanvasDiagram;
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ eval("{!function(t,e){ true?module.exports=e():0}(this,()=>(()=>{\"use strict\";
|
|
|
36
36
|
(module, __webpack_exports__, __webpack_require__) {
|
|
37
37
|
|
|
38
38
|
"use strict";
|
|
39
|
-
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, `canvas-diagram {\n box-sizing: border-box;\n display: inline-block;\n}`, \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/style/CanvasDiagram.css?./node_modules/css-loader/dist/cjs.js\n}");
|
|
39
|
+
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, `canvas-diagram {\n box-sizing: border-box;\n display: inline-block;\n}\n\ncanvas-diagram > canvas {\n display: block;\n /* the chart draws on a transparent canvas; the host page may give it a\n background, e.g. the surface colour of its theme */\n background: var(--chart-background, transparent);\n border-radius: var(--chart-border-radius, 0);\n touch-action: pan-y;\n}\n`, \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/style/CanvasDiagram.css?./node_modules/css-loader/dist/cjs.js\n}");
|
|
40
40
|
|
|
41
41
|
/***/ },
|
|
42
42
|
|
|
@@ -146,7 +146,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa
|
|
|
146
146
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
147
147
|
|
|
148
148
|
"use strict";
|
|
149
|
-
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ ChartEngine)\n/* harmony export */ });\n/* harmony import */ var _plugin_EnrichGroupInfosPlugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./plugin/EnrichGroupInfosPlugin */ \"./src/plugin/EnrichGroupInfosPlugin.ts\");\n/* harmony import */ var _Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Factory/DataGroupFactory */ \"./src/Factory/DataGroupFactory.ts\");\n/* harmony import */ var _plugin_LegendsPlugin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./plugin/LegendsPlugin */ \"./src/plugin/LegendsPlugin.ts\");\n/* harmony import */ var _Registry_EntityRegistry__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./Registry/EntityRegistry */ \"./src/Registry/EntityRegistry.ts\");\n/* harmony import */ var _plugin_PaginationPlugin__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./plugin/PaginationPlugin */ \"./src/plugin/PaginationPlugin.ts\");\n/* harmony import */ var _Camera__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./Camera */ \"./src/Camera.ts\");\n/**\n * Library ChartEngine\n * Created 26.11.22\n *\n * @author Christoph Ehlers\n */\n\n\n\n\n\n\nconst _runStack = [];\nlet _isRunning = false;\nconst _exec = () => {\n if (_isRunning)\n return;\n _isRunning = true;\n window.requestAnimationFrame(() => {\n _runStack.map(f => f());\n _isRunning = false;\n if (_runStack.length > 0)\n _exec();\n });\n};\nclass ChartEngine {\n _camera = new _Camera__WEBPACK_IMPORTED_MODULE_5__[\"default\"]();\n _canvasElement;\n _entityRegistry = new _Registry_EntityRegistry__WEBPACK_IMPORTED_MODULE_3__[\"default\"]();\n _ctx;\n _dataGroups = [];\n _dataGroupsSignature = '';\n _processedDataGroups = [];\n _plugins = [];\n _pluginProperties = {};\n _created = false;\n _runStackEntry;\n constructor(canvasElement, properties = {\n plugins: []\n }) {\n this._canvasElement = canvasElement;\n this._ctx = canvasElement.getContext('2d');\n this._canvasElement.height = this._canvasElement.offsetHeight;\n this._canvasElement.width = this._canvasElement.offsetWidth;\n [\n new _plugin_EnrichGroupInfosPlugin__WEBPACK_IMPORTED_MODULE_0__[\"default\"](),\n new _plugin_PaginationPlugin__WEBPACK_IMPORTED_MODULE_4__[\"default\"](),\n new _plugin_LegendsPlugin__WEBPACK_IMPORTED_MODULE_2__[\"default\"](),\n ...(properties.plugins || []),\n ].map(plugin => {\n this.addPlugin(plugin);\n });\n this._canvasElement.addEventListener('pointermove', (e) => {\n this._plugins.map(p => p.handleHover(e.offsetX + this._camera.getX(), e.offsetY + this._camera.getY()));\n });\n this._canvasElement.addEventListener('wheel', (e) => {\n this._plugins.map(p => p.handleScroll(e.deltaX, e.deltaY));\n });\n this._canvasElement.addEventListener('pointerout', (e) => {\n this._plugins.map(p => p.handleLeave());\n });\n }\n addPlugin(plugin) {\n for (let key in this._pluginProperties) {\n plugin.setProperty(key, this._pluginProperties[key]);\n }\n plugin\n .setCamera(this._camera)\n .setEntityRegistry(this._entityRegistry);\n this._plugins.push(plugin);\n if (this._created) {\n // The chart is already running, so the plugin has to catch up.\n plugin.create(this._canvasElement, this.getPipelineDataGroups());\n }\n return this;\n }\n getDrawAreaRect() {\n return {\n x: 0,\n y: 0,\n w: this._canvasElement.width,\n h: this._canvasElement.height\n };\n }\n getPlugin(pluginName) {\n return this._plugins.find((plugin) => plugin.getName() === pluginName);\n }\n getPlugins() {\n return this._plugins.sort((p1, p2) => p1.getPriority() - p2.getPriority());\n }\n setPluginProperty(propertyName, value) {\n this._pluginProperties[propertyName] = value;\n this._plugins.map(p => p.setProperty(propertyName, value));\n return this;\n }\n create() {\n this._plugins.map(p => p.create(this._canvasElement, this.getPipelineDataGroups()));\n this._created = true;\n return this;\n }\n addDataGroup(dataGroup) {\n this._dataGroups.push(dataGroup);\n return this;\n }\n /**\n * Returns the data groups currently held by the engine.\n * The returned groups are the live objects; changes made to them are\n * picked up with the next update cycle.\n */\n getDataGroups() {\n return this._dataGroups;\n }\n /**\n * Returns a single data group, addressed either by its label or by its index.\n */\n getDataGroup(group) {\n if (typeof group === 'number') {\n return this._dataGroups[group];\n }\n return this._dataGroups.find((dataGroup) => dataGroup.label === group);\n }\n /**\n * Replaces all data groups at once.\n */\n setDataGroups(dataGroups) {\n this._dataGroups = dataGroups || [];\n return this;\n }\n /**\n * Replaces the complete data of the chart. Accepts everything\n * `DataGroupFactory.parseDataGroupsDefinition` understands: a JSON string,\n * a plain array of values or an object keyed by group name.\n */\n setData(dataDefinition) {\n return this.setDataGroups(_Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__[\"default\"].parseDataGroupsDefinition(dataDefinition));\n }\n /**\n * Removes a data group, addressed either by its label or by its index.\n */\n removeDataGroup(group) {\n const index = typeof group === 'number' ? group : this._dataGroups.findIndex((dataGroup) => dataGroup.label === group);\n if (index > -1 && index < this._dataGroups.length) {\n this._dataGroups.splice(index, 1);\n }\n return this;\n }\n /**\n * Removes all data groups.\n */\n clearData() {\n this._dataGroups = [];\n return this;\n }\n addValue(value, groupName = '') {\n const dataGroup = this._dataGroups.find((group) => group.label === groupName);\n if (dataGroup) {\n dataGroup.data.push(_Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__[\"default\"].createDataValue(value));\n }\n else {\n this._dataGroups.push(_Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__[\"default\"].createDataGroup([value], groupName));\n }\n return this;\n }\n /**\n * Replaces a single value within a data group.\n */\n setValue(group, dataIndex, value) {\n const dataGroup = this.getDataGroup(group);\n if (!dataGroup) {\n throw new Error('ChartEngine.setValue: data group \"' + group + '\" not found.');\n }\n if (!Number.isSafeInteger(dataIndex) || dataIndex < 0) {\n throw new Error('ChartEngine.setValue: data index must be a non-negative integer.');\n }\n while (dataGroup.data.length <= dataIndex) {\n dataGroup.data.push(_Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__[\"default\"].createDataValue(null));\n }\n dataGroup.data[dataIndex] = _Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__[\"default\"].createDataValue(value);\n return this;\n }\n /**\n * Removes a single value from a data group.\n */\n removeValue(group, dataIndex) {\n const dataGroup = this.getDataGroup(group);\n if (dataGroup && dataIndex > -1 && dataIndex < dataGroup.data.length) {\n dataGroup.data.splice(dataIndex, 1);\n }\n return this;\n }\n draw() {\n this._ctx.clearRect(0, 0, this._canvasElement.width, this._canvasElement.height);\n this.getPlugins().map(p => p.draw());\n return this;\n }\n update() {\n let dataGroups = this.getPipelineDataGroups();\n this.getPlugins().map(p => {\n dataGroups = p.update(dataGroups).dataGroups;\n });\n return this;\n }\n /**\n * Returns the data groups handed to the plugins. They are a detached copy\n * of the data owned by the engine and are renewed whenever that data\n * changed. Without that separation the plugins would hold references to\n * the very objects manipulated from outside, so neither they nor the\n * engine could tell a manipulation from the state of the last frame.\n */\n getPipelineDataGroups() {\n const signature = JSON.stringify(this._dataGroups);\n if (signature !== this._dataGroupsSignature) {\n this._dataGroupsSignature = signature;\n this._processedDataGroups = JSON.parse(signature);\n }\n return this._processedDataGroups;\n }\n start() {\n if (this._runStackEntry) {\n // already running\n return;\n }\n if (!this._created) {\n this.create();\n }\n this._runStackEntry = () => {\n this.update();\n this.draw();\n };\n _runStack.push(this._runStackEntry);\n _exec();\n }\n /**\n * Stops the render loop of this engine. The data stays untouched, so the\n * chart can be started again later on.\n */\n stop() {\n if (this._runStackEntry) {\n const index = _runStack.indexOf(this._runStackEntry);\n if (index > -1) {\n _runStack.splice(index, 1);\n }\n this._runStackEntry = undefined;\n }\n return this;\n }\n isRunning() {\n return this._runStackEntry !== undefined;\n }\n getEngineCallbacks() {\n return {\n updateData: (indexGroup, indexData, newData) => {\n this._dataGroups[indexGroup].data[indexData] = newData;\n return this;\n }\n };\n }\n}\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/ChartEngine.ts?\n}");
|
|
149
|
+
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ ChartEngine)\n/* harmony export */ });\n/* harmony import */ var _plugin_EnrichGroupInfosPlugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./plugin/EnrichGroupInfosPlugin */ \"./src/plugin/EnrichGroupInfosPlugin.ts\");\n/* harmony import */ var _Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Factory/DataGroupFactory */ \"./src/Factory/DataGroupFactory.ts\");\n/* harmony import */ var _plugin_LegendsPlugin__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./plugin/LegendsPlugin */ \"./src/plugin/LegendsPlugin.ts\");\n/* harmony import */ var _Registry_EntityRegistry__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./Registry/EntityRegistry */ \"./src/Registry/EntityRegistry.ts\");\n/* harmony import */ var _plugin_PaginationPlugin__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./plugin/PaginationPlugin */ \"./src/plugin/PaginationPlugin.ts\");\n/* harmony import */ var _Camera__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./Camera */ \"./src/Camera.ts\");\n/**\n * Library ChartEngine\n * Created 26.11.22\n *\n * @author Christoph Ehlers\n */\n\n\n\n\n\n\nconst _runStack = [];\nlet _isRunning = false;\nconst _exec = () => {\n if (_isRunning)\n return;\n _isRunning = true;\n window.requestAnimationFrame(() => {\n _runStack.map(f => f());\n _isRunning = false;\n if (_runStack.length > 0)\n _exec();\n });\n};\nclass ChartEngine {\n _camera = new _Camera__WEBPACK_IMPORTED_MODULE_5__[\"default\"]();\n _canvasElement;\n _entityRegistry = new _Registry_EntityRegistry__WEBPACK_IMPORTED_MODULE_3__[\"default\"]();\n _ctx;\n _dataGroups = [];\n _dataGroupsSignature = '';\n _processedDataGroups = [];\n _plugins = [];\n _pluginProperties = {};\n _created = false;\n _runStackEntry;\n constructor(canvasElement, properties = {\n plugins: []\n }) {\n this._canvasElement = canvasElement;\n this._ctx = canvasElement.getContext('2d');\n this._canvasElement.height = this._canvasElement.offsetHeight;\n this._canvasElement.width = this._canvasElement.offsetWidth;\n [\n new _plugin_EnrichGroupInfosPlugin__WEBPACK_IMPORTED_MODULE_0__[\"default\"](),\n new _plugin_PaginationPlugin__WEBPACK_IMPORTED_MODULE_4__[\"default\"](),\n new _plugin_LegendsPlugin__WEBPACK_IMPORTED_MODULE_2__[\"default\"](),\n ...(properties.plugins || []),\n ].map(plugin => {\n this.addPlugin(plugin);\n });\n const hover = (e) => {\n const position = this.toCanvasPosition(e.offsetX, e.offsetY);\n this._plugins.map(p => p.handleHover(position.x + this._camera.getX(), position.y + this._camera.getY()));\n };\n this._canvasElement.addEventListener('pointermove', hover);\n // touch devices have no hover, a tap shows the values instead\n this._canvasElement.addEventListener('pointerdown', hover);\n this._canvasElement.addEventListener('wheel', (e) => {\n this._plugins.map(p => p.handleScroll(e.deltaX, e.deltaY));\n });\n this._canvasElement.addEventListener('pointerout', (e) => {\n this._plugins.map(p => p.handleLeave());\n });\n }\n addPlugin(plugin) {\n for (let key in this._pluginProperties) {\n plugin.setProperty(key, this._pluginProperties[key]);\n }\n plugin\n .setCamera(this._camera)\n .setEntityRegistry(this._entityRegistry);\n this._plugins.push(plugin);\n if (this._created) {\n // The chart is already running, so the plugin has to catch up.\n plugin.create(this._canvasElement, this.getPipelineDataGroups());\n }\n return this;\n }\n /**\n * Changes the size of the canvas (in canvas pixels). The plugins rebuild\n * their buffers, so the chart is drawn sharp in the new size instead of\n * being stretched by css.\n */\n resize(width, height) {\n width = Math.max(1, Math.round(width));\n height = Math.max(1, Math.round(height));\n if (width === this._canvasElement.width && height === this._canvasElement.height) {\n return this;\n }\n this._canvasElement.width = width;\n this._canvasElement.height = height;\n if (this._created) {\n this._plugins.map(p => p.resize ? p.resize(this._canvasElement) : p.invalidate?.());\n }\n return this;\n }\n /**\n * Converts a pointer position (css pixels) into canvas pixels - both differ\n * as soon as the canvas is scaled by css, e.g. by `width: 100%`.\n */\n toCanvasPosition(offsetX, offsetY) {\n const clientWidth = this._canvasElement.clientWidth;\n const clientHeight = this._canvasElement.clientHeight;\n return {\n x: clientWidth > 0 ? offsetX * this._canvasElement.width / clientWidth : offsetX,\n y: clientHeight > 0 ? offsetY * this._canvasElement.height / clientHeight : offsetY,\n };\n }\n getDrawAreaRect() {\n return {\n x: 0,\n y: 0,\n w: this._canvasElement.width,\n h: this._canvasElement.height\n };\n }\n getPlugin(pluginName) {\n return this._plugins.find((plugin) => plugin.getName() === pluginName);\n }\n getPlugins() {\n return this._plugins.sort((p1, p2) => p1.getPriority() - p2.getPriority());\n }\n setPluginProperty(propertyName, value) {\n this._pluginProperties[propertyName] = value;\n this._plugins.map(p => p.setProperty(propertyName, value));\n return this;\n }\n create() {\n this._plugins.map(p => p.create(this._canvasElement, this.getPipelineDataGroups()));\n this._created = true;\n return this;\n }\n addDataGroup(dataGroup) {\n this._dataGroups.push(dataGroup);\n return this;\n }\n /**\n * Returns the data groups currently held by the engine.\n * The returned groups are the live objects; changes made to them are\n * picked up with the next update cycle.\n */\n getDataGroups() {\n return this._dataGroups;\n }\n /**\n * Returns a single data group, addressed either by its label or by its index.\n */\n getDataGroup(group) {\n if (typeof group === 'number') {\n return this._dataGroups[group];\n }\n return this._dataGroups.find((dataGroup) => dataGroup.label === group);\n }\n /**\n * Replaces all data groups at once.\n */\n setDataGroups(dataGroups) {\n this._dataGroups = dataGroups || [];\n return this;\n }\n /**\n * Replaces the complete data of the chart. Accepts everything\n * `DataGroupFactory.parseDataGroupsDefinition` understands: a JSON string,\n * a plain array of values or an object keyed by group name.\n */\n setData(dataDefinition) {\n return this.setDataGroups(_Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__[\"default\"].parseDataGroupsDefinition(dataDefinition));\n }\n /**\n * Removes a data group, addressed either by its label or by its index.\n */\n removeDataGroup(group) {\n const index = typeof group === 'number' ? group : this._dataGroups.findIndex((dataGroup) => dataGroup.label === group);\n if (index > -1 && index < this._dataGroups.length) {\n this._dataGroups.splice(index, 1);\n }\n return this;\n }\n /**\n * Removes all data groups.\n */\n clearData() {\n this._dataGroups = [];\n return this;\n }\n /**\n * Appends a value - e.g. a new measurement that arrived after the page was\n * loaded - to a data group. The value may be given as `{value, timestamp}`\n * or `{value, label}`. A group with a `maxLength` drops its oldest values,\n * so a live chart keeps its window. Without a group name the value goes to\n * the only group of the chart; an unknown group is created.\n */\n addValue(value, groupName = '') {\n const dataGroup = groupName === '' && this._dataGroups.length === 1\n ? this._dataGroups[0]\n : this._dataGroups.find((group) => group.label === groupName);\n if (dataGroup) {\n dataGroup.data.push(_Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__[\"default\"].createDataValue(value));\n if (dataGroup.maxLength && dataGroup.data.length > dataGroup.maxLength) {\n dataGroup.data.splice(0, dataGroup.data.length - dataGroup.maxLength);\n }\n }\n else {\n this._dataGroups.push(_Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__[\"default\"].createDataGroup([value], groupName));\n }\n return this;\n }\n /**\n * Replaces a single value within a data group.\n */\n setValue(group, dataIndex, value) {\n const dataGroup = this.getDataGroup(group);\n if (!dataGroup) {\n throw new Error('ChartEngine.setValue: data group \"' + group + '\" not found.');\n }\n if (!Number.isSafeInteger(dataIndex) || dataIndex < 0) {\n throw new Error('ChartEngine.setValue: data index must be a non-negative integer.');\n }\n while (dataGroup.data.length <= dataIndex) {\n dataGroup.data.push(_Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__[\"default\"].createDataValue(null));\n }\n dataGroup.data[dataIndex] = _Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_1__[\"default\"].createDataValue(value);\n return this;\n }\n /**\n * Removes a single value from a data group.\n */\n removeValue(group, dataIndex) {\n const dataGroup = this.getDataGroup(group);\n if (dataGroup && dataIndex > -1 && dataIndex < dataGroup.data.length) {\n dataGroup.data.splice(dataIndex, 1);\n }\n return this;\n }\n draw() {\n this._ctx.clearRect(0, 0, this._canvasElement.width, this._canvasElement.height);\n this.getPlugins().map(p => p.draw());\n return this;\n }\n update() {\n let dataGroups = this.getPipelineDataGroups();\n this.getPlugins().map(p => {\n dataGroups = p.update(dataGroups).dataGroups;\n });\n return this;\n }\n /**\n * Returns the data groups handed to the plugins. They are a detached copy\n * of the data owned by the engine and are renewed whenever that data\n * changed. Without that separation the plugins would hold references to\n * the very objects manipulated from outside, so neither they nor the\n * engine could tell a manipulation from the state of the last frame.\n */\n getPipelineDataGroups() {\n const signature = JSON.stringify(this._dataGroups);\n if (signature !== this._dataGroupsSignature) {\n this._dataGroupsSignature = signature;\n this._processedDataGroups = JSON.parse(signature);\n }\n return this._processedDataGroups;\n }\n start() {\n if (this._runStackEntry) {\n // already running\n return;\n }\n if (!this._created) {\n this.create();\n }\n this._runStackEntry = () => {\n this.update();\n this.draw();\n };\n _runStack.push(this._runStackEntry);\n _exec();\n }\n /**\n * Stops the render loop of this engine. The data stays untouched, so the\n * chart can be started again later on.\n */\n stop() {\n if (this._runStackEntry) {\n const index = _runStack.indexOf(this._runStackEntry);\n if (index > -1) {\n _runStack.splice(index, 1);\n }\n this._runStackEntry = undefined;\n }\n return this;\n }\n isRunning() {\n return this._runStackEntry !== undefined;\n }\n getEngineCallbacks() {\n return {\n updateData: (indexGroup, indexData, newData) => {\n this._dataGroups[indexGroup].data[indexData] = newData;\n return this;\n }\n };\n }\n}\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/ChartEngine.ts?\n}");
|
|
150
150
|
|
|
151
151
|
/***/ },
|
|
152
152
|
|
|
@@ -168,7 +168,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa
|
|
|
168
168
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
169
169
|
|
|
170
170
|
"use strict";
|
|
171
|
-
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ DataGroupFactory)\n/* harmony export */ });\nclass DataGroupFactory {\n static buildCounter = 0;\n static _colors = [\n '#e84949',\n '#30cb30',\n '#f0a30a',\n '#1d91e3',\n '#ec459e',\n '#9f4ef4',\n '#f4f44e',\n '#4ef4f4',\n '#4ef49f',\n '#f49f4e',\n '#4e9bf4',\n ];\n static _lastColorIndex = -1;\n /**\n * Normalizes a single value definition into a TData object.\n * Accepts plain numbers, ranges ([start, end]), null (empty value) and\n * objects like {value, valueStart, label}.\n */\n static createDataValue(valueDef) {\n let value = valueDef;\n let label = undefined;\n let valueStart = valueDef;\n let valueEnd = valueDef;\n if (valueDef !== null && valueDef !== undefined) {\n if (typeof valueDef === 'object' && !Array.isArray(valueDef)) {\n if (Array.isArray(valueDef.value)) {\n if (valueDef.value.length === 2) {\n valueStart = valueDef.value[0];\n value = valueDef.value[1];\n valueEnd = valueDef.value[1];\n }\n else {\n console.warn('valueDef.value must be an array with 2 elements', valueDef);\n }\n }\n else {\n value = valueDef.value;\n valueEnd = valueDef.value;\n valueStart = valueDef.valueStart === undefined ? 0 : valueDef.valueStart;\n }\n label = valueDef.label;\n }\n else {\n if (Array.isArray(valueDef) && valueDef.length === 2) {\n valueStart = valueDef[0];\n value = valueDef[1];\n valueEnd = valueDef[1];\n label = valueDef[0] + ' - ' + valueDef[1];\n }\n }\n }\n
|
|
171
|
+
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ DataGroupFactory)\n/* harmony export */ });\n/* harmony import */ var _Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../Utils/ChartStyle */ \"./src/Utils/ChartStyle.ts\");\n\nclass DataGroupFactory {\n static buildCounter = 0;\n static _colors = [\n '#e84949',\n '#30cb30',\n '#f0a30a',\n '#1d91e3',\n '#ec459e',\n '#9f4ef4',\n '#f4f44e',\n '#4ef4f4',\n '#4ef49f',\n '#f49f4e',\n '#4e9bf4',\n ];\n static _lastColorIndex = -1;\n /**\n * Normalizes a single value definition into a TData object.\n * Accepts plain numbers, ranges ([start, end]), null (empty value) and\n * objects like {value, valueStart, label}.\n */\n static createDataValue(valueDef) {\n let value = valueDef;\n let label = undefined;\n let timestamp = undefined;\n let valueStart = valueDef;\n let valueEnd = valueDef;\n if (valueDef !== null && valueDef !== undefined) {\n if (typeof valueDef === 'object' && !Array.isArray(valueDef)) {\n if (Array.isArray(valueDef.value)) {\n if (valueDef.value.length === 2) {\n valueStart = valueDef.value[0];\n value = valueDef.value[1];\n valueEnd = valueDef.value[1];\n }\n else {\n console.warn('valueDef.value must be an array with 2 elements', valueDef);\n }\n }\n else {\n value = valueDef.value;\n valueEnd = valueDef.value;\n valueStart = valueDef.valueStart === undefined ? 0 : valueDef.valueStart;\n }\n label = valueDef.label;\n timestamp = DataGroupFactory.normalizeTimestamp(valueDef.timestamp);\n }\n else {\n if (Array.isArray(valueDef) && valueDef.length === 2) {\n valueStart = valueDef[0];\n value = valueDef[1];\n valueEnd = valueDef[1];\n label = valueDef[0] + ' - ' + valueDef[1];\n }\n }\n }\n const data = {\n empty: (value === null || value === undefined),\n hasLabel: (valueDef !== null && valueDef !== undefined && valueDef.label !== undefined),\n label,\n value,\n valueEnd,\n valueStart\n };\n if (timestamp !== undefined) {\n data.timestamp = timestamp;\n }\n return data;\n }\n /**\n * Unix timestamps in seconds or milliseconds (see `toMilliseconds`),\n * stored as milliseconds; everything else means \"no timestamp\".\n */\n static normalizeTimestamp(timestamp) {\n return (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_0__.toMilliseconds)(timestamp);\n }\n static createDataGroup(values, groupLabel = '', color = '') {\n DataGroupFactory.buildCounter++;\n if (color === '') {\n color = DataGroupFactory.getNextColor();\n }\n return {\n color,\n label: groupLabel === '' ? 'Group#' + DataGroupFactory.buildCounter : groupLabel,\n description: \"\",\n data: (values || []).map((valueDef) => DataGroupFactory.createDataValue(valueDef)),\n infos: {}\n };\n }\n /**\n * Parses a data group definition. Beside the JSON notation used by the\n * `value` attribute of `<canvas-diagram>` an already parsed object/array\n * may be passed, so data can be handed over programmatically at runtime.\n */\n static parseDataGroupsDefinition(value) {\n const valueObj = typeof value === 'string' ? JSON.parse(value) : value;\n const result = [];\n if (valueObj === null || valueObj === undefined) {\n return result;\n }\n if (Array.isArray(valueObj)) {\n if (valueObj.length > 0) {\n result.push(DataGroupFactory.createDataGroup(valueObj));\n }\n }\n else {\n if (typeof valueObj === 'object') {\n Object.keys(valueObj).map((key) => {\n if (Array.isArray(valueObj[key])) {\n result.push(DataGroupFactory.createDataGroup(valueObj[key], key));\n }\n else {\n const definition = valueObj[key] || {};\n const dataGroup = DataGroupFactory.createDataGroup(definition.data, key, definition.color || '');\n result.push(DataGroupFactory.applyGroupOptions(dataGroup, definition));\n }\n });\n }\n }\n return result;\n }\n /**\n * Applies the optional parts of an object definition to a data group:\n * `unit` (shown in tooltips and axis labels), `labels` (one label per value),\n * `timestamps` (one unix timestamp per value, in seconds or milliseconds -\n * shown as time labels) and `maxLength` (values added later push the oldest\n * ones out, so a live chart keeps its time window). Labels and timestamps of\n * single values given as `{value, label, timestamp}` win.\n */\n static applyGroupOptions(dataGroup, definition) {\n if (typeof definition.unit === 'string' && definition.unit !== '') {\n dataGroup.unit = definition.unit;\n }\n const maxLength = Number(definition.maxLength);\n if (Number.isSafeInteger(maxLength) && maxLength > 0) {\n dataGroup.maxLength = maxLength;\n }\n dataGroup.data.map((data, index) => {\n const label = Array.isArray(definition.labels) ? definition.labels[index] : undefined;\n if (!data.hasLabel && label !== undefined && label !== null && label !== '') {\n data.label = String(label);\n data.hasLabel = true;\n }\n const timestamp = Array.isArray(definition.timestamps)\n ? DataGroupFactory.normalizeTimestamp(definition.timestamps[index])\n : undefined;\n if (data.timestamp === undefined && timestamp !== undefined) {\n data.timestamp = timestamp;\n }\n });\n return dataGroup;\n }\n static getNextColor() {\n DataGroupFactory._lastColorIndex++;\n if (DataGroupFactory._lastColorIndex >= DataGroupFactory._colors.length) {\n DataGroupFactory._lastColorIndex = 0;\n }\n return DataGroupFactory._colors[DataGroupFactory._lastColorIndex];\n }\n static setColor(color, index) {\n DataGroupFactory._colors[index] = color;\n }\n static setColors(colors) {\n DataGroupFactory._colors = colors;\n }\n}\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/Factory/DataGroupFactory.ts?\n}");
|
|
172
172
|
|
|
173
173
|
/***/ },
|
|
174
174
|
|
|
@@ -194,6 +194,17 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa
|
|
|
194
194
|
|
|
195
195
|
/***/ },
|
|
196
196
|
|
|
197
|
+
/***/ "./src/Utils/ChartStyle.ts"
|
|
198
|
+
/*!*********************************!*\
|
|
199
|
+
!*** ./src/Utils/ChartStyle.ts ***!
|
|
200
|
+
\*********************************/
|
|
201
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
202
|
+
|
|
203
|
+
"use strict";
|
|
204
|
+
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ DEFAULT_THEME: () => (/* binding */ DEFAULT_THEME),\n/* harmony export */ decimalsOf: () => (/* binding */ decimalsOf),\n/* harmony export */ formatTimestamps: () => (/* binding */ formatTimestamps),\n/* harmony export */ formatValue: () => (/* binding */ formatValue),\n/* harmony export */ niceTicks: () => (/* binding */ niceTicks),\n/* harmony export */ pageLocale: () => (/* binding */ pageLocale),\n/* harmony export */ readTheme: () => (/* binding */ readTheme),\n/* harmony export */ toMilliseconds: () => (/* binding */ toMilliseconds),\n/* harmony export */ withAlpha: () => (/* binding */ withAlpha)\n/* harmony export */ });\n/**\n * Library ChartStyle\n * Created 29.09.26\n *\n * Shared helpers for the look of the charts: theme colours, colour alpha,\n * number formatting and \"nice\" axis ticks.\n *\n * @author Christoph Ehlers\n */\n/**\n * The defaults are mid-tone colours, so a chart stays readable on a dark as\n * well as on a light background when the host page defines nothing.\n */\nconst DEFAULT_THEME = {\n textColor: '#9a9a9a',\n gridColor: 'rgba(128, 128, 128, 0.18)',\n tooltipBackground: 'rgba(18, 18, 18, 0.92)',\n tooltipBorder: 'rgba(255, 255, 255, 0.12)',\n tooltipTextColor: '#f0f0f0',\n font: '11px system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif',\n};\nconst THEME_CSS_PROPERTIES = {\n textColor: '--chart-text-color',\n gridColor: '--chart-grid-color',\n tooltipBackground: '--chart-tooltip-background',\n tooltipBorder: '--chart-tooltip-border',\n tooltipTextColor: '--chart-tooltip-text-color',\n font: '--chart-font',\n};\n/**\n * Reads the theme from the CSS custom properties `--chart-*` of the given\n * element, so the host page can adapt the charts to its own theme without\n * touching any plugin options.\n */\nfunction readTheme(element) {\n const theme = { ...DEFAULT_THEME };\n if (!element || typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {\n return theme;\n }\n let style;\n try {\n style = window.getComputedStyle(element);\n }\n catch (e) {\n // not a real dom element (e.g. an offscreen canvas)\n return theme;\n }\n Object.keys(THEME_CSS_PROPERTIES).map((key) => {\n const value = (style.getPropertyValue(THEME_CSS_PROPERTIES[key]) || '').trim();\n if (value !== '') {\n theme[key] = value;\n }\n });\n return theme;\n}\n/**\n * Returns the given colour with the given opacity. Hex colours (#rgb, #rrggbb,\n * #rrggbbaa) and rgb()/rgba() are supported; any other notation is returned\n * unchanged, which only costs the transparency.\n */\nfunction withAlpha(color, alpha) {\n const value = (color || '').trim();\n const a = Math.max(0, Math.min(1, alpha));\n let hex = value.startsWith('#') ? value.substring(1) : '';\n if (hex.length === 3 || hex.length === 4) {\n hex = hex.split('').map((c) => c + c).join('');\n }\n if (/^[0-9a-f]{6}([0-9a-f]{2})?$/i.test(hex)) {\n const r = parseInt(hex.substring(0, 2), 16);\n const g = parseInt(hex.substring(2, 4), 16);\n const b = parseInt(hex.substring(4, 6), 16);\n const baseAlpha = hex.length === 8 ? parseInt(hex.substring(6, 8), 16) / 255 : 1;\n return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + round(baseAlpha * a, 3) + ')';\n }\n const rgb = value.match(/^rgba?\\(\\s*([\\d.]+)[\\s,]+([\\d.]+)[\\s,]+([\\d.]+)(?:[\\s,/]+([\\d.]+%?))?\\s*\\)$/i);\n if (rgb) {\n let baseAlpha = rgb[4] === undefined ? 1 : parseFloat(rgb[4]);\n if (rgb[4] !== undefined && rgb[4].endsWith('%')) {\n baseAlpha = baseAlpha / 100;\n }\n return 'rgba(' + rgb[1] + ', ' + rgb[2] + ', ' + rgb[3] + ', ' + round(baseAlpha * a, 3) + ')';\n }\n return value;\n}\n/**\n * The language of the page (`<html lang>`), so numbers and times are written\n * the way the rest of the page writes them.\n */\nfunction pageLocale() {\n if (typeof document === 'undefined' || !document.documentElement) {\n return undefined;\n }\n return document.documentElement.lang || undefined;\n}\nfunction round(value, decimals) {\n const factor = Math.pow(10, decimals);\n return Math.round(value * factor) / factor;\n}\n/**\n * Formats a value for axis labels and tooltips: at most `maxDecimals`\n * decimals, grouped by the locale of the page.\n */\nfunction formatValue(value, maxDecimals = 2, locale = pageLocale()) {\n if (value === null || value === undefined || value === '') {\n return '–';\n }\n if (typeof value !== 'number' || !isFinite(value)) {\n return String(value);\n }\n try {\n return value.toLocaleString(locale, { maximumFractionDigits: maxDecimals });\n }\n catch (e) {\n return String(round(value, maxDecimals));\n }\n}\n/**\n * Calculates evenly spaced, human friendly axis ticks (1, 2, 2.5, 5 × 10^n)\n * which enclose the given range.\n */\nfunction niceTicks(min, max, maxTicks = 5) {\n if (!isFinite(min) || !isFinite(max)) {\n return { min: 0, max: 1, step: 1, ticks: [0, 1] };\n }\n if (min > max) {\n [min, max] = [max, min];\n }\n if (min === max) {\n // A flat line still needs a visible range around it.\n const spread = min === 0 ? 1 : Math.abs(min) * 0.1;\n min -= spread;\n max += spread;\n }\n const roughStep = (max - min) / Math.max(1, maxTicks - 1);\n const magnitude = Math.pow(10, Math.floor(Math.log10(roughStep)));\n const step = [1, 2, 2.5, 5, 10]\n .map((factor) => factor * magnitude)\n .find((candidate) => candidate >= roughStep) ?? 10 * magnitude;\n const niceMin = Math.floor(min / step) * step;\n const niceMax = Math.ceil(max / step) * step;\n const ticks = [];\n for (let tick = niceMin; tick <= niceMax + step / 2; tick += step) {\n // removes floating point noise like 0.30000000000000004\n ticks.push(parseFloat(tick.toPrecision(12)));\n }\n return { min: niceMin, max: niceMax, step, ticks };\n}\n/**\n * Returns the number of decimals needed to show the given step exactly.\n */\nfunction decimalsOf(step) {\n if (!isFinite(step) || step <= 0) {\n return 0;\n }\n const text = parseFloat(step.toPrecision(12)).toString();\n const exponent = text.match(/e-(\\d+)$/);\n if (exponent) {\n return parseInt(exponent[1], 10);\n }\n const dot = text.indexOf('.');\n return dot === -1 ? 0 : text.length - dot - 1;\n}\n/**\n * Values below this are unix timestamps in seconds, values above in\n * milliseconds: 1e11 seconds is the year 5138, 1e11 milliseconds is March\n * 1973 - so seconds up to 5138 and milliseconds from 1973 on are told apart\n * reliably.\n */\nconst SECONDS_MILLISECONDS_BOUNDARY = 1e11;\n/**\n * Normalizes a unix timestamp in seconds or milliseconds (see\n * SECONDS_MILLISECONDS_BOUNDARY) to milliseconds. 0, negative values, null\n * and anything unparsable mean \"no timestamp\".\n */\nfunction toMilliseconds(timestamp) {\n const value = typeof timestamp === 'string' ? parseFloat(timestamp) : timestamp;\n if (typeof value !== 'number' || !isFinite(value) || value <= 0) {\n return undefined;\n }\n return value < SECONDS_MILLISECONDS_BOUNDARY ? value * 1000 : value;\n}\n/**\n * Turns unix timestamps (seconds or milliseconds) into short labels. The date\n * is only part of the label when the timestamps span more than one day.\n */\nfunction formatTimestamps(timestamps, locale = pageLocale()) {\n const dates = timestamps.map((timestamp) => {\n const milliseconds = toMilliseconds(timestamp);\n return milliseconds === undefined ? undefined : new Date(milliseconds);\n });\n const days = new Set(dates.filter((date) => date !== undefined).map((date) => date.toDateString()));\n const options = days.size > 1\n ? { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }\n : { hour: '2-digit', minute: '2-digit' };\n return dates.map((date) => {\n if (date === undefined) {\n return undefined;\n }\n try {\n return date.toLocaleString(locale, options);\n }\n catch (e) {\n return date.toISOString();\n }\n });\n}\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/Utils/ChartStyle.ts?\n}");
|
|
205
|
+
|
|
206
|
+
/***/ },
|
|
207
|
+
|
|
197
208
|
/***/ "./src/customElement/CanvasDiagram.ts"
|
|
198
209
|
/*!********************************************!*\
|
|
199
210
|
!*** ./src/customElement/CanvasDiagram.ts ***!
|
|
@@ -201,7 +212,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa
|
|
|
201
212
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
202
213
|
|
|
203
214
|
"use strict";
|
|
204
|
-
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _style_CanvasDiagram_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../style/CanvasDiagram.css */ \"./src/style/CanvasDiagram.css\");\n/* harmony import */ var _Registry_DiagramRegistry__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../Registry/DiagramRegistry */ \"./src/Registry/DiagramRegistry.ts\");\n/* harmony import */ var _cehlers88_ceutils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @cehlers88/ceutils */ \"./node_modules/@cehlers88/ceutils/dist/index.js\");\n/* harmony import */ var _cehlers88_ceutils__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_cehlers88_ceutils__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../Factory/DataGroupFactory */ \"./src/Factory/DataGroupFactory.ts\");\n/* harmony import */ var _ChartEngine__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../ChartEngine */ \"./src/ChartEngine.ts\");\n/**\n * Element CanvasDiagram\n * Created 03.10.24\n *\n * @author Christoph Ehlers\n */\n\n\n\n\n\nclass CanvasDiagram extends HTMLElement {\n _cleanDom = true;\n _engine;\n _ignoreValueAttribute = false;\n _pendingActions = [];\n static get observedAttributes() {\n return ['value'];\n }\n connectedCallback() {\n if (this._engine) {\n // The element was moved within the dom
|
|
215
|
+
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _style_CanvasDiagram_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../style/CanvasDiagram.css */ \"./src/style/CanvasDiagram.css\");\n/* harmony import */ var _Registry_DiagramRegistry__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../Registry/DiagramRegistry */ \"./src/Registry/DiagramRegistry.ts\");\n/* harmony import */ var _cehlers88_ceutils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @cehlers88/ceutils */ \"./node_modules/@cehlers88/ceutils/dist/index.js\");\n/* harmony import */ var _cehlers88_ceutils__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_cehlers88_ceutils__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../Factory/DataGroupFactory */ \"./src/Factory/DataGroupFactory.ts\");\n/* harmony import */ var _ChartEngine__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../ChartEngine */ \"./src/ChartEngine.ts\");\n/**\n * Element CanvasDiagram\n * Created 03.10.24\n *\n * @author Christoph Ehlers\n */\n\n\n\n\n\nclass CanvasDiagram extends HTMLElement {\n _cleanDom = true;\n _engine;\n _ignoreValueAttribute = false;\n _pendingActions = [];\n _resizeObserver;\n _responsive = false;\n _runningBeforeDisconnect = false;\n static get observedAttributes() {\n return ['value'];\n }\n connectedCallback() {\n if (this._engine) {\n // The element was moved within the dom or inserted again: no need to\n // build it again, but it has to pick up what disconnecting ended.\n _Registry_DiagramRegistry__WEBPACK_IMPORTED_MODULE_1__.diagramRegistry.register(this);\n this.observeSize();\n if (this._runningBeforeDisconnect) {\n this._engine.start();\n }\n return;\n }\n _Registry_DiagramRegistry__WEBPACK_IMPORTED_MODULE_1__.diagramRegistry.register(this);\n const canvas = this.createCanvas();\n this.appendChild(canvas);\n this._engine = new _ChartEngine__WEBPACK_IMPORTED_MODULE_4__[\"default\"](canvas);\n this.evalArguments();\n this.flushPendingActions();\n this.observeSize();\n }\n disconnectedCallback() {\n if (this._engine) {\n this._runningBeforeDisconnect = this._engine.isRunning();\n this._engine.stop();\n }\n if (this._resizeObserver) {\n this._resizeObserver.disconnect();\n this._resizeObserver = undefined;\n }\n _Registry_DiagramRegistry__WEBPACK_IMPORTED_MODULE_1__.diagramRegistry.unregister(this);\n }\n attributeChangedCallback(name, oldValue, newValue) {\n if (name !== 'value' || this._ignoreValueAttribute || newValue === null) {\n return;\n }\n if (!this._engine) {\n // Attribute is evaluated within connectedCallback anyway.\n return;\n }\n this.setData(newValue);\n }\n addPlugin(plugin) {\n return this.withEngine(engine => engine.addPlugin(plugin));\n }\n getPlugin(pluginName) {\n return this._engine ? this._engine.getPlugin(pluginName) : undefined;\n }\n /**\n * Changes the size of the diagram (in pixels) and redraws it sharply in\n * the new size.\n */\n resize(width, height) {\n this.style.height = Math.round(height) + 'px';\n if (!this._responsive) {\n this.style.width = Math.round(width) + 'px';\n }\n return this.withEngine(engine => engine.resize(width, height));\n }\n getEngine() {\n return this._engine;\n }\n addDataGroup(dataGroup) {\n return this.withEngine(engine => engine.addDataGroup(dataGroup));\n }\n addValue(value, groupName = '') {\n return this.withEngine(engine => engine.addValue(value, groupName));\n }\n clearData() {\n return this.withEngine(engine => engine.clearData());\n }\n getDataGroup(group) {\n return this._engine ? this._engine.getDataGroup(group) : undefined;\n }\n getDataGroups() {\n return this._engine ? this._engine.getDataGroups() : [];\n }\n removeDataGroup(group) {\n return this.withEngine(engine => engine.removeDataGroup(group));\n }\n removeValue(group, dataIndex) {\n return this.withEngine(engine => engine.removeValue(group, dataIndex));\n }\n /**\n * Replaces the complete data of the diagram, at any time - also long after\n * the page was loaded.\n */\n setData(dataDefinition) {\n return this.withEngine(engine => engine.setData(dataDefinition));\n }\n setDataGroups(dataGroups) {\n return this.withEngine(engine => engine.setDataGroups(dataGroups));\n }\n setValue(group, dataIndex, value) {\n return this.withEngine(engine => engine.setValue(group, dataIndex, value));\n }\n start() {\n return this.withEngine(engine => engine.start());\n }\n stop() {\n return this.withEngine(engine => engine.stop());\n }\n /**\n * Runs the given action against the engine. If the element is not mounted\n * yet, the action is queued and executed as soon as the engine exists,\n * so data can be set before as well as after the element was connected.\n */\n withEngine(action) {\n if (this._engine) {\n action(this._engine);\n }\n else {\n this._pendingActions.push(action);\n }\n return this;\n }\n flushPendingActions() {\n const actions = this._pendingActions;\n this._pendingActions = [];\n actions.map(action => action(this._engine));\n }\n /**\n * A width given in percent (`width=\"100%\"`) or as `auto` makes the diagram\n * responsive: it takes the width of its container and is redrawn in the\n * new size whenever that changes.\n */\n createCanvas() {\n const canvas = _cehlers88_ceutils__WEBPACK_IMPORTED_MODULE_2__.domLib.createElement('canvas');\n const widthAttribute = (this.getAttribute('width') || '').trim();\n this._responsive = widthAttribute === 'auto' || widthAttribute.endsWith('%');\n this.setAttribute('style', 'width:0px; height:0px;');\n [\n { name: 'height', defaultValue: 150 },\n { name: 'width', defaultValue: 300 },\n ].map((attrDef) => {\n let value = (this.hasAttribute(attrDef.name) ? this.getAttribute(attrDef.name) : attrDef.defaultValue.toString());\n value = value.replace('px', '');\n if (attrDef.name === 'width' && this._responsive) {\n this.style.width = widthAttribute === 'auto' ? '100%' : widthAttribute;\n this.style.display = 'block';\n canvas.style.width = '100%';\n // the real width is known once the element was laid out\n canvas.setAttribute('width', Math.floor(this.clientWidth || 300).toString());\n return;\n }\n canvas.setAttribute(attrDef.name, value);\n this.style[attrDef.name] = value + 'px';\n });\n return canvas;\n }\n observeSize() {\n if (!this._responsive || this._resizeObserver || typeof ResizeObserver === 'undefined') {\n return;\n }\n this._resizeObserver = new ResizeObserver(() => {\n const width = Math.floor(this.clientWidth);\n const canvas = this._engine?.getDrawAreaRect();\n if (!this._engine || width < 1 || !canvas) {\n return;\n }\n this._engine.resize(width, canvas.h);\n });\n this._resizeObserver.observe(this);\n }\n evalArguments() {\n [\n {\n name: 'value', defaultValue: '[]', callback: (value) => {\n _Factory_DataGroupFactory__WEBPACK_IMPORTED_MODULE_3__[\"default\"].parseDataGroupsDefinition(value).map(dataGroupDef => {\n this._engine.addDataGroup(dataGroupDef);\n });\n }\n },\n {\n name: 'autostart', defaultValue: 'true', callback: (value) => {\n if (value === 'true') {\n this.start();\n }\n }\n }\n ].map(attrDef => {\n const callbackArgument = this.hasAttribute(attrDef.name) ? this.getAttribute(attrDef.name) : attrDef.defaultValue;\n attrDef.callback(callbackArgument);\n if (this._cleanDom) {\n // Removing the attribute must not be mistaken for a data change.\n this._ignoreValueAttribute = true;\n this.removeAttribute(attrDef.name);\n this._ignoreValueAttribute = false;\n }\n });\n }\n}\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (CanvasDiagram);\ncustomElements.define('canvas-diagram', CanvasDiagram);\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/customElement/CanvasDiagram.ts?\n}");
|
|
205
216
|
|
|
206
217
|
/***/ },
|
|
207
218
|
|
|
@@ -234,7 +245,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa
|
|
|
234
245
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
235
246
|
|
|
236
247
|
"use strict";
|
|
237
|
-
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ AbstractPlugin)\n/* harmony export */ });\nclass AbstractPlugin {\n _camera;\n _dataGroups = [];\n _entityRegistry;\n _fixed = false;\n _priority = 50;\n bufferCtx;\n ctx;\n redrawBuffer = true;\n _dataGroupsChanged = true;\n _buffer;\n _dataGroupsSignature = '';\n _stateChangedListener;\n _drawAreaRect;\n _properties = {};\n constructor(props = {}) {\n if (props.enabled === undefined) {\n props.enabled = true;\n }\n Object.keys(props).map(key => {\n this.setProperty(key, props[key]);\n });\n }\n get needsRedraw() {\n return this.redrawBuffer;\n }\n setStateChangedListener(listener) {\n this._stateChangedListener = listener;\n return this;\n }\n setState(newState) {\n return this;\n }\n update(dataGroups) {\n if (this.getProperty('enabled', true) === false)\n return { dataGroups };\n this.detectDataGroupsChange(dataGroups);\n if (this._dataGroupsChanged) {\n // Data was manipulated in the meantime, so the buffer is outdated.\n this.redrawBuffer = true;\n this.onDataGroupsChanged(dataGroups);\n }\n this._dataGroups = this.updateDataGroups(dataGroups).dataGroups;\n return { dataGroups: this._dataGroups };\n }\n updateDataGroups(dataGroups) {\n return { dataGroups };\n }\n addEntity(entity) {\n this._entityRegistry.addEntity(this.getName(), entity);\n return this;\n }\n create(canvasElement, dataGroups) {\n this.createBuffer(canvasElement);\n this.setDrawAreaRect({\n x: this.getProperty('x', 0),\n y: this.getProperty('y', 0),\n w: canvasElement.width,\n h: canvasElement.height\n });\n this.ctx = canvasElement.getContext('2d');\n return {\n dataGroups,\n plugin: this\n };\n }\n drawBuffer() {\n this.redrawBuffer = false;\n }\n draw(forceRedraw = false) {\n const padding = this.getProperty('padding', 0);\n if (this.needsRedraw || forceRedraw) {\n this.bufferCtx.clearRect(0, 0, this._buffer.width, this._buffer.height);\n this.drawBuffer();\n //console.log('redraw buffer', this.bufferCtx.canvas.width, this.bufferCtx.canvas.height);\n }\n
|
|
248
|
+
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ AbstractPlugin)\n/* harmony export */ });\n/* harmony import */ var _Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../Utils/ChartStyle */ \"./src/Utils/ChartStyle.ts\");\n\nclass AbstractPlugin {\n _camera;\n _dataGroups = [];\n _entityRegistry;\n _fixed = false;\n _priority = 50;\n bufferCtx;\n ctx;\n redrawBuffer = true;\n _dataGroupsChanged = true;\n _theme = { ..._Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_0__.DEFAULT_THEME };\n _buffer;\n _dataGroupsSignature = '';\n _stateChangedListener;\n _drawAreaRect;\n _properties = {};\n constructor(props = {}) {\n if (props.enabled === undefined) {\n props.enabled = true;\n }\n Object.keys(props).map(key => {\n this.setProperty(key, props[key]);\n });\n }\n get needsRedraw() {\n return this.redrawBuffer;\n }\n setStateChangedListener(listener) {\n this._stateChangedListener = listener;\n return this;\n }\n setState(newState) {\n return this;\n }\n update(dataGroups) {\n if (this.getProperty('enabled', true) === false)\n return { dataGroups };\n this.detectDataGroupsChange(dataGroups);\n if (this._dataGroupsChanged) {\n // Data was manipulated in the meantime, so the buffer is outdated.\n this.redrawBuffer = true;\n this.onDataGroupsChanged(dataGroups);\n }\n this._dataGroups = this.updateDataGroups(dataGroups).dataGroups;\n return { dataGroups: this._dataGroups };\n }\n /**\n * Hook for plugins that have to prepare themselves (e.g. resize their\n * buffer) before the changed data is processed.\n */\n onDataGroupsChanged(dataGroups) {\n }\n /**\n * Compares the given data groups by content against the ones seen by the\n * last update and remembers the result in `_dataGroupsChanged`. Comparing\n * by content (instead of holding on to the data groups themselves) is what\n * makes manipulations of already known data groups visible.\n */\n detectDataGroupsChange(dataGroups) {\n const signature = JSON.stringify(dataGroups);\n this._dataGroupsChanged = signature !== this._dataGroupsSignature;\n this._dataGroupsSignature = signature;\n }\n updateDataGroups(dataGroups) {\n return { dataGroups };\n }\n addEntity(entity) {\n this._entityRegistry.addEntity(this.getName(), entity);\n return this;\n }\n create(canvasElement, dataGroups) {\n this.createBuffer(canvasElement);\n this.setDrawAreaRect({\n x: this.getProperty('x', 0),\n y: this.getProperty('y', 0),\n w: canvasElement.width,\n h: canvasElement.height\n });\n this.ctx = canvasElement.getContext('2d');\n this._theme = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_0__.readTheme)(canvasElement);\n return {\n dataGroups,\n plugin: this\n };\n }\n /**\n * Adapts the plugin to a new size of the canvas. Unlike `create()` this\n * has no side effects besides rebuilding the buffer, so it can be called\n * as often as the canvas changes its size.\n */\n resize(canvasElement) {\n this.createBuffer(canvasElement);\n this.setDrawAreaRect({\n x: this.getProperty('x', 0),\n y: this.getProperty('y', 0),\n w: canvasElement.width,\n h: canvasElement.height\n });\n this._theme = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_0__.readTheme)(canvasElement);\n return this.invalidate();\n }\n /**\n * Forces the plugin to process its data and to redraw its buffer with the\n * next frame, even if the data did not change.\n */\n invalidate() {\n this._dataGroupsSignature = '';\n this.redrawBuffer = true;\n return this;\n }\n /**\n * Returns a colour/font of the theme. A plugin property of the same name\n * (e.g. `textColor`) wins over the `--chart-*` CSS properties of the canvas.\n */\n getThemeValue(key) {\n return this.getProperty(key, this._theme[key]);\n }\n drawBuffer() {\n this.redrawBuffer = false;\n }\n draw(forceRedraw = false) {\n const padding = this.getProperty('padding', 0);\n if (this.needsRedraw || forceRedraw) {\n this.bufferCtx.clearRect(0, 0, this._buffer.width, this._buffer.height);\n this.drawBuffer();\n //console.log('redraw buffer', this.bufferCtx.canvas.width, this.bufferCtx.canvas.height);\n }\n this.ctx.drawImage(this._buffer, this._fixed ? 0 : this._camera.x, this._fixed ? 0 : this._camera.y, this._drawAreaRect.w, this._drawAreaRect.h, this._drawAreaRect.x + padding, this._drawAreaRect.y + padding, this._drawAreaRect.w - (2 * padding), this._drawAreaRect.h - (2 * padding));\n return this;\n }\n getDrawAreaRect() {\n return this._drawAreaRect;\n }\n getPriority() {\n return this._priority;\n }\n getProperty(propertyName, defaultResult) {\n return this._properties[propertyName] ?? defaultResult;\n }\n handleClick(x, y) {\n return this;\n }\n handleHover(x, y) {\n return this;\n }\n handleLeave() {\n this.handleHover(-1, -1);\n return this;\n }\n handleScroll(x, y) {\n return this;\n }\n setBuffer(buffer) {\n this._buffer = buffer;\n this.bufferCtx = buffer.getContext('2d');\n return this;\n }\n setCamera(camera) {\n this._camera = camera;\n return this;\n }\n setDrawAreaRect(rect) {\n this._drawAreaRect = rect;\n return this;\n }\n setEntityRegistry(entityRegistry) {\n this._entityRegistry = entityRegistry;\n return this;\n }\n setProperty(propertyName, value) {\n this._properties[propertyName] = value;\n return this;\n }\n init() {\n return this;\n }\n createBuffer(canvasElement, width = null, height = null) {\n this._buffer = document.createElement('canvas');\n this._buffer.width = width ?? canvasElement.width;\n this._buffer.height = height ?? canvasElement.height;\n this.bufferCtx = this._buffer.getContext('2d');\n }\n}\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/plugin/AbstractPlugin.ts?\n}");
|
|
238
249
|
|
|
239
250
|
/***/ },
|
|
240
251
|
|
|
@@ -267,7 +278,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa
|
|
|
267
278
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
268
279
|
|
|
269
280
|
"use strict";
|
|
270
|
-
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ ERangeType: () => (/* binding */ ERangeType),\n/* harmony export */ \"default\": () => (/* binding */ DiagramLine)\n/* harmony export */ });\n/* harmony import */ var _AbstractDiagramPlugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AbstractDiagramPlugin */ \"./src/plugin/AbstractDiagramPlugin.ts\");\n/* harmony import */ var _Event_TooltipEvent__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../Event/TooltipEvent */ \"./src/Event/TooltipEvent.ts\");\n/**\n * Library DiagramLine\n * Created 26.11.22\n *\n * @author Christoph Ehlers\n */\n\n\nvar ERangeType;\n(function (ERangeType) {\n ERangeType[\"ZERO_BASED\"] = \"zero_based\";\n ERangeType[\"MIN_MAX\"] = \"min_max\";\n})(ERangeType || (ERangeType = {}));\nclass DiagramLine extends _AbstractDiagramPlugin__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n bulletPointRadius = 2;\n bulletPointRadiusHover = 5;\n dataPoints = [];\n helpLines = [];\n hoverPosition = { x: 0, y: 0 };\n rangeType = ERangeType.MIN_MAX;\n syncGroupsPosition = false;\n zeroLineValue = 0;\n zeroLineValuePercentage = 0;\n constructor(props = {}) {\n super({\n fill: true,\n showBulletPoints: false,\n showHelpLines: false,\n showZeroLine: false,\n padding: 0,\n ...props\n });\n }\n drawHelpLines() {\n if (!this.getProperty('showHelpLines', false))\n return this;\n this.helpLines.map((helpLine) => {\n this.bufferCtx.strokeStyle = helpLine.color;\n this.bufferCtx.fillStyle = helpLine.color;\n this.bufferCtx.beginPath();\n this.bufferCtx.moveTo(super.getDrawAreaRect().x, helpLine.y);\n this.bufferCtx.lineTo(super.getDrawAreaRect().x + this.bufferCtx.canvas.width, helpLine.y);\n this.bufferCtx.stroke();\n this.bufferCtx.fillText(helpLine.label, super.getDrawAreaRect().x, helpLine.y);\n });\n return this;\n }\n drawBuffer() {\n const singlePercentageHeight = this.getDrawAreaRect().h / 100; // 1% height\n this.drawHelpLines();\n if (this._showZeroLine()) {\n this.bufferCtx.strokeStyle = '#000000';\n this.bufferCtx.beginPath();\n this.bufferCtx.moveTo(this.getDrawAreaRect().x, this.getDrawAreaRect().y + this.getDrawAreaRect().h - singlePercentageHeight * this.zeroLineValuePercentage);\n this.bufferCtx.lineTo(this.getDrawAreaRect().x + this.bufferCtx.canvas.width, this.getDrawAreaRect().y + this.getDrawAreaRect().h - singlePercentageHeight * this.zeroLineValuePercentage);\n this.bufferCtx.stroke();\n }\n //console.log('viewHeight', this.getDrawAreaRect().h, this);\n this.dataPoints.map(group => {\n this.bufferCtx.beginPath();\n let lastX = 0;\n group.map((dataPoint, index) => {\n this.bufferCtx.strokeStyle = dataPoint.color;\n this.bufferCtx.fillStyle = dataPoint.color + '88';\n this.bufferCtx.lineWidth = dataPoint.isHighlighted ? 2 : 1;\n if (index === 0) {\n if (this.getProperty('fill', false)) {\n this.bufferCtx.moveTo(dataPoint.x, this.getDrawAreaRect().y + this.getDrawAreaRect().h);\n this.bufferCtx.lineTo(dataPoint.x, dataPoint.y);\n return;\n }\n this.bufferCtx.moveTo(dataPoint.x, dataPoint.y);\n }\n else {\n this.bufferCtx.lineTo(dataPoint.x, dataPoint.y);\n }\n lastX = dataPoint.x;\n });\n if (this.getProperty('fill', false)) {\n this.bufferCtx.lineTo(lastX, this.getDrawAreaRect().y + this.getDrawAreaRect().h);\n this.bufferCtx.fill();\n }\n this.bufferCtx.stroke();\n });\n if (this.getProperty('showBulletPoints', false)) {\n this.dataPoints.map(group => {\n group.map((dataPoint) => {\n const mouseDistance = Math.sqrt(Math.pow(this.hoverPosition.x - dataPoint.x, 2) + Math.pow(this.hoverPosition.y - dataPoint.y, 2));\n if (mouseDistance < this.bulletPointRadiusHover) {\n _Event_TooltipEvent__WEBPACK_IMPORTED_MODULE_1__[\"default\"].dispatch('test', {});\n }\n this.bufferCtx.fillStyle = dataPoint.color;\n this.bufferCtx.beginPath();\n this.bufferCtx.arc(dataPoint.x, dataPoint.y, (mouseDistance < this.bulletPointRadiusHover ? this.bulletPointRadiusHover : this.bulletPointRadius), 0, 2 * Math.PI);\n this.bufferCtx.fill();\n });\n });\n }\n return this;\n }\n handleHover(x, y) {\n this.hoverPosition = { x, y };\n return this;\n }\n updateDataGroups(dataGroups) {\n if (!this._dataGroupsChanged)\n return { dataGroups };\n this._dataGroups = dataGroups;\n if (dataGroups.length === 0 || dataGroups.every((dataGroup) => dataGroup.data.length === 0)) {\n // Nothing (left) to draw - e.g. after the data was cleared at runtime.\n this.dataPoints = [];\n this.helpLines = [];\n return { dataGroups };\n }\n const maxValue = Math.max(...dataGroups.map((group) => group.infos.max));\n const minValue = Math.min(...this._dataGroups.map((group) => group.infos.min), (this._showZeroLine() ? 0 : maxValue));\n this.zeroLineValue = Math.min(0, ...dataGroups.map((group) => group.infos.min)) * -1;\n this.zeroLineValuePercentage = this.zeroLineValue / (maxValue + this.zeroLineValue) * 100;\n const groupWithMostData = this._dataGroups.reduce((prev, current) => prev.data.length > current.data.length ? prev : current);\n const pointGap = this.calcPointGap(groupWithMostData);\n const largestDecimalStep = Math.max(...this._dataGroups.map((group) => group.infos.largestDecimalStep));\n const globalRange = (maxValue - minValue) === 0 ? 1 : maxValue - minValue;\n this.helpLines = [];\n for (let i = 1; i <= 10; i++) {\n let height = (this.getDrawAreaRect().h / 100) * Math.round(((i * largestDecimalStep) - minValue) / globalRange * 100);\n if (largestDecimalStep * i < minValue || largestDecimalStep * i > maxValue) {\n continue;\n }\n this.helpLines.push({\n color: '#ee4444',\n y: Math.round(this.getDrawAreaRect().h - height),\n label: (i * largestDecimalStep).toString()\n });\n }\n this.dataPoints = this._dataGroups.map((dataGroup) => {\n return dataGroup.data.map((dataPoint, index) => {\n let height = this.getDrawAreaRect().h - (this.getDrawAreaRect().h / 100) * Math.round((dataPoint.value - minValue) / globalRange * 100);\n if (dataPoint.empty)\n return undefined;\n return {\n ...dataPoint,\n x: this.getDrawAreaRect().x + pointGap * index,\n y: height,\n color: dataGroup.color,\n };\n }).filter((dataPoint) => dataPoint !== undefined);\n });\n return { dataGroups };\n }\n getDrawAreaRect() {\n const { x, y, h, w } = super.getDrawAreaRect();\n const manipulator = 0; //(this.getProperty('showBulletPoints', false) ? this.bulletPointRadiusHover : 0);\n return {\n x: x + manipulator,\n y: y + manipulator,\n h: h - (manipulator * 2),\n w: w - (manipulator * 2)\n };\n }\n getName() {\n return \"DiagramLine\";\n }\n _showZeroLine() {\n return this.getProperty('showZeroLine', false);\n }\n calcPointGap(dataGroup) {\n if (dataGroup.data.length < 2) {\n return this.getDrawAreaRect().w - (this.bulletPointRadius * 2);\n }\n return (this.getDrawAreaRect().w - (this.bulletPointRadius * 2)) / (dataGroup.data.length - 1);\n /*return this.getDrawAreaRect().w /\n (this.getProperty('viewSize', -1) === -1 ?\n (this.syncGroupsPosition ? Math.max(...this._dataGroups.map((group) => group.data.length - 1)) : dataGroup.data.length - 1) :\n this.getProperty('viewSize', -1) - 1)\n ;*/\n }\n}\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/plugin/DiagramLine.ts?\n}");
|
|
281
|
+
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ ERangeType: () => (/* binding */ ERangeType),\n/* harmony export */ \"default\": () => (/* binding */ DiagramLine)\n/* harmony export */ });\n/* harmony import */ var _AbstractDiagramPlugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AbstractDiagramPlugin */ \"./src/plugin/AbstractDiagramPlugin.ts\");\n/* harmony import */ var _Event_TooltipEvent__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../Event/TooltipEvent */ \"./src/Event/TooltipEvent.ts\");\n/* harmony import */ var _Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../Utils/ChartStyle */ \"./src/Utils/ChartStyle.ts\");\n/**\n * Library DiagramLine\n * Created 26.11.22\n * Last modified 29.09.26\n *\n * @author Christoph Ehlers\n */\n\n\n\nvar ERangeType;\n(function (ERangeType) {\n ERangeType[\"ZERO_BASED\"] = \"zero_based\";\n ERangeType[\"MIN_MAX\"] = \"min_max\";\n})(ERangeType || (ERangeType = {}));\nclass DiagramLine extends _AbstractDiagramPlugin__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n static X_AXIS_HEIGHT = 14;\n static PLOT_INSET_TOP = 6;\n static PLOT_INSET_BOTTOM = 3;\n static TOOLTIP_ENTITY_SOURCE = 'tooltip';\n static _instanceCounter = 0;\n _tooltipEntityId = 'DiagramLine.tooltip.' + (++DiagramLine._instanceCounter);\n bulletPointRadius = 2;\n bulletPointRadiusHover = 4;\n dataPoints = [];\n hoverIndex = -1;\n hoverPosition = { x: -1, y: -1 };\n maxDataLength = 0;\n plotRect = { x: 0, y: 0, w: 0, h: 0 };\n pointGap = 0;\n rangeType = ERangeType.MIN_MAX;\n yAxis = { min: 0, max: 1, step: 1, ticks: [] };\n constructor(props = {}) {\n super({\n decimals: 2,\n fill: true,\n lineWidth: 2,\n maxYTicks: 5,\n showBulletPoints: false,\n showGrid: true,\n showTooltip: true,\n showXLabels: true,\n showYLabels: true,\n showZeroLine: false,\n smooth: true,\n padding: 0,\n ...props\n });\n }\n drawBuffer() {\n const ctx = this.bufferCtx;\n this.drawGrid();\n if (this._showZeroLine() && this.yAxis.min <= 0 && this.yAxis.max >= 0) {\n const y = Math.round(this.valueToY(0)) + .5;\n ctx.strokeStyle = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.withAlpha)(this.getThemeValue('textColor'), .45);\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(0, y);\n ctx.lineTo(ctx.canvas.width, y);\n ctx.stroke();\n }\n this.dataPoints.map(group => {\n this.splitIntoSegments(group).map(segment => this.drawSegment(segment));\n });\n if (this.getProperty('showBulletPoints', false)) {\n this.dataPoints.map(group => {\n group.map((dataPoint) => {\n ctx.fillStyle = dataPoint.color;\n ctx.beginPath();\n ctx.arc(dataPoint.x, dataPoint.y, this.bulletPointRadius, 0, 2 * Math.PI);\n ctx.fill();\n });\n });\n }\n this.drawXLabels();\n super.drawBuffer();\n }\n /**\n * Draws the buffer and on top of it everything that must not scroll with\n * the camera or changes with every pointer move: the value axis and the\n * hover crosshair with its tooltip.\n */\n draw(forceRedraw = false) {\n super.draw(forceRedraw);\n if (this.getProperty('enabled', true) === false || this.dataPoints.length === 0) {\n return this;\n }\n this.drawYLabels();\n this.drawHover();\n return this;\n }\n handleHover(x, y) {\n this.hoverPosition = { x, y };\n return this;\n }\n updateDataGroups(dataGroups) {\n if (!this._dataGroupsChanged)\n return { dataGroups };\n this._dataGroups = dataGroups;\n if (dataGroups.length === 0 || dataGroups.every((dataGroup) => dataGroup.data.length === 0)) {\n // Nothing (left) to draw - e.g. after the data was cleared at runtime.\n this.dataPoints = [];\n this.maxDataLength = 0;\n return { dataGroups };\n }\n const filledGroups = dataGroups.filter((group) => group.data.some((data) => !data.empty));\n let minValue = filledGroups.length === 0 ? 0 : Math.min(...filledGroups.map((group) => group.infos.minValue ?? group.infos.min ?? 0));\n let maxValue = filledGroups.length === 0 ? 0 : Math.max(...filledGroups.map((group) => group.infos.max ?? 0));\n if (this._showZeroLine() || this.rangeType === ERangeType.ZERO_BASED) {\n minValue = Math.min(0, minValue);\n maxValue = Math.max(0, maxValue);\n }\n this.plotRect = this.calcPlotRect(dataGroups);\n const maxTicks = Math.max(2, Math.min(this.getProperty('maxYTicks', 5), Math.floor(this.plotRect.h / 22) + 1));\n this.yAxis = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.niceTicks)(minValue, maxValue, maxTicks);\n this.maxDataLength = Math.max(...dataGroups.map((group) => group.data.length));\n this.pointGap = this.calcPointGap(this.maxDataLength);\n this.dataPoints = this._dataGroups.map((dataGroup) => {\n // time labels are formatted per group, so values appended later are\n // written the same way as the initial ones\n const timeLabels = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.formatTimestamps)(dataGroup.data.map((data) => data.timestamp));\n return dataGroup.data.map((dataPoint, index) => {\n if (dataPoint.empty)\n return undefined;\n const label = dataPoint.hasLabel ? dataPoint.label : timeLabels[index];\n return {\n ...dataPoint,\n hasLabel: label !== undefined && label !== '',\n label,\n index,\n x: this.indexToX(index),\n y: this.valueToY(Number(dataPoint.value)),\n color: dataGroup.color,\n };\n }).filter((dataPoint) => dataPoint !== undefined);\n });\n return { dataGroups };\n }\n getName() {\n return \"DiagramLine\";\n }\n _showZeroLine() {\n return this.getProperty('showZeroLine', false);\n }\n calcPlotRect(dataGroups) {\n const { x, y, w, h } = this.getDrawAreaRect();\n const hasXLabels = this.getProperty('showXLabels', true)\n && dataGroups.some((group) => group.data.some((data) => data.hasLabel || data.timestamp !== undefined));\n const top = DiagramLine.PLOT_INSET_TOP;\n const bottom = DiagramLine.PLOT_INSET_BOTTOM + (hasXLabels ? DiagramLine.X_AXIS_HEIGHT : 0);\n return {\n x,\n y: y + top,\n w,\n h: Math.max(1, h - top - bottom),\n };\n }\n calcPointGap(dataLength) {\n const usableWidth = this.plotRect.w - (this.bulletPointRadiusHover * 2);\n if (dataLength < 2) {\n return usableWidth;\n }\n return usableWidth / (dataLength - 1);\n }\n indexToX(index) {\n if (this.maxDataLength < 2) {\n return this.plotRect.x + this.plotRect.w / 2;\n }\n return this.plotRect.x + this.bulletPointRadiusHover + this.pointGap * index;\n }\n valueToY(value) {\n const range = this.yAxis.max - this.yAxis.min;\n const ratio = range === 0 ? .5 : (value - this.yAxis.min) / range;\n return this.plotRect.y + this.plotRect.h - ratio * this.plotRect.h;\n }\n /**\n * Empty values interrupt the line instead of being drawn as 0.\n */\n splitIntoSegments(points) {\n const segments = [];\n let current = [];\n points.map((point) => {\n if (current.length > 0 && point.index !== current[current.length - 1].index + 1) {\n segments.push(current);\n current = [];\n }\n current.push(point);\n });\n if (current.length > 0) {\n segments.push(current);\n }\n return segments;\n }\n drawSegment(segment) {\n const ctx = this.bufferCtx;\n const color = segment[0].color;\n const baseline = this.plotRect.y + this.plotRect.h;\n if (segment.length === 1) {\n // A lonely value has no neighbour to connect to - show it as a dot.\n ctx.fillStyle = color;\n ctx.beginPath();\n ctx.arc(segment[0].x, segment[0].y, this.bulletPointRadius + .5, 0, 2 * Math.PI);\n ctx.fill();\n return;\n }\n if (this.getProperty('fill', false)) {\n ctx.beginPath();\n ctx.moveTo(segment[0].x, baseline);\n ctx.lineTo(segment[0].x, segment[0].y);\n this.tracePath(segment);\n ctx.lineTo(segment[segment.length - 1].x, baseline);\n ctx.closePath();\n ctx.fillStyle = this.createFillStyle(color);\n ctx.fill();\n }\n ctx.beginPath();\n ctx.moveTo(segment[0].x, segment[0].y);\n this.tracePath(segment);\n ctx.strokeStyle = color;\n ctx.lineWidth = this.getProperty('lineWidth', 2);\n ctx.lineJoin = 'round';\n ctx.lineCap = 'round';\n ctx.stroke();\n ctx.lineWidth = 1;\n }\n createFillStyle(color) {\n const top = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.withAlpha)(color, .35);\n const bottom = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.withAlpha)(color, .03);\n if (top === color || typeof this.bufferCtx.createLinearGradient !== 'function') {\n // unknown colour notation - a plain fill is better than an opaque one\n return top === color ? 'rgba(128, 128, 128, 0.2)' : top;\n }\n const gradient = this.bufferCtx.createLinearGradient(0, this.plotRect.y, 0, this.plotRect.y + this.plotRect.h);\n gradient.addColorStop(0, top);\n gradient.addColorStop(1, bottom);\n return gradient;\n }\n /**\n * Adds the line through the given points to the current path (the path\n * has to be at the first point already). With `smooth` the points are\n * connected by a monotone cubic curve (Fritsch-Carlson): it runs through\n * every value and never overshoots, so no peak is drawn that was never\n * measured.\n */\n tracePath(points) {\n const ctx = this.bufferCtx;\n if (!this.getProperty('smooth', true) || points.length < 3 || typeof ctx.bezierCurveTo !== 'function') {\n points.slice(1).map((point) => ctx.lineTo(point.x, point.y));\n return;\n }\n const n = points.length;\n const secants = [];\n for (let i = 0; i < n - 1; i++) {\n const dx = points[i + 1].x - points[i].x;\n secants.push(dx === 0 ? 0 : (points[i + 1].y - points[i].y) / dx);\n }\n const tangents = [secants[0]];\n for (let i = 1; i < n - 1; i++) {\n tangents.push(secants[i - 1] * secants[i] <= 0 ? 0 : (secants[i - 1] + secants[i]) / 2);\n }\n tangents.push(secants[n - 2]);\n for (let i = 0; i < n - 1; i++) {\n if (secants[i] === 0) {\n tangents[i] = 0;\n tangents[i + 1] = 0;\n continue;\n }\n const a = tangents[i] / secants[i];\n const b = tangents[i + 1] / secants[i];\n const sum = a * a + b * b;\n if (sum > 9) {\n const factor = 3 / Math.sqrt(sum);\n tangents[i] = factor * a * secants[i];\n tangents[i + 1] = factor * b * secants[i];\n }\n }\n for (let i = 0; i < n - 1; i++) {\n const p0 = points[i];\n const p1 = points[i + 1];\n const third = (p1.x - p0.x) / 3;\n ctx.bezierCurveTo(p0.x + third, p0.y + tangents[i] * third, p1.x - third, p1.y - tangents[i + 1] * third, p1.x, p1.y);\n }\n }\n drawGrid() {\n if (!this.isGridEnabled())\n return;\n const ctx = this.bufferCtx;\n ctx.strokeStyle = this.getThemeValue('gridColor');\n ctx.lineWidth = 1;\n this.yAxis.ticks.map((tick) => {\n const y = Math.round(this.valueToY(tick)) + .5;\n ctx.beginPath();\n ctx.moveTo(0, y);\n ctx.lineTo(ctx.canvas.width, y);\n ctx.stroke();\n });\n }\n isGridEnabled() {\n return this.getProperty('showGrid', true) || this.getProperty('showHelpLines', false);\n }\n /**\n * Labels of the values along the x axis (e.g. the time of a measurement).\n * Only as many as fit next to each other are drawn.\n */\n drawXLabels() {\n if (!this.getProperty('showXLabels', true))\n return;\n // the group with the most labelled values, not simply the longest one:\n // a long group without labels must not hide the labels of another\n const labelled = this.dataPoints\n .map((group) => group.filter((point) => point.hasLabel))\n .reduce((prev, current) => current.length > prev.length ? current : prev, []);\n if (labelled.length === 0)\n return;\n const ctx = this.bufferCtx;\n ctx.font = this.getThemeValue('font');\n ctx.fillStyle = this.getThemeValue('textColor');\n ctx.textBaseline = 'bottom';\n const labelWidth = Math.max(...labelled.map((point) => ctx.measureText(point.label).width)) + 16;\n const slots = Math.max(1, Math.floor(this.plotRect.w / labelWidth));\n const step = Math.max(1, Math.ceil(labelled.length / slots));\n const y = this.getDrawAreaRect().y + this.getDrawAreaRect().h;\n for (let i = 0; i < labelled.length; i += step) {\n const point = labelled[i];\n const half = ctx.measureText(point.label).width / 2;\n const x = Math.min(Math.max(point.x - half, 0), ctx.canvas.width - half * 2);\n ctx.textAlign = 'left';\n ctx.fillText(point.label, x, y);\n }\n }\n drawYLabels() {\n if (!this.getProperty('showYLabels', true))\n return;\n const ctx = this.ctx;\n // the step decides: fewer decimals would make neighbouring ticks look alike\n const decimals = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.decimalsOf)(this.yAxis.step);\n const unit = this.commonUnit();\n const right = this.getDrawAreaRect().x + this.getDrawAreaRect().w - 3;\n ctx.save();\n ctx.font = this.getThemeValue('font');\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n this.yAxis.ticks.map((tick) => {\n const y = this.toScreenY(this.valueToY(tick));\n const text = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.formatValue)(tick, decimals) + (unit ? ' ' + unit : '');\n const width = ctx.measureText(text).width;\n // a backdrop keeps the label readable where the line runs through it\n ctx.fillStyle = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.withAlpha)(this.getThemeValue('tooltipBackground'), .55);\n ctx.fillRect(right - width - 3, y - 7, width + 5, 14);\n ctx.fillStyle = this.getThemeValue('textColor');\n ctx.fillText(text, right, y);\n });\n ctx.restore();\n }\n drawHover() {\n this.publishTooltipRect(null);\n if (!this.getProperty('showTooltip', true))\n return;\n const index = this.findHoverIndex();\n if (index !== this.hoverIndex) {\n this.hoverIndex = index;\n _Event_TooltipEvent__WEBPACK_IMPORTED_MODULE_1__[\"default\"].dispatch('hover', { index, points: index === -1 ? [] : this.pointsAt(index) });\n }\n if (index === -1)\n return;\n const points = this.pointsAt(index);\n const ctx = this.ctx;\n const x = Math.round(this.toScreenX(this.indexToX(index))) + .5;\n const top = this.toScreenY(this.plotRect.y);\n const bottom = this.toScreenY(this.plotRect.y + this.plotRect.h);\n ctx.save();\n ctx.strokeStyle = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.withAlpha)(this.getThemeValue('textColor'), .6);\n ctx.lineWidth = 1;\n if (typeof ctx.setLineDash === 'function') {\n ctx.setLineDash([3, 3]);\n }\n ctx.beginPath();\n ctx.moveTo(x, top);\n ctx.lineTo(x, bottom);\n ctx.stroke();\n if (typeof ctx.setLineDash === 'function') {\n ctx.setLineDash([]);\n }\n points.map(({ point }) => {\n if (!point)\n return;\n ctx.beginPath();\n ctx.arc(x, this.toScreenY(point.y), this.bulletPointRadiusHover, 0, 2 * Math.PI);\n ctx.fillStyle = point.color;\n ctx.fill();\n ctx.lineWidth = 2;\n ctx.strokeStyle = this.getThemeValue('tooltipBackground');\n ctx.stroke();\n });\n this.drawTooltip(x, points);\n ctx.restore();\n }\n drawTooltip(x, points) {\n const ctx = this.ctx;\n const decimals = this.getProperty('decimals', 2);\n const single = points.length === 1;\n const header = points.map(({ point }) => point && point.hasLabel ? point.label : '').find((label) => label !== '')\n ?? (single ? points[0].group.label : '');\n const rows = points.map(({ group, point }) => {\n const value = point ? (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.formatValue)(point.value, decimals) + (group.unit ? ' ' + group.unit : '') : '–';\n return { color: group.color, text: single ? value : group.label + ': ' + value };\n });\n const paddingX = 7;\n const paddingY = 5;\n const rowHeight = 14;\n const swatch = 8;\n ctx.font = this.getThemeValue('font');\n const headerWidth = header ? ctx.measureText(header).width : 0;\n const rowsWidth = Math.max(...rows.map((row) => ctx.measureText(row.text).width + swatch + 5));\n const width = Math.ceil(Math.max(headerWidth, rowsWidth) + paddingX * 2);\n const height = (rows.length + (header ? 1 : 0)) * rowHeight + paddingY * 2;\n const area = this.getDrawAreaRect();\n let boxX = x + 10;\n if (boxX + width > area.x + area.w - 2) {\n boxX = x - 10 - width;\n }\n boxX = Math.max(area.x + 2, boxX);\n const boxY = Math.max(area.y + 2, Math.min(this.toScreenY(this.plotRect.y), area.y + area.h - height - 2));\n ctx.fillStyle = this.getThemeValue('tooltipBackground');\n ctx.strokeStyle = this.getThemeValue('tooltipBorder');\n ctx.lineWidth = 1;\n ctx.beginPath();\n if (typeof ctx.roundRect === 'function') {\n ctx.roundRect(boxX + .5, boxY + .5, width, height, 4);\n }\n else {\n ctx.rect(boxX + .5, boxY + .5, width, height);\n }\n ctx.fill();\n ctx.stroke();\n this.publishTooltipRect({ x: boxX, y: boxY, width, height });\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n let rowY = boxY + paddingY + rowHeight / 2;\n if (header) {\n ctx.fillStyle = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_2__.withAlpha)(this.getThemeValue('tooltipTextColor'), .7);\n ctx.fillText(header, boxX + paddingX, rowY);\n rowY += rowHeight;\n }\n rows.map((row) => {\n ctx.fillStyle = row.color;\n ctx.fillRect(boxX + paddingX, rowY - swatch / 2, swatch, swatch);\n ctx.fillStyle = this.getThemeValue('tooltipTextColor');\n ctx.fillText(row.text, boxX + paddingX + swatch + 5, rowY);\n rowY += rowHeight;\n });\n }\n /**\n * Tells the other plugins of the chart (e.g. the legend) where the tooltip\n * is drawn in this frame - `null` when there is none.\n */\n publishTooltipRect(rect) {\n if (!this._entityRegistry)\n return;\n this._entityRegistry.addEntity(DiagramLine.TOOLTIP_ENTITY_SOURCE, {\n ...(rect ?? { x: 0, y: 0, width: 0, height: 0 }),\n data: { visible: rect !== null },\n source: this.getName(),\n }, this._tooltipEntityId);\n }\n /**\n * The index of the value next to the pointer, -1 if the pointer is not\n * over the chart.\n */\n findHoverIndex() {\n const { x, y } = this.hoverPosition;\n if (x < 0 || y < 0 || this.maxDataLength === 0) {\n return -1;\n }\n const bufferX = this.toBufferX(x);\n if (this.maxDataLength < 2) {\n return 0;\n }\n const index = Math.round((bufferX - this.indexToX(0)) / this.pointGap);\n const clamped = Math.max(0, Math.min(this.maxDataLength - 1, index));\n return this.pointsAt(clamped).some(({ point }) => point !== undefined) ? clamped : -1;\n }\n pointsAt(index) {\n return this._dataGroups.map((group, groupIndex) => ({\n group,\n point: (this.dataPoints[groupIndex] || []).find((point) => point.index === index),\n }));\n }\n commonUnit() {\n const units = new Set(this._dataGroups.map((group) => group.unit || ''));\n return units.size === 1 ? [...units][0] : '';\n }\n /*\n * The buffer is drawn shifted by the camera and scaled into the padded\n * draw area (see AbstractPlugin.draw); these convert between both.\n */\n toScreenX(bufferX) {\n const area = this.getDrawAreaRect();\n const padding = this.getProperty('padding', 0);\n return area.x + padding + (bufferX - this._camera.x) * (area.w - 2 * padding) / area.w;\n }\n toScreenY(bufferY) {\n const area = this.getDrawAreaRect();\n const padding = this.getProperty('padding', 0);\n return area.y + padding + (bufferY - this._camera.y) * (area.h - 2 * padding) / area.h;\n }\n toBufferX(hoverX) {\n // hover positions already contain the camera offset\n const area = this.getDrawAreaRect();\n const padding = this.getProperty('padding', 0);\n return (hoverX - this._camera.x - area.x - padding) * area.w / (area.w - 2 * padding) + this._camera.x;\n }\n}\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/plugin/DiagramLine.ts?\n}");
|
|
271
282
|
|
|
272
283
|
/***/ },
|
|
273
284
|
|
|
@@ -278,7 +289,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa
|
|
|
278
289
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
279
290
|
|
|
280
291
|
"use strict";
|
|
281
|
-
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ EnrichGroupInfosPlugin)\n/* harmony export */ });\n/* harmony import */ var _AbstractPlugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AbstractPlugin */ \"./src/plugin/AbstractPlugin.ts\");\n/**\n * Library EnrichGroupInfosPlugin\n * Created 05.05,24\n *\n * @author Christoph Ehlers\n */\n\nclass EnrichGroupInfosPlugin extends _AbstractPlugin__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n constructor() {\n super();\n this._priority = 1;\n }\n updateDataGroups(dataGroups) {\n if (!this._dataGroupsChanged)\n return { dataGroups: this._dataGroups };\n this._dataGroups = dataGroups.map((dataGroup) => {\n const values = dataGroup.data.map((data) => data.value);\n const
|
|
292
|
+
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ EnrichGroupInfosPlugin)\n/* harmony export */ });\n/* harmony import */ var _AbstractPlugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AbstractPlugin */ \"./src/plugin/AbstractPlugin.ts\");\n/**\n * Library EnrichGroupInfosPlugin\n * Created 05.05,24\n *\n * @author Christoph Ehlers\n */\n\nclass EnrichGroupInfosPlugin extends _AbstractPlugin__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n constructor() {\n super();\n this._priority = 1;\n }\n updateDataGroups(dataGroups) {\n if (!this._dataGroupsChanged)\n return { dataGroups: this._dataGroups };\n this._dataGroups = dataGroups.map((dataGroup) => {\n const values = dataGroup.data.map((data) => data.value);\n // Empty values (gaps) must not pull the range down to 0.\n const filled = dataGroup.data.filter((data) => !data.empty && isFinite(Number(data.value)));\n const filledValues = filled.map((data) => Number(data.value));\n const valueStarts = filled.map((data) => Number(data.valueStart ?? data.value));\n // A group may be empty (e.g. right after clearing or before the\n // first value was added), so the infos have to stay finite.\n const min = filled.length === 0 ? 0 : Math.min(...valueStarts);\n const max = filled.length === 0 ? 0 : Math.max(...filledValues);\n const range = max - min;\n return {\n ...dataGroup,\n infos: {\n ...dataGroup.infos,\n largestDecimalStep: Math.max(this.getLargestDecimalStep(max), 0),\n max,\n min,\n // the smallest value itself - `min` includes the start of ranges\n minValue: filled.length === 0 ? 0 : Math.min(...filledValues),\n range,\n valuePercentage: values.map((value) => range === 0 ? 0 : (value - min) / range * 100)\n }\n };\n });\n return {\n dataGroups: this._dataGroups\n };\n }\n getLargestDecimalStep(x) {\n if (x <= 0) {\n return 0;\n }\n return Math.pow(10, Math.floor(Math.log10(x)));\n }\n getName() {\n return \"EnrichGroupInfosPlugin\";\n }\n}\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/plugin/EnrichGroupInfosPlugin.ts?\n}");
|
|
282
293
|
|
|
283
294
|
/***/ },
|
|
284
295
|
|
|
@@ -289,7 +300,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa
|
|
|
289
300
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
290
301
|
|
|
291
302
|
"use strict";
|
|
292
|
-
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ ERangeType: () => (/* binding */ ERangeType),\n/* harmony export */ \"default\": () => (/* binding */ LegendsPlugin)\n/* harmony export */ });\n/* harmony import */ var _AbstractPlugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AbstractPlugin */ \"./src/plugin/AbstractPlugin.ts\");\n/* harmony import */ var
|
|
303
|
+
eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ ERangeType: () => (/* binding */ ERangeType),\n/* harmony export */ \"default\": () => (/* binding */ LegendsPlugin)\n/* harmony export */ });\n/* harmony import */ var _AbstractPlugin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AbstractPlugin */ \"./src/plugin/AbstractPlugin.ts\");\n/* harmony import */ var _Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../Utils/ChartStyle */ \"./src/Utils/ChartStyle.ts\");\n/**\n * Library LegendsPlugin\n * Created 30.09.24\n * Last modified 29.09.26\n *\n * @author Christoph Ehlers\n */\n\n\nvar ERangeType;\n(function (ERangeType) {\n ERangeType[\"ZERO_BASED\"] = \"zero_based\";\n ERangeType[\"MIN_MAX\"] = \"min_max\";\n})(ERangeType || (ERangeType = {}));\nclass LegendsPlugin extends _AbstractPlugin__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n static NAME = \"LegendsPlugin\";\n _legendRect = null;\n constructor(props = {}) {\n super({\n x: 5,\n y: 5,\n hideOnHover: true,\n labelBoxHeight: 8,\n labelBoxWidth: 8,\n showSingleGroup: false,\n ...props\n });\n this._priority = 100;\n this._fixed = true;\n }\n /**\n * The groups the legend lists. A single group needs no legend - the\n * tooltip and the surrounding page name it already.\n */\n getVisibleGroups() {\n if (this._dataGroups.length < 2 && !this.getProperty('showSingleGroup', false)) {\n return [];\n }\n return this._dataGroups;\n }\n drawBuffer() {\n const groups = this.getVisibleGroups();\n this._legendRect = null;\n if (groups.length === 0) {\n super.drawBuffer();\n return;\n }\n const ctx = this.bufferCtx;\n const area = this.getDrawAreaRect();\n const labelBoxHeight = this.getProperty('labelBoxHeight', 8);\n const labelBoxWidth = this.getProperty('labelBoxWidth', 8);\n const rowHeight = 14;\n const gap = 10;\n const padding = 5;\n const maxWidth = area.w - area.x - 2 * padding;\n ctx.font = this.getThemeValue('font');\n // the entries are laid out side by side and wrap into a new row when\n // the canvas is too narrow\n const entries = [];\n let x = 0;\n let row = 0;\n let neededWidth = 0;\n groups.map((group) => {\n const width = labelBoxWidth + 4 + ctx.measureText(group.label).width;\n if (x > 0 && x + width > maxWidth) {\n x = 0;\n row++;\n }\n entries.push({ group, x, row });\n x += width + gap;\n neededWidth = Math.max(neededWidth, x - gap);\n });\n const neededHeight = (row + 1) * rowHeight;\n this._legendRect = { x: area.x, y: area.y, width: neededWidth + 2 * padding, height: neededHeight + padding };\n ctx.fillStyle = (0,_Utils_ChartStyle__WEBPACK_IMPORTED_MODULE_1__.withAlpha)(this.getThemeValue('tooltipBackground'), .75);\n ctx.beginPath();\n if (typeof ctx.roundRect === 'function') {\n ctx.roundRect(area.x, area.y, neededWidth + 2 * padding, neededHeight + padding, 4);\n }\n else {\n ctx.rect(area.x, area.y, neededWidth + 2 * padding, neededHeight + padding);\n }\n ctx.fill();\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n entries.map(({ group, x, row }) => {\n const centerY = area.y + padding / 2 + row * rowHeight + rowHeight / 2;\n ctx.fillStyle = group.color;\n ctx.fillRect(area.x + padding + x, centerY - labelBoxHeight / 2, labelBoxWidth, labelBoxHeight);\n ctx.fillStyle = this.getThemeValue('tooltipTextColor');\n ctx.fillText(group.label, area.x + padding + x + labelBoxWidth + 4, centerY);\n });\n super.drawBuffer();\n }\n draw(forceRedraw = false) {\n if (this.getProperty('hideOnHover', true) && this.isCoveredByTooltip()) {\n // the tooltip lists the groups itself and must stay readable\n return this;\n }\n return super.draw(forceRedraw);\n }\n /**\n * Whether a tooltip published by another plugin (see\n * DiagramLine.TOOLTIP_ENTITY_SOURCE) overlaps the legend in this frame.\n */\n isCoveredByTooltip() {\n const legend = this._legendRect;\n if (!legend || !this._entityRegistry) {\n return false;\n }\n return this._entityRegistry.getEntities('tooltip').some((tooltip) => tooltip.data?.visible\n && tooltip.x < legend.x + legend.width && tooltip.x + tooltip.width > legend.x\n && tooltip.y < legend.y + legend.height && tooltip.y + tooltip.height > legend.y);\n }\n getName() {\n return LegendsPlugin.NAME;\n }\n}\n\n\n//# sourceURL=webpack://@cehlers88/chart/./src/plugin/LegendsPlugin.ts?\n}");
|
|
293
304
|
|
|
294
305
|
/***/ },
|
|
295
306
|
|
|
@@ -12,6 +12,7 @@ export interface ICanvasDiagram {
|
|
|
12
12
|
getPlugin(pluginName: string): IChartPlugin | undefined;
|
|
13
13
|
removeDataGroup(group: string | number): ICanvasDiagram;
|
|
14
14
|
removeValue(group: string | number, dataIndex: number): ICanvasDiagram;
|
|
15
|
+
resize(width: number, height: number): ICanvasDiagram;
|
|
15
16
|
setData(dataDefinition: string | any[] | object): ICanvasDiagram;
|
|
16
17
|
setDataGroups(dataGroups: TDataGroup[]): ICanvasDiagram;
|
|
17
18
|
setValue(group: string | number, dataIndex: number, value: any): ICanvasDiagram;
|
|
@@ -10,6 +10,7 @@ export interface IChartEngine {
|
|
|
10
10
|
getPlugin(pluginName: string): IChartPlugin;
|
|
11
11
|
removeDataGroup(group: string | number): IChartEngine;
|
|
12
12
|
removeValue(group: string | number, dataIndex: number): IChartEngine;
|
|
13
|
+
resize(width: number, height: number): IChartEngine;
|
|
13
14
|
setData(dataDefinition: string | any[] | object): IChartEngine;
|
|
14
15
|
setDataGroups(dataGroups: TDataGroup[]): IChartEngine;
|
|
15
16
|
setValue(group: string | number, dataIndex: number, value: any): IChartEngine;
|
|
@@ -20,6 +20,8 @@ export interface IChartPlugin {
|
|
|
20
20
|
handleHover(x: number, y: number): IChartPlugin;
|
|
21
21
|
handleLeave(): IChartPlugin;
|
|
22
22
|
handleScroll(x: number, y: number): IChartPlugin;
|
|
23
|
+
invalidate?(): IChartPlugin;
|
|
24
|
+
resize?(canvasElement: HTMLCanvasElement): IChartPlugin;
|
|
23
25
|
setCamera(camera: any): IChartPlugin;
|
|
24
26
|
setDrawAreaRect(rect: TRect): IChartPlugin;
|
|
25
27
|
setEntityRegistry(entityRegistry: any): IChartPlugin;
|
|
@@ -10,6 +10,7 @@ import { TRect } from "../type/TRect";
|
|
|
10
10
|
import Camera from "../Camera";
|
|
11
11
|
import EntityRegistry from "../Registry/EntityRegistry";
|
|
12
12
|
import { IChartEntity } from "../interface/IChartEntity";
|
|
13
|
+
import { TChartTheme } from "../Utils/ChartStyle";
|
|
13
14
|
export default abstract class AbstractPlugin implements IChartPlugin {
|
|
14
15
|
protected _camera: Camera;
|
|
15
16
|
protected _dataGroups: TDataGroup[];
|
|
@@ -20,6 +21,7 @@ export default abstract class AbstractPlugin implements IChartPlugin {
|
|
|
20
21
|
protected ctx: CanvasRenderingContext2D;
|
|
21
22
|
protected redrawBuffer: boolean;
|
|
22
23
|
protected _dataGroupsChanged: boolean;
|
|
24
|
+
private _theme;
|
|
23
25
|
private _buffer;
|
|
24
26
|
private _dataGroupsSignature;
|
|
25
27
|
private _stateChangedListener;
|
|
@@ -36,6 +38,18 @@ export default abstract class AbstractPlugin implements IChartPlugin {
|
|
|
36
38
|
update(dataGroups: TDataGroup[]): {
|
|
37
39
|
dataGroups: TDataGroup[];
|
|
38
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Hook for plugins that have to prepare themselves (e.g. resize their
|
|
43
|
+
* buffer) before the changed data is processed.
|
|
44
|
+
*/
|
|
45
|
+
protected onDataGroupsChanged(dataGroups: TDataGroup[]): void;
|
|
46
|
+
/**
|
|
47
|
+
* Compares the given data groups by content against the ones seen by the
|
|
48
|
+
* last update and remembers the result in `_dataGroupsChanged`. Comparing
|
|
49
|
+
* by content (instead of holding on to the data groups themselves) is what
|
|
50
|
+
* makes manipulations of already known data groups visible.
|
|
51
|
+
*/
|
|
52
|
+
private detectDataGroupsChange;
|
|
39
53
|
updateDataGroups(dataGroups: TDataGroup[]): {
|
|
40
54
|
dataGroups: TDataGroup[];
|
|
41
55
|
};
|
|
@@ -44,6 +58,22 @@ export default abstract class AbstractPlugin implements IChartPlugin {
|
|
|
44
58
|
dataGroups: TDataGroup[];
|
|
45
59
|
plugin: AbstractPlugin;
|
|
46
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Adapts the plugin to a new size of the canvas. Unlike `create()` this
|
|
63
|
+
* has no side effects besides rebuilding the buffer, so it can be called
|
|
64
|
+
* as often as the canvas changes its size.
|
|
65
|
+
*/
|
|
66
|
+
resize(canvasElement: HTMLCanvasElement): AbstractPlugin;
|
|
67
|
+
/**
|
|
68
|
+
* Forces the plugin to process its data and to redraw its buffer with the
|
|
69
|
+
* next frame, even if the data did not change.
|
|
70
|
+
*/
|
|
71
|
+
invalidate(): AbstractPlugin;
|
|
72
|
+
/**
|
|
73
|
+
* Returns a colour/font of the theme. A plugin property of the same name
|
|
74
|
+
* (e.g. `textColor`) wins over the `--chart-*` CSS properties of the canvas.
|
|
75
|
+
*/
|
|
76
|
+
protected getThemeValue(key: keyof TChartTheme): string;
|
|
47
77
|
drawBuffer(): void;
|
|
48
78
|
draw(forceRedraw?: boolean): IChartPlugin;
|
|
49
79
|
getDrawAreaRect(): TRect;
|
|
@@ -59,17 +89,5 @@ export default abstract class AbstractPlugin implements IChartPlugin {
|
|
|
59
89
|
setEntityRegistry(entityRegistry: EntityRegistry): AbstractPlugin;
|
|
60
90
|
setProperty(propertyName: string, value: any): AbstractPlugin;
|
|
61
91
|
init(): AbstractPlugin;
|
|
62
|
-
/**
|
|
63
|
-
* Hook for plugins that have to prepare themselves (e.g. resize their
|
|
64
|
-
* buffer) before the changed data is processed.
|
|
65
|
-
*/
|
|
66
|
-
protected onDataGroupsChanged(dataGroups: TDataGroup[]): void;
|
|
67
92
|
protected createBuffer(canvasElement: HTMLCanvasElement, width?: number | null, height?: number | null): void;
|
|
68
|
-
/**
|
|
69
|
-
* Compares the given data groups by content against the ones seen by the
|
|
70
|
-
* last update and remembers the result in `_dataGroupsChanged`. Comparing
|
|
71
|
-
* by content (instead of holding on to the data groups themselves) is what
|
|
72
|
-
* makes manipulations of already known data groups visible.
|
|
73
|
-
*/
|
|
74
|
-
private detectDataGroupsChange;
|
|
75
93
|
}
|
|
@@ -1,42 +1,105 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Library DiagramLine
|
|
3
3
|
* Created 26.11.22
|
|
4
|
+
* Last modified 29.09.26
|
|
4
5
|
*
|
|
5
6
|
* @author Christoph Ehlers
|
|
6
7
|
*/
|
|
7
8
|
import AbstractPlugin from "./AbstractPlugin";
|
|
8
9
|
import { TDataGroup } from "../type/TDataGroup";
|
|
9
|
-
import {
|
|
10
|
+
import { IChartPlugin } from "../interface/IChartPlugin";
|
|
10
11
|
import AbstractDiagramPlugin from "./AbstractDiagramPlugin";
|
|
11
12
|
export declare enum ERangeType {
|
|
12
13
|
ZERO_BASED = "zero_based",
|
|
13
14
|
MIN_MAX = "min_max"
|
|
14
15
|
}
|
|
15
16
|
export default class DiagramLine extends AbstractDiagramPlugin {
|
|
17
|
+
private static X_AXIS_HEIGHT;
|
|
18
|
+
private static PLOT_INSET_TOP;
|
|
19
|
+
private static PLOT_INSET_BOTTOM;
|
|
20
|
+
static TOOLTIP_ENTITY_SOURCE: string;
|
|
21
|
+
private static _instanceCounter;
|
|
22
|
+
private _tooltipEntityId;
|
|
16
23
|
private bulletPointRadius;
|
|
17
24
|
private bulletPointRadiusHover;
|
|
18
25
|
private dataPoints;
|
|
19
|
-
private
|
|
26
|
+
private hoverIndex;
|
|
20
27
|
private hoverPosition;
|
|
28
|
+
private maxDataLength;
|
|
29
|
+
private plotRect;
|
|
30
|
+
private pointGap;
|
|
21
31
|
private rangeType;
|
|
22
|
-
private
|
|
23
|
-
private zeroLineValue;
|
|
24
|
-
private zeroLineValuePercentage;
|
|
32
|
+
private yAxis;
|
|
25
33
|
constructor(props?: {
|
|
34
|
+
decimals?: number;
|
|
26
35
|
fill?: boolean;
|
|
36
|
+
lineWidth?: number;
|
|
37
|
+
maxYTicks?: number;
|
|
27
38
|
padding?: number;
|
|
28
39
|
showBulletPoints?: boolean;
|
|
40
|
+
showGrid?: boolean;
|
|
29
41
|
showHelpLines?: boolean;
|
|
42
|
+
showTooltip?: boolean;
|
|
43
|
+
showXLabels?: boolean;
|
|
44
|
+
showYLabels?: boolean;
|
|
30
45
|
showZeroLine?: boolean;
|
|
46
|
+
smooth?: boolean;
|
|
47
|
+
[key: string]: any;
|
|
31
48
|
});
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
drawBuffer(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Draws the buffer and on top of it everything that must not scroll with
|
|
52
|
+
* the camera or changes with every pointer move: the value axis and the
|
|
53
|
+
* hover crosshair with its tooltip.
|
|
54
|
+
*/
|
|
55
|
+
draw(forceRedraw?: boolean): IChartPlugin;
|
|
34
56
|
handleHover(x: number, y: number): AbstractPlugin;
|
|
35
57
|
updateDataGroups(dataGroups: TDataGroup[]): {
|
|
36
58
|
dataGroups: TDataGroup[];
|
|
37
59
|
};
|
|
38
|
-
getDrawAreaRect(): TRect;
|
|
39
60
|
getName(): string;
|
|
40
61
|
private _showZeroLine;
|
|
62
|
+
private calcPlotRect;
|
|
41
63
|
private calcPointGap;
|
|
64
|
+
private indexToX;
|
|
65
|
+
private valueToY;
|
|
66
|
+
/**
|
|
67
|
+
* Empty values interrupt the line instead of being drawn as 0.
|
|
68
|
+
*/
|
|
69
|
+
private splitIntoSegments;
|
|
70
|
+
private drawSegment;
|
|
71
|
+
private createFillStyle;
|
|
72
|
+
/**
|
|
73
|
+
* Adds the line through the given points to the current path (the path
|
|
74
|
+
* has to be at the first point already). With `smooth` the points are
|
|
75
|
+
* connected by a monotone cubic curve (Fritsch-Carlson): it runs through
|
|
76
|
+
* every value and never overshoots, so no peak is drawn that was never
|
|
77
|
+
* measured.
|
|
78
|
+
*/
|
|
79
|
+
private tracePath;
|
|
80
|
+
private drawGrid;
|
|
81
|
+
private isGridEnabled;
|
|
82
|
+
/**
|
|
83
|
+
* Labels of the values along the x axis (e.g. the time of a measurement).
|
|
84
|
+
* Only as many as fit next to each other are drawn.
|
|
85
|
+
*/
|
|
86
|
+
private drawXLabels;
|
|
87
|
+
private drawYLabels;
|
|
88
|
+
private drawHover;
|
|
89
|
+
private drawTooltip;
|
|
90
|
+
/**
|
|
91
|
+
* Tells the other plugins of the chart (e.g. the legend) where the tooltip
|
|
92
|
+
* is drawn in this frame - `null` when there is none.
|
|
93
|
+
*/
|
|
94
|
+
private publishTooltipRect;
|
|
95
|
+
/**
|
|
96
|
+
* The index of the value next to the pointer, -1 if the pointer is not
|
|
97
|
+
* over the chart.
|
|
98
|
+
*/
|
|
99
|
+
private findHoverIndex;
|
|
100
|
+
private pointsAt;
|
|
101
|
+
private commonUnit;
|
|
102
|
+
private toScreenX;
|
|
103
|
+
private toScreenY;
|
|
104
|
+
private toBufferX;
|
|
42
105
|
}
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Library LegendsPlugin
|
|
3
3
|
* Created 30.09.24
|
|
4
|
-
* Last modified
|
|
4
|
+
* Last modified 29.09.26
|
|
5
5
|
*
|
|
6
6
|
* @author Christoph Ehlers
|
|
7
7
|
*/
|
|
8
8
|
import AbstractPlugin from "./AbstractPlugin";
|
|
9
|
+
import { IChartPlugin } from "../interface/IChartPlugin";
|
|
9
10
|
export declare enum ERangeType {
|
|
10
11
|
ZERO_BASED = "zero_based",
|
|
11
12
|
MIN_MAX = "min_max"
|
|
12
13
|
}
|
|
13
14
|
export default class LegendsPlugin extends AbstractPlugin {
|
|
14
15
|
static NAME: string;
|
|
16
|
+
private _legendRect;
|
|
15
17
|
constructor(props?: {
|
|
16
18
|
x?: number;
|
|
17
19
|
y?: number;
|
|
20
|
+
hideOnHover?: boolean;
|
|
21
|
+
showSingleGroup?: boolean;
|
|
22
|
+
[key: string]: any;
|
|
18
23
|
});
|
|
24
|
+
/**
|
|
25
|
+
* The groups the legend lists. A single group needs no legend - the
|
|
26
|
+
* tooltip and the surrounding page name it already.
|
|
27
|
+
*/
|
|
28
|
+
private getVisibleGroups;
|
|
19
29
|
drawBuffer(): void;
|
|
30
|
+
draw(forceRedraw?: boolean): IChartPlugin;
|
|
31
|
+
/**
|
|
32
|
+
* Whether a tooltip published by another plugin (see
|
|
33
|
+
* DiagramLine.TOOLTIP_ENTITY_SOURCE) overlaps the legend in this frame.
|
|
34
|
+
*/
|
|
35
|
+
private isCoveredByTooltip;
|
|
20
36
|
getName(): string;
|
|
21
37
|
}
|
package/dist/type/TData.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cehlers88/chart",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "A library to create charts using JavaScript and Canvas-Element.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "webpack",
|
|
11
11
|
"watch": "webpack --watch",
|
|
12
|
-
"test": "jest"
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"prepublishOnly": "npm test && npm run build"
|
|
13
14
|
},
|
|
14
15
|
"repository": {
|
|
15
16
|
"type": "git",
|