@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,83 @@
|
|
1
|
+
use wasm_bindgen::prelude::*;
|
2
|
+
|
3
|
+
fn add_point(x : f32, y : f32, prev_x : f32, prev_y : f32, point_i : usize, positions: &mut [f32], prev_positions: &mut [f32], vertices: &mut [f32], indices: &mut [u32]) {
|
4
|
+
for j in 0..4 {
|
5
|
+
positions[point_i * 8 + 2 * j] = x;
|
6
|
+
positions[point_i * 8 + 2 * j + 1] = y;
|
7
|
+
prev_positions[point_i * 8 + 2 * j] = prev_x;
|
8
|
+
prev_positions[point_i * 8 + 2 * j + 1] = prev_y;
|
9
|
+
vertices[point_i*4 + j] = j as f32;
|
10
|
+
}
|
11
|
+
|
12
|
+
indices[point_i * 6] = (point_i * 4) as u32;
|
13
|
+
indices[point_i * 6 + 1] = (point_i * 4 + 1) as u32;
|
14
|
+
indices[point_i * 6 + 2] = (point_i * 4 + 3) as u32;
|
15
|
+
|
16
|
+
indices[point_i * 6 + 3] = (point_i * 4) as u32;
|
17
|
+
indices[point_i * 6 + 4] = (point_i * 4 + 2) as u32;
|
18
|
+
indices[point_i * 6 + 5] = (point_i * 4 + 3) as u32;
|
19
|
+
}
|
20
|
+
|
21
|
+
#[wasm_bindgen]
|
22
|
+
pub fn extract_vertices(dpi_increase: f64, null_mask: &[u8], y_values: &[f64], min_y_values: &[f64], max_y_values: &[f64], positions: &mut [f32], prev_positions: &mut [f32], vertices: &mut [f32], indices: &mut [u32], dashed : bool, dash0 : usize, dash1 : usize) {
|
23
|
+
let mut previously_discontinuous = true;
|
24
|
+
|
25
|
+
let mut point_i = 0;
|
26
|
+
let mut path_i = 0;
|
27
|
+
|
28
|
+
let mut prev_x : f32;
|
29
|
+
let mut prev_y : f32;
|
30
|
+
|
31
|
+
for i in 0..y_values.len() {
|
32
|
+
if dashed && path_i % (dash0 + dash1) >= dash0 {
|
33
|
+
path_i += 1;
|
34
|
+
continue;
|
35
|
+
}
|
36
|
+
|
37
|
+
let x : f32 = (i as f32)*(dpi_increase as f32);
|
38
|
+
let y = y_values[i] as f32;
|
39
|
+
|
40
|
+
if (null_mask[i] & 0b001) > 0 { // y null
|
41
|
+
previously_discontinuous = true;
|
42
|
+
continue;
|
43
|
+
}
|
44
|
+
|
45
|
+
if previously_discontinuous {
|
46
|
+
path_i = 0;
|
47
|
+
prev_x = x - 1.0;
|
48
|
+
prev_y = y;
|
49
|
+
} else {
|
50
|
+
prev_x = ((i - 1) as f32)*(dpi_increase as f32);
|
51
|
+
prev_y = y_values[i - 1] as f32;
|
52
|
+
}
|
53
|
+
|
54
|
+
add_point(x, y, prev_x, prev_y, point_i, positions, prev_positions, vertices, indices);
|
55
|
+
point_i += 1;
|
56
|
+
path_i += 1;
|
57
|
+
|
58
|
+
let min_y = min_y_values[i];
|
59
|
+
let max_y = max_y_values[i];
|
60
|
+
|
61
|
+
if min_y != max_y {
|
62
|
+
if (null_mask[i] & 0b010) == 0 {
|
63
|
+
add_point(x, min_y as f32, prev_x, prev_y, point_i, positions, prev_positions, vertices, indices);
|
64
|
+
prev_y = min_y as f32;
|
65
|
+
point_i += 1;
|
66
|
+
path_i += 1;
|
67
|
+
}
|
68
|
+
|
69
|
+
if (null_mask[i] & 0b100) == 0 {
|
70
|
+
add_point(x, max_y as f32, prev_x, prev_y, point_i, positions, prev_positions, vertices, indices);
|
71
|
+
prev_y = max_y as f32;
|
72
|
+
point_i += 1;
|
73
|
+
path_i += 1;
|
74
|
+
}
|
75
|
+
|
76
|
+
add_point(x, y, prev_x, prev_y, point_i, positions, prev_positions, vertices, indices);
|
77
|
+
point_i += 1;
|
78
|
+
path_i += 1;
|
79
|
+
}
|
80
|
+
|
81
|
+
previously_discontinuous = false;
|
82
|
+
}
|
83
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
use wasm_bindgen::prelude::*;
|
2
|
+
|
3
|
+
#[wasm_bindgen]
|
4
|
+
pub fn get_point_number(null_mask: &[u8], y_values: &[f64], min_y_values: &[f64], max_y_values: &[f64], dashed : bool, dash0 : usize, dash1 : usize) -> f64 {
|
5
|
+
let mut previously_discontinuous = true;
|
6
|
+
|
7
|
+
let mut point_i = 0;
|
8
|
+
let mut path_i = 0;
|
9
|
+
|
10
|
+
for i in 0..y_values.len() {
|
11
|
+
if dashed && path_i % (dash0 + dash1) >= dash0 {
|
12
|
+
path_i += 1;
|
13
|
+
continue;
|
14
|
+
}
|
15
|
+
|
16
|
+
if (null_mask[i] & 0b001) > 0 { // y null
|
17
|
+
previously_discontinuous = true;
|
18
|
+
continue;
|
19
|
+
}
|
20
|
+
|
21
|
+
if previously_discontinuous {
|
22
|
+
path_i = 0;
|
23
|
+
}
|
24
|
+
|
25
|
+
point_i += 1;
|
26
|
+
path_i += 1;
|
27
|
+
|
28
|
+
let min_y = min_y_values[i];
|
29
|
+
let max_y = max_y_values[i];
|
30
|
+
|
31
|
+
if min_y != max_y {
|
32
|
+
if (null_mask[i] & 0b010) == 0 {
|
33
|
+
point_i += 1;
|
34
|
+
path_i += 1;
|
35
|
+
}
|
36
|
+
|
37
|
+
if (null_mask[i] & 0b110) == 0 {
|
38
|
+
point_i += 1;
|
39
|
+
path_i += 1;
|
40
|
+
}
|
41
|
+
|
42
|
+
point_i += 1;
|
43
|
+
path_i += 1;
|
44
|
+
}
|
45
|
+
|
46
|
+
previously_discontinuous = false;
|
47
|
+
}
|
48
|
+
|
49
|
+
point_i as f64
|
50
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
use wasm_bindgen::prelude::*;
|
2
|
+
mod selected_space_to_render_space;
|
3
|
+
mod extract_vertices;
|
4
|
+
mod get_point_number;
|
5
|
+
|
6
|
+
// This is like the `main` function, except for JavaScript.
|
7
|
+
#[wasm_bindgen(start)]
|
8
|
+
pub fn main_js() -> Result<(), JsValue> {
|
9
|
+
// This provides better error messages in debug mode.
|
10
|
+
// It's disabled in release mode so it doesn't bloat up the file size.
|
11
|
+
#[cfg(debug_assertions)]
|
12
|
+
console_error_panic_hook::set_once();
|
13
|
+
|
14
|
+
Ok(())
|
15
|
+
}
|
@@ -0,0 +1,131 @@
|
|
1
|
+
use wasm_bindgen::prelude::*;
|
2
|
+
use js_sys::JsString;
|
3
|
+
|
4
|
+
#[wasm_bindgen]
|
5
|
+
extern "C" {
|
6
|
+
pub type SelectedToRenderParams;
|
7
|
+
|
8
|
+
#[wasm_bindgen(method, getter = renderWidth)]
|
9
|
+
fn render_width(this: &SelectedToRenderParams) -> f64;
|
10
|
+
#[wasm_bindgen(method, getter = renderHeight)]
|
11
|
+
fn render_height(this: &SelectedToRenderParams) -> f64;
|
12
|
+
#[wasm_bindgen(method, getter = minX)]
|
13
|
+
fn min_x(this: &SelectedToRenderParams) -> f64;
|
14
|
+
#[wasm_bindgen(method, getter = maxX)]
|
15
|
+
fn max_x(this: &SelectedToRenderParams) -> f64;
|
16
|
+
#[wasm_bindgen(method, getter = minY)]
|
17
|
+
fn min_y(this: &SelectedToRenderParams) -> f64;
|
18
|
+
#[wasm_bindgen(method, getter = maxY)]
|
19
|
+
fn max_y(this: &SelectedToRenderParams) -> f64;
|
20
|
+
#[wasm_bindgen(method, getter = scale)]
|
21
|
+
fn scale(this: &SelectedToRenderParams) -> JsString;
|
22
|
+
}
|
23
|
+
|
24
|
+
#[inline]
|
25
|
+
fn value_space_to_render_space(y: f64, min_y : f64, max_y : f64, log_scale : bool, render_height : f64) -> f64 {
|
26
|
+
let mut scaled_y = y;
|
27
|
+
if log_scale {
|
28
|
+
scaled_y = y.log10();
|
29
|
+
}
|
30
|
+
|
31
|
+
let percent = (scaled_y - min_y)/(max_y - min_y);
|
32
|
+
|
33
|
+
return render_height * (1.0 - percent);
|
34
|
+
}
|
35
|
+
|
36
|
+
#[wasm_bindgen]
|
37
|
+
pub fn selected_space_to_render_space(length : usize, data : &[f64], data_null_mask : &[u8], params : &SelectedToRenderParams, null_mask: &mut [u8], y_values: &mut [f64], min_y_values: &mut [f64], max_y_values: &mut [f64]) {
|
38
|
+
let min_x = params.min_x();
|
39
|
+
let max_x = params.max_x();
|
40
|
+
let min_y = params.min_y();
|
41
|
+
let max_y = params.max_y();
|
42
|
+
let render_width = params.render_width();
|
43
|
+
let render_height = params.render_height();
|
44
|
+
let log_scale = params.scale() == "log";
|
45
|
+
|
46
|
+
let mut i : usize = 0;
|
47
|
+
let mut prev_i : i32 = (i as i32) - 1;
|
48
|
+
|
49
|
+
for pixel_x in 0..(render_width.ceil() as usize) {
|
50
|
+
// find the x value that corresponds to the x pixel
|
51
|
+
let x = ((pixel_x as f64)/((render_width as f64) - 1.0))*(max_x - min_x) + min_x;
|
52
|
+
|
53
|
+
// set i such that data[i][0] < x <= data[i+1][0]
|
54
|
+
let mut min_seen_y : Option<f64> = None;
|
55
|
+
let mut max_seen_y : Option<f64> = None;
|
56
|
+
|
57
|
+
if i > 0 && i <= length && data_null_mask[i - 1] > 0 {
|
58
|
+
i -= 1;
|
59
|
+
}
|
60
|
+
|
61
|
+
if (i + 2) < length && data[2*(i + 1)] < x {
|
62
|
+
i += 1;
|
63
|
+
}
|
64
|
+
|
65
|
+
while (i + 2) < length && data[2*(i + 1)] < x {
|
66
|
+
if data_null_mask[i] > 0 {
|
67
|
+
i += 1;
|
68
|
+
continue;
|
69
|
+
}
|
70
|
+
|
71
|
+
let cur_y = data[2*i + 1];
|
72
|
+
|
73
|
+
if min_seen_y == None || cur_y < min_seen_y.unwrap() {
|
74
|
+
min_seen_y = Some(cur_y);
|
75
|
+
}
|
76
|
+
|
77
|
+
if max_seen_y == None || cur_y > max_seen_y.unwrap() {
|
78
|
+
max_seen_y = Some(cur_y);
|
79
|
+
}
|
80
|
+
|
81
|
+
i += 1;
|
82
|
+
}
|
83
|
+
|
84
|
+
let mut cur_null_mask : u8 = 0;
|
85
|
+
|
86
|
+
match min_seen_y {
|
87
|
+
Some(value) => min_y_values[pixel_x] = value_space_to_render_space(value, min_y, max_y, log_scale, render_height),
|
88
|
+
None => cur_null_mask |= 0b010
|
89
|
+
}
|
90
|
+
|
91
|
+
match max_seen_y {
|
92
|
+
Some(value) => max_y_values[pixel_x] = value_space_to_render_space(value, min_y, max_y, log_scale, render_height),
|
93
|
+
None => cur_null_mask |= 0b100
|
94
|
+
}
|
95
|
+
|
96
|
+
// pass any discontinuities along
|
97
|
+
if (i + 1) >= length || (data_null_mask[i] > 0) || (data_null_mask[i + 1] > 0) {
|
98
|
+
if (i + 1) >= length || data_null_mask[i] > 0 {
|
99
|
+
cur_null_mask |= 0b001; // mark y as null
|
100
|
+
} else {
|
101
|
+
let y = data[2*i + 1];
|
102
|
+
y_values[pixel_x] = value_space_to_render_space(y, min_y, max_y, log_scale, render_height);
|
103
|
+
}
|
104
|
+
|
105
|
+
i += 1;
|
106
|
+
|
107
|
+
null_mask[pixel_x] = cur_null_mask;
|
108
|
+
continue;
|
109
|
+
}
|
110
|
+
|
111
|
+
// interpolate
|
112
|
+
let x_before = data[2*i];
|
113
|
+
let y_before = data[2*i + 1];
|
114
|
+
|
115
|
+
let x_after = data[2*(i + 1)];
|
116
|
+
let y_after = data[2*(i + 1) + 1];
|
117
|
+
|
118
|
+
let percent = (x - x_before) / (x_after - x_before);
|
119
|
+
let mut y = percent * (y_after - y_before) + y_before;
|
120
|
+
|
121
|
+
// we're at the first point after the direction changed. Don't interpolate
|
122
|
+
if prev_i != (i as i32) {
|
123
|
+
y = y_before;
|
124
|
+
}
|
125
|
+
|
126
|
+
y_values[pixel_x] = value_space_to_render_space(y, min_y, max_y, log_scale, render_height);
|
127
|
+
null_mask[pixel_x] = cur_null_mask;
|
128
|
+
|
129
|
+
prev_i = i as i32;
|
130
|
+
}
|
131
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export function averageLoopTimes(loopTimes) {
|
2
|
+
const result = {};
|
3
|
+
|
4
|
+
for (let loopTime of loopTimes) {
|
5
|
+
for (let [key, value] of Object.entries(loopTime)) {
|
6
|
+
result[key] = (result[key] || 0) + value;
|
7
|
+
}
|
8
|
+
}
|
9
|
+
|
10
|
+
for (let [key, value] of Object.entries(result)) {
|
11
|
+
result[key] = value/loopTimes.length;
|
12
|
+
}
|
13
|
+
|
14
|
+
return result;
|
15
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import scaleBounds from '../renderer/scale_bounds';
|
2
|
+
|
3
|
+
export default function boundCalculatorFromSelection({ minPixelX, maxPixelX, minPixelY, maxPixelY}, { elementWidth, elementHeight, selection, axes }) {
|
4
|
+
if (Math.abs(maxPixelX - minPixelX) < 1 || Math.abs(maxPixelY - minPixelY) < 1) {
|
5
|
+
return;
|
6
|
+
}
|
7
|
+
|
8
|
+
const minX = (minPixelX/elementWidth)*(selection.maxX - selection.minX) + selection.minX;
|
9
|
+
const maxX = (maxPixelX/elementWidth)*(selection.maxX - selection.minX) + selection.minX;
|
10
|
+
|
11
|
+
const byAxis = [];
|
12
|
+
for (let { currentBounds, scale } of axes) {
|
13
|
+
const scaledBounds = scaleBounds({ ...currentBounds, scale });
|
14
|
+
let maxY = (1 - minPixelY/elementHeight)*(scaledBounds.maxY - scaledBounds.minY) + scaledBounds.minY;
|
15
|
+
let minY = (1 - maxPixelY/elementHeight)*(scaledBounds.maxY - scaledBounds.minY) + scaledBounds.minY;
|
16
|
+
|
17
|
+
if (scale === 'log') {
|
18
|
+
minY = Math.pow(10, minY);
|
19
|
+
maxY = Math.pow(10, maxY);
|
20
|
+
}
|
21
|
+
byAxis.push({
|
22
|
+
minX,
|
23
|
+
maxX,
|
24
|
+
minY,
|
25
|
+
maxY
|
26
|
+
});
|
27
|
+
}
|
28
|
+
|
29
|
+
return () => {
|
30
|
+
return {
|
31
|
+
minX,
|
32
|
+
maxX,
|
33
|
+
byAxis
|
34
|
+
};
|
35
|
+
};
|
36
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
const BOUND_CALCULATORS = {
|
2
|
+
all: () => {},
|
3
|
+
lastMinute: (globalBounds) => {
|
4
|
+
if (!globalBounds.dates) {
|
5
|
+
return;
|
6
|
+
}
|
7
|
+
|
8
|
+
return {
|
9
|
+
minX: Math.max(new Date(globalBounds.maxX).valueOf() - 60*1000, globalBounds.minX)
|
10
|
+
};
|
11
|
+
},
|
12
|
+
last10Minutes: (globalBounds) => {
|
13
|
+
if (!globalBounds.dates) {
|
14
|
+
return;
|
15
|
+
}
|
16
|
+
|
17
|
+
return {
|
18
|
+
minX: Math.max(new Date(globalBounds.maxX).valueOf() - 10*60*1000, globalBounds.minX)
|
19
|
+
};
|
20
|
+
},
|
21
|
+
lastHour: (globalBounds) => {
|
22
|
+
if (!globalBounds.dates) {
|
23
|
+
return;
|
24
|
+
}
|
25
|
+
|
26
|
+
return {
|
27
|
+
minX: Math.max(new Date(globalBounds.maxX).valueOf() - 60*60*1000, globalBounds.minX)
|
28
|
+
};
|
29
|
+
},
|
30
|
+
lastDay: (globalBounds) => {
|
31
|
+
if (!globalBounds.dates) {
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
|
35
|
+
return {
|
36
|
+
minX: Math.max(new Date(globalBounds.maxX).valueOf() - 24*60*60*1000, globalBounds.minX)
|
37
|
+
};
|
38
|
+
}
|
39
|
+
};
|
40
|
+
|
41
|
+
export default BOUND_CALCULATORS;
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import nameForSeries from '../helpers/name_for_series.js';
|
2
|
+
|
3
|
+
export default function calculateAnnotationsState({ annotations, series, sizing, selection }) {
|
4
|
+
const shownSeries = new Set(series.map((singleSeries, i) => {
|
5
|
+
return {
|
6
|
+
name: nameForSeries(singleSeries, i),
|
7
|
+
hidden: singleSeries.hidden
|
8
|
+
};
|
9
|
+
}).filter(({ hidden }) => !hidden).map(({ name }) => name));
|
10
|
+
|
11
|
+
const { elementWidth } = sizing;
|
12
|
+
const { minX, maxX } = selection;
|
13
|
+
|
14
|
+
const renderableAnnotations = annotations.filter((annotation) => {
|
15
|
+
if (!annotation.series) {
|
16
|
+
return true;
|
17
|
+
}
|
18
|
+
|
19
|
+
for (let seriesName of annotation.series) {
|
20
|
+
if (shownSeries.has(seriesName)) {
|
21
|
+
return true;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
return false;
|
26
|
+
}).map((annotation) => {
|
27
|
+
let xAsNumber = annotation.x;
|
28
|
+
if (typeof xAsNumber === 'string') {
|
29
|
+
xAsNumber = new Date(xAsNumber).valueOf();
|
30
|
+
} else if (xAsNumber instanceof Date) {
|
31
|
+
xAsNumber = xAsNumber.valueOf();
|
32
|
+
}
|
33
|
+
|
34
|
+
let xEndAsNumber = annotation.xEnd || xAsNumber;
|
35
|
+
if (typeof xEndAsNumber === 'string') {
|
36
|
+
xEndAsNumber = new Date(xEndAsNumber).valueOf();
|
37
|
+
} else if (xEndAsNumber instanceof Date) {
|
38
|
+
xEndAsNumber = xEndAsNumber.valueOf();
|
39
|
+
}
|
40
|
+
|
41
|
+
const xStart = Math.min(xAsNumber, xEndAsNumber);
|
42
|
+
const xEnd = Math.max(xAsNumber, xEndAsNumber);
|
43
|
+
|
44
|
+
const pixelX = (xStart - minX)/(maxX - minX) * elementWidth;
|
45
|
+
const pixelEnd = (xEnd - minX)/(maxX - minX) * elementWidth;
|
46
|
+
const width = Math.max(pixelEnd - pixelX, 1);
|
47
|
+
|
48
|
+
return {
|
49
|
+
...annotation,
|
50
|
+
pixelX,
|
51
|
+
width
|
52
|
+
};
|
53
|
+
});
|
54
|
+
|
55
|
+
return {
|
56
|
+
annotations: renderableAnnotations,
|
57
|
+
elementWidth
|
58
|
+
};
|
59
|
+
}
|
@@ -0,0 +1,104 @@
|
|
1
|
+
function finalizeBounds(bounds, { dates }) {
|
2
|
+
const initial = (bounds.minX === null && bounds.maxX === null) || bounds.minY === null || bounds.maxY === null;
|
3
|
+
|
4
|
+
for (let key of Object.keys(bounds)) {
|
5
|
+
if (typeof bounds[key] !== 'number') {
|
6
|
+
bounds[key] = 0;
|
7
|
+
}
|
8
|
+
}
|
9
|
+
|
10
|
+
bounds.initial = initial;
|
11
|
+
bounds.dates = dates;
|
12
|
+
|
13
|
+
return bounds;
|
14
|
+
}
|
15
|
+
|
16
|
+
function percentileBounds(inDataSpace, bounds, {percentile=100, percentileAsymmetry=0}={}) {
|
17
|
+
let dates = false;
|
18
|
+
|
19
|
+
bounds.minX = inDataSpace[0][0];
|
20
|
+
if (bounds.minX instanceof Date) {
|
21
|
+
bounds.minX = bounds.minX.valueOf();
|
22
|
+
dates = true;
|
23
|
+
}
|
24
|
+
|
25
|
+
bounds.maxX = inDataSpace[inDataSpace.length - 1][0];
|
26
|
+
if (bounds.maxX instanceof Date) {
|
27
|
+
bounds.maxX = bounds.maxX.valueOf();
|
28
|
+
dates = true;
|
29
|
+
}
|
30
|
+
|
31
|
+
const sortedByY = inDataSpace
|
32
|
+
.filter(([_x, y]) => typeof y === 'number')
|
33
|
+
.sort(([_x1, y1], [_x2, y2]) => y1 - y2);
|
34
|
+
|
35
|
+
if (!sortedByY.length) {
|
36
|
+
return finalizeBounds(bounds, {dates});
|
37
|
+
}
|
38
|
+
|
39
|
+
const asymmetry = Math.min(Math.abs(percentileAsymmetry), (100-percentile)/2) * (percentileAsymmetry < 0 ? -1 : 1);
|
40
|
+
const inBottomPercentile = (100-percentile)/2 + asymmetry;
|
41
|
+
const inTopPercentile = (100-percentile)/2 - asymmetry;
|
42
|
+
|
43
|
+
const startIndex = Math.floor((sortedByY.length-1)*inBottomPercentile/100);
|
44
|
+
const endIndex = Math.floor((sortedByY.length-1)*(100-inTopPercentile)/100);
|
45
|
+
|
46
|
+
bounds.minY = sortedByY[startIndex][1];
|
47
|
+
bounds.maxY = sortedByY[endIndex][1];
|
48
|
+
|
49
|
+
return finalizeBounds(bounds, {dates});
|
50
|
+
}
|
51
|
+
|
52
|
+
export default function calculateDataBounds(inDataSpace, {percentile=100, percentileAsymmetry=0}={}) {
|
53
|
+
let bounds = {
|
54
|
+
minX: null,
|
55
|
+
maxX: null,
|
56
|
+
minY: null,
|
57
|
+
maxY: null,
|
58
|
+
closestSpacing: null
|
59
|
+
};
|
60
|
+
|
61
|
+
if (percentile !== 100 && inDataSpace.length) {
|
62
|
+
return percentileBounds(inDataSpace, bounds, {percentile, percentileAsymmetry});
|
63
|
+
}
|
64
|
+
|
65
|
+
let dates = false;
|
66
|
+
let prevX = null;
|
67
|
+
|
68
|
+
for (let [x, y] of inDataSpace) {
|
69
|
+
if (x instanceof Date) {
|
70
|
+
x = x.valueOf();
|
71
|
+
dates = true;
|
72
|
+
}
|
73
|
+
|
74
|
+
if (typeof bounds.minX !== 'number' || x < bounds.minX) {
|
75
|
+
bounds.minX = x;
|
76
|
+
}
|
77
|
+
|
78
|
+
if (typeof bounds.maxX !== 'number' || x > bounds.maxX) {
|
79
|
+
bounds.maxX = x;
|
80
|
+
}
|
81
|
+
|
82
|
+
if (typeof prevX === 'number' && typeof x === 'number') {
|
83
|
+
const spacing = x - prevX;
|
84
|
+
if (typeof bounds.closestSpacing !== 'number' || spacing < bounds.closestSpacing) {
|
85
|
+
bounds.closestSpacing = spacing;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
prevX = x;
|
89
|
+
|
90
|
+
if (typeof y !== 'number') {
|
91
|
+
continue;
|
92
|
+
}
|
93
|
+
|
94
|
+
if (typeof bounds.minY !== 'number' || y < bounds.minY) {
|
95
|
+
bounds.minY = y;
|
96
|
+
}
|
97
|
+
|
98
|
+
if (typeof bounds.maxY !== 'number' || y > bounds.maxY) {
|
99
|
+
bounds.maxY = y;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
return finalizeBounds(bounds, {dates});
|
104
|
+
}
|