WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 43e5fba

Browse files
authored
Merge pull request #5653 from ag-grid/ag-16239/bar-series-performance-improvements-pt3d
AG-16239 - BarSeries performance improvements Pt. 3d
2 parents 57c9e45 + 85c037d commit 43e5fba

File tree

14 files changed

+371
-128
lines changed

14 files changed

+371
-128
lines changed

packages/ag-charts-community/src/chart/caption.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ export class Caption extends BaseProperties implements CaptionLike {
8484
private truncated = false;
8585
private proxyText?: BoundedTextWidget;
8686
private proxyTextListeners?: Array<() => void>;
87+
private lastProxyTextContent?: string;
88+
private lastProxyBBox?: { x: number; y: number; width: number; height: number };
8789

8890
registerInteraction(moduleCtx: ModuleContext, where: 'beforebegin' | 'afterend') {
8991
return moduleCtx.eventsHub.on('layout:complete', () => this.updateA11yText(moduleCtx, where));
@@ -129,8 +131,26 @@ export class Caption extends BaseProperties implements CaptionLike {
129131
this.proxyText.addListener('mouseleave', (ev) => this.handleMouseLeave(moduleCtx, ev)),
130132
];
131133
}
132-
this.proxyText.textContent = toPlainText(this.text);
133-
this.proxyText.setBounds(bbox);
134+
135+
// Only update DOM if content changed - avoids unnecessary DOM operations
136+
const textContent = toPlainText(this.text);
137+
if (textContent !== this.lastProxyTextContent) {
138+
this.proxyText.textContent = textContent;
139+
this.lastProxyTextContent = textContent;
140+
}
141+
142+
// Only update bounds if they changed
143+
const { lastProxyBBox } = this;
144+
if (
145+
lastProxyBBox == null ||
146+
bbox.x !== lastProxyBBox.x ||
147+
bbox.y !== lastProxyBBox.y ||
148+
bbox.width !== lastProxyBBox.width ||
149+
bbox.height !== lastProxyBBox.height
150+
) {
151+
this.proxyText.setBounds(bbox);
152+
this.lastProxyBBox = { x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height };
153+
}
134154
}
135155

136156
private handleMouseMove(moduleCtx: ModuleContext, event?: MouseWidgetEvent<'mousemove'>) {
@@ -161,5 +181,7 @@ export class Caption extends BaseProperties implements CaptionLike {
161181
this.proxyTextListeners = undefined;
162182
this.proxyText.destroy();
163183
this.proxyText = undefined;
184+
this.lastProxyTextContent = undefined;
185+
this.lastProxyBBox = undefined;
164186
}
165187
}

packages/ag-charts-community/src/chart/series/aggregation.ts

Lines changed: 161 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -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

379484
export function compactAggregationIndices(

0 commit comments

Comments
 (0)