@@ -215,36 +215,49 @@ export function createAggregationIndices(
215215 maxRange : number ,
216216 {
217217 positive,
218+ split = false ,
218219 xNeedsValueOf = true ,
219220 yNeedsValueOf = true ,
220221 } : {
221222 positive ?: boolean ;
223+ split ?: boolean ;
222224 xNeedsValueOf ?: boolean ;
223225 yNeedsValueOf ?: boolean ;
224226 } = { }
225227) : {
226228 indexData : Uint32Array ;
227229 valueData : Float64Array ;
230+ negativeIndexData ?: Uint32Array ;
231+ negativeValueData ?: Float64Array ;
228232} {
229233 // NOTE: This function has been aggressively optimized for performance over readability, please
230234 // take care not to undo optimizations when making changes here.
231235 const nan = Number . NaN ; // Local constant for faster access
232236 const indexData = new Uint32Array ( maxRange * AGGREGATION_SPAN ) ;
233237 const valueData = new Float64Array ( maxRange * AGGREGATION_SPAN ) ;
238+
239+ // For split mode, we need a second set of arrays
240+ const negativeIndexData = split ? new Uint32Array ( maxRange * AGGREGATION_SPAN ) : undefined ;
241+ const negativeValueData = split ? new Float64Array ( maxRange * AGGREGATION_SPAN ) : undefined ;
242+
234243 const continuous = Number . isFinite ( d0 ) && Number . isFinite ( d1 ) ;
235244 const domainCount = xValues . length ;
236245
237246 // Pre-fill with sentinel values for continuous case (potentially sparse data).
238247 if ( continuous ) {
239248 valueData . fill ( nan ) ;
240249 indexData . fill ( AGGREGATION_INDEX_UNSET ) ;
250+ if ( split ) {
251+ negativeValueData ! . fill ( nan ) ;
252+ negativeIndexData ! . fill ( AGGREGATION_INDEX_UNSET ) ;
253+ }
241254 }
242255
243256 // Pre-compute domain range for continuous case
244257 const domainRange = continuous ? d1 - d0 : 0 ;
245258 const invDomainCount = 1 / domainCount ;
246259
247- // Cache for current bucket to reduce array access overhead
260+ // Cache for current bucket to reduce array access overhead (Positive / Default)
248261 let lastAggIndex = - 1 ;
249262 let cachedXMinIndex = - 1 ;
250263 let cachedXMinValue = nan ;
@@ -255,6 +268,17 @@ export function createAggregationIndices(
255268 let cachedYMaxIndex = - 1 ;
256269 let cachedYMaxValue = nan ;
257270
271+ // Cache for current bucket (Negative, only used if split=true)
272+ let negLastAggIndex = - 1 ;
273+ let negCachedXMinIndex = - 1 ;
274+ let negCachedXMinValue = nan ;
275+ let negCachedXMaxIndex = - 1 ;
276+ let negCachedXMaxValue = nan ;
277+ let negCachedYMinIndex = - 1 ;
278+ let negCachedYMinValue = nan ;
279+ let negCachedYMaxIndex = - 1 ;
280+ let negCachedYMaxValue = nan ;
281+
258282 const xValuesLength = xValues . length ;
259283 const yArraysSame = yMaxValues === yMinValues ;
260284 for ( let datumIndex = 0 ; datumIndex < xValuesLength ; datumIndex ++ ) {
@@ -276,8 +300,18 @@ export function createAggregationIndices(
276300 yMin = yMinValue ?? nan ;
277301 }
278302
279- // Early continue for positive check
280- if ( positive != null && yMax >= 0 !== positive ) continue ;
303+ // Determine which bucket set to use
304+ let isPositiveDatum = true ;
305+ if ( split ) {
306+ // In split mode, we decide based on sign
307+ // Positive bucket gets values >= 0
308+ // Negative bucket gets values < 0
309+ // (Using yMax as the discriminator, similar to original logic)
310+ isPositiveDatum = yMax >= 0 ;
311+ } else if ( positive != null && yMax >= 0 !== positive ) {
312+ // In non-split mode, filter by 'positive' arg if present
313+ continue ;
314+ }
281315
282316 // Optimize xRatio calculation with pre-computed values
283317 let xRatio : number ;
@@ -297,66 +331,126 @@ export function createAggregationIndices(
297331 const bucketIndex = Math . floor ( xRatio * maxRange ) ;
298332 const aggIndex = ( bucketIndex < maxRange ? bucketIndex : maxRange - 1 ) << 2 ;
299333
300- // Reset cache when switching buckets
301- if ( aggIndex !== lastAggIndex ) {
302- if ( lastAggIndex !== - 1 ) {
303- // Inline flushCache - group writes by array for cache locality
304- indexData [ lastAggIndex ] = cachedXMinIndex ;
305- indexData [ lastAggIndex + 1 ] = cachedXMaxIndex ;
306- indexData [ lastAggIndex + 2 ] = cachedYMinIndex ;
307- indexData [ lastAggIndex + 3 ] = cachedYMaxIndex ;
308- valueData [ lastAggIndex ] = cachedXMinValue ;
309- valueData [ lastAggIndex + 1 ] = cachedXMaxValue ;
310- valueData [ lastAggIndex + 2 ] = cachedYMinValue ;
311- valueData [ lastAggIndex + 3 ] = cachedYMaxValue ;
334+ if ( isPositiveDatum ) {
335+ // Reset cache when switching buckets
336+ if ( aggIndex !== lastAggIndex ) {
337+ if ( lastAggIndex !== - 1 ) {
338+ // Inline flushCache - group writes by array for cache locality
339+ indexData [ lastAggIndex ] = cachedXMinIndex ;
340+ indexData [ lastAggIndex + 1 ] = cachedXMaxIndex ;
341+ indexData [ lastAggIndex + 2 ] = cachedYMinIndex ;
342+ indexData [ lastAggIndex + 3 ] = cachedYMaxIndex ;
343+ valueData [ lastAggIndex ] = cachedXMinValue ;
344+ valueData [ lastAggIndex + 1 ] = cachedXMaxValue ;
345+ valueData [ lastAggIndex + 2 ] = cachedYMinValue ;
346+ valueData [ lastAggIndex + 3 ] = cachedYMaxValue ;
347+ }
348+ lastAggIndex = aggIndex ;
349+ // Inline resetCache
350+ cachedXMinIndex = - 1 ;
351+ cachedXMinValue = nan ;
352+ cachedXMaxIndex = - 1 ;
353+ cachedXMaxValue = nan ;
354+ cachedYMinIndex = - 1 ;
355+ cachedYMinValue = nan ;
356+ cachedYMaxIndex = - 1 ;
357+ cachedYMaxValue = nan ;
312358 }
313- lastAggIndex = aggIndex ;
314- // Inline resetCache
315- cachedXMinIndex = - 1 ;
316- cachedXMinValue = nan ;
317- cachedXMaxIndex = - 1 ;
318- cachedXMaxValue = nan ;
319- cachedYMinIndex = - 1 ;
320- cachedYMinValue = nan ;
321- cachedYMaxIndex = - 1 ;
322- cachedYMaxValue = nan ;
323- }
324359
325- // Pre-compute NaN checks (NaN is the only value where x !== x)
326- const yMinValid = yMin === yMin ;
327- const yMaxValid = yMax === yMax ;
328-
329- // Fast path: bucket is unset (first value in bucket)
330- if ( cachedXMinIndex === - 1 ) {
331- cachedXMinIndex = datumIndex ;
332- cachedXMinValue = xRatio ;
333- cachedXMaxIndex = datumIndex ;
334- cachedXMaxValue = xRatio ;
335- if ( yMinValid ) {
336- cachedYMinIndex = datumIndex ;
337- cachedYMinValue = yMin ;
338- }
339- if ( yMaxValid ) {
340- cachedYMaxIndex = datumIndex ;
341- cachedYMaxValue = yMax ;
342- }
343- } else {
344- // Slow path: bucket has values, need comparisons
345- if ( xRatio < cachedXMinValue ) {
360+ // Pre-compute NaN checks (NaN is the only value where x !== x)
361+ const yMinValid = yMin === yMin ;
362+ const yMaxValid = yMax === yMax ;
363+
364+ // Fast path: bucket is unset (first value in bucket)
365+ if ( cachedXMinIndex === - 1 ) {
346366 cachedXMinIndex = datumIndex ;
347367 cachedXMinValue = xRatio ;
348- }
349- if ( xRatio > cachedXMaxValue ) {
350368 cachedXMaxIndex = datumIndex ;
351369 cachedXMaxValue = xRatio ;
370+ if ( yMinValid ) {
371+ cachedYMinIndex = datumIndex ;
372+ cachedYMinValue = yMin ;
373+ }
374+ if ( yMaxValid ) {
375+ cachedYMaxIndex = datumIndex ;
376+ cachedYMaxValue = yMax ;
377+ }
378+ } else {
379+ // Slow path: bucket has values, need comparisons
380+ if ( xRatio < cachedXMinValue ) {
381+ cachedXMinIndex = datumIndex ;
382+ cachedXMinValue = xRatio ;
383+ }
384+ if ( xRatio > cachedXMaxValue ) {
385+ cachedXMaxIndex = datumIndex ;
386+ cachedXMaxValue = xRatio ;
387+ }
388+ if ( yMinValid && yMin < cachedYMinValue ) {
389+ cachedYMinIndex = datumIndex ;
390+ cachedYMinValue = yMin ;
391+ }
392+ if ( yMaxValid && yMax > cachedYMaxValue ) {
393+ cachedYMaxIndex = datumIndex ;
394+ cachedYMaxValue = yMax ;
395+ }
352396 }
353- if ( yMinValid && yMin < cachedYMinValue ) {
354- cachedYMinIndex = datumIndex ;
355- cachedYMinValue = yMin ;
397+ } else {
398+ // Negative Datum (Split mode only)
399+ if ( aggIndex !== negLastAggIndex ) {
400+ if ( negLastAggIndex !== - 1 ) {
401+ negativeIndexData ! [ negLastAggIndex ] = negCachedXMinIndex ;
402+ negativeIndexData ! [ negLastAggIndex + 1 ] = negCachedXMaxIndex ;
403+ negativeIndexData ! [ negLastAggIndex + 2 ] = negCachedYMinIndex ;
404+ negativeIndexData ! [ negLastAggIndex + 3 ] = negCachedYMaxIndex ;
405+ negativeValueData ! [ negLastAggIndex ] = negCachedXMinValue ;
406+ negativeValueData ! [ negLastAggIndex + 1 ] = negCachedXMaxValue ;
407+ negativeValueData ! [ negLastAggIndex + 2 ] = negCachedYMinValue ;
408+ negativeValueData ! [ negLastAggIndex + 3 ] = negCachedYMaxValue ;
409+ }
410+ negLastAggIndex = aggIndex ;
411+ negCachedXMinIndex = - 1 ;
412+ negCachedXMinValue = nan ;
413+ negCachedXMaxIndex = - 1 ;
414+ negCachedXMaxValue = nan ;
415+ negCachedYMinIndex = - 1 ;
416+ negCachedYMinValue = nan ;
417+ negCachedYMaxIndex = - 1 ;
418+ negCachedYMaxValue = nan ;
356419 }
357- if ( yMaxValid && yMax > cachedYMaxValue ) {
358- cachedYMaxIndex = datumIndex ;
359- cachedYMaxValue = yMax ;
420+
421+ const yMinValid = yMin === yMin ;
422+ const yMaxValid = yMax === yMax ;
423+
424+ if ( negCachedXMinIndex === - 1 ) {
425+ negCachedXMinIndex = datumIndex ;
426+ negCachedXMinValue = xRatio ;
427+ negCachedXMaxIndex = datumIndex ;
428+ negCachedXMaxValue = xRatio ;
429+ if ( yMinValid ) {
430+ negCachedYMinIndex = datumIndex ;
431+ negCachedYMinValue = yMin ;
432+ }
433+ if ( yMaxValid ) {
434+ negCachedYMaxIndex = datumIndex ;
435+ negCachedYMaxValue = yMax ;
436+ }
437+ } else {
438+ if ( xRatio < negCachedXMinValue ) {
439+ negCachedXMinIndex = datumIndex ;
440+ negCachedXMinValue = xRatio ;
441+ }
442+ if ( xRatio > negCachedXMaxValue ) {
443+ negCachedXMaxIndex = datumIndex ;
444+ negCachedXMaxValue = xRatio ;
445+ }
446+ if ( yMinValid && yMin < negCachedYMinValue ) {
447+ negCachedYMinIndex = datumIndex ;
448+ negCachedYMinValue = yMin ;
449+ }
450+ if ( yMaxValid && yMax > negCachedYMaxValue ) {
451+ negCachedYMaxIndex = datumIndex ;
452+ negCachedYMaxValue = yMax ;
453+ }
360454 }
361455 }
362456 }
@@ -373,7 +467,18 @@ export function createAggregationIndices(
373467 valueData [ lastAggIndex + 3 ] = cachedYMaxValue ;
374468 }
375469
376- return { indexData, valueData } ;
470+ if ( split && negLastAggIndex !== - 1 ) {
471+ negativeIndexData ! [ negLastAggIndex ] = negCachedXMinIndex ;
472+ negativeIndexData ! [ negLastAggIndex + 1 ] = negCachedXMaxIndex ;
473+ negativeIndexData ! [ negLastAggIndex + 2 ] = negCachedYMinIndex ;
474+ negativeIndexData ! [ negLastAggIndex + 3 ] = negCachedYMaxIndex ;
475+ negativeValueData ! [ negLastAggIndex ] = negCachedXMinValue ;
476+ negativeValueData ! [ negLastAggIndex + 1 ] = negCachedXMaxValue ;
477+ negativeValueData ! [ negLastAggIndex + 2 ] = negCachedYMinValue ;
478+ negativeValueData ! [ negLastAggIndex + 3 ] = negCachedYMaxValue ;
479+ }
480+
481+ return { indexData, valueData, negativeIndexData, negativeValueData } ;
377482}
378483
379484export function compactAggregationIndices (
0 commit comments