svelte-plots-basic 1.1.3 → 2.0.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/README.md +54 -69
- package/package.json +28 -33
- package/src/2d/Area.svelte +61 -0
- package/src/2d/Axes.svelte +500 -0
- package/src/2d/Axis.svelte +50 -0
- package/src/2d/AxisLines.svelte +54 -0
- package/src/2d/AxisTickLabels.svelte +82 -0
- package/src/2d/Bars.svelte +65 -0
- package/src/2d/Box.svelte +50 -0
- package/src/2d/ColorLegend.svelte +36 -0
- package/src/2d/Legend.svelte +148 -0
- package/src/2d/Lines.svelte +49 -0
- package/src/2d/Multilines.svelte +67 -0
- package/src/2d/Points.svelte +79 -0
- package/src/2d/Rectangles.svelte +74 -0
- package/src/2d/Segments.svelte +65 -0
- package/src/2d/TextLabels.svelte +107 -0
- package/src/2d/TextLegend.svelte +53 -0
- package/src/2d/XAxis.svelte +99 -0
- package/src/2d/YAxis.svelte +99 -0
- package/src/{index.js → 2d/index.js} +10 -6
- package/src/3d/Axes.svelte +445 -0
- package/src/3d/Axis.svelte +64 -0
- package/src/3d/AxisLines.svelte +50 -0
- package/src/3d/AxisTickLabels.svelte +54 -0
- package/src/3d/Lines.svelte +49 -0
- package/src/3d/Mesh.svelte +188 -0
- package/src/3d/Points.svelte +61 -0
- package/src/3d/Segments.svelte +69 -0
- package/src/3d/TextLabels.svelte +79 -0
- package/src/3d/XAxis.svelte +123 -0
- package/src/3d/YAxis.svelte +123 -0
- package/src/3d/ZAxis.svelte +123 -0
- package/src/3d/index.js +13 -0
- package/src/Colors.js +36 -1
- package/src/Utils.js +205 -0
- package/dist/index.js +0 -4209
- package/dist/index.mjs +0 -4187
- package/src/AreaSeries.svelte +0 -53
- package/src/Axes.svelte +0 -538
- package/src/BarSeries.svelte +0 -78
- package/src/Box.svelte +0 -35
- package/src/LineSeries.svelte +0 -50
- package/src/Rectangles.svelte +0 -64
- package/src/ScatterSeries.svelte +0 -85
- package/src/Segments.svelte +0 -48
- package/src/TextLabels.svelte +0 -78
- package/src/TextLegend.svelte +0 -38
- package/src/XAxis.svelte +0 -69
- package/src/YAxis.svelte +0 -72
package/src/AreaSeries.svelte
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
|
|
3
|
-
import { getContext } from 'svelte';
|
|
4
|
-
import { mrange } from 'mdatools/stat';
|
|
5
|
-
import { Colors } from './Colors';
|
|
6
|
-
|
|
7
|
-
// input parameters
|
|
8
|
-
export let xValues;
|
|
9
|
-
export let yValues;
|
|
10
|
-
export let title = "";
|
|
11
|
-
export let lineWidth = 1;
|
|
12
|
-
export let lineColor = Colors.PRIMARY;
|
|
13
|
-
export let fillColor = Colors.PRIMARY;
|
|
14
|
-
export let opacity = 1;
|
|
15
|
-
export let lineType = 1;
|
|
16
|
-
|
|
17
|
-
/* sanity check of input parameters */
|
|
18
|
-
if (!Array.isArray(xValues) || !Array.isArray(yValues) || xValues.length != yValues.length) {
|
|
19
|
-
throw("AreaSeries: parameters 'xValues' and 'yValues' must be numeric vectors of the same length.");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// compute ranges for x and y values
|
|
23
|
-
const xValuesRange = mrange(xValues, 0.05);
|
|
24
|
-
const yValuesRange = mrange(yValues, 0.05);
|
|
25
|
-
|
|
26
|
-
// get axes context and adjust axes limits
|
|
27
|
-
const axes = getContext('axes');
|
|
28
|
-
axes.adjustXAxisLimits(xValuesRange);
|
|
29
|
-
axes.adjustYAxisLimits(yValuesRange);
|
|
30
|
-
|
|
31
|
-
// get reactive variables needed to compute coordinates
|
|
32
|
-
const xLim = axes.xLim;
|
|
33
|
-
const yLim = axes.yLim;
|
|
34
|
-
const axesWidth = axes.width;
|
|
35
|
-
const axesHeight = axes.height;
|
|
36
|
-
const scale = axes.scale;
|
|
37
|
-
|
|
38
|
-
// reactive variables for coordinates of data points in pixels
|
|
39
|
-
$: y0 = axes.scaleY([0], $yLim, $axesHeight);
|
|
40
|
-
$: x = axes.scaleX(xValues, $xLim, $axesWidth);
|
|
41
|
-
$: y = axes.scaleY(yValues, $yLim, $axesHeight);
|
|
42
|
-
$: p = x !== undefined && y !== undefined ? x.map((v, i) => `${v},${y[i]}`).join(' ') : undefined;
|
|
43
|
-
$: areaStyleStr = `opacity:${opacity};fill:${fillColor};stroke:${lineColor};stroke-width: ${lineWidth}px;stroke-dasharray:${axes.LINE_STYLES[$scale][lineType - 1]}`;
|
|
44
|
-
</script>
|
|
45
|
-
|
|
46
|
-
{#if p !== undefined}
|
|
47
|
-
<g class="series lineseries" style="{areaStyleStr}" title="{title}">
|
|
48
|
-
<polygon points="{x[0] + "," + y0 + " " + p + " " + x[x.length - 1] + "," + y0[0]}"/>
|
|
49
|
-
</g>
|
|
50
|
-
{/if}
|
|
51
|
-
|
|
52
|
-
<style>
|
|
53
|
-
</style>
|
package/src/Axes.svelte
DELETED
|
@@ -1,538 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { setContext, createEventDispatcher, onMount, onDestroy } from 'svelte';
|
|
3
|
-
import { writable } from 'svelte/store';
|
|
4
|
-
|
|
5
|
-
/* input parameters */
|
|
6
|
-
export let limX = [undefined, undefined]; // limits for x-axis (in plot units) [min, max]
|
|
7
|
-
export let limY = [undefined, undefined]; // limits for y-axis (in plot units) [min, max]
|
|
8
|
-
export let title = ""; // title of the plot
|
|
9
|
-
export let xLabel = ""; // label for x-axis
|
|
10
|
-
export let yLabel = ""; // label for y-axis
|
|
11
|
-
export let multiSeries = true; // is the plot for one series or for many
|
|
12
|
-
|
|
13
|
-
// event dispatcher
|
|
14
|
-
const dispatch = createEventDispatcher();
|
|
15
|
-
|
|
16
|
-
// how big are margins (number of pixels in unit margin value) between axis and plot area if axis are shown
|
|
17
|
-
const AXES_MARGIN_FACTORS = {
|
|
18
|
-
"small": 30,
|
|
19
|
-
"medium": 40,
|
|
20
|
-
"large": 50
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// number of ticks along each axis
|
|
24
|
-
const TICK_NUM = {
|
|
25
|
-
"small": 5,
|
|
26
|
-
"medium": 8,
|
|
27
|
-
"large": 12
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// margin between plot series elements and data labels
|
|
31
|
-
const LABELS_MARGIN = {
|
|
32
|
-
"small": 10,
|
|
33
|
-
"medium": 15,
|
|
34
|
-
"large": 20
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// line styles for different scales and types
|
|
39
|
-
const LINE_STYLES = {
|
|
40
|
-
small: ["0", "3,3", "1,1", "3,1"],
|
|
41
|
-
medium: ["0", "5,5", "2,2", "5,2"],
|
|
42
|
-
large: ["0", "7,7", "3,3", "7,3"],
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// constant to make clip path ID unique
|
|
46
|
-
const clipPathID = "plottingArea" + Math.round(Math.random() * 10000);
|
|
47
|
-
|
|
48
|
-
/* parameters for internal use inside the component */
|
|
49
|
-
let plotElement;
|
|
50
|
-
let axesElement;
|
|
51
|
-
let axesMargins = [0.034, 0.034, 0.034, 0.034]; // initial margins (will be multiplied to FACTORS)
|
|
52
|
-
|
|
53
|
-
/* reactive parameters to be shared with children via context */
|
|
54
|
-
const width = writable(100); // actual width of plotting area in pixels
|
|
55
|
-
const height = writable(100); // actual height of plotting area in pixels
|
|
56
|
-
const xLim = writable([undefined, undefined]); // actual limits for x-axis in plot units
|
|
57
|
-
const yLim = writable([undefined, undefined]); // actual limits for y-axis in plot units
|
|
58
|
-
const scale = writable("medium"); // scale factor (how big the shown plot is)
|
|
59
|
-
const isOk = writable(false); // are axes ready for drawing
|
|
60
|
-
|
|
61
|
-
/** Adds margins for x-axis (e.g. when x-axis must be shown) */
|
|
62
|
-
const addXAxisMargins = function() {
|
|
63
|
-
axesMargins[0] = 1;
|
|
64
|
-
axesMargins[2] = 0.5;
|
|
65
|
-
axesMargins[1] = axesMargins[1] > 0.5 ? axesMargins[1] : 0.5;
|
|
66
|
-
axesMargins[3] = axesMargins[3] > 0.5 ? axesMargins[3] : 0.5;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Adds margins for y-axis (e.g. when y-axis must be shown) */
|
|
70
|
-
const addYAxisMargins = function() {
|
|
71
|
-
axesMargins[1] = 1;
|
|
72
|
-
axesMargins[3] = 0.5;
|
|
73
|
-
axesMargins[0] = axesMargins[0] > 0.5 ? axesMargins[0] : 0.5;
|
|
74
|
-
axesMargins[2] = axesMargins[2] > 0.5 ? axesMargins[2] : 0.5;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Adjusts limits for x-axis (e.g. when new series is added)
|
|
78
|
-
* @param {Array} newLim - vector with new limits (two values)
|
|
79
|
-
*/
|
|
80
|
-
const adjustXAxisLimits = function(newLim) {
|
|
81
|
-
if (!limX.some(v => v === undefined)) return;
|
|
82
|
-
xLim.update(lim => adjustAxisLimits(lim, newLim));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Adjusts limits for y-axis (e.g. when new series is added)
|
|
86
|
-
* @param {Array} newLim - vector with new limits (two values)
|
|
87
|
-
*/
|
|
88
|
-
const adjustYAxisLimits = function(newLim) {
|
|
89
|
-
if (!limY.some(v => v === undefined)) return;
|
|
90
|
-
yLim.update(lim => adjustAxisLimits(lim, newLim));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Adjusts x- or y- axis limits (e.g. when new elements are added)
|
|
94
|
-
* @param {Array} lim - vector with limits for current axis (two values)
|
|
95
|
-
* @param {Array} newLim - vector with new limits (two values)
|
|
96
|
-
* @returns {Array} vector with rescaled values
|
|
97
|
-
*
|
|
98
|
-
* The new limits are set separately for min and max. Either if current value is undefined or
|
|
99
|
-
* if new value is outside the current limits (smaller than min or larger than max).
|
|
100
|
-
*/
|
|
101
|
-
const adjustAxisLimits = function(lim, newLim) {
|
|
102
|
-
|
|
103
|
-
let adjustedLim = [
|
|
104
|
-
(lim[0] !== undefined && multiSeries === true && lim[0] < newLim[0]) ? lim[0] : newLim[0],
|
|
105
|
-
(lim[1] !== undefined && multiSeries === true && lim[1] > newLim[1]) ? lim[1] : newLim[1]
|
|
106
|
-
];
|
|
107
|
-
|
|
108
|
-
// special case when both limits are zero
|
|
109
|
-
if (adjustedLim[0] === 0 && adjustedLim[1] === 0) {
|
|
110
|
-
adjustedLim = [-0.1, 0.1];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// special case when limits are equal (add ±5%)
|
|
114
|
-
if (adjustedLim[0] === adjustedLim[1]) {
|
|
115
|
-
adjustedLim = [adjustedLim[0] * 0.95, adjustedLim[0] * 1.05];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return adjustedLim;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Rescales x-values from plot coordinates to screen (SVG) coordinates
|
|
122
|
-
* @param {Array} x - vector with coordinates (or objects size) in original plot coordinates
|
|
123
|
-
* @param {Array} xLim - vector with current limits for x-axis in original plot coordinates
|
|
124
|
-
* @param {number} width - width of coordinate system in pixels
|
|
125
|
-
* @param {boolean} doSizeScreen - scale size of objects (true) or coordinates
|
|
126
|
-
* @returns {Array} vector with rescaled values
|
|
127
|
-
*/
|
|
128
|
-
const scaleX = function(x, xLim, width, doSizeScale = false) {
|
|
129
|
-
if (!$isOk || x === undefined || !Array.isArray(x)) return undefined;
|
|
130
|
-
|
|
131
|
-
if (doSizeScale) {
|
|
132
|
-
// scale size of objects instead of coordinates
|
|
133
|
-
return x.map(v => v / (xLim[1] - xLim[0]) * (width - margins[1] - margins[3]));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return x.map(v => (v - xLim[0]) / (xLim[1] - xLim[0]) * (width - margins[1] - margins[3]) + margins[1]);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** Rescales x-values from plot coordinates to screen (SVG) coordinates
|
|
140
|
-
* @param {Array} x - vector with coordinates (or objects size) in original plot coordinates
|
|
141
|
-
* @param {Array} xLim - vector with current limits for x-axis in original plot coordinates
|
|
142
|
-
* @param {number} width - width of coordinate system in pixels
|
|
143
|
-
* @param {boolean} doSizeScreen - scale size of objects (true) or coordinates
|
|
144
|
-
* @returns {Array} vector with rescaled values
|
|
145
|
-
*/
|
|
146
|
-
const scaleY = function(y, yLim, height, doSizeScale = false) {
|
|
147
|
-
if (!$isOk || y === undefined || !Array.isArray(y)) return undefined;
|
|
148
|
-
|
|
149
|
-
if (doSizeScale) {
|
|
150
|
-
// scale size of objects instead of coordinates
|
|
151
|
-
return y.map(v => v / (yLim[1] - yLim[0]) * (height - margins[0] - margins[2]));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// for coordinates we also need to invert (flip) the y-axis
|
|
155
|
-
return y.map(v => (yLim[1] - v) / (yLim[1] - yLim[0]) * (height - margins[0] - margins[2]) + margins[2]);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** Computes nice tick values for axis
|
|
159
|
-
* @param {Array} ticks - vector with ticks if alredy available (if not, new will be computed)
|
|
160
|
-
* @param {Array} lim - vector with axis limits tickets must be computed for
|
|
161
|
-
* @param {number} maxTickNum - maximum number of ticks to compute
|
|
162
|
-
* @param {boolean} round - round or not the fractions when computing nice numbers for the ticks
|
|
163
|
-
* @returns {Array} a vector with computed tick positions
|
|
164
|
-
*/
|
|
165
|
-
const getAxisTicks = function(ticks, lim, maxTickNum, round = true) {
|
|
166
|
-
|
|
167
|
-
// if ticks are already provided do not recompute them
|
|
168
|
-
if (ticks !== undefined) return ticks;
|
|
169
|
-
|
|
170
|
-
// check if limits are ok
|
|
171
|
-
if (!Array.isArray(lim) || lim[0] === undefined || lim[1] === undefined) return undefined;
|
|
172
|
-
|
|
173
|
-
// get range as a nice number and compute min, max and steps for the tick sequence
|
|
174
|
-
const range = niceNum(lim[1] - lim[0], round);
|
|
175
|
-
const tickSpacing = niceNum(range / (maxTickNum - 1), round);
|
|
176
|
-
const tickMin = Math.ceil(lim[0] / tickSpacing) * tickSpacing;
|
|
177
|
-
const tickMax = Math.floor(lim[1] / tickSpacing) * tickSpacing;
|
|
178
|
-
|
|
179
|
-
// recompute maxTickNum
|
|
180
|
-
maxTickNum = Math.round((tickMax - tickMin + 1) / tickSpacing) + 1;
|
|
181
|
-
|
|
182
|
-
// create a sequence and return
|
|
183
|
-
ticks = [...Array(maxTickNum)].map((x, i) => tickMin + i * tickSpacing);
|
|
184
|
-
|
|
185
|
-
// if step is smaller than 1 round values to remove small decimals accidentiall added by JS
|
|
186
|
-
if (Math.abs(tickSpacing) < 1) {
|
|
187
|
-
const r = Math.pow(10, 1 + Math.round(-Math.log10(tickSpacing)));
|
|
188
|
-
ticks = ticks.map(v => Math.round((v + Number.EPSILON) * r) / r)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// make sure the ticks are not aligned with axes limits
|
|
192
|
-
return ticks.filter(x => x >= lim[0] & x <= lim[1]);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** Computes a nice spacing value for a given range
|
|
196
|
-
* @param {numeric} localRange - a range (max - min)
|
|
197
|
-
* @param {boolean} round - round or not the fractions when computing the number
|
|
198
|
-
* @returns {numeric} the computed spacing value
|
|
199
|
-
*/
|
|
200
|
-
function niceNum( localRange, round) {
|
|
201
|
-
|
|
202
|
-
const exponent = Math.floor(Math.log10(localRange));
|
|
203
|
-
const fraction = localRange / Math.pow(10, exponent);
|
|
204
|
-
let niceFraction;
|
|
205
|
-
|
|
206
|
-
if (round) {
|
|
207
|
-
if (fraction < 1.5)
|
|
208
|
-
niceFraction = 1;
|
|
209
|
-
else if (fraction < 3)
|
|
210
|
-
niceFraction = 2;
|
|
211
|
-
else if (fraction < 7)
|
|
212
|
-
niceFraction = 5;
|
|
213
|
-
else
|
|
214
|
-
niceFraction = 10;
|
|
215
|
-
} else {
|
|
216
|
-
if (fraction <= 1)
|
|
217
|
-
niceFraction = 1;
|
|
218
|
-
else if (fraction <= 2)
|
|
219
|
-
niceFraction = 2;
|
|
220
|
-
else if (fraction <= 5)
|
|
221
|
-
niceFraction = 5;
|
|
222
|
-
else
|
|
223
|
-
niceFraction = 10;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return niceFraction * Math.pow(10, exponent);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/** Computes a scale level
|
|
230
|
-
* @param {numeric} width - width of plotting area in pixels
|
|
231
|
-
* @param {numeric} height - height of plotting area in pixels
|
|
232
|
-
* @returns {text} the scale level ("small", "medium" or "large")
|
|
233
|
-
*/
|
|
234
|
-
function getScale(width, height) {
|
|
235
|
-
if (height < 300.2 || width < 300.2) return "small";
|
|
236
|
-
if (height < 600.2 || width < 600.2) return "medium";
|
|
237
|
-
return "large";
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/* context with Axes constants, properties and methods to share with children */
|
|
241
|
-
let context = {
|
|
242
|
-
|
|
243
|
-
// methods
|
|
244
|
-
addXAxisMargins: addXAxisMargins,
|
|
245
|
-
addYAxisMargins: addYAxisMargins,
|
|
246
|
-
adjustXAxisLimits: adjustXAxisLimits,
|
|
247
|
-
adjustYAxisLimits: adjustYAxisLimits,
|
|
248
|
-
getAxisTicks: getAxisTicks,
|
|
249
|
-
scaleX: scaleX,
|
|
250
|
-
scaleY: scaleY,
|
|
251
|
-
|
|
252
|
-
// variables
|
|
253
|
-
isOk: isOk,
|
|
254
|
-
scale: scale,
|
|
255
|
-
width: width,
|
|
256
|
-
height: height,
|
|
257
|
-
xLim: xLim,
|
|
258
|
-
yLim: yLim,
|
|
259
|
-
|
|
260
|
-
// constants
|
|
261
|
-
LINE_STYLES: LINE_STYLES,
|
|
262
|
-
LABELS_MARGIN: LABELS_MARGIN,
|
|
263
|
-
TICK_NUM: TICK_NUM,
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
setContext('axes', context);
|
|
267
|
-
|
|
268
|
-
// handle click on plot elements and dispatch manual events
|
|
269
|
-
function dispatchClickEvent(eventName, el) {
|
|
270
|
-
dispatch(eventName, {seriesTitle: el.parentNode.getAttribute('title'), elementID: el.dataset.id});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function handleClick(e) {
|
|
274
|
-
|
|
275
|
-
// scatter plot markers
|
|
276
|
-
if (e.target.tagName === "text" && e.target.parentNode.classList.contains("series_scatter")) {
|
|
277
|
-
dispatchClickEvent("markerclick", e.target);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// bar plot bars
|
|
282
|
-
if (e.target.tagName === "rect" && e.target.parentNode.classList.contains("series_bar")) {
|
|
283
|
-
dispatchClickEvent("barclick", e.target);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// outside any plot element
|
|
288
|
-
dispatch("axesclick")
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
// this is reactive in case if limX and limY are interactively changed by parent script
|
|
293
|
-
$: if (!limX.some(v => v === undefined)) xLim.update(v => limX);
|
|
294
|
-
$: if (!limY.some(v => v === undefined)) yLim.update(v => limY);
|
|
295
|
-
|
|
296
|
-
// computes real margins in pixels based on current scale
|
|
297
|
-
$: margins = axesMargins.map(v => v * AXES_MARGIN_FACTORS[$scale]);
|
|
298
|
-
|
|
299
|
-
// computes status which tells that axes limits look fine and it is safe to draw
|
|
300
|
-
// the status is based on the axis limits validity
|
|
301
|
-
$: isOk.update(v =>
|
|
302
|
-
Array.isArray($yLim) &&
|
|
303
|
-
Array.isArray($xLim) &&
|
|
304
|
-
$xLim.length === 2 &&
|
|
305
|
-
$yLim.length === 2 &&
|
|
306
|
-
!$yLim.some(v => v === undefined) &&
|
|
307
|
-
!$xLim.some(v => v === undefined) &&
|
|
308
|
-
!$yLim.some(v => isNaN(v)) &&
|
|
309
|
-
!$xLim.some(v => isNaN(v)) &&
|
|
310
|
-
$xLim[1] !== $xLim[0] &&
|
|
311
|
-
$yLim[1] !== $yLim[0] &&
|
|
312
|
-
$width > (margins[1] + margins[3]) &&
|
|
313
|
-
$height > (margins[0] + margins[2])
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
// computes coordinates for clip path box
|
|
317
|
-
$: cpx = $isOk ? scaleX($xLim, $xLim, $width) : [0, 1];
|
|
318
|
-
$: cpy = $isOk ? scaleY($yLim, $yLim, $height) : [1, 0];
|
|
319
|
-
|
|
320
|
-
/* observer for the axes area size - to update size of axes */
|
|
321
|
-
const ro2 = new ResizeObserver(entries => {
|
|
322
|
-
for (let entry of entries) {
|
|
323
|
-
const acr = axesElement.getBoundingClientRect();
|
|
324
|
-
width.update(x => acr.width);
|
|
325
|
-
height.update(x => acr.height);
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
/* observer for the plot area size — to update scale */
|
|
330
|
-
const ro1 = new ResizeObserver(entries => {
|
|
331
|
-
for (let entry of entries) {
|
|
332
|
-
const pcr = plotElement.getBoundingClientRect();
|
|
333
|
-
scale.update(x => getScale(pcr.width, pcr.height));
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
onMount(() => {
|
|
338
|
-
ro1.observe(plotElement);
|
|
339
|
-
ro2.observe(axesElement);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
onDestroy(() => {
|
|
343
|
-
ro1.unobserve(plotElement);
|
|
344
|
-
ro2.unobserve(axesElement);
|
|
345
|
-
})
|
|
346
|
-
</script>
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
<div class="plot {'plot_' + $scale}" bind:this={plotElement} class:plot_error="{!$isOk}">
|
|
350
|
-
|
|
351
|
-
<!-- plot title and axis labels -->
|
|
352
|
-
{#if title !== ""}<div class="axes__title">{@html title}</div>{/if}
|
|
353
|
-
{#if yLabel !== ""}<div class="axes__ylabel"><span>{@html yLabel}</span></div>{/if}
|
|
354
|
-
{#if xLabel !== ""}<div class="axes__xlabel"><span>{@html xLabel}</span></div>{/if}
|
|
355
|
-
|
|
356
|
-
<!-- axes (coordinate system) -->
|
|
357
|
-
<div class="axes-wrapper" bind:this={axesElement} >
|
|
358
|
-
<svg on:click={handleClick} preserveAspectRatio="none" class="axes">
|
|
359
|
-
|
|
360
|
-
<!-- define clipping path -->
|
|
361
|
-
<defs>
|
|
362
|
-
<clipPath id="{clipPathID}">
|
|
363
|
-
<rect style="pointer-events:none" x="{cpx[0]}" y="{cpy[1]}" width = "{cpx[1] - cpx[0]}" height="{cpy[0] - cpy[1]}"></rect>
|
|
364
|
-
</clipPath>
|
|
365
|
-
</defs>
|
|
366
|
-
|
|
367
|
-
<!-- axis and box -->
|
|
368
|
-
<slot name="xaxis"></slot>
|
|
369
|
-
<slot name="yaxis"></slot>
|
|
370
|
-
|
|
371
|
-
<!-- main plot content -->
|
|
372
|
-
<g class="axes-content" clip-path="url(#{clipPathID})">
|
|
373
|
-
<slot></slot>
|
|
374
|
-
</g>
|
|
375
|
-
|
|
376
|
-
<!-- axis and box -->
|
|
377
|
-
<slot name="box"></slot>
|
|
378
|
-
|
|
379
|
-
</svg>
|
|
380
|
-
</div>
|
|
381
|
-
|
|
382
|
-
{#if !$isOk}
|
|
383
|
-
<p class="message_error">
|
|
384
|
-
Axes component was not properly initialized. <br />
|
|
385
|
-
Add plot series (check that coordinates are numeric) or define axes limits manually.
|
|
386
|
-
</p>
|
|
387
|
-
{/if}
|
|
388
|
-
|
|
389
|
-
</div>
|
|
390
|
-
|
|
391
|
-
<style>
|
|
392
|
-
|
|
393
|
-
/* Plot (main container) */
|
|
394
|
-
:global(.plot) {
|
|
395
|
-
font-family: Arial, Helvetica, sans-serif;
|
|
396
|
-
|
|
397
|
-
display: grid;
|
|
398
|
-
grid-template-columns: min-content auto;
|
|
399
|
-
grid-template-rows: min-content auto min-content;
|
|
400
|
-
grid-template-areas:
|
|
401
|
-
". title"
|
|
402
|
-
"ylab axes"
|
|
403
|
-
". xlab";
|
|
404
|
-
|
|
405
|
-
box-sizing: border-box;
|
|
406
|
-
background: #fefefe;
|
|
407
|
-
min-width: 100px;
|
|
408
|
-
min-height: 50px;
|
|
409
|
-
width: 100%;
|
|
410
|
-
height: 100%;
|
|
411
|
-
padding: 0;
|
|
412
|
-
margin: 0;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
:global(.plot_small) {
|
|
416
|
-
font-size: 11px;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
:global(.plot_medium) {
|
|
420
|
-
font-size: 13px;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
:global(.plot_large) {
|
|
424
|
-
font-size: 16px;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
:global(.plot_error) {
|
|
428
|
-
display: flex;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
:global(.plot_error > .axes-wrapper) {
|
|
432
|
-
display: none;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
:global(.message_error) {
|
|
436
|
-
font-size: 1.2em;
|
|
437
|
-
color: crimson;
|
|
438
|
-
padding: 20px;
|
|
439
|
-
text-align: center;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/* Axes (coordinate system) */
|
|
443
|
-
.axes-wrapper {
|
|
444
|
-
grid-area: axes;
|
|
445
|
-
position:relative;
|
|
446
|
-
box-sizing: border-box;
|
|
447
|
-
display: flex;
|
|
448
|
-
width: 100%;
|
|
449
|
-
height: 100%;
|
|
450
|
-
padding: 0;
|
|
451
|
-
margin: 0;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
:global(.axes) {
|
|
455
|
-
display: block;
|
|
456
|
-
box-sizing: border-box;
|
|
457
|
-
position:absolute;
|
|
458
|
-
|
|
459
|
-
padding: 0;
|
|
460
|
-
margin: 0;
|
|
461
|
-
height: 100%;
|
|
462
|
-
width: 100%;
|
|
463
|
-
max-height: 100%;
|
|
464
|
-
max-width: 100%;
|
|
465
|
-
min-height: 100%;
|
|
466
|
-
min-width: 100%;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
:global(.axes-content) {
|
|
470
|
-
padding: 0;
|
|
471
|
-
margin: 0;
|
|
472
|
-
height: 100%;
|
|
473
|
-
width: 100%;
|
|
474
|
-
max-height: 100%;
|
|
475
|
-
max-width: 100%;
|
|
476
|
-
min-height: 100%;
|
|
477
|
-
min-width: 100%;
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
:global(.axes__xlabel) {
|
|
482
|
-
grid-area: xlab;
|
|
483
|
-
font-size: 1.0em;
|
|
484
|
-
font-weight: 600;
|
|
485
|
-
padding: 0.25em;
|
|
486
|
-
text-align: center;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
:global(.axes__ylabel) {
|
|
491
|
-
grid-area: ylab;
|
|
492
|
-
font-size: 1.0em;
|
|
493
|
-
font-weight: 600;
|
|
494
|
-
padding: 0.25em;
|
|
495
|
-
text-align: center;
|
|
496
|
-
vertical-align: middle;
|
|
497
|
-
display: flex;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
:global(.axes__ylabel > span) {
|
|
501
|
-
width: 1.5em;
|
|
502
|
-
line-height: 1.5em;
|
|
503
|
-
display: inline-block;
|
|
504
|
-
writing-mode: vertical-rl;
|
|
505
|
-
transform: rotate(180deg);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
:global(.axes__title) {
|
|
510
|
-
background: transparent;
|
|
511
|
-
grid-area: title;
|
|
512
|
-
|
|
513
|
-
font-size: 1.3em;
|
|
514
|
-
font-weight: bold;
|
|
515
|
-
line-height: 1.2em;
|
|
516
|
-
padding: 0.5em 0;
|
|
517
|
-
text-align: center;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/* Axis */
|
|
521
|
-
:global(.axis-labels) {
|
|
522
|
-
fill: #303030;
|
|
523
|
-
font-size: 0.95em;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
/* Data labels */
|
|
527
|
-
:global(.labels) {
|
|
528
|
-
fill: #606060;
|
|
529
|
-
font-size: 0.90em;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
:global(.labels) {
|
|
533
|
-
visibility: hidden;
|
|
534
|
-
transition:visibility 0.25s linear, opacity 0.25s linear;
|
|
535
|
-
opacity: 0;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
</style>
|
package/src/BarSeries.svelte
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { getContext } from 'svelte';
|
|
3
|
-
import { Colors } from './Colors';
|
|
4
|
-
import { max, mrange, diff } from 'mdatools/stat';
|
|
5
|
-
import Rectangles from './Rectangles.svelte';
|
|
6
|
-
|
|
7
|
-
/* input parameters */
|
|
8
|
-
export let xValues;
|
|
9
|
-
export let yValues;
|
|
10
|
-
export let barWidth = 0.8;
|
|
11
|
-
export let title = "";
|
|
12
|
-
export let faceColor = Colors.PRIMARY;
|
|
13
|
-
export let borderColor = Colors.PRIMARY;
|
|
14
|
-
export let showLabels = "no"; // can be "no", "hover", "always"
|
|
15
|
-
|
|
16
|
-
// TODO: implemented later
|
|
17
|
-
//export let labels = yValues;
|
|
18
|
-
|
|
19
|
-
/* internal parameters */
|
|
20
|
-
let width;
|
|
21
|
-
let left;
|
|
22
|
-
let top;
|
|
23
|
-
let height;
|
|
24
|
-
|
|
25
|
-
// to access shared parameters and methods from Axes
|
|
26
|
-
const axes = getContext('axes');
|
|
27
|
-
|
|
28
|
-
/* reactive actions related to x-values, fires when there are changes in:
|
|
29
|
-
* - xValues
|
|
30
|
-
* - barWidth
|
|
31
|
-
*/
|
|
32
|
-
$: {
|
|
33
|
-
|
|
34
|
-
if (!Array.isArray(xValues)) {
|
|
35
|
-
throw("BarSeries: parameter 'xValues' must be a numeric vector.");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (barWidth <= 0 || barWidth > 1) {
|
|
39
|
-
throw("BarSeries: parameters 'barWidth' should be between 0 and 1.");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const xValuesRange = mrange(xValues, 0.1);
|
|
43
|
-
xValuesRange[0] = xValuesRange[0] - barWidth * diff(xValuesRange) / xValues.length * 0.5
|
|
44
|
-
xValuesRange[1] = xValuesRange[1] + barWidth * diff(xValuesRange) / xValues.length * 0.5
|
|
45
|
-
axes.adjustXAxisLimits(xValuesRange);
|
|
46
|
-
|
|
47
|
-
width = Array(xValues.length).fill(max(diff(xValues)) * barWidth);
|
|
48
|
-
left = xValues.map((v, i) => v - width[i]/2);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/* reactive actions related to y-values, fires when there are changes in:
|
|
52
|
-
* - yValues
|
|
53
|
-
*/
|
|
54
|
-
$: {
|
|
55
|
-
|
|
56
|
-
if (!Array.isArray(yValues) || xValues.length != yValues.length) {
|
|
57
|
-
throw("BarSeries: parameter 'yValues' must be a numeric vector of the same length as 'xValues'.");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const yValuesRange = mrange(yValues, showLabels === "no" ? 0.05 : 0.20);
|
|
61
|
-
axes.adjustYAxisLimits(yValuesRange);
|
|
62
|
-
|
|
63
|
-
top = yValues.map(v => v > 0 ? v : 0);
|
|
64
|
-
height = yValues.map(v => Math.abs(v));
|
|
65
|
-
}
|
|
66
|
-
</script>
|
|
67
|
-
|
|
68
|
-
<Rectangles style="series_bar" {left} {top} {width} {height} {borderColor} {faceColor} {title} />
|
|
69
|
-
|
|
70
|
-
<style>
|
|
71
|
-
:global(.series_bar > rect){
|
|
72
|
-
stroke-width: 1px;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
:global(.series_bar > rect:hover){
|
|
76
|
-
opacity: 90%;
|
|
77
|
-
}
|
|
78
|
-
</style>
|
package/src/Box.svelte
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { getContext } from 'svelte';
|
|
3
|
-
import { Colors } from './Colors';
|
|
4
|
-
import Rectangles from './Rectangles.svelte';
|
|
5
|
-
|
|
6
|
-
/* input parameters */
|
|
7
|
-
export let slot;
|
|
8
|
-
|
|
9
|
-
// check that the box is located in a correct slot
|
|
10
|
-
if (slot !== "box") {
|
|
11
|
-
throw("Component Box must have \"slot='box'\" attribute.")
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// get axes context and reactive variables needed to compute coordinates
|
|
15
|
-
const axes = getContext('axes');
|
|
16
|
-
const xLim = axes.xLim;
|
|
17
|
-
const yLim = axes.yLim;
|
|
18
|
-
const isOk = axes.isOk;
|
|
19
|
-
|
|
20
|
-
// reactive variables for coordinates of box points in pixels
|
|
21
|
-
$: left = [$xLim[0]];
|
|
22
|
-
$: top = [$yLim[1]];
|
|
23
|
-
$: width = [$xLim[1] - $xLim[0]];
|
|
24
|
-
$: height = [$yLim[1] - $yLim[0]];
|
|
25
|
-
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
|
-
{#if $isOk}
|
|
29
|
-
<g style="pointer-events:none" class="mdaplot__axes-box">
|
|
30
|
-
<Rectangles {left} {top} {width} {height} borderColor="{Colors.DARKGRAY}" faceColor="transparent"></Rectangles>
|
|
31
|
-
</g>
|
|
32
|
-
{/if}
|
|
33
|
-
|
|
34
|
-
<style>
|
|
35
|
-
</style>
|