Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX:[DEV-2183] Charts Bottom Legend - Reverse Labels not working #1824

Merged
merged 8 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"showSuppressedSymbol": true,
"show": true
},

"orientation": "vertical",
"legend": {
"hide": false,
Expand Down
4 changes: 4 additions & 0 deletions packages/chart/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@
<!-- <div class="react-container" data-config="/https://www.cdc.gov/poxvirus/mpox/modules/data-viz/mpx-age-sex1.json"></div> -->

<!-- TESTS DATE EXCLUSIONS -->
<!-- <div
class="react-container"
data-config="/examples/feature/tests-date-exclusions/date-exclusions-config.json"
></div> -->
<!-- <div class="react-container" data-config="/examples/feature/boxplot/boxplot.json"></div> -->
<!-- <div class="react-container" data-config="/examples/feature/tests-case-rate/case-rate-example-config.json"></div> -->

Expand Down
4 changes: 3 additions & 1 deletion packages/chart/src/components/EditorPanel/EditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
12 changes: 3 additions & 9 deletions packages/chart/src/components/Legend/Legend.Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -50,14 +50,12 @@ const Legend: React.FC<LegendProps> = 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)
}

Expand Down Expand Up @@ -196,11 +194,7 @@ const Legend: React.FC<LegendProps> = forwardRef(
})}
</div>

<LegendSuppression
config={config}
isLegendBottom={isLegendBottom}
setHasSuppression={setHasSuppression}
/>
<LegendSuppression config={config} isLegendBottom={isLegendBottom} />
</>
)
}}
Expand Down
4 changes: 1 addition & 3 deletions packages/chart/src/components/Legend/Legend.Suppression.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface LegendProps {
isLegendBottom: boolean
}

const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom, setHasSuppression }) => {
const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom }) => {
const { preliminaryData, visualizationType, visualizationSubType, legend } = config

const hasOpenCircleEffects = () =>
Expand Down Expand Up @@ -105,8 +105,6 @@ const LegendSuppression: React.FC<LegendProps> = ({ config, isLegendBottom, setH
config.visualizationSubType !== 'stacked' &&
preliminaryData?.some(pd => pd.label && pd.type === 'suppression' && pd.value && (pd?.style || pd.symbol))

setHasSuppression(shouldShowSuppressedInfo())

return (
<React.Fragment>
{hasOpenCircleEffects() && (
Expand Down
58 changes: 32 additions & 26 deletions packages/chart/src/components/Legend/helpers/createFormatLabels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -93,31 +109,21 @@ export const createFormatLabels =
return reverseLabels(seriesLabels)
}

// DEV-4161: replaceable series name in the legend
adamdoe marked this conversation as resolved.
Show resolved Hide resolved
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 = []

Expand Down
60 changes: 20 additions & 40 deletions packages/chart/src/components/Legend/helpers/getLegendClasses.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
7 changes: 5 additions & 2 deletions packages/chart/src/components/Legend/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
26 changes: 4 additions & 22 deletions packages/chart/src/scss/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,6 @@
overflow-y: auto;
}

.d-flex {
adamdoe marked this conversation as resolved.
Show resolved Hide resolved
display: flex;
}
.flex-column-reverse {
adamdoe marked this conversation as resolved.
Show resolved Hide resolved
flex-direction: column-reverse;
}

.cdc-open-viz-module.type-dashboard {
.cdc-open-viz-module.type-chart.isEditor {
.cdc-chart-inner-container {
Expand Down Expand Up @@ -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;
Expand All @@ -208,6 +190,10 @@
flex-basis: auto;
}
}

&.double-column.reverse-items div.legend-item:last-child {
margin-bottom: 0.2rem !important;
}
}

.legend-item {
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/components/DataTable/helpers/customSort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading