Skip to content

Commit 80340af

Browse files
authored
Charting tool enhancements - Errors bars for Bar and Line chart (#1869)
### version 6.66.0 *Released*: 23 October 2025 - ChartBuilderModal support for bar/line chart aggregate method and error bar options - useOverlayTriggerState update to not close popover on document click that is a select option target - Factor ChartFieldRangeScaleOptions.tsx out of ChartFieldOption.tsx - Create ChartFieldAggregateOptions.tsx and move y-axis bar chart aggregate method dropdown into tooltip - support for error bar radio options as separate overlay or to be included in axis options overlay - Update ChartBuilderModal to pass down aggregate and error bar options to ChartConfig
1 parent e7fc128 commit 80340af

18 files changed

Lines changed: 862 additions & 393 deletions

packages/components/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@labkey/components",
3-
"version": "6.65.2",
3+
"version": "6.66.0",
44
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
55
"sideEffects": false,
66
"files": [

packages/components/releaseNotes/components.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
# @labkey/components
22
Components, models, actions, and utility functions for LabKey applications and pages
33

4-
### version TBD
5-
*Released*: TBD
4+
### version 6.66.0
5+
*Released*: 23 October 2025
6+
- ChartBuilderModal support for bar/line chart aggregate method and error bar options
7+
- useOverlayTriggerState update to not close popover on document click that is a select option target
8+
- Factor ChartFieldRangeScaleOptions.tsx out of ChartFieldOption.tsx
9+
- Create ChartFieldAggregateOptions.tsx and move y-axis bar chart aggregate method dropdown into tooltip
10+
- support for error bar radio options as separate overlay or to be included in axis options overlay
11+
- Update ChartBuilderModal to pass down aggregate and error bar options to ChartConfig
12+
13+
### version 6.65.2
14+
*Released*: 22 October 2025
615
- Various minor fixes for exception reports
716

817
### version 6.65.1

packages/components/src/internal/OverlayTrigger.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import React, {
2-
cloneElement,
32
Children,
3+
cloneElement,
4+
CSSProperties,
45
FC,
5-
useRef,
6-
ReactElement,
7-
useState,
8-
useCallback,
96
MutableRefObject,
10-
CSSProperties,
11-
useMemo,
127
PropsWithChildren,
8+
ReactElement,
9+
useCallback,
1310
useEffect,
11+
useMemo,
12+
useRef,
13+
useState,
1414
} from 'react';
1515
import { createPortal } from 'react-dom';
1616

@@ -86,7 +86,9 @@ export function useOverlayTriggerState<T extends Element = HTMLDivElement>(
8686
event => {
8787
const isToggle = event.target === targetRef.current;
8888
const insideToggle = portalEl?.contains(event.target);
89-
if (!isToggle && !insideToggle) {
89+
const isSelectOption =
90+
event.target instanceof HTMLElement && event.target.classList.contains('select-input__option');
91+
if (!isToggle && !insideToggle && !isSelectOption) {
9092
setShow(false);
9193
}
9294
},
@@ -168,9 +170,9 @@ export const OverlayTrigger: FC<Props> = ({
168170
return (
169171
<div
170172
className={className_}
173+
onClick={onClick}
171174
onMouseEnter={onMouseEnter}
172175
onMouseLeave={onMouseLeave}
173-
onClick={onClick}
174176
style={style}
175177
>
176178
{clonedChild}

packages/components/src/internal/components/chart/ChartBuilderModal.test.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,13 @@ import {
1919

2020
import {
2121
ChartBuilderModal,
22-
ChartTypeInfo,
2322
getChartBuilderChartConfig,
2423
getChartBuilderQueryConfig,
2524
getChartRenderMsg,
2625
getDefaultBarChartAxisLabel,
27-
MAX_POINT_DISPLAY,
28-
MAX_ROWS_PREVIEW,
2926
} from './ChartBuilderModal';
30-
import { ChartConfig, ChartQueryConfig, GenericChartModel } from './models';
27+
import { MAX_POINT_DISPLAY, MAX_ROWS_PREVIEW } from './constants';
28+
import { ChartConfig, ChartQueryConfig, ChartTypeInfo, GenericChartModel } from './models';
3129

3230
const BAR_CHART_TYPE = {
3331
name: 'bar_chart',
@@ -319,7 +317,7 @@ describe('ChartBuilderModal', () => {
319317
validate(false, true, true);
320318
});
321319

322-
test('init from bar chart with y axis value and default aggregate method', () => {
320+
test('init from bar chart with y axis value and default aggregate method', async () => {
323321
const savedChartModel = {
324322
canShare: true,
325323
canDelete: true,
@@ -348,12 +346,20 @@ describe('ChartBuilderModal', () => {
348346
);
349347

350348
validate(false, true, true);
351-
expect(document.querySelectorAll('input')).toHaveLength(8);
349+
expect(document.querySelectorAll('input')).toHaveLength(6);
352350
expect(document.querySelector('input[name=y]').getAttribute('value')).toBe('field2');
351+
expect(document.querySelectorAll('.fa-gear')).toHaveLength(1); // gear icon for y-axis
352+
await userEvent.click(document.querySelector('.fa-gear'));
353+
expect(document.querySelectorAll('input')).toHaveLength(13);
354+
expect(document.querySelector('input[value=automatic]').hasAttribute('checked')).toBe(true);
355+
expect(document.querySelector('input[value=manual]').hasAttribute('checked')).toBe(false);
353356
expect(document.querySelector('input[name=aggregate-method]').getAttribute('value')).toBe('SUM');
357+
expect(document.querySelectorAll('input[name=error-bar-method]')).toHaveLength(3);
358+
expect(document.querySelector('input[value=SD]').hasAttribute('checked')).toBe(false);
359+
expect(document.querySelector('input[value=SEM]').hasAttribute('checked')).toBe(false);
354360
});
355361

356-
test('init from bar chart with y axis value and aggregate method', () => {
362+
test('init from bar chart with y axis value and aggregate method', async () => {
357363
const savedChartModel = {
358364
canShare: true,
359365
canDelete: true,
@@ -363,7 +369,10 @@ describe('ChartBuilderModal', () => {
363369
visualizationConfig: {
364370
chartConfig: {
365371
renderType: 'bar_chart',
366-
measures: { x: { name: 'field1' }, y: { name: 'field2', aggregate: { value: 'MEAN' } } },
372+
measures: {
373+
x: { name: 'field1' },
374+
y: { name: 'field2', aggregate: { value: 'MEAN' }, errorBars: 'SEM' },
375+
},
367376
labels: { x: 'Field 1', y: 'Field 2' },
368377
},
369378
queryConfig: {
@@ -382,9 +391,17 @@ describe('ChartBuilderModal', () => {
382391
);
383392

384393
validate(false, true, true);
385-
expect(document.querySelectorAll('input')).toHaveLength(8);
394+
expect(document.querySelectorAll('input')).toHaveLength(6);
386395
expect(document.querySelector('input[name=y]').getAttribute('value')).toBe('field2');
396+
expect(document.querySelectorAll('.fa-gear')).toHaveLength(1); // gear icon for y-axis
397+
await userEvent.click(document.querySelector('.fa-gear'));
398+
expect(document.querySelectorAll('input')).toHaveLength(13);
399+
expect(document.querySelector('input[value=automatic]').hasAttribute('checked')).toBe(true);
400+
expect(document.querySelector('input[value=manual]').hasAttribute('checked')).toBe(false);
387401
expect(document.querySelector('input[name=aggregate-method]').getAttribute('value')).toBe('MEAN');
402+
expect(document.querySelectorAll('input[name=error-bar-method]')).toHaveLength(3);
403+
expect(document.querySelector('input[value=SD]').hasAttribute('checked')).toBe(false);
404+
expect(document.querySelector('input[value=SEM]').hasAttribute('checked')).toBe(true);
388405
expect(document.querySelectorAll('input[name=trendlineType]')).toHaveLength(0);
389406
});
390407

@@ -490,10 +507,12 @@ describe('ChartBuilderModal', () => {
490507

491508
await userEvent.click(document.querySelectorAll('.fa-gear')[0]); // x-axis options icon, click to close
492509
await userEvent.click(document.querySelectorAll('.fa-gear')[1]); // y-axis options icon
493-
expect(document.querySelectorAll('.radioinput-label.selected')[0].textContent).toBe('Log');
510+
expect(document.querySelectorAll('.radioinput-label.selected')).toHaveLength(3); // error bar, scale, range
511+
expect(document.querySelectorAll('.radioinput-label.selected')[0].textContent).toBe('None');
512+
expect(document.querySelectorAll('.radioinput-label.selected')[1].textContent).toBe('Log');
513+
expect(document.querySelectorAll('.radioinput-label.selected')[2].textContent).toBe('Automatic');
494514
expect(document.querySelectorAll('input[name=scaleTrans]')[0].hasAttribute('checked')).toBe(false); // linear
495515
expect(document.querySelectorAll('input[name=scaleTrans]')[1].hasAttribute('checked')).toBe(true); // log
496-
expect(document.querySelectorAll('.radioinput-label.selected')[1].textContent).toBe('Automatic');
497516
});
498517

499518
test('canDelete and canShare false', () => {

0 commit comments

Comments
 (0)