@windborne/grapher 1.0.25 → 1.0.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windborne/grapher",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "Graphing library",
5
5
  "main": "src/index.js",
6
6
  "module": "dist/bundle.esm.js",
package/readme.md CHANGED
@@ -68,166 +68,166 @@ Grapher supports multiple data formats within a series:
68
68
 
69
69
  ### <a id="grapherprops"></a>Schema `GrapherProps`
70
70
 
71
- | Prop | Type | Required |
72
- |------|------|----------|
73
- | **series** | [SeriesData](#seriesdata)`[]` | **✓** |
74
- | id | `string` | ✗ |
75
- | webgl | `boolean` | ✗ |
76
- | requireWASM | `boolean` | ✗ |
77
- | checkIntersection | `boolean` | ✗ |
78
- | onAxisChange | `(axes: any) => void` | ✗ |
79
- | onRenderTime | `(time: number) => void` | ✗ |
80
- | exportStateController | `(controller: any) => void` | ✗ |
81
- | onPointDrag | `(point: any) => void` | ✗ |
82
- | onDraggablePointsDoubleClick | `(point: any) => void` | ✗ |
83
- | onPointClick | `(point: any) => void` | ✗ |
84
- | timingFrameCount | `number` | ✗ |
85
- | stateControllerInitialization | `object` | ✗ |
86
- | syncPool | `SyncPool` | ✗ |
87
- | dragPositionYOffset | `number` | ✗ |
88
- | theme | `'day' \| 'night' \| 'export'` | ✗ |
89
- | title | `string` | ✗ |
90
- | fullscreen | `boolean` | ✗ |
91
- | bodyHeight | `number` | ✗ |
92
- | height | `number \| string` | ✗ |
93
- | width | `number \| string` | ✗ |
94
- | showAxes | `boolean` | ✗ |
95
- | showRangeGraph | `boolean` | ✗ |
96
- | showRangeSelectors | `boolean` | ✗ |
97
- | showSeriesKey | `boolean` | ✗ |
98
- | showTooltips | `boolean` | ✗ |
99
- | showGrid | `boolean` | ✗ |
100
- | showAxisColors | `boolean` | ✗ |
101
- | bigLabels | `boolean` | ✗ |
102
- | xTickUnit | `'year'` | ✗ |
103
- | formatXAxisLabel | `(value: any) => string` | ✗ |
104
- | xAxisIntegersOnly | `boolean` | ✗ |
105
- | clockStyle | `'12h' \| '24h'` | ✗ |
106
- | timeZone | `string` | ✗ |
107
- | markRangeGraphDates | `boolean` | ✗ |
108
- | boundsSelectionEnabled | `boolean` | ✗ |
109
- | customBoundsSelectors | [CustomBoundsSelector](#customboundsselector)`[]` | ✗ |
110
- | customBoundsSelectorsOnly | `boolean` | ✗ |
111
- | sidebarEnabled | `boolean` | ✗ |
112
- | defaultBoundsCalculator | `string` | ✗ |
113
- | percentile | `number` | ✗ |
114
- | defaultShowAnnotations | `boolean` | ✗ |
115
- | defaultShowOptions | `boolean` | ✗ |
116
- | defaultShowIndividualPoints | `boolean` | ✗ |
117
- | defaultShowSidebar | `boolean` | ✗ |
118
- | defaultLineWidth | `number` | ✗ |
119
- | tooltipOptions | [TooltipOptions](#tooltipoptions) | ✗ |
120
- | annotations | [Annotation](#annotation)`[]` | ✗ |
121
- | draggablePoints | [DraggablePoint](#draggablepoint)`[]` | ✗ |
122
- | verticalLines | [VerticalLine](#verticalline)`[]` | ✗ |
71
+ | Prop | Type | Required | Description |
72
+ |------|------|----------|-------------|
73
+ | **series** | [SeriesData](#seriesdata)`[]` | **✓** | The data for the graph and is the only property that is truly required. See "Series Data Formats" section for more details. |
74
+ | id | `string` | ✗ | Unique identifier for the grapher instance. |
75
+ | webgl | `boolean` | ✗ | If true, will render with WebGL rather than a 2D context. This is more performant, but uses more resources. |
76
+ | requireWASM | `boolean` | ✗ | If true, will wait until the WASM extensions are ready before it renders. This can be useful when your app has expensive initialization. |
77
+ | checkIntersection | `boolean` | ✗ | Controls intersection observer usage for performance optimization. |
78
+ | onAxisChange | `(axes: any) => void` | ✗ | Called every time the axes change. Receives an array of objects with the `axis` property set to {left, right}-{index}. Useful for saving state between reloads. |
79
+ | onRenderTime | `(time: number) => void` | ✗ | Called every time the grapher renders with diagnostic information about how long rendering took. |
80
+ | exportStateController | `(controller: any) => void` | ✗ | Callback that receives the state controller instance for external manipulation. |
81
+ | onPointDrag | `(point: any) => void` | ✗ | Callback function that fires when draggable points are moved. |
82
+ | onDraggablePointsDoubleClick | `(point: any) => void` | ✗ | Callback function that fires when draggable points are double-clicked. |
83
+ | onPointClick | `(point: any) => void` | ✗ | Click handler for data points. |
84
+ | timingFrameCount | `number` | ✗ | Sets the number of frames for when the state controller's `averageLoopTime` method is called. |
85
+ | stateControllerInitialization | `object` | ✗ | Options for initializing the internal state controller. |
86
+ | syncPool | `SyncPool` | ✗ | For synchronizing with other grapher instances. |
87
+ | dragPositionYOffset | `number` | ✗ | Y-offset for drag positioning, used in multigrapher. |
88
+ | theme | `'day' \| 'night' \| 'export'` | ✗ | Sets the theme of grapher. You can also override any CSS property directly in a stylesheet. |
89
+ | title | `string` | ✗ | Sets the title text for the graph. |
90
+ | fullscreen | `boolean` | ✗ | If true, displays the graph in fullscreen mode. |
91
+ | bodyHeight | `number` | ✗ | Sets the height of the graph body (i.e., excluding range graph, series controls, etc.). |
92
+ | height | `number \| string` | ✗ | Sets the height of the entire graph. |
93
+ | width | `number \| string` | ✗ | Sets the width of the graph. |
94
+ | showAxes | `boolean` | ✗ | Whether to show the axes on the graph. |
95
+ | showRangeGraph | `boolean` | ✗ | Whether to show the smaller range graph below the main graph. |
96
+ | showRangeSelectors | `boolean` | ✗ | Whether to show the top bar with range selection (e.g., "last day" button) and other options. |
97
+ | showSeriesKey | `boolean` | ✗ | Whether to show the key of which series have which colors. |
98
+ | showTooltips | `boolean` | ✗ | Whether to display tooltips when hovering over data points. |
99
+ | showGrid | `boolean` | ✗ | Whether to show grid lines on the graph. |
100
+ | showAxisColors | `boolean` | ✗ | Whether to color-code axes based on the series they represent. |
101
+ | bigLabels | `boolean` | ✗ | If true, uses larger text for labels. |
102
+ | xTickUnit | `'year'` | ✗ | Specifies the unit for x-axis ticks. Currently supports 'year'. |
103
+ | formatXAxisLabel | `(value: any) => string` | ✗ | A custom function to format the x-axis labels. This function should take a single argument (the raw x-value) and return a string to display as the label. |
104
+ | xAxisIntegersOnly | `boolean` | ✗ | If true, only displays integer values on the x-axis. |
105
+ | clockStyle | `'12h' \| '24h'` | ✗ | Format for displaying time, either '12h' or '24h'. |
106
+ | timeZone | `string` | ✗ | Time zone for date/time display. Can be 'local', 'utc', or a full timezone string. |
107
+ | markRangeGraphDates | `boolean` | ✗ | Whether to mark significant dates on the range graph. |
108
+ | boundsSelectionEnabled | `boolean` | ✗ | Whether to enable the bounds selection feature. |
109
+ | customBoundsSelectors | [CustomBoundsSelector](#customboundsselector)`[]` | ✗ | Array of custom range selector objects with label, calculator, and optional datesOnly properties. |
110
+ | customBoundsSelectorsOnly | `boolean` | ✗ | If true, only displays custom bounds selectors. |
111
+ | sidebarEnabled | `boolean` | ✗ | Whether to enable the sidebar. |
112
+ | defaultBoundsCalculator | `string` | ✗ | String identifier for the default bounds calculator to use. |
113
+ | percentile | `number` | ✗ | Sets the percentile value for calculations. |
114
+ | defaultShowAnnotations | `boolean` | ✗ | Default visibility of annotations. |
115
+ | defaultShowOptions | `boolean` | ✗ | Default visibility of the options panel. |
116
+ | defaultShowIndividualPoints | `boolean` | ✗ | Default setting for showing individual data points. |
117
+ | defaultShowSidebar | `boolean` | ✗ | Default visibility of the sidebar. |
118
+ | defaultLineWidth | `number` | ✗ | Default width of the lines in the graph. |
119
+ | tooltipOptions | [TooltipOptions](#tooltipoptions) | ✗ | Configures tooltip appearance and behavior with various options. |
120
+ | annotations | [Annotation](#annotation)`[]` | ✗ | Array of annotation objects to display on the graph with position, content, and series targeting. |
121
+ | draggablePoints | [DraggablePoint](#draggablepoint)`[]` | ✗ | Array of interactive point objects with position, styling, and event handlers. |
122
+ | verticalLines | [VerticalLine](#verticalline)`[]` | ✗ | Array of vertical line objects to display on the graph with position, styling, and text options. |
123
123
 
124
124
  ### <a id="seriesdata"></a>Schema `SeriesData`
125
125
 
126
- | Prop | Type | Required |
127
- |------|------|----------|
128
- | **data** | `any[] \| Observable \| Function` | **✓** |
129
- | type | `'values' \| 'tuples' \| 'objects' \| 'tuple_observable' \| 'object_observable' \| 'infer'` | ✗ |
130
- | xKey | `string` | ✗ |
131
- | yKey | `string` | ✗ |
132
- | xUnixDates | `boolean` | ✗ |
133
- | color | `string \| number` | ✗ |
134
- | name | `string` | ✗ |
135
- | xLabel | `string` | ✗ |
136
- | yLabel | `string` | ✗ |
137
- | rendering | `'line' \| 'bar' \| 'area' \| 'shadow'` | ✗ |
138
- | ignoreDiscontinuities | `boolean` | ✗ |
139
- | dashed | `boolean` | ✗ |
140
- | dashPattern | `number[]` | ✗ |
141
- | width | `number` | ✗ |
142
- | axis | `string \| object` | ✗ |
143
- | rangeSelectorWidth | `number` | ✗ |
144
- | expandYWith | `number[]` | ✗ |
145
- | defaultAlwaysTooltipped | `boolean` | ✗ |
146
- | square | `boolean` | ✗ |
147
- | shiftXBy | `number` | ✗ |
148
- | graph | `number` | ✗ |
149
- | background | `object` | ✗ |
150
- | hideFromKey | `boolean` | ✗ |
151
- | showIndividualPoints | `boolean` | ✗ |
152
- | negativeColor | `string` | ✗ |
153
- | gradient | `string[] \| [number, string][]` | ✗ |
154
- | zeroLineWidth | `number` | ✗ |
155
- | zeroLineColor | `string` | ✗ |
156
- | zeroLineY | `number \| 'bottom'` | ✗ |
157
- | pointRadius | `number` | ✗ |
158
- | tooltipWidth | `number` | ✗ |
159
- | hasAreaBottom | `boolean` | ✗ |
160
- | shadowColor | `string` | ✗ |
161
- | rangeKey | `string` | ✗ |
162
- | cutoffTime | `number \| Date \| 'now'` | ✗ |
126
+ | Prop | Type | Required | Description |
127
+ |------|------|----------|-------------|
128
+ | **data** | `any[] \| Observable \| Function` | **✓** | The actual data points (see Data Formats section). |
129
+ | type | `'values' \| 'tuples' \| 'objects' \| 'tuple_observable' \| 'object_observable' \| 'infer'` | ✗ | Data type or 'infer' to automatically detect. |
130
+ | xKey | `string` | ✗ | Property name for x-values when using object data. |
131
+ | yKey | `string` | ✗ | Property name for y-values when using object data. |
132
+ | xUnixDates | `boolean` | ✗ | Whether x-values are Unix timestamps. |
133
+ | color | `string \| number` | ✗ | Series color (string or number). |
134
+ | name | `string` | ✗ | Series name for display in legend. |
135
+ | xLabel | `string` | ✗ | Label for x-axis. |
136
+ | yLabel | `string` | ✗ | Label for y-axis. |
137
+ | rendering | `'line' \| 'bar' \| 'area' \| 'shadow'` | ✗ | Visual representation (defaults to 'line'). The 'shadow' option creates an area chart with individual point-based trapezoid gradients extending downward. |
138
+ | ignoreDiscontinuities | `boolean` | ✗ | Whether to connect points across gaps. |
139
+ | dashed | `boolean` | ✗ | Whether to use dashed lines. |
140
+ | dashPattern | `number[]` | ✗ | Array defining dash pattern. |
141
+ | width | `number` | ✗ | Line width. |
142
+ | axis | `string \| object` | ✗ | Axis specification for the series. |
143
+ | rangeSelectorWidth | `number` | ✗ | Width of the range selector for this series. |
144
+ | expandYWith | `number[]` | ✗ | Values to include when calculating y-axis range. |
145
+ | defaultAlwaysTooltipped | `boolean` | ✗ | Whether to always show tooltip for this series. |
146
+ | square | `boolean` | ✗ | Whether to render the series with square points. |
147
+ | shiftXBy | `number` | ✗ | Value to shift x-coordinates by. |
148
+ | graph | `number` | ✗ | Affects which graph this series belongs to in multigrapher. |
149
+ | background | `object` | ✗ | Background configuration. |
150
+ | hideFromKey | `boolean` | ✗ | Whether to hide this series from the legend. |
151
+ | showIndividualPoints | `boolean` | ✗ | Whether to show individual data points. |
152
+ | negativeColor | `string` | ✗ | Color for negative values. |
153
+ | gradient | `string[] \| [number, string][]` | ✗ | Gradient configuration, only applies to area rendering. |
154
+ | zeroLineWidth | `number` | ✗ | Width of the zero line, only applies to bar and area rendering. |
155
+ | zeroLineColor | `string` | ✗ | Color of the zero line, only applies to bar and area rendering. |
156
+ | zeroLineY | `number \| 'bottom'` | ✗ | Y-coordinate of the zero line, only applies to bar and area rendering. Defaults to zero; may also be the string "bottom". |
157
+ | pointRadius | `number` | ✗ | Radius of points, only applies to area rendering. |
158
+ | tooltipWidth | `number` | ✗ | Expected width of the tooltip. Will make the tooltip switch sides when this width plus the tooltip left position is greater than the graph width. |
159
+ | hasAreaBottom | `boolean` | ✗ | Read the bottom of the area from data. By default, the bottom of an area will just be zero; this allows changing that via passing in `[[x1, bottom], [x1, top], [x2, bottom], [x2, top]]` to data. |
160
+ | shadowColor | `string` | ✗ | Color of the shadow. |
161
+ | rangeKey | `string` | ✗ | If provided, will draw range bars. The range value should be of shape `{min: number, max: number}`. Not compatible with webgl. |
162
+ | cutoffTime | `number \| Date \| 'now'` | ✗ | Creates visual cutoff effects in line, area, bar, and shadow renderings. Can be a timestamp, Date object, or 'now' for current time. |
163
163
 
164
164
  ### <a id="tooltipoptions"></a>Schema `TooltipOptions`
165
165
 
166
- | Prop | Type | Required |
167
- |------|------|----------|
168
- | includeSeriesLabel | `boolean` | ✗ |
169
- | includeXLabel | `boolean` | ✗ |
170
- | includeYLabel | `boolean` | ✗ |
171
- | includeXValue | `boolean` | ✗ |
172
- | includeYValue | `boolean` | ✗ |
173
- | floating | `boolean` | ✗ |
174
- | alwaysFixedPosition | `boolean` | ✗ |
175
- | floatPosition | `'top' \| 'bottom'` | ✗ |
176
- | floatDelta | `number` | ✗ |
177
- | savingDisabled | `boolean` | ✗ |
178
- | customTooltip | `React.ComponentType<any>` | ✗ |
179
- | combineTooltips | `boolean \| number` | ✗ |
166
+ | Prop | Type | Required | Description |
167
+ |------|------|----------|-------------|
168
+ | includeSeriesLabel | `boolean` | ✗ | Whether to show series name in tooltip. |
169
+ | includeXLabel | `boolean` | ✗ | Whether to show x-axis label in tooltip. |
170
+ | includeYLabel | `boolean` | ✗ | Whether to show y-axis label in tooltip. |
171
+ | includeXValue | `boolean` | ✗ | Whether to show x-axis value in tooltip. |
172
+ | includeYValue | `boolean` | ✗ | Whether to show y-axis value in tooltip. |
173
+ | floating | `boolean` | ✗ | Whether tooltip floats or is fixed position. |
174
+ | alwaysFixedPosition | `boolean` | ✗ | Forces tooltip to always use fixed position. |
175
+ | floatPosition | `'top' \| 'bottom'` | ✗ | Placement of floating tooltip. |
176
+ | floatDelta | `number` | ✗ | Pixel offset for floating tooltip positioning. |
177
+ | savingDisabled | `boolean` | ✗ | Prevents tooltip settings from being saved. |
178
+ | customTooltip | `React.ComponentType<any>` | ✗ | A react component to use as a custom tooltip. If used in conjunction with `combineTooltips`, see combined tooltips examples. |
179
+ | combineTooltips | `boolean \| number` | ✗ | If true, combines multiple tooltips into one when multiple series are shown. Can alternatively be set to a threshold in pixels for how close values need to be in order to be combined. |
180
180
 
181
181
  ### <a id="customboundsselector"></a>Schema `CustomBoundsSelector`
182
182
 
183
- | Prop | Type | Required |
184
- |------|------|----------|
185
- | **label** | `string` | **✓** |
186
- | **calculator** | `(globalBounds?: any) => any` | **✓** |
187
- | datesOnly | `boolean` | ✗ |
183
+ | Prop | Type | Required | Description |
184
+ |------|------|----------|-------------|
185
+ | **label** | `string` | **✓** | Display text for the selector. |
186
+ | **calculator** | `(globalBounds?: any) => any` | **✓** | Function that determines the bounds. |
187
+ | datesOnly | `boolean` | ✗ | If true, only works with date values. |
188
188
 
189
189
  ### <a id="annotation"></a>Schema `Annotation`
190
190
 
191
- | Prop | Type | Required |
192
- |------|------|----------|
193
- | x | `string \| number \| Date` | ✗ |
194
- | startX | `string \| number \| Date` | ✗ |
195
- | endX | `string \| number \| Date` | ✗ |
196
- | series | `string[]` | ✗ |
197
- | content | `string` | ✗ |
198
- | lineOnly | `boolean` | ✗ |
191
+ | Prop | Type | Required | Description |
192
+ |------|------|----------|-------------|
193
+ | x | `string \| number \| Date` | ✗ | Position on x-axis where annotation should appear. |
194
+ | startX | `string \| number \| Date` | ✗ | Start position for range annotations. |
195
+ | endX | `string \| number \| Date` | ✗ | End position for range annotations. |
196
+ | series | `string[]` | ✗ | Optional array of series names the annotation applies to. |
197
+ | content | `string` | ✗ | Text content of the annotation. |
198
+ | lineOnly | `boolean` | ✗ | Shows only the line portion of annotations without background/content areas. |
199
199
 
200
200
  ### <a id="draggablepoint"></a>Schema `DraggablePoint`
201
201
 
202
- | Prop | Type | Required |
203
- |------|------|----------|
204
- | **x** | `number` | **✓** |
205
- | **y** | `number` | **✓** |
206
- | radius | `number` | ✗ |
207
- | fillColor | `string` | ✗ |
208
- | strokeColor | `string` | ✗ |
209
- | strokeWidth | `number` | ✗ |
210
- | onClick | `(point: `[DraggablePoint](#draggablepoint)`) => void` | ✗ |
211
- | onDoubleClick | `(point: `[DraggablePoint](#draggablepoint)`) => void` | ✗ |
202
+ | Prop | Type | Required | Description |
203
+ |------|------|----------|-------------|
204
+ | **x** | `number` | **✓** | X-coordinate position. |
205
+ | **y** | `number` | **✓** | Y-coordinate position. |
206
+ | radius | `number` | ✗ | Optional size of the point. |
207
+ | fillColor | `string` | ✗ | Optional interior color. |
208
+ | strokeColor | `string` | ✗ | Optional outline color. |
209
+ | strokeWidth | `number` | ✗ | Optional outline width. |
210
+ | onClick | `(point: `[DraggablePoint](#draggablepoint)`) => void` | ✗ | Optional click handler function. |
211
+ | onDoubleClick | `(point: `[DraggablePoint](#draggablepoint)`) => void` | ✗ | Optional double-click handler function. |
212
212
 
213
213
  ### <a id="verticalline"></a>Schema `VerticalLine`
214
214
 
215
- | Prop | Type | Required |
216
- |------|------|----------|
217
- | **x** | `number` | **✓** |
218
- | color | `string` | ✗ |
219
- | lineTop | `number` | ✗ |
220
- | width | `number` | ✗ |
221
- | markTop | `boolean` | ✗ |
222
- | style | `object` | ✗ |
223
- | markerStyle | `object` | ✗ |
224
- | text | `string` | ✗ |
225
- | textTop | `number` | ✗ |
226
- | textStyle | `object` | ✗ |
227
- | minPixelX | `number` | ✗ |
228
- | maxPixelX | `number` | ✗ |
229
- | onRangeGraph | `boolean \| object` | ✗ |
230
- | onRangeGraphOnly | `boolean` | ✗ |
215
+ | Prop | Type | Required | Description |
216
+ |------|------|----------|-------------|
217
+ | **x** | `number` | **✓** | X-coordinate position where the line should appear. |
218
+ | color | `string` | ✗ | Optional line color. |
219
+ | lineTop | `number` | ✗ | Optional value to specify the top position of the line. |
220
+ | width | `number` | ✗ | Optional line width. |
221
+ | markTop | `boolean` | ✗ | Whether to add a marker at the top of the line. |
222
+ | style | `object` | ✗ | Optional styling object for the line. |
223
+ | markerStyle | `object` | ✗ | Optional styling object for the marker. |
224
+ | text | `string` | ✗ | Optional text to display alongside the line. |
225
+ | textTop | `number` | ✗ | Optional value to specify the vertical position of the text. |
226
+ | textStyle | `object` | ✗ | Optional styling object for the text. |
227
+ | minPixelX | `number` | ✗ | If the x position of the line in pixels is less than this value, the line will be hidden. |
228
+ | maxPixelX | `number` | ✗ | If the x position of the line in pixels is greater than this value, the line will be hidden. |
229
+ | onRangeGraph | `boolean \| object` | ✗ | If true, will show the line on the range graph as well. This may also be an object with any of the above options to adjust the styling. |
230
+ | onRangeGraphOnly | `boolean` | ✗ | If true, the vertical line will only appear on the range graph and not the primary graph. |
231
231
 
232
232
  ## Developing
233
233
  Other than an `npm install`, you'll need to install rust and [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/).
@@ -335,6 +335,8 @@ export default class RangeGraph extends React.PureComponent {
335
335
  width={pixelMaxX - pixelMinX}
336
336
  height={totalHeight}
337
337
  className="target-selection-outline"
338
+ onMouseDown={this.startScroll}
339
+ onTouchStart={this.startScroll}
338
340
  />
339
341
  </g>
340
342
 
package/src/grapher.scss CHANGED
@@ -675,7 +675,8 @@
675
675
 
676
676
  .target-selection-outline {
677
677
  stroke: $range-graph-selection-outline-color;
678
- fill: none;
678
+ cursor: ew-resize;
679
+ fill: transparent;
679
680
  }
680
681
 
681
682
  .selection-bar-track {
@@ -109,7 +109,23 @@ export default class GraphBodyRenderer extends Eventable {
109
109
  }
110
110
 
111
111
  let cutoffIndex = -1;
112
- // cutoff time calculations for visible bounds-based approach
112
+ let cutoffTime = null;
113
+ let cutoffData = singleSeries.data;
114
+
115
+ const isObjectFormat = singleSeries.data && singleSeries.data.length > 0 &&
116
+ typeof singleSeries.data[0] === 'object' &&
117
+ !Array.isArray(singleSeries.data[0]);
118
+
119
+ if (isObjectFormat && singleSeries.cutoffTime) {
120
+ cutoffData = singleSeries.data.map(point => {
121
+ const xValue = point[singleSeries.xKey || 'x'];
122
+ const yValue = point[singleSeries.yKey || 'y'];
123
+
124
+ const convertedX = typeof xValue === 'string' ? new Date(xValue) : xValue;
125
+
126
+ return [convertedX, yValue];
127
+ });
128
+ }
113
129
 
114
130
  if (singleSeries.cutoffTime && singleSeries.data && singleSeries.data.length > 0) {
115
131
  let cutoffDate;
@@ -121,40 +137,48 @@ export default class GraphBodyRenderer extends Eventable {
121
137
  cutoffDate = singleSeries.cutoffTime;
122
138
  }
123
139
 
124
- // getting the ghost point
125
- const cutoffTime = cutoffDate instanceof Date ? cutoffDate.getTime() : cutoffDate;
140
+ cutoffTime = cutoffDate instanceof Date ? cutoffDate.getTime() : cutoffDate;
126
141
 
127
- for (let i = 0; i < singleSeries.data.length - 1; i++) {
128
- const currentPoint = singleSeries.data[i];
129
- const nextPoint = singleSeries.data[i + 1];
142
+ for (let i = 0; i < cutoffData.length - 1; i++) {
143
+ const currentPoint = cutoffData[i];
144
+ const nextPoint = cutoffData[i + 1];
130
145
 
131
- const currentTime = Array.isArray(currentPoint) ?
132
- (currentPoint[0] instanceof Date ? currentPoint[0].getTime() : currentPoint[0]) : i;
133
- const nextTime = Array.isArray(nextPoint) ?
134
- (nextPoint[0] instanceof Date ? nextPoint[0].getTime() : nextPoint[0]) : (i + 1);
146
+ const currentTime = currentPoint[0] instanceof Date ? currentPoint[0].getTime() : currentPoint[0];
147
+ const nextTime = nextPoint[0] instanceof Date ? nextPoint[0].getTime() : nextPoint[0];
135
148
 
136
149
  if (currentTime <= cutoffTime && cutoffTime <= nextTime) {
137
- // interpolate exact position between these two points
138
150
  const timeRatio = (cutoffTime - currentTime) / (nextTime - currentTime);
139
151
  cutoffIndex = i + timeRatio;
140
152
  break;
141
153
  } else if (currentTime > cutoffTime) {
142
- // cutoff is before the first data point
143
154
  cutoffIndex = i;
144
155
  break;
145
156
  }
146
157
  }
147
158
 
148
- // cutoff is after all data points
149
159
  if (cutoffIndex === -1) {
150
- cutoffIndex = singleSeries.data.length - 1;
160
+ cutoffIndex = cutoffData.length - 1;
151
161
  }
152
-
153
-
154
- // Note: cutoffIndex is used for cutoff calculations but we no longer split data
155
162
  }
156
163
 
157
164
  const getIndividualPoints = (useDataSpace) => {
165
+ if (!useDataSpace && inRenderSpace && inRenderSpace.yValues) {
166
+ const individualPoints = [];
167
+ const { yValues, nullMask } = inRenderSpace;
168
+
169
+ for (let pixelX = 0; pixelX < yValues.length; pixelX++) {
170
+ if (nullMask[pixelX] === 0) {
171
+ individualPoints.push([pixelX, yValues[pixelX]]);
172
+ }
173
+ }
174
+
175
+ if (individualPoints.length === 0) {
176
+ return getIndividualPoints(true);
177
+ }
178
+
179
+ return individualPoints;
180
+ }
181
+
158
182
  if (!bounds) {
159
183
  bounds = singleSeries.axis.currentBounds;
160
184
  }
@@ -165,17 +189,33 @@ export default class GraphBodyRenderer extends Eventable {
165
189
  data = singleSeries.inDataSpace;
166
190
  }
167
191
 
168
- for (let [x, y] of data) {
169
- if (y === null) {
192
+ for (let i = 0; i < data.length; i++) {
193
+ let x, y;
194
+
195
+ if (Array.isArray(data[i])) {
196
+ [x, y] = data[i];
197
+ } else if (typeof data[i] === 'object' && data[i] !== null) {
198
+ x = data[i][singleSeries.xKey];
199
+ y = data[i][singleSeries.yKey];
200
+ } else {
201
+ continue;
202
+ }
203
+
204
+ if (y === null || y === undefined) {
170
205
  continue;
171
206
  }
172
207
 
208
+ let xValue = x instanceof Date ? x.getTime() : x;
209
+ let boundsMinX = bounds.minX instanceof Date ? bounds.minX.getTime() : bounds.minX;
210
+ let boundsMaxX = bounds.maxX instanceof Date ? bounds.maxX.getTime() : bounds.maxX;
211
+
173
212
  individualPoints.push([
174
- (x - bounds.minX) / (bounds.maxX - bounds.minX) * this._sizing.renderWidth,
213
+ (xValue - boundsMinX) / (boundsMaxX - boundsMinX) * this._sizing.renderWidth,
175
214
  (1.0 - (y - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight
176
215
  ]);
177
216
  }
178
217
 
218
+
179
219
  return individualPoints;
180
220
  };
181
221
 
@@ -224,7 +264,6 @@ export default class GraphBodyRenderer extends Eventable {
224
264
  return;
225
265
  }
226
266
 
227
- //we still need a canvas context for cpu stuff
228
267
  if (!this._context2d) {
229
268
  this._context2d = this._canvas.getContext('2d', { willReadFrequently: false });
230
269
  }
@@ -235,7 +274,6 @@ export default class GraphBodyRenderer extends Eventable {
235
274
  }
236
275
 
237
276
  if (this._webgl) {
238
- // make sure we don't have any webgl stuff in the way before we get back to CPU rendering
239
277
  this._context.flush();
240
278
  }
241
279
 
@@ -275,7 +313,7 @@ export default class GraphBodyRenderer extends Eventable {
275
313
  if (singleSeries.cutoffTime) {
276
314
  barParams.cutoffIndex = cutoffIndex;
277
315
  barParams.cutoffOpacity = 0.35;
278
- barParams.originalData = singleSeries.data;
316
+ barParams.originalData = cutoffData;
279
317
  barParams.renderCutoffGradient = cutoffIndex >= 0;
280
318
 
281
319
  const selection = this === this._stateController.rangeGraphRenderer
@@ -301,11 +339,10 @@ export default class GraphBodyRenderer extends Eventable {
301
339
  inRenderSpaceAreaBottom
302
340
  };
303
341
 
304
- // add cutoff information for gradient area rendering
305
342
  if (singleSeries.cutoffTime) {
306
343
  areaParams.cutoffIndex = cutoffIndex;
307
344
  areaParams.cutoffOpacity = 0.35;
308
- areaParams.originalData = singleSeries.data;
345
+ areaParams.originalData = cutoffData;
309
346
  areaParams.renderCutoffGradient = cutoffIndex >= 0;
310
347
  areaParams.isPreview = this === this._stateController.rangeGraphRenderer;
311
348
 
@@ -344,7 +381,9 @@ export default class GraphBodyRenderer extends Eventable {
344
381
 
345
382
  let zero = singleSeries.zeroLineY === 'bottom' ?
346
383
  this._sizing.renderHeight :
347
- (1.0 - ((singleSeries.zeroLineY || 0) - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight;
384
+ singleSeries.zeroLineY !== undefined ?
385
+ (1.0 - ((singleSeries.zeroLineY) - bounds.minY) / (bounds.maxY - bounds.minY)) * this._sizing.renderHeight :
386
+ this._sizing.renderHeight;
348
387
 
349
388
  const boundsChanged = !this._lastBounds ||
350
389
  bounds.minY !== this._lastBounds.minY ||
@@ -372,21 +411,20 @@ export default class GraphBodyRenderer extends Eventable {
372
411
  inRenderSpaceAreaBottom
373
412
  };
374
413
 
375
- // add cutoff information for gradient shadow rendering
376
414
  if (singleSeries.cutoffTime) {
377
415
  shadowParams.cutoffIndex = cutoffIndex;
378
416
  shadowParams.cutoffOpacity = 0.35;
379
- shadowParams.originalData = singleSeries.data;
417
+ shadowParams.originalData = cutoffData;
380
418
  shadowParams.renderCutoffGradient = cutoffIndex >= 0;
381
419
  shadowParams.isPreview = this === this._stateController.rangeGraphRenderer;
382
420
 
383
421
  const selection = this === this._stateController.rangeGraphRenderer
384
422
  ? this._stateController._bounds
385
423
  : (this._stateController._selection || this._stateController._bounds);
386
- shadowParams.selectionBounds = selection;
424
+ shadowParams.selectionBounds = selection || bounds;
387
425
  }
388
426
 
389
- this._shadowProgram.draw(getIndividualPoints(true), shadowParams);
427
+ this._shadowProgram.draw(getIndividualPoints(false), shadowParams);
390
428
 
391
429
  if (this._webgl) {
392
430
  const gl = this._context;
@@ -398,7 +436,6 @@ export default class GraphBodyRenderer extends Eventable {
398
436
 
399
437
  if (singleSeries.zeroLineWidth && singleSeries.zeroLineWidth > 0) {
400
438
  if (this._context2d) {
401
- // in non-webgl mode, use the existing 2d context
402
439
  this._context2d.save();
403
440
  this._context2d.strokeStyle = singleSeries.zeroLineColor || getColor(singleSeries.color, singleSeries.index, singleSeries.multigrapherSeriesIndex);
404
441
  this._context2d.lineWidth = singleSeries.zeroLineWidth;
@@ -410,7 +447,6 @@ export default class GraphBodyRenderer extends Eventable {
410
447
  this._context2d.stroke();
411
448
  this._context2d.restore();
412
449
  } else {
413
- // in webgl mode, we instead create an overlay 2d canvas for the zero line
414
450
  if (!this._zeroLineCanvas) {
415
451
  this._zeroLineCanvas = document.createElement('canvas');
416
452
  this._zeroLineCanvas.style.position = 'absolute';
@@ -460,20 +496,18 @@ export default class GraphBodyRenderer extends Eventable {
460
496
  return;
461
497
  }
462
498
 
463
- // Add cutoff information to drawParams for gradient line rendering
464
499
  if (singleSeries.cutoffTime) {
465
500
  drawParams.cutoffIndex = cutoffIndex;
466
501
  drawParams.cutoffOpacity = 0.35;
467
- drawParams.originalData = singleSeries.data;
468
- drawParams.renderCutoffGradient = cutoffIndex >= 0; // Only render cutoff if valid cutoff
502
+ drawParams.originalData = cutoffData;
503
+ drawParams.renderCutoffGradient = cutoffIndex >= 0;
469
504
  drawParams.currentBounds = bounds;
470
- drawParams.isPreview = this === this._stateController.rangeGraphRenderer; // Flag for preview rendering
505
+ drawParams.isPreview = this === this._stateController.rangeGraphRenderer;
471
506
 
472
- // Always set selectionBounds with fallback
473
507
  const selection = this === this._stateController.rangeGraphRenderer
474
508
  ? this._stateController._bounds
475
509
  : (this._stateController._selection || this._stateController._bounds);
476
- drawParams.selectionBounds = selection;
510
+ drawParams.selectionBounds = selection || bounds;
477
511
  }
478
512
 
479
513
  if (this._webgl) {
@@ -108,7 +108,7 @@ export default class LineProgram {
108
108
  gl.uniform4f(gl.getUniformLocation(this._program, 'shadowColor'), ...colorToVector(shadowColor));
109
109
 
110
110
  const cutoffX = parameters.cutoffX !== undefined ? parameters.cutoffX : -1.0; // Use parameter or disable
111
- const cutoffOpacity = parameters.cutoffOpacity !== undefined ? parameters.cutoffOpacity : 1.0;
111
+ const cutoffOpacity = parameters.cutoffOpacity !== undefined ? parameters.cutoffOpacity : 0.35;
112
112
 
113
113
  gl.uniform1f(gl.getUniformLocation(this._program, 'cutoffX'), cutoffX);
114
114
  gl.uniform1f(gl.getUniformLocation(this._program, 'cutoffOpacity'), cutoffOpacity);
@@ -260,7 +260,7 @@ export default class LineProgram {
260
260
  if (timeRatio < 0) {
261
261
  this.draw(dataInRenderSpace, { ...parameters, renderCutoffGradient: false });
262
262
  } else if (timeRatio > 1) {
263
- const reducedOpacityColor = this.applyReducedOpacity(parameters.color, cutoffOpacity);
263
+ const reducedOpacityColor = applyReducedOpacity(parameters.color, cutoffOpacity);
264
264
  this.draw(dataInRenderSpace, {
265
265
  ...parameters,
266
266
  color: reducedOpacityColor,
@@ -290,7 +290,7 @@ export default class LineProgram {
290
290
  if (cutoffTime < visibleMinTime) {
291
291
  this.draw(dataInRenderSpace, { ...parameters, renderCutoffGradient: false });
292
292
  } else if (cutoffTime > visibleMaxTime) {
293
- const reducedOpacityColor = this.applyReducedOpacity(parameters.color, cutoffOpacity);
293
+ const reducedOpacityColor = applyReducedOpacity(parameters.color, cutoffOpacity);
294
294
  this.draw(dataInRenderSpace, {
295
295
  ...parameters,
296
296
  color: reducedOpacityColor,