@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,2 @@
1
+ export const Y_AXIS_WIDTH = 32;
2
+ export const BIG_Y_AXIS_LABEL_OFFSET = 20;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Finds the closest point to the target
3
+ *
4
+ * @param {Array} data - the data, in data space
5
+ * @param {Number} targetX - the x coordinate to get closest to
6
+ * @param {Object} [searchParams]
7
+ * @param {String} [searchParams.searchType] - whether to search for the closest, one before, or one after
8
+ * @param {Boolean} [searchParams.returnIndex] - whether to return the index or the object itself
9
+ * @param {Number} [startIndex] - where to start the search from
10
+ * @param {Number} [endIndex] - where to end the search
11
+ * @return {Array|Number}
12
+ */
13
+ export default function binarySearch(data, targetX, searchParams={}, startIndex=0, endIndex=undefined) {
14
+ if (endIndex === undefined) {
15
+ endIndex = data.length - 1;
16
+ }
17
+
18
+ if (data.length === 0) {
19
+ return searchParams.returnIndex ? -1 : [null, null];
20
+ }
21
+
22
+ const middleIndex = Math.floor((startIndex + endIndex)/2);
23
+
24
+ if (targetX === data[middleIndex][0] || (data[middleIndex][0] instanceof Date && data[middleIndex][0].valueOf() === targetX)) {
25
+ if (searchParams.returnIndex) {
26
+ return middleIndex;
27
+ } else {
28
+ return data[middleIndex];
29
+ }
30
+ }
31
+
32
+ if (startIndex === endIndex) {
33
+ if (data[startIndex][0] < targetX && searchParams.searchType === 'before') {
34
+ return searchParams.returnIndex ? startIndex : data[startIndex];
35
+ } else if (data[startIndex][0] > targetX && searchParams.searchType === 'after') {
36
+ return searchParams.returnIndex ? startIndex : data[startIndex];
37
+ } else {
38
+ return searchParams.returnIndex ? -1 : [null, null];
39
+ }
40
+ }
41
+
42
+ if (endIndex - 1 === startIndex) {
43
+ let index;
44
+
45
+ if (searchParams.searchType === 'before') {
46
+ index = startIndex;
47
+ // index = (targetX <= data[startIndex][0]) ? startIndex : endIndex;
48
+ } else if (searchParams.searchType === 'after') {
49
+ index = endIndex;
50
+ // index = (targetX >= data[endIndex][0]) ? endIndex : startIndex;
51
+ } else {
52
+ index = Math.abs(data[startIndex][0] - targetX) > Math.abs(data[endIndex][0] - targetX) ?
53
+ endIndex :
54
+ startIndex;
55
+ }
56
+
57
+ return searchParams.returnIndex ? index : data[index];
58
+ }
59
+
60
+ if (targetX > data[middleIndex][0]) {
61
+ return binarySearch(data, targetX, searchParams, middleIndex, endIndex);
62
+ }
63
+
64
+ if (targetX < data[middleIndex][0]) {
65
+ return binarySearch(data, targetX, searchParams, startIndex, middleIndex);
66
+ }
67
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Returns the color as a [r, g, b, a] array with domain 0 to 1 for use in webgl
3
+ *
4
+ * @param color
5
+ * @return {[number, number, number, number]|[number, number, number, number]}
6
+ */
7
+ export default function colorToVector(color) {
8
+ if (color === 'black') {
9
+ color = '#000000';
10
+ } else if (color === 'white') {
11
+ color = '#FFFFFF';
12
+ } else if (color === 'transparent') {
13
+ return [0, 0, 0, 0];
14
+ }
15
+
16
+ if (color.startsWith('rgb')) {
17
+ const parts = color.split(',').map((part) => parseFloat(part.match(/\d+(\.\d+)?/)[0]));
18
+ return [
19
+ parts[0]/255,
20
+ parts[1]/255,
21
+ parts[2]/255,
22
+ parts.length >= 4 ? parts[3] : 1
23
+ ];
24
+ }
25
+
26
+ if (typeof color !== 'string' || !/^#[\dA-F]{6}$/i.test(color)) {
27
+ throw new Error(`Color must be a hex string: ${color}`);
28
+ }
29
+
30
+ const r = parseInt(color.substr(1, 2), 16)/255;
31
+ const g = parseInt(color.substr(3, 2), 16)/255;
32
+ const b = parseInt(color.substr(5, 2), 16)/255;
33
+ const a = 1.0;
34
+ return [r, g, b, a];
35
+ }
@@ -0,0 +1,27 @@
1
+ export const LINE_COLORS = [
2
+ '#F1C232',
3
+ '#1259f8',
4
+ '#cb4b4b',
5
+ '#4da74d',
6
+ '#9440ed',
7
+ '#61e0ed',
8
+ '#ed6d2c',
9
+ '#ed13c6',
10
+ '#bbed59'
11
+ ];
12
+
13
+ export default function getColor(seriesColor, i, multigrapherSeriesIndex) {
14
+ if (typeof seriesColor === 'string') {
15
+ return seriesColor;
16
+ }
17
+
18
+ if (typeof seriesColor === 'number') {
19
+ return LINE_COLORS[seriesColor % LINE_COLORS.length];
20
+ }
21
+
22
+ if (multigrapherSeriesIndex !== undefined) {
23
+ return LINE_COLORS[multigrapherSeriesIndex % LINE_COLORS.length];
24
+ }
25
+
26
+ return LINE_COLORS[i % LINE_COLORS.length];
27
+ }
@@ -0,0 +1,159 @@
1
+ import PropTypes from 'prop-types';
2
+ import {DATA_TYPES} from '../state/data_types';
3
+
4
+ // checking the shape of data is slow if there's a lot of it
5
+ // it can be convenient in development, but should not be done when testing high cardinality data
6
+ const ENABLE_DATA_CHECK = false;
7
+
8
+ const PointY = function (props, propName) {
9
+ if (typeof props[propName] !== 'number' && props[propName] !== null) {
10
+ return new Error(`${propName} needs to be a number or null`);
11
+ }
12
+
13
+ return null;
14
+ };
15
+
16
+ const PointTuple = function (props, propName) {
17
+ if (!Array.isArray(props) || props.length !== 2) { // eslint-disable-line react/prop-types
18
+ return new Error(`${propName} needs to be an array of length two`);
19
+ }
20
+
21
+ const [x, y] = props;
22
+ if (typeof x !== 'number' && !(x instanceof Date)) {
23
+ return new Error(`${propName}.x needs to be a number or date`);
24
+ }
25
+
26
+ if (typeof y !== 'number' && y !== null) {
27
+ return new Error(`${propName}.y needs to be a number or null`);
28
+ }
29
+
30
+ return null;
31
+ };
32
+
33
+ const Data = ENABLE_DATA_CHECK ? PropTypes.oneOfType([
34
+ PropTypes.arrayOf(PointY), // values
35
+ PropTypes.arrayOf(PropTypes.arrayOf(PointTuple)), // tuples
36
+ PropTypes.arrayOf(PropTypes.object), // objects
37
+ PropTypes.shape({ observe: PropTypes.func.isRequired }), // observable
38
+ PropTypes.func // generator function
39
+ ]) : PropTypes.any;
40
+
41
+ const SingleSeries = PropTypes.shape({
42
+ data: Data.isRequired,
43
+ type: PropTypes.oneOf([
44
+ ...DATA_TYPES,
45
+ 'infer'
46
+ ]),
47
+ xKey: PropTypes.string,
48
+ yKey: PropTypes.string,
49
+ xUnixDates: PropTypes.bool,
50
+ color: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
51
+ name: PropTypes.string,
52
+ xLabel: PropTypes.string,
53
+ yLabel: PropTypes.string,
54
+ ignoreDiscontinuities: PropTypes.bool,
55
+ dashed: PropTypes.bool,
56
+ dashPattern: PropTypes.arrayOf(PropTypes.number),
57
+ width: PropTypes.number,
58
+ rangeSelectorWidth: PropTypes.number,
59
+ axis: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
60
+ expandYWith: PropTypes.arrayOf(PropTypes.number),
61
+ defaultAlwaysTooltipped: PropTypes.bool,
62
+ square: PropTypes.bool,
63
+ shiftXBy: PropTypes.number,
64
+ graph: PropTypes.number, // affects multigrapher only
65
+ background: PropTypes.object,
66
+ hideFromKey: PropTypes.bool,
67
+ showIndividualPoints: PropTypes.bool,
68
+ rendering: PropTypes.oneOf(['line', 'bar', 'area']), // defaults to line
69
+ negativeColor: PropTypes.string, // only applies to bar
70
+ gradient: PropTypes.array, // only applies to area
71
+ zeroLineWidth: PropTypes.number, // only applies to bar and area
72
+ zeroLineColor: PropTypes.string, // only applies to bar and area
73
+ pointRadius: PropTypes.number // only applies to area
74
+ });
75
+
76
+ const Series = PropTypes.arrayOf(SingleSeries);
77
+
78
+ const Axis = PropTypes.shape({
79
+ axisIndex: PropTypes.number.isRequired,
80
+ series: PropTypes.array.isRequired,
81
+ side: PropTypes.oneOf(['left', 'right']).isRequired,
82
+ scale: PropTypes.oneOf(['linear', 'log']).isRequired,
83
+ label: PropTypes.string
84
+ });
85
+
86
+ const Axes = PropTypes.arrayOf(Axis);
87
+
88
+ const CustomBoundsSelector = PropTypes.shape({
89
+ label: PropTypes.string.isRequired,
90
+ calculator: PropTypes.func.isRequired,
91
+ datesOnly: PropTypes.bool
92
+ });
93
+
94
+ const CustomBoundsSelectors = PropTypes.arrayOf(CustomBoundsSelector);
95
+
96
+ const TooltipOptionsRaw = {
97
+ includeSeriesLabel: PropTypes.bool,
98
+ includeXLabel: PropTypes.bool,
99
+ includeYLabel: PropTypes.bool,
100
+ includeXValue: PropTypes.bool,
101
+ includeYValue: PropTypes.bool,
102
+ floating: PropTypes.bool,
103
+ alwaysFixedPosition: PropTypes.bool,
104
+ floatPosition: PropTypes.oneOf(['top', 'bottom']),
105
+ floatDelta: PropTypes.number,
106
+ savingDisabled: PropTypes.bool
107
+ };
108
+
109
+ const TooltipOptions = PropTypes.shape(TooltipOptionsRaw);
110
+
111
+ const Annotation = PropTypes.shape({
112
+ x: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Date)]).isRequired,
113
+ xEnd: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Date)]),
114
+ series: PropTypes.arrayOf(PropTypes.string),
115
+ content: PropTypes.string.isRequired
116
+ });
117
+ const Annotations = PropTypes.arrayOf(Annotation);
118
+
119
+ const DraggablePoint = PropTypes.shape({
120
+ x: PropTypes.number.isRequired,
121
+ y: PropTypes.number.isRequired,
122
+ radius: PropTypes.number,
123
+ fillColor: PropTypes.string,
124
+ strokeColor: PropTypes.string,
125
+ strokeWidth: PropTypes.number,
126
+ onClick: PropTypes.func,
127
+ onDoubleClick: PropTypes.func
128
+ });
129
+ const DraggablePoints = PropTypes.arrayOf(DraggablePoint);
130
+
131
+ const VerticalLine = PropTypes.shape({
132
+ x: PropTypes.number.isRequired,
133
+ color: PropTypes.string,
134
+ width: PropTypes.number,
135
+ markTop: PropTypes.bool,
136
+ style: PropTypes.object,
137
+ markerStyle: PropTypes.object
138
+ });
139
+ const VerticalLines = PropTypes.arrayOf(VerticalLine);
140
+
141
+ const CustomPropTypes = {
142
+ Data,
143
+ SingleSeries,
144
+ Series,
145
+ Axis,
146
+ Axes,
147
+ CustomBoundsSelector,
148
+ CustomBoundsSelectors,
149
+ TooltipOptions,
150
+ TooltipOptionsRaw,
151
+ Annotations,
152
+ DraggablePoint,
153
+ DraggablePoints,
154
+ VerticalLine,
155
+ VerticalLines
156
+ };
157
+
158
+
159
+ export default CustomPropTypes;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Flattens simple data
3
+ * Returns an array of tuples (x value, simple point)
4
+ *
5
+ * @param simpleData
6
+ * @param series
7
+ * @param inDataSpace
8
+ * @return {[]}
9
+ */
10
+ export default function flattenSimpleData(simpleData, {series, inDataSpace }) {
11
+ const flattened = [];
12
+ let fI = 0;
13
+
14
+ for (let point of simpleData) {
15
+ const yValue = extractYValue(point, series);
16
+
17
+ if (series.yKey && Array.isArray(yValue)) {
18
+ for (let subpoint of point[series.yKey]) {
19
+ if (series.ignoreDiscontinuities && typeof subpoint[series.yKey] !== 'number' && !Array.isArray(subpoint)) {
20
+ continue;
21
+ }
22
+
23
+ const x = inDataSpace[fI++][0];
24
+
25
+ if (series.square && flattened.length > 0) {
26
+ flattened.push([x, flattened[flattened.length - 1][1]]);
27
+ }
28
+
29
+ flattened.push([x, subpoint]);
30
+ }
31
+
32
+ continue;
33
+ }
34
+
35
+ if (series.ignoreDiscontinuities && (yValue === undefined || yValue === null)) {
36
+ continue;
37
+ }
38
+
39
+ const x = inDataSpace[fI++][0];
40
+
41
+ if (series.square && flattened.length > 0) {
42
+ flattened.push([x, flattened[flattened.length - 1][1]]);
43
+ }
44
+
45
+ flattened.push([x, point]);
46
+ }
47
+
48
+ if (flattened.length !== inDataSpace.length) {
49
+ console.warn('Flattening didn\'t give the same length as it has in data space'); // eslint-disable-line no-console
50
+ }
51
+
52
+ return flattened;
53
+ }
54
+
55
+ export function extractYValue(point, series) {
56
+ if (series.yKey) {
57
+ return point[series.yKey];
58
+ }
59
+
60
+ if (Array.isArray(point)) {
61
+ if (point.length === 1) {
62
+ return point[0];
63
+ } else {
64
+ return point[1];
65
+ }
66
+ }
67
+
68
+ return point;
69
+ }
70
+
71
+ export function extractXValue(point, series) {
72
+ if (series.xKey) {
73
+ return point[series.xKey];
74
+ }
75
+
76
+ if (Array.isArray(point)) {
77
+ return point[0];
78
+ }
79
+
80
+ return point;
81
+ }
@@ -0,0 +1,233 @@
1
+ export function calculatePrecision(value) {
2
+ return Math.max(-Math.log10(Math.abs(value)) + 2, 0);
3
+ }
4
+
5
+ export function calculateTimePrecision(minDate, maxDate) {
6
+ minDate = new Date(minDate);
7
+ maxDate = new Date(maxDate);
8
+
9
+ const range = maxDate.valueOf() - minDate.valueOf();
10
+ if (range < 3*1000) {
11
+ return 'ms';
12
+ } else if (range < 10*60*1000) {
13
+ return 's';
14
+ } else {
15
+ return 'm';
16
+ }
17
+ }
18
+
19
+ export function roundToPrecision(value, precision=null) {
20
+ if (precision === null) {
21
+ precision = calculatePrecision(value);
22
+ }
23
+
24
+ if (isNaN(precision) || precision > 100) {
25
+ return value.toString();
26
+ }
27
+
28
+ const rounded = value.toFixed(Math.ceil(precision));
29
+ let stripped = rounded;
30
+ if (rounded.includes('.')) {
31
+ stripped = stripped.replace(/\.?0+$/g, '');
32
+ }
33
+
34
+ if (stripped === '') {
35
+ return '0';
36
+ }
37
+
38
+ return stripped;
39
+ }
40
+
41
+ const DATE_TIME_FORMATTERS = {};
42
+
43
+ /**
44
+ * Given a timezone string, gets the offset relative to utc in milliseconds
45
+ * For example, America/New_York in winter is GMT-05:00, so it evaluates to -5*60*60*1000
46
+ *
47
+ * @param {String} timeZone - the time zone string
48
+ * @param {Date} sampleDate - a date to use in the conversions, since it can be time-of-year dependent with Daylight Savings Time
49
+ * @return {number|null}
50
+ */
51
+ function timezoneToOffsetMS(timeZone, sampleDate) {
52
+ try { // formats are finicky, so give up rather than abort rendering
53
+ let datetimeFormatter = DATE_TIME_FORMATTERS[timeZone];
54
+ if (!datetimeFormatter) {
55
+ let properTimeZone = timeZone;
56
+ if (!timeZone || timeZone === 'local') {
57
+ properTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
58
+ }
59
+
60
+ datetimeFormatter = new Intl.DateTimeFormat('en-US', {
61
+ timeZone: properTimeZone,
62
+ timeZoneName: 'longOffset'
63
+ });
64
+ DATE_TIME_FORMATTERS[timeZone] = datetimeFormatter;
65
+ }
66
+
67
+ const parts = datetimeFormatter.formatToParts(sampleDate);
68
+ const offsetPart = parts.find(part => part.type === 'timeZoneName');
69
+
70
+ if (!offsetPart) {
71
+ return null;
72
+ }
73
+
74
+ if (offsetPart.value === 'GMT') {
75
+ return 0;
76
+ }
77
+
78
+ if (!/^GMT[+-]\d{2}:\d{2}$/.test(offsetPart.value)) {
79
+ return null;
80
+ }
81
+
82
+ const [hours, minutes] = offsetPart.value.slice(3).split(':');
83
+
84
+ return parseInt(hours)*60*60*1000 + parseInt(minutes)*60*1000;
85
+ } catch (e) {
86
+ console.error(new Error(`Could not parse timezone offset for ${sampleDate} in ${timeZone}`)); // eslint-disable-line no-console
87
+ console.error(e); // eslint-disable-line no-console
88
+ return null;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Gets the Date object that represents the start of day in a given timezone
94
+ * Note that this is still a native date object, so it will be in the local timezone
95
+ * Its timestamp, however, will correspond to the start of the day in the given timezone
96
+ *
97
+ * @param date
98
+ * @param timezone
99
+ * @return {Date}
100
+ */
101
+ export function startOfDayInTimezone(date, timezone) {
102
+ if (!timezone) {
103
+ const startOfDay = new Date(date);
104
+ startOfDay.setHours(0, 0, 0, 0);
105
+ return startOfDay;
106
+ }
107
+
108
+ const offset = timezoneToOffsetMS(timezone, date); // ms between timezone and utc
109
+ let startOfDay = new Date(date);
110
+ startOfDay.setUTCHours(0, 0, 0, 0);
111
+
112
+ const difference = startOfDay.valueOf() - date.valueOf();
113
+
114
+ // if we would have gone forward a day when offset is taken into account, we need to go back a day again
115
+ if (difference > offset) {
116
+ startOfDay = new Date(startOfDay.valueOf() - 24*60*60*1000);
117
+ }
118
+
119
+ return new Date(startOfDay.valueOf() - offset);
120
+ }
121
+
122
+ function formatTime(time, {precision, justTime, justDate, justMonthAndDay, unitOverride, clockStyle='24h', timeZone}) {
123
+ const utc = timeZone && timeZone.toLowerCase() === 'utc';
124
+ if (timeZone && !utc && timeZone !== 'local' && window.Intl && window.Intl.DateTimeFormat) {
125
+ const offset = timezoneToOffsetMS(timeZone, time);
126
+ const localOffset = timezoneToOffsetMS('local', time);
127
+
128
+ if (typeof offset === 'number' && typeof localOffset === 'number') {
129
+ time = new Date(time.valueOf() + offset - localOffset);
130
+ }
131
+ }
132
+
133
+ const year = utc ? time.getUTCFullYear() : time.getFullYear();
134
+ const month = (utc ? time.getUTCMonth() : time.getMonth()) + 1;
135
+ const day = utc ? time.getUTCDate() : time.getDate();
136
+
137
+ if (unitOverride === 'year') {
138
+ return year.toString();
139
+ }
140
+
141
+ if (justDate) {
142
+ return utc ? `${month}/${day}/${year}` : time.toLocaleDateString();
143
+ }
144
+
145
+ if (justMonthAndDay) {
146
+ // eg Jan 19
147
+ const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
148
+ return `${monthNames[month - 1]} ${day}`;
149
+ }
150
+
151
+ const hours = utc ? time.getUTCHours() : time.getHours();
152
+ const minutes = utc ? time.getUTCMinutes() : time.getMinutes();
153
+ const seconds = utc ? time.getUTCSeconds() : time.getSeconds();
154
+ const milliseconds = utc ? time.getUTCMilliseconds() : time.getMilliseconds();
155
+
156
+ let timeString;
157
+
158
+ if (clockStyle === '12h') {
159
+ timeString = `${((hours + 11) % 12 + 1).toString()}:${minutes.toString().padStart(2, '0')}`;
160
+ } else {
161
+ timeString = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
162
+ }
163
+
164
+ if (precision === 's' || precision === 'ms') {
165
+ timeString += `:${seconds.toString().padStart(2, '0')}`;
166
+ }
167
+
168
+ if (precision === 'ms') {
169
+ timeString += `.${milliseconds.toString().padStart(3, '0')}`;
170
+ }
171
+
172
+ if (clockStyle === '12h') {
173
+ timeString += hours >= 12 ? 'pm' : 'am';
174
+ }
175
+
176
+ if (justTime) {
177
+ return timeString;
178
+ }
179
+
180
+ if (utc) {
181
+ timeString += ' UTC';
182
+ }
183
+
184
+ return (utc ? `${month}/${day}/${year}` : time.toLocaleDateString()) + ' ' + timeString;
185
+ }
186
+
187
+ export function formatX(x, {dates=false, precision=null, justTime=false, justDate=false, justMonthAndDay=false, clockStyle='24h', unitOverride, timeZone, integersOnly=false, inverseEnumMap}={}) {
188
+ if (dates && !(x instanceof Date)) {
189
+ x = new Date(x);
190
+
191
+ if (isNaN(x)) {
192
+ return 'Invalid Date';
193
+ }
194
+ }
195
+
196
+ if (x instanceof Date) {
197
+ return formatTime(x, {precision, justTime, justDate, justMonthAndDay, unitOverride, clockStyle, timeZone});
198
+ }
199
+
200
+ if (isNaN(x)) {
201
+ return 'NaN';
202
+ }
203
+
204
+ if (inverseEnumMap) {
205
+ if (Math.abs(x - Math.round(x)) > 1e-10) {
206
+ return '';
207
+ }
208
+
209
+ return inverseEnumMap[Math.round(x)];
210
+ }
211
+
212
+ if (integersOnly && Math.abs(x - Math.round(x)) > 1e-10) {
213
+ return '';
214
+ }
215
+
216
+ return roundToPrecision(x, precision);
217
+ }
218
+
219
+ export function formatY(y, {precision=null, log=false}={}) {
220
+ if (y === null) {
221
+ return 'null';
222
+ }
223
+
224
+ if (isNaN(y)) {
225
+ return 'NaN';
226
+ }
227
+
228
+ if (log) {
229
+ return `10^${roundToPrecision(Math.log10(y), precision)}`;
230
+ }
231
+
232
+ return roundToPrecision(y, precision);
233
+ }
@@ -0,0 +1,10 @@
1
+ export default function generatorParamsEqual(a, b) {
2
+ if (a === undefined || b === undefined) {
3
+ return a === b;
4
+ }
5
+
6
+ return (a.minX === b.minX) &&
7
+ (a.maxX === b.maxX) &&
8
+ (a.sizing.elementWidth === b.sizing.elementWidth) &&
9
+ (a.sizing.renderWidth === b.sizing.renderWidth);
10
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Gets the proper name for a series
3
+ *
4
+ * @param {Object} singleSeries
5
+ * @param {Number} index
6
+ * @return {string}
7
+ */
8
+ export default function nameForSeries(singleSeries, index) {
9
+ let name = singleSeries.name || singleSeries.yKey;
10
+
11
+ if (!name) {
12
+ name = index.toString();
13
+ }
14
+
15
+ return name;
16
+ }