Skip to content
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
@@ -0,0 +1,4 @@
Significance: minor
Type: changed

Change Geo Chart data format to handle all Google Charts data
55 changes: 39 additions & 16 deletions projects/js-packages/charts/src/charts/geo-chart/geo-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import clsx from 'clsx';
import { FC, useContext } from 'react';
import { FC, useContext, useMemo } from 'react';
import { Chart, type GoogleChartOptions } from 'react-google-charts';
/**
* Internal dependencies
Expand All @@ -21,8 +21,14 @@ const DEFAULT_BACKGROUND_COLOR = '#ffffff';
/**
* Renders a geographical chart using Google Charts GeoChart to visualize data by country.
*
* Supports the full Google Charts data format including custom tooltips, formatted values,
* and multiple data columns for maximum flexibility.
*
* Countries can be identified by full name (e.g., 'United States') or ISO 3166-1 alpha-2
* codes (e.g., 'US'). Full names are recommended for better readability in tooltips.
*
* @param props - The props for the GeoChart component
* @param props.data - Record mapping country IDs to numeric values
* @param props.data - Data in Google Charts format (array of arrays with headers)
* @param props.width - Width of the chart in pixels
* @param props.height - Height of the chart in pixels
* @param props.className - Additional CSS class name for the chart container
Expand Down Expand Up @@ -64,20 +70,37 @@ const GeoChartInternal: FC< GeoChartProps > = ( {
const defaultFillColorHex =
normalizeColorToHex( featureFillColor, null, resolveCssVariable ) || DEFAULT_FEATURE_FILL_COLOR;

// Transform data from Record<string, number> to Google Charts format
// Google Charts expects [['Country', 'Value'], ['US', 100], ['CA', 50], ...]
// Country codes must be ISO 3166-1 alpha-2 format (2-letter codes)
const chartData = [ [ 'Country', 'Value' ], ...Object.entries( data ) ];
// Check if data has HTML tooltips (column with role: 'tooltip' and p.html: true)
const hasHtmlTooltips = useMemo(
() =>
data.length > 0 &&
data[ 0 ].some(
col =>
typeof col === 'object' &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use optional chaining to simply the conditions here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No unfortunately, due to TS. Claude's explanation:

The verbose version is required because GoogleDataTableColumn can be either a string or an object. While optional chaining (?.) helps with null/undefined checks, TypeScript still needs explicit type narrowing to distinguish between strings and objects in a union type.

The issue: The data format allows columns to be defined as either:
Simple strings: 'Country'
Objects with metadata: { type: 'string', role: 'tooltip', p: { html: true } }

Why optional chaining alone doesn't work: When TypeScript sees a union type (string | object), it can't safely assume properties exist even with ?. because strings don't have those properties at all. We need the typeof check and in operator to properly narrow the type.

This is the correct TypeScript pattern for type-safe property access on union types.

col !== null &&
'role' in col &&
col.role === 'tooltip' &&
'p' in col &&
typeof col.p === 'object' &&
col.p !== null &&
'html' in col.p &&
col.p.html === true
),
[ data ]
);

const options: GoogleChartOptions = {
colorAxis: { colors: [ lightColorHex, fullColorHex ] },
backgroundColor: backgroundColorHex,
datalessRegionColor: defaultFillColorHex,
defaultColor: defaultFillColorHex,
tooltip: { trigger: 'focus' },
legend: 'none',
keepAspectRatio: true,
};
const options: GoogleChartOptions = useMemo(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

() => ( {
colorAxis: { colors: [ lightColorHex, fullColorHex ] },
backgroundColor: backgroundColorHex,
datalessRegionColor: defaultFillColorHex,
defaultColor: defaultFillColorHex,
tooltip: { trigger: 'focus', isHtml: hasHtmlTooltips },
legend: 'none',
keepAspectRatio: true,
} ),
[ lightColorHex, fullColorHex, backgroundColorHex, defaultFillColorHex, hasHtmlTooltips ]
);

return (
<div
Expand All @@ -89,7 +112,7 @@ const GeoChartInternal: FC< GeoChartProps > = ( {
chartType="GeoChart"
width={ width }
height={ height }
data={ chartData }
data={ data }
options={ options }
loader={ loadingPlaceholder }
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
chartDecorator,
sharedChartArgTypes,
ChartStoryArgs,
viewsByCountry,
themeArgTypes,
} from '../../../stories';
import GeoChart from '../geo-chart';
import type { Meta } from '@storybook/react';

type StoryArgs = ChartStoryArgs< React.ComponentProps< typeof GeoChart > >;

export const geoChartMetaArgs: Meta< StoryArgs > = {
title: 'JS Packages/Charts Library/Charts/Geo Chart',
component: GeoChart,
parameters: {
layout: 'centered',
},
decorators: [ chartDecorator ],
argTypes: {
...sharedChartArgTypes,
...themeArgTypes,
},
};

export const geoChartStoryArgs = {
data: viewsByCountry,
withPadding: false,
height: 500,
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Main component for rendering geographical data on an interactive world map using

| Prop | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| `data` | `Record<string, number>` | - | **Required.** Record mapping country ISO 3166-1 alpha-2 codes (e.g., 'US', 'CA', 'GB') to numeric values |
| `data` | `GeoData` | - | **Required.** Data in Google Charts format. First row contains column headers, subsequent rows contain data. Countries can be identified by full name (e.g., 'United States') or ISO codes (e.g., 'US'). Full names are recommended for better readability in tooltips. |
| `width` | `number` | - | **Required.** Width of the chart in pixels |
| `height` | `number` | - | **Required.** Height of the chart in pixels |
| `className` | `string` | - | Additional CSS class name for the chart container |
Expand All @@ -24,11 +24,44 @@ Main component for rendering geographical data on an interactive world map using
```typescript
interface GeoChartProps
extends Pick<BaseChartProps, 'className' | 'chartId' | 'width' | 'height'> {
data: Record<string, number>;
data: GeoData;
renderPlaceholder?: () => React.ReactNode;
}
```

## GeoData Type

The `GeoData` type uses Google Charts native data format for maximum flexibility:

```typescript
type GeoData = [GoogleDataTableColumn[], ...GoogleDataTableRow[]];
```

Supports:
- Full country names or ISO 3166-1 alpha-2 codes
- Custom tooltips (text or HTML)
- Formatted values (display format separate from actual value)
- Multiple data columns
- Cell-level properties and styling

**Basic Example:**
```typescript
const data: GeoData = [
['Country', 'Value'],
['United States', 100],
['Canada', 50],
];
```

**With Custom Tooltips:**
```typescript
const data: GeoData = [
['Country', 'Value', { type: 'string', role: 'tooltip', p: { html: true } }],
['United States', 100, '<b>USA</b><br/>100 visitors'],
['Canada', 50, '<b>Canada</b><br/>50 visitors'],
];
```

## Theme Properties

The following properties can be customized via the theme system:
Expand All @@ -39,9 +72,40 @@ The following properties can be customized via the theme system:
| `backgroundColor` | `string` | `'#FFFFFF'` | Background color of the map |
| `geoChart.featureFillColor` | `string` | `'var(--jp-white, #ffffff)'` | Fill color for countries without data values |

## Country ISO Codes

The `data` prop requires two-letter ISO 3166-1 alpha-2 country codes. Common examples:
## Country Identification

Countries can be identified in two ways:

### Full Country Names (Recommended)

Use full country names for better readability:

| Country Name |
| ------------ |
| United States |
| Canada |
| United Kingdom |
| Germany |
| France |
| Japan |
| Australia |
| Brazil |
| India |
| China |
| Mexico |
| Spain |
| Italy |
| Netherlands |
| Russia |
| South Korea |
| South Africa |
| Nigeria |
| Argentina |
| Saudi Arabia |

### ISO 3166-1 Alpha-2 Codes

Alternatively, use two-letter ISO country codes:

| Code | Country |
| ---- | ------- |
Expand All @@ -53,20 +117,8 @@ The `data` prop requires two-letter ISO 3166-1 alpha-2 country codes. Common exa
| JP | Japan |
| AU | Australia |
| BR | Brazil |
| IN | India |
| CN | China |
| MX | Mexico |
| ES | Spain |
| IT | Italy |
| NL | Netherlands |
| RU | Russia |
| KR | South Korea |
| ZA | South Africa |
| NG | Nigeria |
| AR | Argentina |
| SA | Saudi Arabia |

For a complete list, refer to the [ISO 3166-1 alpha-2 codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).

For a complete list of ISO codes, refer to the [ISO 3166-1 alpha-2 codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).

## Map Projection

Expand Down
Loading
Loading