From 92db0d388fefaa71d6bc3c094e6d4a0d4ffa166b Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 4 Dec 2025 08:17:17 -0600 Subject: [PATCH 01/31] - Chart builder updates for series color scale options - ChartColorInputs for single color geomOptions and series specific color and shape value map - ChartConfig measuresOptions to store per series mapping object - Hide and show color options based on selected chart type and measures --- .../components/releaseNotes/components.md | 8 + .../components/chart/ChartColorInputs.tsx | 380 ++++++++++++++++++ .../components/chart/ChartSettingsPanel.tsx | 4 +- .../internal/components/chart/constants.ts | 22 + .../src/internal/components/chart/models.ts | 7 + .../src/internal/components/chart/utils.ts | 2 + packages/components/src/theme/form.scss | 8 + 7 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 packages/components/src/internal/components/chart/ChartColorInputs.tsx diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index d41b997610..ef23596c08 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,6 +1,14 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages +### version TBD +*Released*: TBD December 2025 +- Chart builder updates for series color scale options + - ChartColorInputs for single color geomOptions and series specific color and shape value map + - ChartConfig measuresOptions to store per series mapping object + - Hide and show color options based on selected chart type and measures + - ColorPickerInput update to support fixed position + ### version 7.1.0 *Released*: 3 December 2025 - ChartBuilderModal diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.tsx new file mode 100644 index 0000000000..0aa3db182e --- /dev/null +++ b/packages/components/src/internal/components/chart/ChartColorInputs.tsx @@ -0,0 +1,380 @@ +import React, { FC, memo, useCallback, useEffect, useMemo, useState } from 'react'; +import classNames from 'classnames'; +import { Utils } from '@labkey/api'; +import { ChartConfig, ChartConfigSetter, MeasureOption } from './models'; +import { ColorPickerInput } from '../forms/input/ColorPickerInput'; +import { COLOR_OPTIONS_PER_TYPE, COLOR_PALETTE_OPTIONS, SHAPE_OPTIONS } from './constants'; +import { SelectInput } from '../forms/input/SelectInput'; +import { selectDistinctRows } from '../../query/api'; +import { QueryModel } from '../../../public/QueryModel/QueryModel'; +import { ColorIcon } from '../base/ColorIcon'; +import { LABKEY_VIS } from '../../constants'; + +const showColorOption = function (chartConfig: ChartConfig, optionName: string): boolean { + const chartType = chartConfig.renderType; + const isBarChart = chartType === 'bar_chart'; + const isBoxPlot = chartType === 'box_plot'; + const isLinePlot = chartType === 'line_plot'; + const isScatterPlot = chartType === 'scatter_plot'; + const hasSeries = chartConfig.measures?.series !== undefined; + const hasXSub = chartConfig.measures?.xSub !== undefined; + const hasColor = chartConfig.measures?.color !== undefined; + + switch (optionName) { + case 'boxFillColor': + return COLOR_OPTIONS_PER_TYPE.boxFillColor.indexOf(chartType) > -1 && (!isBarChart || !hasXSub); // bar chart if config has groupBy measure + case 'colorPaletteScale': + return ( + COLOR_OPTIONS_PER_TYPE.colorPaletteScale.indexOf(chartType) > -1 && + (!isLinePlot || hasSeries) && // line plot if config has series measure + (!isBarChart || hasXSub) && // bar chart if config has groupBy measure + (!isBoxPlot || hasColor) && // box plot if config has color measure + (!isScatterPlot || hasColor) // scatter plot if config has color measure + ); + case 'lineColor': + return COLOR_OPTIONS_PER_TYPE.lineColor.indexOf(chartType) > -1 && (!isBarChart || !hasXSub); // bar chart if config has groupBy measure + case 'pointFillColor': + return ( + COLOR_OPTIONS_PER_TYPE.pointFillColor.indexOf(chartType) > -1 && + (!isLinePlot || !hasSeries) && // line plot if config has series measure + (!isBoxPlot || !hasColor) && // box plot if config has color measure + (!isScatterPlot || !hasColor) // scatter plot if config has color measure + ); + default: + return false; + } +}; + +interface ShapeOptionRendererProps { + isValueRenderer: boolean; + name: string; +} +const ShapeOptionRenderer: FC = memo(({ name, isValueRenderer }) => { + const size = 10; + const iconSize = name === 'diamond' ? size / 2.5 : size / 2; + const icon = LABKEY_VIS.Scale.ShapeMap[name](iconSize); + const className = classNames('chart-builder-type-option', { 'chart-builder-type-option--value': isValueRenderer }); + return ( + + + + + + ); +}); +ShapeOptionRenderer.displayName = 'ShapeOptionRenderer'; + +function shapeOptionRenderer(option) { + return ; +} + +function shapeValueRenderer(option) { + return ; +} + +interface SeriesOptionRendererProps { + isValueRenderer: boolean; + name: string; + seriesOptionMap: Record>; +} +const SeriesOptionRenderer: FC = memo(({ name, seriesOptionMap, isValueRenderer }) => { + const value = seriesOptionMap?.[name]?.color; + const className = classNames('chart-builder-type-option', { 'chart-builder-type-option--value': isValueRenderer }); + return ( + + {name} + + ); +}); +SeriesOptionRenderer.displayName = 'SeriesOptionRenderer'; + +function seriesOptionRenderer(option, seriesOptionMap) { + return ( + + ); +} + +interface ChartColorInputsProps { + chartConfig: ChartConfig; + model: QueryModel; + setChartConfig: ChartConfigSetter; +} + +export const ChartColorInputs: FC = memo(({ chartConfig, model, setChartConfig }) => { + const isBoxPlot = chartConfig.renderType === 'box_plot'; + const isLinePlot = chartConfig.renderType === 'line_plot'; + + const boxFillColor = + chartConfig.geomOptions.boxFillColor === 'none' ? undefined : (chartConfig.geomOptions.boxFillColor as string); + const showBoxFillColor = useMemo(() => showColorOption(chartConfig, 'boxFillColor'), [chartConfig]); + const lineColor = chartConfig.geomOptions.lineColor as string; + const showLineColor = useMemo(() => showColorOption(chartConfig, 'lineColor'), [chartConfig]); + const pointFillColor = chartConfig.geomOptions.pointFillColor as string; + const showPointFillColor = useMemo(() => showColorOption(chartConfig, 'pointFillColor'), [chartConfig]); + const colorPaletteScale = chartConfig.geomOptions.colorPaletteScale; + const showColorPaletteScale = useMemo(() => showColorOption(chartConfig, 'colorPaletteScale'), [chartConfig]); + + const setGeomOptions = useCallback( + options => { + setChartConfig(current => ({ + ...current, + geomOptions: { ...current.geomOptions, ...options }, + })); + }, + [setChartConfig] + ); + + const onColorPaletteChange = useCallback( + (_: never, value: string) => { + setGeomOptions({ colorPaletteScale: value }); + }, + [setGeomOptions] + ); + + const onColorChange = useCallback( + (name: string, value: string) => { + // value comes in as #FFFFFF from ColorPickerInput, but we want it without the # + if (value?.startsWith('#')) { + value = value.substring(1); + } + setGeomOptions({ [name]: value }); + }, + [setGeomOptions] + ); + const onBoxFillColorChange = useCallback( + (_: never, value: string) => { + onColorChange('boxFillColor', value ?? 'none'); + }, + [onColorChange] + ); + const onLineColorChange = useCallback( + (_: never, value: string) => { + onColorChange('lineColor', value); + }, + [onColorChange] + ); + const onPointFillColorChange = useCallback( + (_: never, value: string) => { + onColorChange('pointFillColor', value); + }, + [onColorChange] + ); + + return ( + <> + {showBoxFillColor && ( +
+
+ + +
+
+ )} + {showLineColor && ( +
+
+ + +
+
+ )} + {showPointFillColor && ( +
+
+ + +
+
+ )} + {showColorPaletteScale && ( +
+
+ + +
+
+ )} + + + ); +}); +ChartColorInputs.displayName = 'ChartColorInputs'; + +interface SeriesLineStyleInputProps { + chartConfig: ChartConfig; + model: QueryModel; + setChartConfig: ChartConfigSetter; +} +export const SeriesLineStyleInput: FC = memo(({ chartConfig, model, setChartConfig }) => { + const [distinctSeriesOptions, setDistinctSeriesOptions] = useState<{ label: string; value: string }[]>(); + const [selectedSeries, setSelectedSeries] = useState(); + const [seriesOptionMap, setSeriesOptionMap] = useState>( + chartConfig.measuresOptions?.series ?? {} + ); + + useEffect(() => { + const fetchDistinctSeries = async () => { + setDistinctSeriesOptions(undefined); + setSelectedSeries(undefined); + + if (chartConfig.measures?.series) { + try { + const response = await selectDistinctRows({ + schemaName: model.schemaQuery.schemaName, + queryName: model.schemaQuery.queryName, + viewName: model.schemaQuery.viewName, + column: chartConfig.measures?.series.fieldKey, + }); + + // map response.values to SelectOption format + const options = response.values.map(value => ({ + label: value === null ? '[Blank]' : value, // TODO verify this + value: value === null ? '[Blank]' : value, + })); + setDistinctSeriesOptions(options); + } catch (error) { + console.error(error); + } + } + }; + + fetchDistinctSeries(); + }, [model.schemaQuery, chartConfig.measures?.series]); + + // call setChartConfig whenever seriesOptionMap changes + useEffect(() => { + setChartConfig(current => ({ + ...current, + measuresOptions: { + ...current.measuresOptions, + series: seriesOptionMap, + }, + })); + }, [seriesOptionMap, setChartConfig]); + + const onSeriesSelectChange = useCallback((_: never, value: string) => { + setSelectedSeries(value); + }, []); + + const onSeriesOptionChange = useCallback((series: string, optionName: string, value: any) => { + setSeriesOptionMap(prev => { + const seriesOptions = prev[series] || {}; + const updatedSeriesOptions = { ...seriesOptions }; + // if the value is undefined, remove the option from the seriesOptionMap + if (value === undefined) { + delete updatedSeriesOptions[optionName]; + } else { + updatedSeriesOptions[optionName] = value; + } + // if the updatedSeriesOptions is empty, remove the series from the seriesOptionMap + if (Utils.isEmptyObj(updatedSeriesOptions)) { + const { [series]: _, ...rest } = prev; + return rest; + } + return { + ...prev, + [series]: updatedSeriesOptions, + }; + }); + }, []); + + const onSeriesColorChange = useCallback( + (_: never, value: string) => { + onSeriesOptionChange(selectedSeries, 'color', value); + }, + [onSeriesOptionChange, selectedSeries] + ); + + const onSeriesShapeChange = useCallback( + (_: never, value: string) => { + onSeriesOptionChange(selectedSeries, 'shape', value); + }, + [onSeriesOptionChange, selectedSeries] + ); + + const seriesValueRenderer = useCallback(option => seriesOptionRenderer(option, seriesOptionMap), [seriesOptionMap]); + + if (!distinctSeriesOptions) { + return null; + } + + return ( + <> +
+
+ + +
+
+ {selectedSeries && ( +
+
+
Color
+ +
+
+
Shape
+ +
+
+ )} + + ); +}); +SeriesLineStyleInput.displayName = 'SeriesLineStyleInput'; diff --git a/packages/components/src/internal/components/chart/ChartSettingsPanel.tsx b/packages/components/src/internal/components/chart/ChartSettingsPanel.tsx index 28d7694721..44fe2a2526 100644 --- a/packages/components/src/internal/components/chart/ChartSettingsPanel.tsx +++ b/packages/components/src/internal/components/chart/ChartSettingsPanel.tsx @@ -1,4 +1,5 @@ import React, { ChangeEvent, FC, memo, useCallback, useMemo, useState } from 'react'; +import classNames from 'classnames'; import { BaseChartModel, BaseChartModelSetter, @@ -16,9 +17,9 @@ import { ChartFieldOption } from './ChartFieldOption'; import { QueryModel } from '../../../public/QueryModel/QueryModel'; import { TrendlineOption } from './TrendlineOption'; import { deepCopyChartConfig, hasTrendline } from './utils'; -import classNames from 'classnames'; import { useEnterEscape } from '../../../public/useEnterEscape'; import { ChartLabelInput } from './ChartLabelInput'; +import { ChartColorInputs } from './ChartColorInputs'; function changedIntValue(strVal: string, currentVal: number): [value: number, changed: boolean] { strVal = strVal.trim(); @@ -378,6 +379,7 @@ export const ChartSettingsPanel: FC = memo(props => { value={chartConfig?.labels?.subtitle} /> + ); }); diff --git a/packages/components/src/internal/components/chart/constants.ts b/packages/components/src/internal/components/chart/constants.ts index a92d60e7cb..eeb55ea396 100644 --- a/packages/components/src/internal/components/chart/constants.ts +++ b/packages/components/src/internal/components/chart/constants.ts @@ -19,3 +19,25 @@ export const AGGREGATE_METHODS = [ { label: 'Mean', value: 'MEAN' }, { label: 'Median', value: 'MEDIAN' }, ]; + +// see vis/src/scale.js for options +export const COLOR_PALETTE_OPTIONS = [ + { label: 'Light (Default)', value: 'ColorDiscrete' }, + { label: 'Dark', value: 'DarkColorDiscrete' }, +]; + +// see vis/src/scale.js for options +export const SHAPE_OPTIONS = [ + { label: 'Circle', value: 'circle' }, + { label: 'Diamond', value: 'diamond' }, + { label: 'Square', value: 'square' }, + { label: 'Triangle', value: 'triangle' }, + { label: 'Cross', value: 'x' }, +]; + +export const COLOR_OPTIONS_PER_TYPE = { + boxFillColor: ['bar_chart', 'box_plot'], + colorPaletteScale: ['bar_chart', 'box_plot', 'line_plot', 'scatter_plot', 'pie_chart'], + lineColor: ['bar_chart', 'box_plot'], + pointFillColor: ['box_plot', 'line_plot', 'scatter_plot'], +}; diff --git a/packages/components/src/internal/components/chart/models.ts b/packages/components/src/internal/components/chart/models.ts index a06fdbb90b..4f898787f4 100644 --- a/packages/components/src/internal/components/chart/models.ts +++ b/packages/components/src/internal/components/chart/models.ts @@ -7,12 +7,19 @@ export interface ChartLabels { y?: string; } +export interface MeasureOption { + color?: string; + shape?: string; + // lineType?: string; +} + export interface ChartConfig { geomOptions: Record; gridLinesVisible: string; height?: number; labels: ChartLabels; measures: Record>; // TODO: we can probably do better than any + measuresOptions?: Record>; // map from measures to the options for the distinct values of that measure pointType: string; renderType: string; scales: Record; diff --git a/packages/components/src/internal/components/chart/utils.ts b/packages/components/src/internal/components/chart/utils.ts index 53a6f42418..3587dcf5ee 100644 --- a/packages/components/src/internal/components/chart/utils.ts +++ b/packages/components/src/internal/components/chart/utils.ts @@ -145,6 +145,7 @@ export function deepCopyChartConfig(chartConfig: ChartConfig, chartType = 'bar_c height: 500, labels: {}, measures: {}, + measuresOptions: {}, pointType: 'all', renderType: chartType, scales: {}, @@ -156,6 +157,7 @@ export function deepCopyChartConfig(chartConfig: ChartConfig, chartType = 'bar_c geomOptions: { ...chartConfig.geomOptions }, labels: { ...chartConfig.labels }, measures: { ...chartConfig.measures }, + measuresOptions: { ...chartConfig.measuresOptions }, scales: { ...chartConfig.scales }, }; } diff --git a/packages/components/src/theme/form.scss b/packages/components/src/theme/form.scss index fc7e4df87f..5c4f5a75d9 100644 --- a/packages/components/src/theme/form.scss +++ b/packages/components/src/theme/form.scss @@ -282,6 +282,14 @@ textarea.form-control { width: 12px; } +.color-icon__chip-small { + display: inline-block; + border-radius: 2px; + border: solid 1px $gray; + height: 12px; + width: 12px; +} + .color-icon__circle { display: inline-block; border-radius: 50%; From e87c8644bec17ca5948ecd9f7f873daa5608c982 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 4 Dec 2025 08:17:34 -0600 Subject: [PATCH 02/31] - ColorPickerInput update to support fixed position --- .../forms/input/ColorPickerInput.tsx | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx b/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx index cbd7d79a5f..2475d19fa9 100644 --- a/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx +++ b/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx @@ -9,14 +9,26 @@ interface Props { allowRemove?: boolean; colors?: string[]; disabled?: boolean; + fixedPosition?: boolean; name?: string; + noValueText?: string; onChange: (name: string, value: string) => void; text?: string; value: string; } export const ColorPickerInput: FC = memo(props => { - const { allowRemove, colors, disabled, name, onChange, text, value } = props; + const { + allowRemove, + colors, + disabled, + name, + onChange, + text, + value, + noValueText = 'None', + fixedPosition = false, + } = props; const [showPicker, setShowPicker] = useState(false); const onChangeComplete = useCallback( (color?: ColorResult) => { @@ -31,6 +43,21 @@ export const ColorPickerInput: FC = memo(props => { setShowPicker(s => !s); }, []); + // if value doesn't start with '#', add it + const value_ = value && !value.startsWith('#') ? `#${value}` : value; + + // get the position of the button to position the picker + const [fixedTop, fixedLeft] = (() => { + const button = document.querySelector('.color-picker__button'); + if (!fixedPosition || !button) { + return [0, 0]; + } + const rect = button.getBoundingClientRect(); + return [rect.bottom + 5, rect.left]; + })(); + + const compactPicker = ; + return (
- {text !== undefined && } + {text !== undefined && } - {allowRemove && value && !disabled && ( + {allowRemove && value_ && !disabled && ( )} @@ -53,7 +80,10 @@ export const ColorPickerInput: FC = memo(props => { {showPicker && ( <>
- + {fixedPosition && ( +
{compactPicker}
+ )} + {!fixedPosition && compactPicker} )}
From b3639fe6912b0bf91b10004e431f426d329bd3aa Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 4 Dec 2025 08:18:07 -0600 Subject: [PATCH 03/31] 7.1.0-chartColorScale.0 --- packages/components/package-lock.json | 4 ++-- packages/components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index 97fb15ecb8..61b0673e6e 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.1.0", + "version": "7.1.0-chartColorScale.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.1.0", + "version": "7.1.0-chartColorScale.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index 0a91aef017..468b617dd4 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.1.0", + "version": "7.1.0-chartColorScale.0", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ From b920b0b332c0d4f7c0c3d4b907904eac8a2bd4e2 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 4 Dec 2025 08:38:02 -0600 Subject: [PATCH 04/31] jest fix for existing test --- .../components/chart/ChartBuilderModal.test.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/components/src/internal/components/chart/ChartBuilderModal.test.tsx b/packages/components/src/internal/components/chart/ChartBuilderModal.test.tsx index 20e85ed3e6..082fa3c65c 100644 --- a/packages/components/src/internal/components/chart/ChartBuilderModal.test.tsx +++ b/packages/components/src/internal/components/chart/ChartBuilderModal.test.tsx @@ -117,16 +117,9 @@ describe('ChartBuilderModal', () => { expect(document.querySelectorAll('.chart-settings')).toHaveLength(1); expect(document.querySelectorAll('.chart-builder-modal__chart-preview')).toHaveLength(1); expect(document.querySelector('.modal-title').textContent).toBe(isNew ? 'Create Chart' : 'Edit Chart'); - expect(document.querySelectorAll('.btn')).toHaveLength(canDelete ? 3 : 2); + expect(document.querySelectorAll('.btn:not(.color-picker__button)')).toHaveLength(canDelete ? 3 : 2); expect(document.querySelectorAll('.alert')).toHaveLength(0); - - // TODO update this part of jest test - // hidden chart types are filtered out - // const chartTypeItems = document.querySelectorAll('.chart-builder-type'); - // expect(chartTypeItems).toHaveLength(3); - // expect(chartTypeItems[0].textContent).toBe('Bar'); - // expect(chartTypeItems[1].textContent).toBe('Scatter'); - // expect(chartTypeItems[2].textContent).toBe('Line'); + expect(document.querySelectorAll('.chart-settings__chart-type')).toHaveLength(isNew ? 1 : 0); expect(document.querySelectorAll('input[name="name"]')).toHaveLength(1); expect(document.querySelectorAll('input[name="shared"]')).toHaveLength(canShare ? 1 : 0); @@ -291,7 +284,7 @@ describe('ChartBuilderModal', () => { // click delete button and verify confirm text / buttons await userEvent.click(document.querySelector('.btn-danger')); - const btnItems = document.querySelectorAll('.btn'); + const btnItems = document.querySelectorAll('.btn:not(.color-picker__button)'); expect(btnItems).toHaveLength(2); expect(btnItems[0].textContent).toBe('Cancel'); expect(btnItems[1].textContent).toBe('Delete'); From 9eeb2719c27522eac61e11093c38695bf5ab0fa0 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 4 Dec 2025 09:07:59 -0600 Subject: [PATCH 05/31] ColorPickerInput to use fixedPosition by default --- .../components/chart/ChartColorInputs.tsx | 4 --- .../forms/input/ColorPickerInput.tsx | 31 +++++++++---------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.tsx index 0aa3db182e..b19d618f4d 100644 --- a/packages/components/src/internal/components/chart/ChartColorInputs.tsx +++ b/packages/components/src/internal/components/chart/ChartColorInputs.tsx @@ -172,7 +172,6 @@ export const ChartColorInputs: FC = memo(({ chartConfig, = memo(({ chartConfig,
= memo(({ chartConfig,
= memo(({ chart
Color
void; @@ -27,8 +26,10 @@ export const ColorPickerInput: FC = memo(props => { text, value, noValueText = 'None', - fixedPosition = false, } = props; + const buttonRef = useRef(null); + const [fixedTop, setFixedTop] = useState(); + const [fixedLeft, setFixedLeft] = useState(); const [showPicker, setShowPicker] = useState(false); const onChangeComplete = useCallback( (color?: ColorResult) => { @@ -40,22 +41,18 @@ export const ColorPickerInput: FC = memo(props => { onChangeComplete(); }, [onChangeComplete]); const togglePicker = useCallback(() => { + if (buttonRef.current) { + const rect = buttonRef.current.getBoundingClientRect(); + setFixedTop(rect.bottom + 5); + setFixedLeft(rect.left); + } + setShowPicker(s => !s); }, []); // if value doesn't start with '#', add it const value_ = value && !value.startsWith('#') ? `#${value}` : value; - // get the position of the button to position the picker - const [fixedTop, fixedLeft] = (() => { - const button = document.querySelector('.color-picker__button'); - if (!fixedPosition || !button) { - return [0, 0]; - } - const rect = button.getBoundingClientRect(); - return [rect.bottom + 5, rect.left]; - })(); - const compactPicker = ; return ( @@ -65,6 +62,7 @@ export const ColorPickerInput: FC = memo(props => { className="color-picker__button btn btn-default" onClick={togglePicker} disabled={disabled} + ref={buttonRef} > {text ? text : value ? : noValueText} @@ -80,10 +78,11 @@ export const ColorPickerInput: FC = memo(props => { {showPicker && ( <>
- {fixedPosition && ( -
{compactPicker}
+ {fixedTop && fixedLeft ? ( +
{compactPicker}
+ ) : ( + compactPicker )} - {!fixedPosition && compactPicker} )}
From 07caba7b5d8d296c1d4801edc81b694e8065274e Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 4 Dec 2025 09:32:07 -0600 Subject: [PATCH 06/31] Reset chartConfig.measuresOptions.series when series measure changes or is removed --- .../src/internal/components/chart/ChartColorInputs.tsx | 10 +++++++++- .../src/internal/components/chart/ChartFieldOption.tsx | 8 +++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.tsx index b19d618f4d..40e5a7e6c1 100644 --- a/packages/components/src/internal/components/chart/ChartColorInputs.tsx +++ b/packages/components/src/internal/components/chart/ChartColorInputs.tsx @@ -117,6 +117,7 @@ export const ChartColorInputs: FC = memo(({ chartConfig, const showPointFillColor = useMemo(() => showColorOption(chartConfig, 'pointFillColor'), [chartConfig]); const colorPaletteScale = chartConfig.geomOptions.colorPaletteScale; const showColorPaletteScale = useMemo(() => showColorOption(chartConfig, 'colorPaletteScale'), [chartConfig]); + const showSeriesLineStyle = isLinePlot && chartConfig.measures?.series !== undefined; const setGeomOptions = useCallback( options => { @@ -220,7 +221,14 @@ export const ChartColorInputs: FC = memo(({ chartConfig,
)} - + {showSeriesLineStyle && ( + + )} ); }); diff --git a/packages/components/src/internal/components/chart/ChartFieldOption.tsx b/packages/components/src/internal/components/chart/ChartFieldOption.tsx index 69051d62f2..20f3bc8f83 100644 --- a/packages/components/src/internal/components/chart/ChartFieldOption.tsx +++ b/packages/components/src/internal/components/chart/ChartFieldOption.tsx @@ -54,6 +54,7 @@ export const ChartFieldOption: FC = memo(props => { setScale(DEFAULT_SCALE_VALUES); setChartConfig(current => { let geomOptions = current.geomOptions; + const measuresOptions = current.measuresOptions; const measures = { ...current.measures }; const scales = { ...current.scales }; const labels = { ...current.labels }; @@ -78,7 +79,12 @@ export const ChartFieldOption: FC = memo(props => { geomOptions = { ...geomOptions, trendlineType }; } - const updatedConfig = { ...current, geomOptions, measures, labels }; + // reset the measureOptions.series when field changes + if (name === 'series' && measuresOptions.series !== undefined) { + delete measuresOptions.series; + } + + const updatedConfig = { ...current, geomOptions, measures, measuresOptions, labels }; if (selectedType.name === 'bar_chart') { updatedConfig.labels.y = getBarChartAxisLabel(updatedConfig, current); From 68da304466ea2dba666499e335096c7d7f4bce50 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 4 Dec 2025 09:32:32 -0600 Subject: [PATCH 07/31] Series shape input removal to match color picker remove styling --- .../components/chart/ChartColorInputs.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.tsx index 40e5a7e6c1..54e3619e23 100644 --- a/packages/components/src/internal/components/chart/ChartColorInputs.tsx +++ b/packages/components/src/internal/components/chart/ChartColorInputs.tsx @@ -9,6 +9,7 @@ import { selectDistinctRows } from '../../query/api'; import { QueryModel } from '../../../public/QueryModel/QueryModel'; import { ColorIcon } from '../base/ColorIcon'; import { LABKEY_VIS } from '../../constants'; +import { RemoveEntityButton } from '../buttons/RemoveEntityButton'; const showColorOption = function (chartConfig: ChartConfig, optionName: string): boolean { const chartType = chartConfig.renderType; @@ -184,11 +185,7 @@ export const ChartColorInputs: FC = memo(({ chartConfig,
- +
)} @@ -326,6 +323,10 @@ export const SeriesLineStyleInput: FC = memo(({ chart [onSeriesOptionChange, selectedSeries] ); + const onSeriesShapeRemove = useCallback(() => { + onSeriesOptionChange(selectedSeries, 'shape', undefined); + }, [onSeriesOptionChange, selectedSeries]); + const seriesValueRenderer = useCallback(option => seriesOptionRenderer(option, seriesOptionMap), [seriesOptionMap]); if (!distinctSeriesOptions) { @@ -365,8 +366,9 @@ export const SeriesLineStyleInput: FC = memo(({ chart
Shape
= memo(({ chart value={seriesOptionMap[selectedSeries]?.shape} valueRenderer={shapeValueRenderer} /> + {seriesOptionMap[selectedSeries]?.shape && ( + + )}
)} From d533494777bd7fe98d15df4829c6af901f133fe3 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 4 Dec 2025 10:12:35 -0600 Subject: [PATCH 08/31] Add LetterIcon for use with "Auto" set series values --- .../components/releaseNotes/components.md | 1 + .../components/chart/ChartColorInputs.tsx | 20 ++++++++++++++++++- packages/components/src/theme/form.scss | 14 +++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index ef23596c08..f8eac20bc1 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -8,6 +8,7 @@ Components, models, actions, and utility functions for LabKey applications and p - ChartConfig measuresOptions to store per series mapping object - Hide and show color options based on selected chart type and measures - ColorPickerInput update to support fixed position + - Add LetterIcon for use with "Auto" set series values ### version 7.1.0 *Released*: 3 December 2025 diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.tsx index 54e3619e23..04e8452785 100644 --- a/packages/components/src/internal/components/chart/ChartColorInputs.tsx +++ b/packages/components/src/internal/components/chart/ChartColorInputs.tsx @@ -83,7 +83,16 @@ const SeriesOptionRenderer: FC = memo(({ name, series const className = classNames('chart-builder-type-option', { 'chart-builder-type-option--value': isValueRenderer }); return ( - {name} + {value && ( + <> + {name} + + )} + {!value && ( + <> + {name} + + )} ); }); @@ -387,3 +396,12 @@ export const SeriesLineStyleInput: FC = memo(({ chart ); }); SeriesLineStyleInput.displayName = 'SeriesLineStyleInput'; + +const LetterIcon: FC<{ letter: string }> = ({ letter }) => { + return ( +
+ {letter} +
+ ); +}; +LetterIcon.displayName = 'LetterIcon'; diff --git a/packages/components/src/theme/form.scss b/packages/components/src/theme/form.scss index 5c4f5a75d9..73e0a6cf80 100644 --- a/packages/components/src/theme/form.scss +++ b/packages/components/src/theme/form.scss @@ -298,6 +298,20 @@ textarea.form-control { width: 16px; } +.letter-icon { + display: inline-flex; + width: 12px; + height: 12px; + font-size: 10px; + line-height: 12px; + align-items: center; + justify-content: center; + vertical-align: text-top; + border: 1px solid $gray; + border-radius: 2px; + margin-top: 1px; +} + .content-form { label { font-weight: normal; From 475126cc1f2dddced9e64986162d137ae91e5b43 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 4 Dec 2025 10:13:09 -0600 Subject: [PATCH 09/31] release notes update --- packages/components/releaseNotes/components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index f8eac20bc1..62a5edff86 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -7,7 +7,7 @@ Components, models, actions, and utility functions for LabKey applications and p - ChartColorInputs for single color geomOptions and series specific color and shape value map - ChartConfig measuresOptions to store per series mapping object - Hide and show color options based on selected chart type and measures - - ColorPickerInput update to support fixed position + - ColorPickerInput update to use fixed position by default - Add LetterIcon for use with "Auto" set series values ### version 7.1.0 From 77334fd18ae285232f93cf47963750b5134f81d8 Mon Sep 17 00:00:00 2001 From: cnathe Date: Thu, 4 Dec 2025 11:22:54 -0600 Subject: [PATCH 10/31] new DEFAULT_COLORS constant --- .../components/releaseNotes/components.md | 2 +- .../forms/input/ColorPickerInput.tsx | 67 ++- .../ColorPickerInput.test.tsx.snap | 410 ++++++++++++++---- 3 files changed, 403 insertions(+), 76 deletions(-) diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index 62a5edff86..75941b0935 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -7,7 +7,7 @@ Components, models, actions, and utility functions for LabKey applications and p - ChartColorInputs for single color geomOptions and series specific color and shape value map - ChartConfig measuresOptions to store per series mapping object - Hide and show color options based on selected chart type and measures - - ColorPickerInput update to use fixed position by default + - ColorPickerInput update to use fixed position by default and new DEFAULT_COLORS constant - Add LetterIcon for use with "Auto" set series values ### version 7.1.0 diff --git a/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx b/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx index d620fb24f5..5d9b7b55a3 100644 --- a/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx +++ b/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx @@ -1,10 +1,73 @@ -import React, { FC, memo, useCallback, useState, useRef } from 'react'; +import React, { FC, memo, useCallback, useRef, useState } from 'react'; import { ColorResult, CompactPicker } from 'react-color'; import classNames from 'classnames'; import { ColorIcon } from '../../base/ColorIcon'; import { RemoveEntityButton } from '../../buttons/RemoveEntityButton'; +const DEFAULT_COLORS = [ + '#FFFFFF', + '#F07575', + '#F4AE71', + '#F0E075', + '#E3F075', + '#A8E477', + '#7FF0C3', + '#81C6E9', + '#AC8EEB', + '#D983EC', + '#EE96BC', + '#D6C1A4', + '#BFBFBF', + '#EA4545', + '#EC7812', + '#E3C919', + '#BCCF17', + '#6BC026', + '#1ADB8E', + '#269BD6', + '#7C4DE0', + '#B921DB', + '#E1478A', + '#BB9868', + '#808080', + '#D11717', + '#D26B10', + '#DCB118', + '#A9B314', + '#589E1F', + '#16BB79', + '#2084B6', + '#5B25D0', + '#961BB1', + '#B81E61', + '#8D6C3F', + '#404040', + '#A11212', + '#AF590D', + '#AA8813', + '#868E10', + '#4A841A', + '#13A067', + '#1B6E98', + '#481DA5', + '#7C1692', + '#95184E', + '#745934', + '#000000', + '#7C0E0E', + '#8A460A', + '#8A6E0F', + '#6C730D', + '#396614', + '#108456', + '#175E82', + '#351579', + '#651278', + '#72133C', + '#634C2C', +]; + interface Props { allowRemove?: boolean; colors?: string[]; @@ -19,7 +82,7 @@ interface Props { export const ColorPickerInput: FC = memo(props => { const { allowRemove, - colors, + colors = DEFAULT_COLORS, disabled, name, onChange, diff --git a/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap b/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap index 8834b7215d..230506bbcc 100644 --- a/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap +++ b/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap @@ -123,20 +123,20 @@ exports[`ColorPickerInput showPicker 1`] = `
+
+
+ + +
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
Date: Thu, 4 Dec 2025 11:23:20 -0600 Subject: [PATCH 11/31] 7.1.0-chartColorScale.1 --- packages/components/package-lock.json | 4 ++-- packages/components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index 61b0673e6e..eb072c6cb2 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.1.0-chartColorScale.0", + "version": "7.1.0-chartColorScale.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.1.0-chartColorScale.0", + "version": "7.1.0-chartColorScale.1", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index 468b617dd4..704fe171cb 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.1.0-chartColorScale.0", + "version": "7.1.0-chartColorScale.1", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ From b66f59c29bf4e5ed58257200f8bfbb1aae6f1ab9 Mon Sep 17 00:00:00 2001 From: cnathe Date: Fri, 5 Dec 2025 08:53:18 -0600 Subject: [PATCH 12/31] Color option layout updates - set left color to 1/4 of modal width, with min width at 330px --- .../components/chart/ChartColorInputs.tsx | 63 +++++++++---------- packages/components/src/theme/charts.scss | 6 +- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.tsx index 04e8452785..63915a1367 100644 --- a/packages/components/src/internal/components/chart/ChartColorInputs.tsx +++ b/packages/components/src/internal/components/chart/ChartColorInputs.tsx @@ -128,6 +128,7 @@ export const ChartColorInputs: FC = memo(({ chartConfig, const colorPaletteScale = chartConfig.geomOptions.colorPaletteScale; const showColorPaletteScale = useMemo(() => showColorOption(chartConfig, 'colorPaletteScale'), [chartConfig]); const showSeriesLineStyle = isLinePlot && chartConfig.measures?.series !== undefined; + const showAnyColorOptions = showBoxFillColor || showLineColor || showPointFillColor; const setGeomOptions = useCallback( options => { @@ -177,37 +178,35 @@ export const ChartColorInputs: FC = memo(({ chartConfig, return ( <> - {showBoxFillColor && ( + {showAnyColorOptions && (
-
- - -
-
- )} - {showLineColor && ( -
-
- - -
-
- )} - {showPointFillColor && ( -
-
- - -
+ {showBoxFillColor && ( +
+ + +
+ )} + {showLineColor && ( +
+ + +
+ )} + {showPointFillColor && ( +
+ + +
+ )}
)} {showColorPaletteScale && ( @@ -363,7 +362,7 @@ export const SeriesLineStyleInput: FC = memo(({ chart
{selectedSeries && (
-
+
Color
= memo(({ chart value={seriesOptionMap[selectedSeries]?.color} />
-
+
Shape
Date: Fri, 5 Dec 2025 09:00:48 -0600 Subject: [PATCH 13/31] 7.1.0-chartColorScale.2 --- packages/components/package-lock.json | 4 ++-- packages/components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index eb072c6cb2..7eeed1bc8e 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.1.0-chartColorScale.1", + "version": "7.1.0-chartColorScale.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.1.0-chartColorScale.1", + "version": "7.1.0-chartColorScale.2", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index 704fe171cb..3cb8f89915 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.1.0-chartColorScale.1", + "version": "7.1.0-chartColorScale.2", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ From 38650b2e2f58c268c5457a1726cd386ca7cc9743 Mon Sep 17 00:00:00 2001 From: cnathe Date: Fri, 5 Dec 2025 11:56:08 -0600 Subject: [PATCH 14/31] jest test for ColorPickerInput noValueText prop --- .../forms/input/ColorPickerInput.test.tsx | 5 +++++ .../ColorPickerInput.test.tsx.snap | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/components/src/internal/components/forms/input/ColorPickerInput.test.tsx b/packages/components/src/internal/components/forms/input/ColorPickerInput.test.tsx index 3d95dda462..5bf1cb2ac7 100644 --- a/packages/components/src/internal/components/forms/input/ColorPickerInput.test.tsx +++ b/packages/components/src/internal/components/forms/input/ColorPickerInput.test.tsx @@ -20,6 +20,11 @@ describe('ColorPickerInput', () => { expect(container).toMatchSnapshot(); }); + test('with noValueText', () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + test('showPicker', async () => { const { container } = render(); await userEvent.click(document.querySelector('.color-picker__button')); diff --git a/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap b/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap index 230506bbcc..50a39bb3cd 100644 --- a/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap +++ b/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap @@ -890,6 +890,27 @@ exports[`ColorPickerInput with button text 1`] = `
`; +exports[`ColorPickerInput with noValueText 1`] = ` +
+
+ +
+
+
+`; + exports[`ColorPickerInput without value 1`] = `
Date: Fri, 5 Dec 2025 14:03:07 -0600 Subject: [PATCH 15/31] jest test for ColorPickerInputs --- .../chart/ChartColorInputs.test.tsx | 256 ++++++++++++++++++ .../components/chart/ChartColorInputs.tsx | 57 ++-- 2 files changed, 287 insertions(+), 26 deletions(-) create mode 100644 packages/components/src/internal/components/chart/ChartColorInputs.test.tsx diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.test.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.test.tsx new file mode 100644 index 0000000000..46fbf98744 --- /dev/null +++ b/packages/components/src/internal/components/chart/ChartColorInputs.test.tsx @@ -0,0 +1,256 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { ChartConfig } from './models'; +import { LABKEY_VIS } from '../../constants'; + +import { ChartColorInputs, ShapeOptionRenderer, SeriesOptionRenderer, showColorOption } from './ChartColorInputs'; +import {makeTestQueryModel} from "../../../public/QueryModel/testUtils"; +import {SchemaQuery} from "../../../public/SchemaQuery"; + +LABKEY_VIS = { + Scale: { + ShapeMap: { circle: jest.fn }, + }, +}; + +describe('showColorOption', () => { + test('boxFillColor', () => { + expect(showColorOption({ renderType: 'bar_chart' } as ChartConfig, 'boxFillColor')).toBe(true); + expect(showColorOption({ renderType: 'box_plot' } as ChartConfig, 'boxFillColor')).toBe(true); + expect(showColorOption({ renderType: 'line_plot' } as ChartConfig, 'boxFillColor')).toBe(false); + expect(showColorOption({ renderType: 'scatter_plot' } as ChartConfig, 'boxFillColor')).toBe(false); + expect(showColorOption({ renderType: 'pie_chart' } as ChartConfig, 'boxFillColor')).toBe(false); + + expect( + showColorOption( + { renderType: 'bar_chart', measures: { xSub: { name: 'test' } } } as ChartConfig, + 'boxFillColor' + ) + ).toBe(false); + }); + + test('colorPaletteScale', () => { + expect(showColorOption({ renderType: 'bar_chart' } as ChartConfig, 'colorPaletteScale')).toBe(false); + expect(showColorOption({ renderType: 'box_plot' } as ChartConfig, 'colorPaletteScale')).toBe(false); + expect(showColorOption({ renderType: 'line_plot' } as ChartConfig, 'colorPaletteScale')).toBe(false); + expect(showColorOption({ renderType: 'scatter_plot' } as ChartConfig, 'colorPaletteScale')).toBe(false); + expect(showColorOption({ renderType: 'pie_chart' } as ChartConfig, 'colorPaletteScale')).toBe(true); + + expect( + showColorOption( + { renderType: 'line_plot', measures: { series: { name: 'test' } } } as ChartConfig, + 'colorPaletteScale' + ) + ).toBe(true); + expect( + showColorOption( + { renderType: 'bar_chart', measures: { xSub: { name: 'test' } } } as ChartConfig, + 'colorPaletteScale' + ) + ).toBe(true); + expect( + showColorOption( + { renderType: 'box_plot', measures: { color: { name: 'test' } } } as ChartConfig, + 'colorPaletteScale' + ) + ).toBe(true); + expect( + showColorOption( + { renderType: 'scatter_plot', measures: { color: { name: 'test' } } } as ChartConfig, + 'colorPaletteScale' + ) + ).toBe(true); + }); + + test('lineColor', () => { + expect(showColorOption({ renderType: 'bar_chart' } as ChartConfig, 'lineColor')).toBe(true); + expect(showColorOption({ renderType: 'box_plot' } as ChartConfig, 'lineColor')).toBe(true); + expect(showColorOption({ renderType: 'line_plot' } as ChartConfig, 'lineColor')).toBe(false); + expect(showColorOption({ renderType: 'scatter_plot' } as ChartConfig, 'lineColor')).toBe(false); + expect(showColorOption({ renderType: 'pie_chart' } as ChartConfig, 'lineColor')).toBe(false); + + expect( + showColorOption( + { renderType: 'bar_chart', measures: { xSub: { name: 'test' } } } as ChartConfig, + 'lineColor' + ) + ).toBe(false); + }); + + test('pointFillColor', () => { + expect(showColorOption({ renderType: 'bar_chart' } as ChartConfig, 'pointFillColor')).toBe(false); + expect(showColorOption({ renderType: 'box_plot' } as ChartConfig, 'pointFillColor')).toBe(true); + expect(showColorOption({ renderType: 'line_plot' } as ChartConfig, 'pointFillColor')).toBe(true); + expect(showColorOption({ renderType: 'scatter_plot' } as ChartConfig, 'pointFillColor')).toBe(true); + expect(showColorOption({ renderType: 'pie_chart' } as ChartConfig, 'pointFillColor')).toBe(false); + + expect( + showColorOption( + { renderType: 'line_plot', measures: { series: { name: 'test' } } } as ChartConfig, + 'pointFillColor' + ) + ).toBe(false); + expect( + showColorOption( + { renderType: 'box_plot', measures: { color: { name: 'test' } } } as ChartConfig, + 'pointFillColor' + ) + ).toBe(false); + expect( + showColorOption( + { renderType: 'scatter_plot', measures: { color: { name: 'test' } } } as ChartConfig, + 'pointFillColor' + ) + ).toBe(false); + }); +}); + +describe('ShapeOptionRenderer', () => { + test('isValueRenderer false', () => { + render(); + expect(document.querySelectorAll('.chart-builder-type-option')).toHaveLength(1); + expect(document.querySelectorAll('.chart-builder-type-option--value')).toHaveLength(0); + }); + + test('isValueRenderer true', () => { + render(); + expect(document.querySelectorAll('.chart-builder-type-option')).toHaveLength(1); + expect(document.querySelectorAll('.chart-builder-type-option--value')).toHaveLength(1); + }); +}); + +describe('SeriesOptionRenderer', () => { + test('isValueRenderer false', () => { + render(); + expect(document.querySelectorAll('.chart-builder-type-option')).toHaveLength(1); + expect(document.querySelectorAll('.chart-builder-type-option--value')).toHaveLength(0); + }); + + test('isValueRenderer true', () => { + render(); + expect(document.querySelectorAll('.chart-builder-type-option')).toHaveLength(1); + expect(document.querySelectorAll('.chart-builder-type-option--value')).toHaveLength(1); + }); + + test('without seriesOptionMap value', () => { + render(); + expect(document.querySelector('.chart-builder-type-option').textContent).toBe('A series1'); + expect(document.querySelectorAll('.color-icon__chip-small')).toHaveLength(0); + expect(document.querySelectorAll('i')).toHaveLength(0); + expect(document.querySelectorAll('.letter-icon')).toHaveLength(1); + }); + + test('with seriesOptionMap value', () => { + render( + + ); + expect(document.querySelector('.chart-builder-type-option').textContent).toBe(' series1'); + expect(document.querySelectorAll('.color-icon__chip-small')).toHaveLength(1); + expect(document.querySelectorAll('i')).toHaveLength(1); + expect(document.querySelector('i').getAttribute('style')).toBe('background-color: red;'); + expect(document.querySelectorAll('.letter-icon')).toHaveLength(0); + }); +}); + +describe('ChartColorInputs', () => { + const model = makeTestQueryModel(new SchemaQuery('schema', 'query'), undefined, [], 0); + + test('default bar chart', () => { + render( + + ); + expect(document.querySelectorAll('.row')).toHaveLength(1); + expect(document.querySelectorAll('.color-picker')).toHaveLength(2); + expect(document.querySelectorAll('.select-input')).toHaveLength(0); + }); + + test('default pie chart', () => { + render( + + ); + expect(document.querySelectorAll('.row')).toHaveLength(2); + expect(document.querySelectorAll('.color-picker')).toHaveLength(0); + expect(document.querySelectorAll('.select-input')).toHaveLength(1); + }); + + test('default box plot', () => { + render( + + ); + expect(document.querySelectorAll('.row')).toHaveLength(1); + expect(document.querySelectorAll('.color-picker')).toHaveLength(3); + expect(document.querySelectorAll('.select-input')).toHaveLength(0); + }); + + test('default scatter plot', () => { + render( + + ); + expect(document.querySelectorAll('.row')).toHaveLength(1); + expect(document.querySelectorAll('.color-picker')).toHaveLength(1); + expect(document.querySelectorAll('.select-input')).toHaveLength(0); + }); + + test('scatter plot with color', () => { + render( + + ); + expect(document.querySelectorAll('.row')).toHaveLength(2); + expect(document.querySelectorAll('.color-picker')).toHaveLength(0); + expect(document.querySelectorAll('.select-input')).toHaveLength(1); + }); + + test('default line plot', () => { + render( + + ); + expect(document.querySelectorAll('.row')).toHaveLength(1); + expect(document.querySelectorAll('.color-picker')).toHaveLength(1); + expect(document.querySelectorAll('.select-input')).toHaveLength(0); + }); + + test('line plot with series', () => { + render( + + ); + expect(document.querySelectorAll('.row')).toHaveLength(2); + expect(document.querySelectorAll('.color-picker')).toHaveLength(0); + expect(document.querySelectorAll('.select-input')).toHaveLength(1); + }); +}); diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.tsx index 63915a1367..63c9aeec78 100644 --- a/packages/components/src/internal/components/chart/ChartColorInputs.tsx +++ b/packages/components/src/internal/components/chart/ChartColorInputs.tsx @@ -11,7 +11,8 @@ import { ColorIcon } from '../base/ColorIcon'; import { LABKEY_VIS } from '../../constants'; import { RemoveEntityButton } from '../buttons/RemoveEntityButton'; -const showColorOption = function (chartConfig: ChartConfig, optionName: string): boolean { +// export for jest testing +export const showColorOption = function (chartConfig: ChartConfig, optionName: string): boolean { const chartType = chartConfig.renderType; const isBarChart = chartType === 'bar_chart'; const isBoxPlot = chartType === 'box_plot'; @@ -50,7 +51,9 @@ interface ShapeOptionRendererProps { isValueRenderer: boolean; name: string; } -const ShapeOptionRenderer: FC = memo(({ name, isValueRenderer }) => { + +// export for jest testing +export const ShapeOptionRenderer: FC = memo(({ name, isValueRenderer }) => { const size = 10; const iconSize = name === 'diamond' ? size / 2.5 : size / 2; const icon = LABKEY_VIS.Scale.ShapeMap[name](iconSize); @@ -78,24 +81,30 @@ interface SeriesOptionRendererProps { name: string; seriesOptionMap: Record>; } -const SeriesOptionRenderer: FC = memo(({ name, seriesOptionMap, isValueRenderer }) => { - const value = seriesOptionMap?.[name]?.color; - const className = classNames('chart-builder-type-option', { 'chart-builder-type-option--value': isValueRenderer }); - return ( - - {value && ( - <> - {name} - - )} - {!value && ( - <> - {name} - - )} - - ); -}); + +// export for jest testing +export const SeriesOptionRenderer: FC = memo( + ({ name, seriesOptionMap, isValueRenderer }) => { + const value = seriesOptionMap?.[name]?.color; + const className = classNames('chart-builder-type-option', { + 'chart-builder-type-option--value': isValueRenderer, + }); + return ( + + {value && ( + <> + {name} + + )} + {!value && ( + <> + {name} + + )} + + ); + } +); SeriesOptionRenderer.displayName = 'SeriesOptionRenderer'; function seriesOptionRenderer(option, seriesOptionMap) { @@ -244,7 +253,7 @@ interface SeriesLineStyleInputProps { model: QueryModel; setChartConfig: ChartConfigSetter; } -export const SeriesLineStyleInput: FC = memo(({ chartConfig, model, setChartConfig }) => { +const SeriesLineStyleInput: FC = memo(({ chartConfig, model, setChartConfig }) => { const [distinctSeriesOptions, setDistinctSeriesOptions] = useState<{ label: string; value: string }[]>(); const [selectedSeries, setSelectedSeries] = useState(); const [seriesOptionMap, setSeriesOptionMap] = useState>( @@ -397,10 +406,6 @@ export const SeriesLineStyleInput: FC = memo(({ chart SeriesLineStyleInput.displayName = 'SeriesLineStyleInput'; const LetterIcon: FC<{ letter: string }> = ({ letter }) => { - return ( -
- {letter} -
- ); + return
{letter}
; }; LetterIcon.displayName = 'LetterIcon'; From d4feea1c0fdb1e63249d4aae12ed2f751c3ec7a8 Mon Sep 17 00:00:00 2001 From: cnathe Date: Mon, 8 Dec 2025 08:06:50 -0600 Subject: [PATCH 16/31] ColorPickerInput.tsx data-name attr for test locator --- .../src/internal/components/forms/input/ColorPickerInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx b/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx index 5d9b7b55a3..d48eeb430c 100644 --- a/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx +++ b/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx @@ -119,7 +119,7 @@ export const ColorPickerInput: FC = memo(props => { const compactPicker = ; return ( -
+
{text !== undefined && } diff --git a/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap b/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap index 50a39bb3cd..f179ada534 100644 --- a/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap +++ b/packages/components/src/internal/components/forms/input/__snapshots__/ColorPickerInput.test.tsx.snap @@ -890,27 +890,6 @@ exports[`ColorPickerInput with button text 1`] = `
`; -exports[`ColorPickerInput with noValueText 1`] = ` -
-
- -
-
-
-`; - exports[`ColorPickerInput without value 1`] = `
- None + + None + diff --git a/packages/components/src/theme/charts.scss b/packages/components/src/theme/charts.scss index 51508e217f..aaa48b87d2 100644 --- a/packages/components/src/theme/charts.scss +++ b/packages/components/src/theme/charts.scss @@ -213,7 +213,7 @@ .chart-settings { flex: 1; - min-width: 330px; + min-width: 350px; border-right: solid 1px $gray-border-light; padding: 15px; overflow-y: auto; @@ -359,3 +359,17 @@ .curve-fit-value-cell { text-align: right; } + +.letter-icon { + display: inline-flex; + width: 12px; + height: 12px; + font-size: 10px; + line-height: 12px; + align-items: center; + justify-content: center; + vertical-align: text-top; + border: 1px solid $gray; + border-radius: 2px; + margin-top: 1px; +} diff --git a/packages/components/src/theme/form.scss b/packages/components/src/theme/form.scss index 73e0a6cf80..28a2bb97dd 100644 --- a/packages/components/src/theme/form.scss +++ b/packages/components/src/theme/form.scss @@ -234,9 +234,14 @@ textarea.form-control { .color-picker { position: relative; }; +.color-picker__button { + padding: 8px 12px; +} .color-picker__button > i { display: inline-block; margin-left: 0.5em; + color: $border-color; + font-weight: bold; } .color-picker__chip { display: inline-block; @@ -273,6 +278,9 @@ textarea.form-control { display: inline-block; padding-left: 4px; } +.color-picker__placeholder { + color: #808080; // match select input placeholder text color +} .color-icon__circle-small { display: inline-block; @@ -298,20 +306,6 @@ textarea.form-control { width: 16px; } -.letter-icon { - display: inline-flex; - width: 12px; - height: 12px; - font-size: 10px; - line-height: 12px; - align-items: center; - justify-content: center; - vertical-align: text-top; - border: 1px solid $gray; - border-radius: 2px; - margin-top: 1px; -} - .content-form { label { font-weight: normal; From 4433d9550208b8f3b943b8525198e9421e0e416e Mon Sep 17 00:00:00 2001 From: cnathe Date: Tue, 9 Dec 2025 11:18:08 -0600 Subject: [PATCH 25/31] 7.1.1-chartColorScale.1 --- packages/components/package-lock.json | 4 ++-- packages/components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index 30c111a88e..b34de25c40 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.1.1-chartColorScale.0", + "version": "7.1.1-chartColorScale.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.1.1-chartColorScale.0", + "version": "7.1.1-chartColorScale.1", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index 98c00baff2..a8fa6e6142 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.1.1-chartColorScale.0", + "version": "7.1.1-chartColorScale.1", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ From b792a5f35baa367a93e6a590a51f9935a70a0db8 Mon Sep 17 00:00:00 2001 From: cnathe Date: Tue, 9 Dec 2025 11:19:32 -0600 Subject: [PATCH 26/31] jest snapshot updates --- .../SampleTypePropertiesPanel.test.tsx.snap | 16 ++++++++++++---- .../__snapshots__/ColorPickerInput.test.tsx.snap | 12 ++++++------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/components/src/internal/components/domainproperties/samples/__snapshots__/SampleTypePropertiesPanel.test.tsx.snap b/packages/components/src/internal/components/domainproperties/samples/__snapshots__/SampleTypePropertiesPanel.test.tsx.snap index 3234738553..c0fbacf3d4 100644 --- a/packages/components/src/internal/components/domainproperties/samples/__snapshots__/SampleTypePropertiesPanel.test.tsx.snap +++ b/packages/components/src/internal/components/domainproperties/samples/__snapshots__/SampleTypePropertiesPanel.test.tsx.snap @@ -230,9 +230,13 @@ exports[`SampleTypePropertiesPanel appPropertiesOnly 1`] = ` class="color-picker__button btn btn-default" type="button" > - None + + None +
- None + + None +
Select color...
Date: Tue, 9 Dec 2025 14:36:35 -0600 Subject: [PATCH 27/31] fix for error display on ChartSettingsPanel.tsx --- .../components/chart/ChartBuilderModal.tsx | 3 +-- .../components/chart/ChartSettingsPanel.tsx | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/components/src/internal/components/chart/ChartBuilderModal.tsx b/packages/components/src/internal/components/chart/ChartBuilderModal.tsx index 9a2cb2a6ef..f7d77e3711 100644 --- a/packages/components/src/internal/components/chart/ChartBuilderModal.tsx +++ b/packages/components/src/internal/components/chart/ChartBuilderModal.tsx @@ -14,7 +14,6 @@ import { RequiresModelAndActions } from '../../../public/QueryModel/withQueryMod import { useServerContext } from '../base/ServerContext'; import { hasPermissions } from '../base/models/User'; -import { Alert } from '../base/Alert'; import { FormButtons } from '../../FormButtons'; import { getContainerFilterForFolder } from '../../query/api'; @@ -354,13 +353,13 @@ export const ChartBuilderModal: FC = memo(({ actions, mo onCancel={onCancel} title={savedChartModel ? 'Edit Chart' : 'Create Chart'} > - {error && {error}} = memo(props => { - const { allowInherit, canShare, chartConfig, chartType, chartModel, isNew, model, setChartConfig, setChartModel } = - props; + const { + allowInherit, + canShare, + chartConfig, + chartType, + chartModel, + error, + isNew, + model, + setChartConfig, + setChartModel, + } = props; const showTrendline = hasTrendline(chartType); const fields = chartType.fields.filter(f => f.name !== 'trendline'); @@ -316,6 +328,7 @@ export const ChartSettingsPanel: FC = memo(props => { return (
+ {error && {error}}

Settings

From 568d3b8fcb1a437cbfa41f48dcd43d3b803ac05d Mon Sep 17 00:00:00 2001 From: cnathe Date: Tue, 9 Dec 2025 14:38:55 -0600 Subject: [PATCH 28/31] [Blank] -> n/a --- .../src/internal/components/chart/ChartColorInputs.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.tsx index 457b9cfba4..c180211228 100644 --- a/packages/components/src/internal/components/chart/ChartColorInputs.tsx +++ b/packages/components/src/internal/components/chart/ChartColorInputs.tsx @@ -283,8 +283,8 @@ const SeriesLineStyleInput: FC = memo(({ chartConfig, // map response.values to SelectOption format const options = response.values.map(value => ({ - label: value === null ? '[Blank]' : value.toString(), // TODO verify this - value: value === null ? '[Blank]' : value.toString(), + label: value === null ? 'n/a' : value.toString(), + value: value === null ? 'n/a' : value.toString(), })); setDistinctSeriesOptions(options); } catch (error) { From c7c908874dbd2209a3af9ec86f83f0fa8dd94d58 Mon Sep 17 00:00:00 2001 From: cnathe Date: Tue, 9 Dec 2025 14:44:44 -0600 Subject: [PATCH 29/31] npm run lint-branch-fix --- .../chart/ChartColorInputs.test.tsx | 34 ++++++++++--------- .../components/chart/ChartColorInputs.tsx | 10 ++++-- .../forms/input/ColorPickerInput.test.tsx | 14 ++++---- .../forms/input/ColorPickerInput.tsx | 23 ++++--------- 4 files changed, 40 insertions(+), 41 deletions(-) diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.test.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.test.tsx index 46fbf98744..6da5532218 100644 --- a/packages/components/src/internal/components/chart/ChartColorInputs.test.tsx +++ b/packages/components/src/internal/components/chart/ChartColorInputs.test.tsx @@ -3,9 +3,9 @@ import { render } from '@testing-library/react'; import { ChartConfig } from './models'; import { LABKEY_VIS } from '../../constants'; -import { ChartColorInputs, ShapeOptionRenderer, SeriesOptionRenderer, showColorOption } from './ChartColorInputs'; -import {makeTestQueryModel} from "../../../public/QueryModel/testUtils"; -import {SchemaQuery} from "../../../public/SchemaQuery"; +import { ChartColorInputs, SeriesOptionRenderer, ShapeOptionRenderer, showColorOption } from './ChartColorInputs'; +import { makeTestQueryModel } from '../../../public/QueryModel/testUtils'; +import { SchemaQuery } from '../../../public/SchemaQuery'; LABKEY_VIS = { Scale: { @@ -141,13 +141,7 @@ describe('SeriesOptionRenderer', () => { }); test('with seriesOptionMap value', () => { - render( - - ); + render(); expect(document.querySelector('.chart-builder-type-option').textContent).toBe(' series1'); expect(document.querySelectorAll('.color-icon__chip-small')).toHaveLength(1); expect(document.querySelectorAll('i')).toHaveLength(1); @@ -214,7 +208,13 @@ describe('ChartColorInputs', () => { test('scatter plot with color', () => { render( @@ -240,11 +240,13 @@ describe('ChartColorInputs', () => { test('line plot with series', () => { render( diff --git a/packages/components/src/internal/components/chart/ChartColorInputs.tsx b/packages/components/src/internal/components/chart/ChartColorInputs.tsx index c180211228..6ad57a187a 100644 --- a/packages/components/src/internal/components/chart/ChartColorInputs.tsx +++ b/packages/components/src/internal/components/chart/ChartColorInputs.tsx @@ -140,9 +140,15 @@ export const ChartColorInputs: FC = memo(({ chartConfig, const lineColor = chartConfig.geomOptions.lineColor as string; const showLineColor = useMemo(() => showColorOption(chartConfig, COLOR_OPTIONS.LINE_COLOR), [chartConfig]); const pointFillColor = chartConfig.geomOptions.pointFillColor as string; - const showPointFillColor = useMemo(() => showColorOption(chartConfig, COLOR_OPTIONS.POINT_FILL_COLOR), [chartConfig]); + const showPointFillColor = useMemo( + () => showColorOption(chartConfig, COLOR_OPTIONS.POINT_FILL_COLOR), + [chartConfig] + ); const colorPaletteScale = chartConfig.geomOptions.colorPaletteScale; - const showColorPaletteScale = useMemo(() => showColorOption(chartConfig, COLOR_OPTIONS.COLOR_PALETTE_SCALE), [chartConfig]); + const showColorPaletteScale = useMemo( + () => showColorOption(chartConfig, COLOR_OPTIONS.COLOR_PALETTE_SCALE), + [chartConfig] + ); const showSeriesLineStyle = isLinePlot && chartConfig.measures?.series !== undefined; const showAnyColorOptions = showBoxFillColor || showLineColor || showPointFillColor; diff --git a/packages/components/src/internal/components/forms/input/ColorPickerInput.test.tsx b/packages/components/src/internal/components/forms/input/ColorPickerInput.test.tsx index cded5b29de..13f22ed08d 100644 --- a/packages/components/src/internal/components/forms/input/ColorPickerInput.test.tsx +++ b/packages/components/src/internal/components/forms/input/ColorPickerInput.test.tsx @@ -6,39 +6,39 @@ import { ColorPickerInput } from './ColorPickerInput'; describe('ColorPickerInput', () => { test('default props', () => { - const { container } = render(); + const { container } = render(); expect(container).toMatchSnapshot(); }); test('without value', () => { - const { container } = render(); + const { container } = render(); expect(container).toMatchSnapshot(); }); test('with button text', () => { - const { container } = render(); + const { container } = render(); expect(container).toMatchSnapshot(); }); test('with placeholder', () => { - render(); + render(); expect(document.querySelector('.color-picker__button')?.textContent).toBe('Auto'); expect(document.querySelectorAll('.color-picker__placeholder')).toHaveLength(1); }); test('showPicker', async () => { - const { container } = render(); + const { container } = render(); await userEvent.click(document.querySelector('.color-picker__button')); expect(container).toMatchSnapshot(); }); test('allowRemove', () => { - const { container } = render(); + const { container } = render(); expect(container).toMatchSnapshot(); }); test('disabled', () => { - const { container } = render(); + const { container } = render(); expect(container).toMatchSnapshot(); }); }); diff --git a/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx b/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx index 5b97d158c5..17f246ad78 100644 --- a/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx +++ b/packages/components/src/internal/components/forms/input/ColorPickerInput.tsx @@ -80,16 +80,7 @@ interface Props { } export const ColorPickerInput: FC = memo(props => { - const { - allowRemove, - colors = DEFAULT_COLORS, - disabled, - name, - onChange, - text, - value, - placeholder = 'None', - } = props; + const { allowRemove, colors = DEFAULT_COLORS, disabled, name, onChange, text, value, placeholder = 'None' } = props; const buttonRef = useRef(null); const [fixedTop, setFixedTop] = useState(); const [fixedLeft, setFixedLeft] = useState(); @@ -116,31 +107,31 @@ export const ColorPickerInput: FC = memo(props => { // if value doesn't start with '#', add it const value_ = value && !value.startsWith('#') ? `#${value}` : value; - const compactPicker = ; + const compactPicker = ; return (
- {text !== undefined && } + {text !== undefined && } {allowRemove && value_ && !disabled && ( - + )}
From 1c0652d95331ba1c370299ef19da02c5603fc099 Mon Sep 17 00:00:00 2001 From: cnathe Date: Tue, 9 Dec 2025 14:45:15 -0600 Subject: [PATCH 30/31] Update release notes with version number and release date --- packages/components/releaseNotes/components.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index 517136bf81..97861c8f53 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,8 +1,8 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages -### version TBD -*Released*: TBD December 2025 +### version 7.2.0 +*Released*: 9 December 2025 - Chart builder updates for series color scale options - ChartColorInputs for single color geomOptions and series specific color and shape value map - ChartConfig measuresOptions to store per series mapping object From eb5ac592bb5f4f00ffa10679dac029b85779a3ae Mon Sep 17 00:00:00 2001 From: cnathe Date: Tue, 9 Dec 2025 14:45:47 -0600 Subject: [PATCH 31/31] 7.2.0 --- packages/components/package-lock.json | 4 ++-- packages/components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index b34de25c40..bbf591d762 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.1.1-chartColorScale.1", + "version": "7.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.1.1-chartColorScale.1", + "version": "7.2.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index a8fa6e6142..be5e9a96e2 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.1.1-chartColorScale.1", + "version": "7.2.0", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [