diff --git a/packages/chart/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json b/packages/chart/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json index 5a3b2bb5f..886e2069f 100644 --- a/packages/chart/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json +++ b/packages/chart/examples/feature/tests-non-numerics/example-combo-bar-nonnumeric.json @@ -103,6 +103,7 @@ "showSuppressedSymbol": true, "show": true }, + "orientation": "vertical", "legend": { "hide": false, diff --git a/packages/chart/index.html b/packages/chart/index.html index 6359d15c6..3147751fc 100644 --- a/packages/chart/index.html +++ b/packages/chart/index.html @@ -101,6 +101,10 @@ + diff --git a/packages/chart/src/components/EditorPanel/EditorPanel.tsx b/packages/chart/src/components/EditorPanel/EditorPanel.tsx index 979f9a3cd..e5fa65167 100644 --- a/packages/chart/src/components/EditorPanel/EditorPanel.tsx +++ b/packages/chart/src/components/EditorPanel/EditorPanel.tsx @@ -3911,7 +3911,9 @@ const EditorPanel = () => { display={ ['bottom', 'top'].includes(config.legend.position) && !config.legend.hide && - config.legend.style !== 'gradient' + config.legend.style !== 'gradient' && + !config.legend.singleRow && + !config.legend.singleRow } value={config.legend.verticalSorted} section='legend' diff --git a/packages/chart/src/components/Legend/Legend.Component.tsx b/packages/chart/src/components/Legend/Legend.Component.tsx index 9b069c3d6..4ae23b194 100644 --- a/packages/chart/src/components/Legend/Legend.Component.tsx +++ b/packages/chart/src/components/Legend/Legend.Component.tsx @@ -9,7 +9,7 @@ import { getMarginTop, getGradientConfig, getMarginBottom } from './helpers/inde import { Label } from '../../types/Label' import { ChartConfig, ViewportSize } from '../../types/ChartConfig' import { ColorScale } from '../../types/ChartContext' -import { forwardRef, useState } from 'react' +import { forwardRef } from 'react' import LegendSuppression from './Legend.Suppression' import LegendGradient from '@cdc/core/components/Legend/Legend.Gradient' import { DimensionsType } from '@cdc/core/types/Dimensions' @@ -50,14 +50,12 @@ const Legend: React.FC = forwardRef( const { innerClasses, containerClasses } = getLegendClasses(config) const { runtime, legend } = config - const [hasSuppression, setHasSuppression] = useState(false) - const isLegendBottom = legend?.position === 'bottom' || (isLegendWrapViewport(currentViewport) && !legend.hide && legend?.position !== 'top') const legendClasses = { - marginBottom: getMarginBottom(isLegendBottom, config, hasSuppression), + marginBottom: getMarginBottom(isLegendBottom, config), marginTop: getMarginTop(isLegendBottom, config) } @@ -196,11 +194,7 @@ const Legend: React.FC = forwardRef( })} - + ) }} diff --git a/packages/chart/src/components/Legend/Legend.Suppression.tsx b/packages/chart/src/components/Legend/Legend.Suppression.tsx index ed665dec9..2b65c5221 100644 --- a/packages/chart/src/components/Legend/Legend.Suppression.tsx +++ b/packages/chart/src/components/Legend/Legend.Suppression.tsx @@ -7,7 +7,7 @@ interface LegendProps { isLegendBottom: boolean } -const LegendSuppression: React.FC = ({ config, isLegendBottom, setHasSuppression }) => { +const LegendSuppression: React.FC = ({ config, isLegendBottom }) => { const { preliminaryData, visualizationType, visualizationSubType, legend } = config const hasOpenCircleEffects = () => @@ -105,8 +105,6 @@ const LegendSuppression: React.FC = ({ config, isLegendBottom, setH config.visualizationSubType !== 'stacked' && preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)) - setHasSuppression(shouldShowSuppressedInfo()) - return ( {hasOpenCircleEffects() && ( diff --git a/packages/chart/src/components/Legend/helpers/createFormatLabels.tsx b/packages/chart/src/components/Legend/helpers/createFormatLabels.tsx index ce893db9e..2b710c990 100644 --- a/packages/chart/src/components/Legend/helpers/createFormatLabels.tsx +++ b/packages/chart/src/components/Legend/helpers/createFormatLabels.tsx @@ -3,13 +3,25 @@ import { FaStar } from 'react-icons/fa' import { Label } from '../../../types/Label' import { ColorScale, TransformedData } from '../../../types/ChartContext' import { ChartConfig } from '../../../types/ChartConfig' +import _ from 'lodash' export const createFormatLabels = (config: ChartConfig, tableData: Object[], data: TransformedData[], colorScale: ColorScale) => (defaultLabels: Label[]): Label[] => { - const { visualizationType, visualizationSubType, series, runtime } = config - - const reverseLabels = labels => (config.legend.reverseLabelOrder && config.legend?.position === 'bottom' ? labels.reverse() : labels) + const { visualizationType, visualizationSubType, series, runtime, legend } = config + const sortVertical = labels => + legend.verticalSorted + ? _.sortBy(_.cloneDeep(labels), label => { + const match = label.datum?.match(/-?\d+(\.\d+)?/) + return match ? parseFloat(match[0]) : Number.MAX_SAFE_INTEGER + }) + : labels + const reverseLabels = labels => { + return config.legend.reverseLabelOrder && + (config.legend?.position === 'bottom' || config.legend?.position === 'top') + ? sortVertical(labels).reverse() + : sortVertical(labels) + } const colorCode = config.legend?.colorCode if (visualizationType === 'Deviation Bar') { const [belowColor, aboveColor] = twoColorPalette[config.twoColor.palette] @@ -62,7 +74,11 @@ export const createFormatLabels = // loop through each stage/group/area on the chart and create a label config.runtime?.forecastingSeriesKeys?.map((outerGroup, index) => { return outerGroup?.stages?.map((stage, index) => { - let colorValue = sequentialPalettes[stage.color]?.[2] ? sequentialPalettes[stage.color]?.[2] : colorPalettes[stage.color]?.[2] ? colorPalettes[stage.color]?.[2] : '#ccc' + let colorValue = sequentialPalettes[stage.color]?.[2] + ? sequentialPalettes[stage.color]?.[2] + : colorPalettes[stage.color]?.[2] + ? colorPalettes[stage.color]?.[2] + : '#ccc' const newLabel = { datum: stage.key, @@ -93,31 +109,21 @@ export const createFormatLabels = return reverseLabels(seriesLabels) } - // DEV-4161: replaceable series name in the legend - const hasNewSeriesName = config.series.filter(item => !!item.name).length > 0 - if (hasNewSeriesName) { - //store unique values to Set by colorCode - const set = new Set() - - config.series.forEach(d => { - set.add(d.name || d.dataKey) - }) - - // create labels with unique values - const uniqueLabels = Array.from(set).map((val, i) => { - const newLabel = { - datum: val, - index: i, - text: val, - value: colorScale(val) - } - return newLabel - }) - + if (config.series.some(item => item.name)) { + const uniqueLabels = Array.from(new Set(config.series.map(d => d.name || d.dataKey))).map((val, i) => ({ + datum: val, + index: i, + text: val, + value: colorScale(val) + })) return reverseLabels(uniqueLabels) } - if ((config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && config.visualizationSubType === 'regular' && config.suppressedData) { + if ( + (config.visualizationType === 'Bar' || config.visualizationType === 'Combo') && + config.visualizationSubType === 'regular' && + config.suppressedData + ) { const lastIndex = defaultLabels.length - 1 let newLabels = [] diff --git a/packages/chart/src/components/Legend/helpers/getLegendClasses.ts b/packages/chart/src/components/Legend/helpers/getLegendClasses.ts index f419a1adb..aad13d357 100644 --- a/packages/chart/src/components/Legend/helpers/getLegendClasses.ts +++ b/packages/chart/src/components/Legend/helpers/getLegendClasses.ts @@ -1,56 +1,36 @@ import { ChartConfig } from './../../../types/ChartConfig' export const getLegendClasses = (config: ChartConfig) => { - const { position, singleRow, reverseLabelOrder, verticalSorted, hideBorder } = config.legend - const containerClasses = ['legend-container'] - const innerClasses = ['legend-container__inner'] - - // Handle legend positioning - switch (position) { - case 'left': - containerClasses.push('left') - break - case 'right': - containerClasses.push('right') - break - case 'bottom': - containerClasses.push('bottom') - innerClasses.push('double-column', 'bottom') - break - case 'top': - containerClasses.push('top') - innerClasses.push('double-column', 'top') - break - } + const { position, singleRow, verticalSorted, hideBorder } = config.legend - // Handle single row configuration for 'bottom' and 'top' positions - if (['bottom', 'top'].includes(position) && singleRow) { - innerClasses.push('single-row') + const containerClassMap = { + left: 'left', + right: 'right', + bottom: 'bottom', + top: 'top' } - // Reverse label order - if (reverseLabelOrder) { - innerClasses.push('d-flex', 'flex-column-reverse') + const innerClassMap = { + bottom: singleRow ? ['single-row', 'bottom'] : ['double-column', 'bottom'], + top: singleRow ? ['single-row', 'top'] : ['double-column', 'top'] } - // Vertical sorting for 'bottom' and 'top' positions + const containerClasses = ['legend-container', containerClassMap[position]].filter(Boolean) + const innerClasses = ['legend-container__inner', ...(innerClassMap[position] || [])] + + // Add vertical sorting class for 'bottom' and 'top' positions if (['bottom', 'top'].includes(position) && verticalSorted) { innerClasses.push('vertical-sorted') } - // Configure border and padding classes - if (['right', 'left'].includes(position) || !position) { - if (hideBorder.side) { - containerClasses.push('border-0') - containerClasses.push('p-0') - } else containerClasses.push('p-3') - } + // Add border and padding classes based on position and hideBorder + const shouldHideBorder = (['right', 'left'].includes(position) || !position) && hideBorder.side + const shouldHideTopBottomBorder = ['top', 'bottom'].includes(position) && hideBorder.topBottom - if (['top', 'bottom'].includes(position)) { - if (hideBorder.topBottom) { - containerClasses.push('border-0') - containerClasses.push('p-0') - } else containerClasses.push('p-3') + if (shouldHideBorder || shouldHideTopBottomBorder) { + containerClasses.push('border-0', 'p-0') + } else { + containerClasses.push('p-3') } return { diff --git a/packages/chart/src/components/Legend/helpers/index.ts b/packages/chart/src/components/Legend/helpers/index.ts index 67206abeb..e5331e598 100644 --- a/packages/chart/src/components/Legend/helpers/index.ts +++ b/packages/chart/src/components/Legend/helpers/index.ts @@ -20,9 +20,12 @@ export const getMarginTop = (isLegendBottom, config) => { } return '27px' } -export const getMarginBottom = (isLegendBottom, config, hasSuppression) => { +export const getMarginBottom = (isLegendBottom, config) => { const isLegendTop = config.legend?.position === 'top' && !config.legend.hide - + const hasSuppression = + !config.legend.hideSuppressionLink && + config.visualizationSubType !== 'stacked' && + config.preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol)) let marginBottom = 0 if (isLegendTop) marginBottom = 27 diff --git a/packages/chart/src/components/Legend/tests/getLegendClasses.test.ts b/packages/chart/src/components/Legend/tests/getLegendClasses.test.ts index 353106770..511e681fd 100644 --- a/packages/chart/src/components/Legend/tests/getLegendClasses.test.ts +++ b/packages/chart/src/components/Legend/tests/getLegendClasses.test.ts @@ -47,41 +47,24 @@ describe('getLegendClasses', () => { } const result = getLegendClasses(config) expect(result.containerClasses).toContain('bottom') - expect(result.innerClasses).toContain('double-column') expect(result.innerClasses).toContain('bottom') expect(result.innerClasses).toContain('single-row') }) - it('should return correct classes for top position with vertical sorting', () => { + it('should return correct classes for TOP position with double column', () => { const config: ChartConfig = { legend: { position: 'top', singleRow: false, reverseLabelOrder: false, - verticalSorted: true, + verticalSorted: false, hideBorder: { side: false, topBottom: false } } } const result = getLegendClasses(config) expect(result.containerClasses).toContain('top') - expect(result.innerClasses).toContain('double-column') expect(result.innerClasses).toContain('top') - expect(result.innerClasses).toContain('vertical-sorted') - }) - - it('should return correct classes for reverse label order', () => { - const config: ChartConfig = { - legend: { - position: 'bottom', - singleRow: false, - reverseLabelOrder: true, - verticalSorted: false, - hideBorder: { side: false, topBottom: false } - } - } - const result = getLegendClasses(config) - expect(result.innerClasses).toContain('d-flex') - expect(result.innerClasses).toContain('flex-column-reverse') + expect(result.innerClasses).toContain('double-column') }) it('should return correct classes for hide border side', () => { diff --git a/packages/chart/src/scss/main.scss b/packages/chart/src/scss/main.scss index dfab6cff8..d5cb49ceb 100644 --- a/packages/chart/src/scss/main.scss +++ b/packages/chart/src/scss/main.scss @@ -50,13 +50,6 @@ overflow-y: auto; } -.d-flex { - display: flex; -} -.flex-column-reverse { - flex-direction: column-reverse; -} - .cdc-open-viz-module.type-dashboard { .cdc-open-viz-module.type-chart.isEditor { .cdc-chart-inner-container { @@ -188,17 +181,6 @@ grid-template-columns: 1fr 1fr; } - &.vertical-sorted { - display: block; - - @include breakpoint(sm) { - column-count: 2; - column-width: 100%; - } - column-gap: 1.5em; - column-fill: balance; - } - &.single-row { display: flex; flex-direction: row; @@ -208,6 +190,10 @@ flex-basis: auto; } } + + &.double-column.reverse-items div.legend-item:last-child { + margin-bottom: 0.2rem !important; + } } .legend-item { @@ -263,10 +249,6 @@ } } - .legend-container__inner.flex-column-reverse div.legend-item:last-child { - margin-bottom: 0.2rem !important; - } - .dynamic-legend-list { // overide traditional legend item that uses !important .legend-item { diff --git a/packages/core/components/DataTable/helpers/customSort.ts b/packages/core/components/DataTable/helpers/customSort.ts index 35bf4e2b2..b86b6eddc 100644 --- a/packages/core/components/DataTable/helpers/customSort.ts +++ b/packages/core/components/DataTable/helpers/customSort.ts @@ -29,7 +29,7 @@ export const customSort = (a, b, sortBy, config) => { const dateA = parseDate(config.xAxis.dateParseFormat, trimmedA)?.getTime() const dateB = parseDate(config.xAxis.dateParseFormat, trimmedB)?.getTime() - console.log(dateA, dateB) + return sortBy.asc ? dateA - dateB : dateB - dateA } // Check if values are numbers