@windborne/grapher 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +85 -0
- package/.idea/codeStyles/Project.xml +19 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/grapher.iml +12 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/0.bundle.js +2 -0
- package/0.bundle.js.map +1 -0
- package/1767282193a714f63082.module.wasm +0 -0
- package/537.bundle.js +2 -0
- package/537.bundle.js.map +1 -0
- package/831.bundle.js +2 -0
- package/831.bundle.js.map +1 -0
- package/bundle.js +2 -0
- package/bundle.js.map +1 -0
- package/package.json +75 -0
- package/readme.md +129 -0
- package/src/components/annotations.js +62 -0
- package/src/components/context_menu.js +73 -0
- package/src/components/draggable_points.js +114 -0
- package/src/components/graph_body.js +292 -0
- package/src/components/graph_title.js +16 -0
- package/src/components/options.js +111 -0
- package/src/components/percentile_button.js +72 -0
- package/src/components/range_graph.js +352 -0
- package/src/components/range_selection.js +175 -0
- package/src/components/range_selection_button.js +26 -0
- package/src/components/range_selection_button_base.js +51 -0
- package/src/components/series_key.js +235 -0
- package/src/components/series_key_axis_container.js +70 -0
- package/src/components/series_key_item.js +52 -0
- package/src/components/sidebar.js +76 -0
- package/src/components/tooltip.js +244 -0
- package/src/components/vertical_lines.js +70 -0
- package/src/components/x_axis.js +124 -0
- package/src/components/y_axis.js +239 -0
- package/src/eventable.js +65 -0
- package/src/grapher.js +367 -0
- package/src/grapher.scss +914 -0
- package/src/helpers/axis_sizes.js +2 -0
- package/src/helpers/binary_search.js +67 -0
- package/src/helpers/color_to_vector.js +35 -0
- package/src/helpers/colors.js +27 -0
- package/src/helpers/custom_prop_types.js +159 -0
- package/src/helpers/flatten_simple_data.js +81 -0
- package/src/helpers/format.js +233 -0
- package/src/helpers/generator_params_equal.js +10 -0
- package/src/helpers/name_for_series.js +16 -0
- package/src/helpers/place_grid.js +257 -0
- package/src/helpers/pyodide_ready.js +13 -0
- package/src/multigrapher.js +105 -0
- package/src/renderer/background.frag +7 -0
- package/src/renderer/background.vert +7 -0
- package/src/renderer/background_program.js +48 -0
- package/src/renderer/circle.frag +26 -0
- package/src/renderer/circle.vert +12 -0
- package/src/renderer/create_gl_program.js +36 -0
- package/src/renderer/draw_area.js +159 -0
- package/src/renderer/draw_background.js +15 -0
- package/src/renderer/draw_bars.js +80 -0
- package/src/renderer/draw_line.js +69 -0
- package/src/renderer/draw_zero_line.js +24 -0
- package/src/renderer/extract_vertices.js +137 -0
- package/src/renderer/graph_body_renderer.js +293 -0
- package/src/renderer/line.frag +51 -0
- package/src/renderer/line.vert +32 -0
- package/src/renderer/line_program.js +125 -0
- package/src/renderer/paths_from.js +72 -0
- package/src/renderer/scale_bounds.js +28 -0
- package/src/renderer/size_canvas.js +59 -0
- package/src/rust/Cargo.lock +233 -0
- package/src/rust/Cargo.toml +35 -0
- package/src/rust/pkg/grapher_rs.d.ts +42 -0
- package/src/rust/pkg/grapher_rs.js +351 -0
- package/src/rust/pkg/grapher_rs_bg.d.ts +11 -0
- package/src/rust/pkg/grapher_rs_bg.wasm +0 -0
- package/src/rust/pkg/index.js +342 -0
- package/src/rust/pkg/index_bg.wasm +0 -0
- package/src/rust/pkg/package.json +14 -0
- package/src/rust/src/extract_vertices.rs +83 -0
- package/src/rust/src/get_point_number.rs +50 -0
- package/src/rust/src/lib.rs +15 -0
- package/src/rust/src/selected_space_to_render_space.rs +131 -0
- package/src/state/average_loop_times.js +15 -0
- package/src/state/bound_calculator_from_selection.js +36 -0
- package/src/state/bound_calculators.js +41 -0
- package/src/state/calculate_annotations_state.js +59 -0
- package/src/state/calculate_data_bounds.js +104 -0
- package/src/state/calculate_tooltip_state.js +241 -0
- package/src/state/data_types.js +13 -0
- package/src/state/expand_bounds.js +58 -0
- package/src/state/find_matching_axis.js +31 -0
- package/src/state/get_default_bounds_calculator.js +15 -0
- package/src/state/hooks.js +164 -0
- package/src/state/infer_type.js +74 -0
- package/src/state/merge_bounds.js +64 -0
- package/src/state/multigraph_state_controller.js +334 -0
- package/src/state/selection_from_global_bounds.js +25 -0
- package/src/state/space_conversions/condense_data_space.js +115 -0
- package/src/state/space_conversions/data_space_to_selected_space.js +328 -0
- package/src/state/space_conversions/selected_space_to_background_space.js +144 -0
- package/src/state/space_conversions/selected_space_to_render_space.js +161 -0
- package/src/state/space_conversions/simple_series_to_data_space.js +229 -0
- package/src/state/state_controller.js +1770 -0
- package/src/state/sync_pool.js +101 -0
- package/test/setup.js +15 -0
- package/test/space_conversions/data_space_to_selected_space.test.js +434 -0
- package/webpack.dev.config.js +109 -0
- package/webpack.prod.config.js +60 -0
- package/webpack.test.config.js +59 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
import {drawZeroLine} from './draw_zero_line';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Draws the data on the canvas
|
5
|
+
* Assumes the data is in individual point render space, ie x and y in pixels
|
6
|
+
*
|
7
|
+
* @param {Array<[number, number]>} individualPoints - data to draw
|
8
|
+
* @param {Object} options - set of options
|
9
|
+
* @param {Object} options.context - the context to draw on
|
10
|
+
* @param {String} options.color - color of the bar to draw
|
11
|
+
* @param {{renderWidth: Number, renderHeight: Number}} options.sizing - size of the canvas, in pixels
|
12
|
+
* @param {Number} options.indexInAxis - index of the series in the axis
|
13
|
+
* @param {Number} options.axisSeriesCount - number of series in the axis
|
14
|
+
* @param {Number} options.zero - y coordinate that represents "zero"
|
15
|
+
* @param {Boolean} options.hasNegatives - if any values are negative (in which case should render from zero)
|
16
|
+
* @param {String} options.negativeColor - color of the bar to draw if negative
|
17
|
+
* @param {String} options.zeroColor - color of the zero line
|
18
|
+
* @param {Number} options.zeroWidth - width of the zero line
|
19
|
+
* @param {Number} options.closestSpacing - closest x spacing between points, in data space
|
20
|
+
* @param {{minX: Number, maxX: Number}} options.bounds - bounds of the data as rendered
|
21
|
+
* @private
|
22
|
+
*/
|
23
|
+
export default function drawBars(individualPoints, {
|
24
|
+
color, context, sizing, indexInAxis, axisSeriesCount, zero, hasNegatives, negativeColor, zeroColor, zeroWidth, closestSpacing, bounds
|
25
|
+
}) {
|
26
|
+
context.strokeStyle = color;
|
27
|
+
context.fillStyle = color;
|
28
|
+
|
29
|
+
const {barWidth, totalBarWidth} = getBarWidths({ closestSpacing, bounds, sizing, axisSeriesCount });
|
30
|
+
const barBottom = hasNegatives ? zero : sizing.renderHeight;
|
31
|
+
|
32
|
+
for (let i = 0; i < individualPoints.length; i++) {
|
33
|
+
const [x, y] = individualPoints[i];
|
34
|
+
|
35
|
+
if (hasNegatives) {
|
36
|
+
if (y <= zero) {
|
37
|
+
context.fillStyle = color;
|
38
|
+
} else {
|
39
|
+
context.fillStyle = negativeColor;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
context.fillRect(x - totalBarWidth / 2 + barWidth*indexInAxis, y, barWidth, barBottom-y);
|
44
|
+
}
|
45
|
+
|
46
|
+
if (zeroWidth) {
|
47
|
+
drawZeroLine(barBottom, {
|
48
|
+
context,
|
49
|
+
sizing,
|
50
|
+
color,
|
51
|
+
zero,
|
52
|
+
zeroColor,
|
53
|
+
zeroWidth
|
54
|
+
});
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Calculates the widths of the bars
|
60
|
+
*
|
61
|
+
* @param {Number} closestSpacing - closest x spacing between points, in data space
|
62
|
+
* @param {{minX: Number, maxX: Number}} bounds - bounds of the data as rendered
|
63
|
+
* @param {{renderWidth: Number, renderHeight: Number}} sizing - size of the canvas, in pixels
|
64
|
+
* @param {Number} axisSeriesCount - number of series in the axis
|
65
|
+
* @return {{barWidth: Number, totalBarWidth: Number, barSpacing: Number}}
|
66
|
+
*/
|
67
|
+
export function getBarWidths({closestSpacing, bounds, sizing, axisSeriesCount}) {
|
68
|
+
// width of bar plus spacing, which we define as the closest distance between points in pixel space
|
69
|
+
const fullBarWidth = closestSpacing / (bounds.maxX - bounds.minX) * sizing.renderWidth;
|
70
|
+
|
71
|
+
const totalBarWidth = fullBarWidth * 0.8;
|
72
|
+
const barSpacing = fullBarWidth * 0.2;
|
73
|
+
const barWidth = totalBarWidth / axisSeriesCount;
|
74
|
+
|
75
|
+
return {
|
76
|
+
barWidth,
|
77
|
+
totalBarWidth,
|
78
|
+
barSpacing
|
79
|
+
};
|
80
|
+
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import {DPI_INCREASE} from './size_canvas';
|
2
|
+
import pathsFrom from './paths_from';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Draws the data on the canvas
|
6
|
+
* Assumes the data is in render space
|
7
|
+
*
|
8
|
+
* @param {{nullMask: Uint8Array, maxYValues: Float64Array, minYValues: Float64Array, yValues: Float64Array}} dataInRenderSpace - the data to render
|
9
|
+
* @param {Object} options - set of options
|
10
|
+
* @param {Object} options.context - the context to draw on
|
11
|
+
* @param {String} options.color - color of the line to draw
|
12
|
+
* @param {Number} [options.width] - line width
|
13
|
+
* @param {Number} [options.shadowBlur] - level to blur shadow to
|
14
|
+
* @param {String} [options.shadowColor] - color of the shadow
|
15
|
+
* @param {String} [options.dashed] - whether or not to make the line dashed
|
16
|
+
* @param {Array<Number>} [options.dashPattern] - dash array for the canvas
|
17
|
+
* @param {Boolean} [options.highlighted] - whether the line is highlighted or not
|
18
|
+
* @param {Boolean} [options.showIndividualPoints] - draw circles at each point
|
19
|
+
* @param {Function} [options.getIndividualPoints] - points to draw circles at. Only called when needed.
|
20
|
+
* @private
|
21
|
+
*/
|
22
|
+
export default function drawLine(dataInRenderSpace, {
|
23
|
+
color, width=1, context, shadowColor='black', shadowBlur=5, dashed=false, dashPattern=null, highlighted=false, showIndividualPoints=false, getIndividualPoints
|
24
|
+
}) {
|
25
|
+
if (highlighted) {
|
26
|
+
width += 2;
|
27
|
+
}
|
28
|
+
width *= DPI_INCREASE;
|
29
|
+
|
30
|
+
context.strokeStyle = color;
|
31
|
+
context.lineWidth = width;
|
32
|
+
context.shadowColor = shadowColor;
|
33
|
+
context.shadowBlur = shadowBlur;
|
34
|
+
|
35
|
+
if (dashed) {
|
36
|
+
context.setLineDash(dashPattern || [5, 5]);
|
37
|
+
} else {
|
38
|
+
context.setLineDash([]);
|
39
|
+
}
|
40
|
+
|
41
|
+
const paths = pathsFrom(dataInRenderSpace);
|
42
|
+
|
43
|
+
for (let path of paths) {
|
44
|
+
context.beginPath();
|
45
|
+
|
46
|
+
for (let i = 0; i < path.length; i++) {
|
47
|
+
const [x, y] = path[i];
|
48
|
+
|
49
|
+
if (i === 0) {
|
50
|
+
context.moveTo(x, y);
|
51
|
+
} else {
|
52
|
+
context.lineTo(x, y);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
context.stroke();
|
57
|
+
}
|
58
|
+
|
59
|
+
if (showIndividualPoints) {
|
60
|
+
context.fillStyle = color;
|
61
|
+
const individualPoints = getIndividualPoints();
|
62
|
+
|
63
|
+
for (let [x, y] of individualPoints) {
|
64
|
+
context.beginPath();
|
65
|
+
context.arc(x, y, width + 4, 0, 2 * Math.PI, false);
|
66
|
+
context.fill();
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
/**
|
2
|
+
* Draws a horizontal line at the specified y-coordinate.
|
3
|
+
*
|
4
|
+
* @param {Number} y
|
5
|
+
* @param {Object} options
|
6
|
+
* @param {CanvasRenderingContext2D} options.context
|
7
|
+
* @param {{renderWidth: Number, renderHeight: Number}} options.sizing
|
8
|
+
* @param {String} options.color
|
9
|
+
* @param {String} options.zeroColor
|
10
|
+
* @param {Number} options.zeroWidth
|
11
|
+
*/
|
12
|
+
export function drawZeroLine(y, { context, sizing, color, zeroColor, zeroWidth}) {
|
13
|
+
if (!zeroWidth) {
|
14
|
+
return;
|
15
|
+
}
|
16
|
+
|
17
|
+
context.strokeStyle = zeroColor || color;
|
18
|
+
context.lineWidth = zeroWidth;
|
19
|
+
|
20
|
+
context.beginPath();
|
21
|
+
context.moveTo(0, y);
|
22
|
+
context.lineTo(sizing.renderWidth, y);
|
23
|
+
context.stroke();
|
24
|
+
}
|
@@ -0,0 +1,137 @@
|
|
1
|
+
import pathsFrom from './paths_from';
|
2
|
+
import {DPI_INCREASE} from './size_canvas';
|
3
|
+
let RustAPI;
|
4
|
+
import('../rust/pkg/index.js').then((module) => {
|
5
|
+
RustAPI = module;
|
6
|
+
});
|
7
|
+
|
8
|
+
/**
|
9
|
+
*
|
10
|
+
* @param {[[Number]]} paths
|
11
|
+
* @param {Boolean} dashed
|
12
|
+
* @param {Array} dashPattern
|
13
|
+
* @return {{prevPositions: Float32Array, indices: Uint32Array, vertices: Float32Array, positions: Float32Array}}
|
14
|
+
*/
|
15
|
+
export function extractVerticesFromPaths(paths, { dashed, dashPattern }) {
|
16
|
+
let pointNumber = 0;
|
17
|
+
for (let path of paths) {
|
18
|
+
if (dashed) {
|
19
|
+
for (let i = 0; i < path.length; i++) {
|
20
|
+
if (dashed && i % (dashPattern[0] + dashPattern[1]) >= dashPattern[0]) {
|
21
|
+
continue;
|
22
|
+
}
|
23
|
+
pointNumber++;
|
24
|
+
}
|
25
|
+
} else {
|
26
|
+
pointNumber += path.length;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
const positions = new Float32Array(pointNumber*8);
|
31
|
+
const prevPositions = new Float32Array(pointNumber*8);
|
32
|
+
const vertices = new Float32Array(pointNumber*4);
|
33
|
+
const indices = new Uint32Array(pointNumber*6);
|
34
|
+
|
35
|
+
if (pointNumber > 0) {
|
36
|
+
const verticesPattern = new Float32Array([0, 1, 2, 3]);
|
37
|
+
vertices.set(verticesPattern);
|
38
|
+
let vertexPointer = verticesPattern.length;
|
39
|
+
let sourceLength = verticesPattern.length;
|
40
|
+
while (vertexPointer < vertices.length) {
|
41
|
+
if (vertexPointer + sourceLength > vertices.length) {
|
42
|
+
sourceLength = vertices.length - vertexPointer;
|
43
|
+
}
|
44
|
+
|
45
|
+
vertices.copyWithin(vertexPointer, 0, sourceLength);
|
46
|
+
vertexPointer += sourceLength;
|
47
|
+
sourceLength <<= 1;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
let pointI = 0;
|
52
|
+
for (let path of paths) {
|
53
|
+
for (let i = 0; i < path.length; i++) {
|
54
|
+
if (dashed && i % (dashPattern[0] + dashPattern[1]) >= dashPattern[0]) {
|
55
|
+
continue;
|
56
|
+
}
|
57
|
+
|
58
|
+
const [x, y] = path[i];
|
59
|
+
|
60
|
+
let prevX, prevY;
|
61
|
+
|
62
|
+
if (i === 0) {
|
63
|
+
prevX = x - 1;
|
64
|
+
prevY = y;
|
65
|
+
} else {
|
66
|
+
[prevX, prevY] = path[i - 1];
|
67
|
+
}
|
68
|
+
|
69
|
+
for (let j = 0; j < 4; j++) {
|
70
|
+
positions[pointI * 8 + 2 * j] = x;
|
71
|
+
positions[pointI * 8 + 2 * j + 1] = y;
|
72
|
+
prevPositions[pointI * 8 + 2 * j] = prevX;
|
73
|
+
prevPositions[pointI * 8 + 2 * j + 1] = prevY;
|
74
|
+
}
|
75
|
+
|
76
|
+
indices[pointI * 6] = pointI * 4;
|
77
|
+
indices[pointI * 6 + 1] = pointI * 4 + 1;
|
78
|
+
indices[pointI * 6 + 2] = pointI * 4 + 3;
|
79
|
+
|
80
|
+
indices[pointI * 6 + 3] = pointI * 4;
|
81
|
+
indices[pointI * 6 + 4] = pointI * 4 + 2;
|
82
|
+
indices[pointI * 6 + 5] = pointI * 4 + 3;
|
83
|
+
|
84
|
+
pointI++;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
return {
|
89
|
+
positions,
|
90
|
+
prevPositions,
|
91
|
+
vertices,
|
92
|
+
indices
|
93
|
+
};
|
94
|
+
}
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Takes the paths and turns them into what's needed for the line webgl program
|
98
|
+
*
|
99
|
+
* @param {{nullMask: Uint8Array, maxYValues: Float64Array, minYValues: Float64Array, yValues: Float64Array, paths?: [[Number]]}} dataInRenderSpace
|
100
|
+
* @param {Boolean} dashed
|
101
|
+
* @param {Array} dashPattern
|
102
|
+
* @return {{prevPositions: Float32Array, indices: Uint32Array, vertices: Float32Array, positions: Float32Array}}
|
103
|
+
*/
|
104
|
+
export default function extractVertices(dataInRenderSpace, { dashed, dashPattern }) {
|
105
|
+
if (dataInRenderSpace.paths) {
|
106
|
+
return extractVerticesFromPaths(dataInRenderSpace.paths, { dashed, dashPattern});
|
107
|
+
}
|
108
|
+
|
109
|
+
if (!RustAPI) {
|
110
|
+
const paths = pathsFrom(dataInRenderSpace);
|
111
|
+
return extractVerticesFromPaths(paths, { dashed, dashPattern});
|
112
|
+
}
|
113
|
+
|
114
|
+
const pointNumber = RustAPI.get_point_number(
|
115
|
+
dataInRenderSpace.nullMask, dataInRenderSpace.yValues, dataInRenderSpace.minYValues, dataInRenderSpace.maxYValues,
|
116
|
+
dashed, dashPattern[0], dashPattern[1]
|
117
|
+
);
|
118
|
+
|
119
|
+
let positions = new Float32Array(pointNumber*8);
|
120
|
+
let prevPositions = new Float32Array(pointNumber*8);
|
121
|
+
let vertices = new Float32Array(pointNumber*4);
|
122
|
+
let indices = new Uint32Array(pointNumber*6);
|
123
|
+
|
124
|
+
RustAPI.extract_vertices(
|
125
|
+
DPI_INCREASE,
|
126
|
+
dataInRenderSpace.nullMask, dataInRenderSpace.yValues, dataInRenderSpace.minYValues, dataInRenderSpace.maxYValues,
|
127
|
+
positions, prevPositions, vertices, indices,
|
128
|
+
dashed, dashPattern[0], dashPattern[1]
|
129
|
+
);
|
130
|
+
|
131
|
+
return {
|
132
|
+
positions,
|
133
|
+
prevPositions,
|
134
|
+
vertices,
|
135
|
+
indices
|
136
|
+
};
|
137
|
+
}
|
@@ -0,0 +1,293 @@
|
|
1
|
+
import sizeCanvas from './size_canvas';
|
2
|
+
import getColor from '../helpers/colors';
|
3
|
+
import LineProgram from './line_program';
|
4
|
+
import drawLine from './draw_line';
|
5
|
+
import Eventable from '../eventable';
|
6
|
+
import drawBackground from './draw_background.js';
|
7
|
+
import BackgroundProgram from './background_program.js';
|
8
|
+
import drawBars from './draw_bars';
|
9
|
+
import drawArea from './draw_area';
|
10
|
+
|
11
|
+
export default class GraphBodyRenderer extends Eventable {
|
12
|
+
|
13
|
+
constructor({stateController, canvasElement, webgl=false, checkIntersection=true }) {
|
14
|
+
super();
|
15
|
+
|
16
|
+
this._stateController = stateController;
|
17
|
+
|
18
|
+
this._checkIntersection = checkIntersection;
|
19
|
+
this._canvas = canvasElement;
|
20
|
+
this._webgl = webgl;
|
21
|
+
if (webgl) {
|
22
|
+
this._context = this._canvas.getContext('webgl');
|
23
|
+
if (this._context) {
|
24
|
+
this._lineProgram = new LineProgram(this._context);
|
25
|
+
} else {
|
26
|
+
alert('WebGL failed! Attempting fallback to CPU rendering');
|
27
|
+
this._webgl = false;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
if (!this._webgl) {
|
32
|
+
this._context = this._canvas.getContext( '2d');
|
33
|
+
this._context2d = this._context;
|
34
|
+
}
|
35
|
+
|
36
|
+
this._initialized = this._initializeCanvas();
|
37
|
+
|
38
|
+
this._boundResize = this.resize.bind(this);
|
39
|
+
this._cachedAxisCount = null;
|
40
|
+
this._onAxisChange = (axes) => {
|
41
|
+
const count = axes.filter(({ series }) => series.length > 0).length;
|
42
|
+
if (this._cachedAxisCount !== count) {
|
43
|
+
this._cachedAxisCount = count;
|
44
|
+
this.resize();
|
45
|
+
}
|
46
|
+
};
|
47
|
+
|
48
|
+
stateController.on('axes_changed', this._onAxisChange);
|
49
|
+
stateController.on('dragging_y_changed', this._boundResize);
|
50
|
+
stateController.on('showing_sidebar_changed', this._boundResize);
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Cleans up after this renderer
|
55
|
+
*/
|
56
|
+
dispose() {
|
57
|
+
this.clearListeners();
|
58
|
+
this._lineProgram && this._lineProgram.dispose();
|
59
|
+
this._cachedAxisCount = null;
|
60
|
+
this._stateController.off('axes_changed', this._onAxisChange);
|
61
|
+
this._stateController.off('dragging_y_changed', this._boundResize);
|
62
|
+
|
63
|
+
if (this._resizeObserver) {
|
64
|
+
this._resizeObserver.disconnect();
|
65
|
+
}
|
66
|
+
|
67
|
+
if (this._intersectionObserver) {
|
68
|
+
this._intersectionObserver.disconnect();
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
clear() {
|
73
|
+
if (this._webgl) {
|
74
|
+
this._lineProgram.clear();
|
75
|
+
} else {
|
76
|
+
this._context.clearRect(0, 0, this._context.canvas.width, this._context.canvas.height);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
render(singleSeries, inRenderSpace, { highlighted, showIndividualPoints, shadowColor, shadowBlur, width, defaultLineWidth, bounds, globalBounds }) {
|
81
|
+
const getIndividualPoints = (useDataSpace) => {
|
82
|
+
if (!bounds) {
|
83
|
+
bounds = singleSeries.axis.currentBounds;
|
84
|
+
}
|
85
|
+
|
86
|
+
const individualPoints = [];
|
87
|
+
let data = singleSeries.inSelectedSpace.data;
|
88
|
+
if (useDataSpace) {
|
89
|
+
data = singleSeries.inDataSpace;
|
90
|
+
}
|
91
|
+
|
92
|
+
for (let [x, y] of data) {
|
93
|
+
if (y === null) {
|
94
|
+
continue;
|
95
|
+
}
|
96
|
+
|
97
|
+
individualPoints.push([
|
98
|
+
(x - bounds.minX) / (bounds.maxX - bounds.minX) * this._sizing.renderWidth,
|
99
|
+
(1.0 - (y - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight
|
100
|
+
]);
|
101
|
+
}
|
102
|
+
|
103
|
+
return individualPoints;
|
104
|
+
};
|
105
|
+
|
106
|
+
const cpuRendering = singleSeries.rendering === 'bar' || singleSeries.rendering === 'area';
|
107
|
+
let commonCPUParams;
|
108
|
+
|
109
|
+
if (cpuRendering) {
|
110
|
+
// we can currently only render bars with the CPU
|
111
|
+
this._context2d = this._context2d || this._canvas.getContext('2d');
|
112
|
+
|
113
|
+
if (this._webgl) {
|
114
|
+
// make sure we don't have any webgl stuff in the way before we get back to CPU rendering
|
115
|
+
this._context.flush();
|
116
|
+
}
|
117
|
+
|
118
|
+
if (!bounds) {
|
119
|
+
bounds = singleSeries.axis.currentBounds;
|
120
|
+
}
|
121
|
+
|
122
|
+
commonCPUParams = {
|
123
|
+
context: this._context2d,
|
124
|
+
color: getColor(singleSeries.color, singleSeries.index, singleSeries.multigrapherSeriesIndex),
|
125
|
+
sizing: this._sizing,
|
126
|
+
zero: (1.0 - (0 - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight,
|
127
|
+
hasNegatives: !!singleSeries.inDataSpace.find((tuple) => tuple[1] < 0),
|
128
|
+
negativeColor: singleSeries.negativeColor,
|
129
|
+
zeroWidth: singleSeries.zeroLineWidth,
|
130
|
+
zeroColor: singleSeries.zeroLineColor
|
131
|
+
};
|
132
|
+
}
|
133
|
+
|
134
|
+
if (singleSeries.rendering === 'bar') {
|
135
|
+
drawBars(getIndividualPoints(true), {
|
136
|
+
...commonCPUParams,
|
137
|
+
indexInAxis: singleSeries.axis.series.indexOf(singleSeries),
|
138
|
+
axisSeriesCount: singleSeries.axis.series.length,
|
139
|
+
closestSpacing: globalBounds.closestSpacing,
|
140
|
+
bounds
|
141
|
+
});
|
142
|
+
return;
|
143
|
+
}
|
144
|
+
|
145
|
+
if (singleSeries.rendering === 'area') {
|
146
|
+
drawArea(getIndividualPoints(true), inRenderSpace, {
|
147
|
+
...commonCPUParams,
|
148
|
+
showIndividualPoints: typeof singleSeries.showIndividualPoints === 'boolean' ? singleSeries.showIndividualPoints : showIndividualPoints,
|
149
|
+
gradient: singleSeries.gradient,
|
150
|
+
pointRadius: singleSeries.pointRadius,
|
151
|
+
highlighted,
|
152
|
+
width: width || singleSeries.width || defaultLineWidth,
|
153
|
+
shadowColor,
|
154
|
+
shadowBlur
|
155
|
+
});
|
156
|
+
return;
|
157
|
+
}
|
158
|
+
|
159
|
+
const drawParams = {
|
160
|
+
color: getColor(singleSeries.color, singleSeries.index, singleSeries.multigrapherSeriesIndex),
|
161
|
+
context: this._context,
|
162
|
+
width: width || singleSeries.width || defaultLineWidth,
|
163
|
+
shadowColor,
|
164
|
+
shadowBlur,
|
165
|
+
dashed: singleSeries.dashed,
|
166
|
+
dashPattern: singleSeries.dashPattern,
|
167
|
+
highlighted,
|
168
|
+
showIndividualPoints: typeof singleSeries.showIndividualPoints === 'boolean' ? singleSeries.showIndividualPoints : showIndividualPoints,
|
169
|
+
getIndividualPoints
|
170
|
+
};
|
171
|
+
|
172
|
+
if (this._webgl) {
|
173
|
+
this._lineProgram.draw(inRenderSpace, drawParams);
|
174
|
+
} else {
|
175
|
+
drawLine(inRenderSpace, drawParams);
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
renderBackground(inBackgroundSpace) {
|
180
|
+
if (!inBackgroundSpace) {
|
181
|
+
return;
|
182
|
+
}
|
183
|
+
|
184
|
+
if (this._webgl) {
|
185
|
+
if (!this._backgroundProgram) {
|
186
|
+
this._backgroundProgram = new BackgroundProgram(this._context);
|
187
|
+
}
|
188
|
+
|
189
|
+
this._backgroundProgram.draw(inBackgroundSpace);
|
190
|
+
} else {
|
191
|
+
drawBackground(inBackgroundSpace, {
|
192
|
+
context: this._context
|
193
|
+
});
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
* Initializes canvas
|
199
|
+
* Currently, just sets sizing
|
200
|
+
*/
|
201
|
+
async _initializeCanvas() {
|
202
|
+
this._sizing = await sizeCanvas(this._canvas, this._context);
|
203
|
+
|
204
|
+
this.emit('size_changed', this._sizing);
|
205
|
+
this._stateController.markSizeChanged();
|
206
|
+
|
207
|
+
if (window.ResizeObserver) {
|
208
|
+
let first = true;
|
209
|
+
let disabled = false;
|
210
|
+
|
211
|
+
this._resizeObserver = new window.ResizeObserver(async () => {
|
212
|
+
if (first) { // always fires once at the beginning
|
213
|
+
first = false;
|
214
|
+
return;
|
215
|
+
}
|
216
|
+
|
217
|
+
if (disabled) {
|
218
|
+
return;
|
219
|
+
}
|
220
|
+
|
221
|
+
disabled = true;
|
222
|
+
await this.resize();
|
223
|
+
disabled = false; // eslint-disable-line require-atomic-updates
|
224
|
+
});
|
225
|
+
|
226
|
+
this._resizeObserver.observe(this._canvas.parentNode);
|
227
|
+
}
|
228
|
+
|
229
|
+
if (this._checkIntersection && window.IntersectionObserver) {
|
230
|
+
this._intersectionObserver = new window.IntersectionObserver((entries) => {
|
231
|
+
clearTimeout(this._intersectionTimeout);
|
232
|
+
|
233
|
+
if (!entries[0].isIntersecting) {
|
234
|
+
return;
|
235
|
+
}
|
236
|
+
|
237
|
+
this._intersectionTimeout = setTimeout(() => {
|
238
|
+
this.resize();
|
239
|
+
}, 50);
|
240
|
+
}, {
|
241
|
+
threshold: 0.1
|
242
|
+
});
|
243
|
+
|
244
|
+
this._intersectionObserver.observe(this._canvas.parentNode);
|
245
|
+
}
|
246
|
+
}
|
247
|
+
|
248
|
+
async resize() {
|
249
|
+
const sizingPromise = sizeCanvas(this._canvas, this._context, { reset: true });
|
250
|
+
this._initialized = sizingPromise;
|
251
|
+
|
252
|
+
this._sizing = await this._initialized;
|
253
|
+
if (this._initialized !== sizingPromise) {
|
254
|
+
return;
|
255
|
+
}
|
256
|
+
|
257
|
+
this.emit('size_changed', this._sizing);
|
258
|
+
this._stateController.markSizeChanged(this);
|
259
|
+
}
|
260
|
+
|
261
|
+
resizeDebounced() {
|
262
|
+
if (this._resizeTimeout) {
|
263
|
+
clearTimeout(this._resizeTimeout);
|
264
|
+
}
|
265
|
+
|
266
|
+
this._resizeTimeout = setTimeout(() => {
|
267
|
+
this.resize();
|
268
|
+
this._resizeTimeout = null;
|
269
|
+
}, 50);
|
270
|
+
}
|
271
|
+
|
272
|
+
recalculatePosition() {
|
273
|
+
if (!this._sizing) {
|
274
|
+
return;
|
275
|
+
}
|
276
|
+
|
277
|
+
this._sizing.boundingRect = this._canvas.getBoundingClientRect();
|
278
|
+
}
|
279
|
+
|
280
|
+
/**
|
281
|
+
* Returns the bounding rect of the element
|
282
|
+
*
|
283
|
+
* @return {DOMRect}
|
284
|
+
*/
|
285
|
+
get boundingRect() {
|
286
|
+
return this._sizing.boundingRect;
|
287
|
+
}
|
288
|
+
|
289
|
+
get sizing() {
|
290
|
+
return this._sizing;
|
291
|
+
}
|
292
|
+
|
293
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
precision highp float;
|
2
|
+
|
3
|
+
uniform vec4 color;
|
4
|
+
uniform float thickness;
|
5
|
+
uniform float shadowBlur;
|
6
|
+
uniform vec4 shadowColor;
|
7
|
+
|
8
|
+
varying vec2 position_vec;
|
9
|
+
varying vec2 prev_position_vec;
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Calculate distance between point and line in screen space (ie, inputs in pixels, returns distance in pixels)
|
13
|
+
*/
|
14
|
+
float distance_from_line() {
|
15
|
+
float x0 = gl_FragCoord.x;
|
16
|
+
float y0 = gl_FragCoord.y;
|
17
|
+
|
18
|
+
// let line be defined by ax + by + c = 0;
|
19
|
+
float a, b, c;
|
20
|
+
|
21
|
+
if (position_vec.x == prev_position_vec.x) {
|
22
|
+
a = 1.0;
|
23
|
+
b = 0.0;
|
24
|
+
c = -position_vec.x;
|
25
|
+
} else {
|
26
|
+
float slope = (position_vec.y - prev_position_vec.y)/(position_vec.x - prev_position_vec.x);
|
27
|
+
float y_intercept = position_vec.y - slope*position_vec.x;
|
28
|
+
|
29
|
+
// y = slope*x + y_intercept
|
30
|
+
// (-slope)(x) + (1)(y) - y_intercept = 0;
|
31
|
+
a = -slope;
|
32
|
+
b = 1.0;
|
33
|
+
c = -y_intercept;
|
34
|
+
}
|
35
|
+
|
36
|
+
return abs(a*x0 + b*y0 + c)/length(vec2(a, b));
|
37
|
+
}
|
38
|
+
|
39
|
+
void main() {
|
40
|
+
vec4 transparent = vec4(0.0, 0.0, 0.0, 0.0);
|
41
|
+
|
42
|
+
float dist = distance_from_line();
|
43
|
+
|
44
|
+
if (dist + shadowBlur >= thickness) {
|
45
|
+
float percent_shadowed = ((thickness - dist) / shadowBlur);
|
46
|
+
gl_FragColor = mix(transparent, shadowColor, percent_shadowed*percent_shadowed);
|
47
|
+
} else {
|
48
|
+
gl_FragColor = vec4(color);
|
49
|
+
gl_FragColor.rgb *= gl_FragColor.a;
|
50
|
+
}
|
51
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
precision highp float;
|
2
|
+
|
3
|
+
attribute vec2 position;
|
4
|
+
attribute vec2 prevPosition;
|
5
|
+
attribute float vertex;
|
6
|
+
|
7
|
+
uniform float width;
|
8
|
+
uniform float height;
|
9
|
+
uniform float thickness;
|
10
|
+
|
11
|
+
varying vec2 position_vec;
|
12
|
+
varying vec2 prev_position_vec;
|
13
|
+
|
14
|
+
void main() {
|
15
|
+
vec2 delta = position - prevPosition;
|
16
|
+
vec2 alpha = prevPosition;
|
17
|
+
vec2 beta = position;
|
18
|
+
|
19
|
+
vec2 normalized_delta = normalize(delta);
|
20
|
+
vec2 normal = vec2(-thickness/2.0 * normalized_delta.y, thickness/2.0 * normalized_delta.x);
|
21
|
+
|
22
|
+
vec2 vertex_position =
|
23
|
+
step(0.5, mod(vertex, 2.0))*alpha + // alpha if vertex is odd, 0 otherwise
|
24
|
+
step(0.5, mod(vertex + 1.0, 2.0))*beta + // beta if vertex is even, 0 otherwise
|
25
|
+
2.0*(step(1.5, vertex)-0.5)*normal // -normal if vertex < 2, +normal otherwise
|
26
|
+
;
|
27
|
+
|
28
|
+
position_vec = vec2(position.x, height - position.y);
|
29
|
+
prev_position_vec = vec2(prevPosition.x, height - prevPosition.y);
|
30
|
+
|
31
|
+
gl_Position = vec4(2.0*vertex_position.x/width - 1.0, 1.0 - 2.0*vertex_position.y/height, 0.0, 1.0);
|
32
|
+
}
|