diff --git a/.changeset/dark-trams-fry.md b/.changeset/dark-trams-fry.md
new file mode 100644
index 0000000000..5643a8e695
--- /dev/null
+++ b/.changeset/dark-trams-fry.md
@@ -0,0 +1,5 @@
+---
+'@lg-charts/core': minor
+---
+
+Small values including zero will show at least a thin bar (1px line minimum) on Bar charts for visibility. Bars with zero values are rendered with 30% opacity to visually differentiate them from bars with small non-zero values.
diff --git a/charts/core/src/BarChart.stories.tsx b/charts/core/src/BarChart.stories.tsx
index 0d0afb6072..290f292491 100644
--- a/charts/core/src/BarChart.stories.tsx
+++ b/charts/core/src/BarChart.stories.tsx
@@ -208,3 +208,69 @@ export const BarWithCategoryAxisLabel: StoryObj<{
);
},
};
+
+export const BarWithMinimumHeight: StoryObj<{
+ barMinHeight: number;
+}> = {
+ args: {
+ barMinHeight: 1,
+ },
+ argTypes: {
+ barMinHeight: {
+ control: { type: 'number', min: 0, max: 10, step: 1 },
+ description: 'Minimum height of bars in pixels',
+ },
+ },
+ render: ({ barMinHeight }) => {
+ // Create data with extreme value differences to demonstrate the feature
+ const extremeValueData = [
+ {
+ name: 'Large Values',
+ data: [
+ [new Date('2024-01-01').getTime(), 1000000000],
+ [new Date('2024-01-02').getTime(), 950000000],
+ [new Date('2024-01-03').getTime(), 1100000000],
+ [new Date('2024-01-04').getTime(), 1050000000],
+ ] as Array<[number, number]>,
+ },
+ {
+ name: 'Small Values',
+ data: [
+ [new Date('2024-01-01').getTime(), 100],
+ [new Date('2024-01-02').getTime(), 50],
+ [new Date('2024-01-03').getTime(), 200],
+ [new Date('2024-01-04').getTime(), 0],
+ ] as Array<[number, number]>,
+ },
+ {
+ name: 'Medium Values',
+ data: [
+ [new Date('2024-01-01').getTime(), 500000],
+ [new Date('2024-01-02').getTime(), 450000],
+ [new Date('2024-01-03').getTime(), 550000],
+ [new Date('2024-01-04').getTime(), 10],
+ ] as Array<[number, number]>,
+ },
+ ];
+
+ return (
+
+ {
+ const date = new Date(value);
+ return date.toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ });
+ }}
+ />
+
+
+ {extremeValueData.map(({ name, data }) => (
+
+ ))}
+
+ );
+ },
+};
diff --git a/charts/core/src/ChartTooltip/ChartTooltip.types.ts b/charts/core/src/ChartTooltip/ChartTooltip.types.ts
index ebe2591a91..b93e7335b6 100644
--- a/charts/core/src/ChartTooltip/ChartTooltip.types.ts
+++ b/charts/core/src/ChartTooltip/ChartTooltip.types.ts
@@ -8,6 +8,41 @@ import { type CallbackDataParams } from 'echarts/types/dist/shared';
*/
export type OptionDataValue = string | number | Date;
+/**
+ * Data can be in array format [x, y] or object format { value: [x, y], itemStyle: {...} }
+ * when individual data points have custom styling (e.g., zero values with reduced opacity).
+ */
+export type SeriesDataItem =
+ | Array
+ | { value: Array; itemStyle?: Record };
+
+/**
+ * Helper function to extract the data array from either format.
+ * ECharts passes data differently depending on whether it's a simple array or an object with itemStyle.
+ */
+export function getDataArray(
+ data: SeriesDataItem | undefined,
+): Array | undefined {
+ if (!data) return undefined;
+
+ // Check if data is an object with a 'value' property (object format)
+ if (
+ typeof data === 'object' &&
+ !Array.isArray(data) &&
+ 'value' in data &&
+ Array.isArray(data.value)
+ ) {
+ return data.value;
+ }
+
+ // Otherwise, assume it's already an array
+ if (Array.isArray(data)) {
+ return data;
+ }
+
+ return undefined;
+}
+
export const AxisPointerType = {
None: 'none',
Line: 'line',
@@ -53,7 +88,11 @@ export interface CallbackSeriesDataPoint extends CallbackDataParams {
axisType: string;
axisValue: string | number;
axisValueLabel: string | number;
- data: Array;
+ /**
+ * Data can be in array format [x, y] or object format { value: [x, y], itemStyle: {...} }
+ * Use getDataArray() helper to extract the array from either format.
+ */
+ data: SeriesDataItem;
/**
* Echarts returns a custom color type which doesn't map to string but is one
*/
diff --git a/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.tsx b/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.tsx
index 204ade29c3..773c1bc570 100644
--- a/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.tsx
+++ b/charts/core/src/ChartTooltip/CustomTooltip/CustomTooltip.tsx
@@ -6,6 +6,8 @@ import { IconButton } from '@leafygreen-ui/icon-button';
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
import { isDefined } from '@leafygreen-ui/lib';
+import { getDataArray } from '../ChartTooltip.types';
+
import {
closeButtonStyles,
getHeaderStyles,
@@ -43,7 +45,10 @@ export function CustomTooltip({
}: CustomTooltipProps) {
const { theme } = useDarkMode(darkMode);
- if (seriesData.length === 0 || !isDefined(seriesData[0].data[0])) {
+ // Extract data array from either format (array or object with value property)
+ const firstDataArray = getDataArray(seriesData[0]?.data);
+
+ if (seriesData.length === 0 || !isDefined(firstDataArray?.[0])) {
return null;
}
diff --git a/charts/core/src/ChartTooltip/CustomTooltip/SeriesList/SeriesList.tsx b/charts/core/src/ChartTooltip/CustomTooltip/SeriesList/SeriesList.tsx
index 1bb9738377..37775bd402 100644
--- a/charts/core/src/ChartTooltip/CustomTooltip/SeriesList/SeriesList.tsx
+++ b/charts/core/src/ChartTooltip/CustomTooltip/SeriesList/SeriesList.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { SeriesName } from '@lg-charts/series-provider';
-import { OptionDataValue } from '../../ChartTooltip.types';
+import { getDataArray, OptionDataValue } from '../../ChartTooltip.types';
import { SeriesListItem } from '../SeriesListItem';
import { getSeriesListStyles } from './SeriesList.styles';
@@ -32,8 +32,11 @@ export function SeriesList({
{seriesData
.sort((a, b) => {
- const [nameA, valueA] = a.data;
- const [nameB, valueB] = b.data;
+ // Extract data arrays from either format (array or object with value property)
+ const dataArrayA = getDataArray(a.data) || [];
+ const dataArrayB = getDataArray(b.data) || [];
+ const [nameA, valueA] = dataArrayA;
+ const [nameB, valueB] = dataArrayB;
if (sort) {
return sort(
@@ -44,16 +47,20 @@ export function SeriesList({
return descendingCompareFn(valueA, valueB);
})
- .map(({ seriesName, data, color }) => (
-
- ))}
+ .map(({ seriesName, data, color }) => {
+ // Extract data array from either format
+ const dataArray = getDataArray(data) || [];
+ return (
+
+ );
+ })}
);
}
diff --git a/charts/core/src/ChartTooltip/CustomTooltip/SeriesListItem/SeriesListItem.types.ts b/charts/core/src/ChartTooltip/CustomTooltip/SeriesListItem/SeriesListItem.types.ts
index 94f54cee66..f1100cdffb 100644
--- a/charts/core/src/ChartTooltip/CustomTooltip/SeriesListItem/SeriesListItem.types.ts
+++ b/charts/core/src/ChartTooltip/CustomTooltip/SeriesListItem/SeriesListItem.types.ts
@@ -1,4 +1,7 @@
-import { CallbackSeriesDataPoint } from '../../ChartTooltip.types';
+import {
+ CallbackSeriesDataPoint,
+ OptionDataValue,
+} from '../../ChartTooltip.types';
import { SeriesListProps } from '../SeriesList/SeriesList.types';
export interface SeriesListItemProps
@@ -7,6 +10,7 @@ export interface SeriesListItemProps
'seriesValueFormatter' | 'seriesNameFormatter'
> {
seriesName?: CallbackSeriesDataPoint['seriesName'];
- data: CallbackSeriesDataPoint['data'];
+ /** Data array in [x, y] format (already extracted from object format if needed) */
+ data: Array;
color: CallbackSeriesDataPoint['color'];
}
diff --git a/charts/core/src/Series/Bar/Bar.tsx b/charts/core/src/Series/Bar/Bar.tsx
index 4138f248f9..2d987b16db 100644
--- a/charts/core/src/Series/Bar/Bar.tsx
+++ b/charts/core/src/Series/Bar/Bar.tsx
@@ -45,12 +45,33 @@ export const Bar = ({
stack,
hoverBehavior = BarHoverBehavior.None,
}: BarProps) => {
+ // Transform data to apply opacity to zero values only
+ const transformedData = React.useMemo(() => {
+ return data.map(([x, y]) => {
+ const value = y as number;
+
+ // Apply 30% opacity (0.3) to zero values to differentiate from 1px minimum height
+ if (value === 0) {
+ return {
+ value: [x, y],
+ itemStyle: {
+ opacity: 0.3,
+ },
+ };
+ }
+
+ // Keep non-zero values in array format for tooltip compatibility
+ return [x, y];
+ });
+ }, [data]);
+
const options = useCallback<
(stylingContext: StylingContext) => EChartSeriesOptions['bar']['options']
>(
stylingContext => ({
clip: false,
stack,
+ barMinHeight: 1,
emphasis: {
focus: getEmphasisFocus(hoverBehavior),
},
@@ -61,7 +82,9 @@ export const Bar = ({
[stack, hoverBehavior],
);
- return ;
+ return (
+
+ );
};
Bar.displayName = 'Bar';