@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.
Files changed (112) hide show
  1. package/.eslintrc.js +85 -0
  2. package/.idea/codeStyles/Project.xml +19 -0
  3. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  4. package/.idea/grapher.iml +12 -0
  5. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  6. package/.idea/misc.xml +6 -0
  7. package/.idea/modules.xml +8 -0
  8. package/.idea/vcs.xml +6 -0
  9. package/0.bundle.js +2 -0
  10. package/0.bundle.js.map +1 -0
  11. package/1767282193a714f63082.module.wasm +0 -0
  12. package/537.bundle.js +2 -0
  13. package/537.bundle.js.map +1 -0
  14. package/831.bundle.js +2 -0
  15. package/831.bundle.js.map +1 -0
  16. package/bundle.js +2 -0
  17. package/bundle.js.map +1 -0
  18. package/package.json +75 -0
  19. package/readme.md +129 -0
  20. package/src/components/annotations.js +62 -0
  21. package/src/components/context_menu.js +73 -0
  22. package/src/components/draggable_points.js +114 -0
  23. package/src/components/graph_body.js +292 -0
  24. package/src/components/graph_title.js +16 -0
  25. package/src/components/options.js +111 -0
  26. package/src/components/percentile_button.js +72 -0
  27. package/src/components/range_graph.js +352 -0
  28. package/src/components/range_selection.js +175 -0
  29. package/src/components/range_selection_button.js +26 -0
  30. package/src/components/range_selection_button_base.js +51 -0
  31. package/src/components/series_key.js +235 -0
  32. package/src/components/series_key_axis_container.js +70 -0
  33. package/src/components/series_key_item.js +52 -0
  34. package/src/components/sidebar.js +76 -0
  35. package/src/components/tooltip.js +244 -0
  36. package/src/components/vertical_lines.js +70 -0
  37. package/src/components/x_axis.js +124 -0
  38. package/src/components/y_axis.js +239 -0
  39. package/src/eventable.js +65 -0
  40. package/src/grapher.js +367 -0
  41. package/src/grapher.scss +914 -0
  42. package/src/helpers/axis_sizes.js +2 -0
  43. package/src/helpers/binary_search.js +67 -0
  44. package/src/helpers/color_to_vector.js +35 -0
  45. package/src/helpers/colors.js +27 -0
  46. package/src/helpers/custom_prop_types.js +159 -0
  47. package/src/helpers/flatten_simple_data.js +81 -0
  48. package/src/helpers/format.js +233 -0
  49. package/src/helpers/generator_params_equal.js +10 -0
  50. package/src/helpers/name_for_series.js +16 -0
  51. package/src/helpers/place_grid.js +257 -0
  52. package/src/helpers/pyodide_ready.js +13 -0
  53. package/src/multigrapher.js +105 -0
  54. package/src/renderer/background.frag +7 -0
  55. package/src/renderer/background.vert +7 -0
  56. package/src/renderer/background_program.js +48 -0
  57. package/src/renderer/circle.frag +26 -0
  58. package/src/renderer/circle.vert +12 -0
  59. package/src/renderer/create_gl_program.js +36 -0
  60. package/src/renderer/draw_area.js +159 -0
  61. package/src/renderer/draw_background.js +15 -0
  62. package/src/renderer/draw_bars.js +80 -0
  63. package/src/renderer/draw_line.js +69 -0
  64. package/src/renderer/draw_zero_line.js +24 -0
  65. package/src/renderer/extract_vertices.js +137 -0
  66. package/src/renderer/graph_body_renderer.js +293 -0
  67. package/src/renderer/line.frag +51 -0
  68. package/src/renderer/line.vert +32 -0
  69. package/src/renderer/line_program.js +125 -0
  70. package/src/renderer/paths_from.js +72 -0
  71. package/src/renderer/scale_bounds.js +28 -0
  72. package/src/renderer/size_canvas.js +59 -0
  73. package/src/rust/Cargo.lock +233 -0
  74. package/src/rust/Cargo.toml +35 -0
  75. package/src/rust/pkg/grapher_rs.d.ts +42 -0
  76. package/src/rust/pkg/grapher_rs.js +351 -0
  77. package/src/rust/pkg/grapher_rs_bg.d.ts +11 -0
  78. package/src/rust/pkg/grapher_rs_bg.wasm +0 -0
  79. package/src/rust/pkg/index.js +342 -0
  80. package/src/rust/pkg/index_bg.wasm +0 -0
  81. package/src/rust/pkg/package.json +14 -0
  82. package/src/rust/src/extract_vertices.rs +83 -0
  83. package/src/rust/src/get_point_number.rs +50 -0
  84. package/src/rust/src/lib.rs +15 -0
  85. package/src/rust/src/selected_space_to_render_space.rs +131 -0
  86. package/src/state/average_loop_times.js +15 -0
  87. package/src/state/bound_calculator_from_selection.js +36 -0
  88. package/src/state/bound_calculators.js +41 -0
  89. package/src/state/calculate_annotations_state.js +59 -0
  90. package/src/state/calculate_data_bounds.js +104 -0
  91. package/src/state/calculate_tooltip_state.js +241 -0
  92. package/src/state/data_types.js +13 -0
  93. package/src/state/expand_bounds.js +58 -0
  94. package/src/state/find_matching_axis.js +31 -0
  95. package/src/state/get_default_bounds_calculator.js +15 -0
  96. package/src/state/hooks.js +164 -0
  97. package/src/state/infer_type.js +74 -0
  98. package/src/state/merge_bounds.js +64 -0
  99. package/src/state/multigraph_state_controller.js +334 -0
  100. package/src/state/selection_from_global_bounds.js +25 -0
  101. package/src/state/space_conversions/condense_data_space.js +115 -0
  102. package/src/state/space_conversions/data_space_to_selected_space.js +328 -0
  103. package/src/state/space_conversions/selected_space_to_background_space.js +144 -0
  104. package/src/state/space_conversions/selected_space_to_render_space.js +161 -0
  105. package/src/state/space_conversions/simple_series_to_data_space.js +229 -0
  106. package/src/state/state_controller.js +1770 -0
  107. package/src/state/sync_pool.js +101 -0
  108. package/test/setup.js +15 -0
  109. package/test/space_conversions/data_space_to_selected_space.test.js +434 -0
  110. package/webpack.dev.config.js +109 -0
  111. package/webpack.prod.config.js +60 -0
  112. 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
+ }