Skip to content

Commit dc7bc33

Browse files
authored
🎨 Improved y-axis scaling and country names in Analytics (#28708)
## Newsletter chart y-axis scaling The **Avg. click rate** chart on Analytics → Newsletters used a fixed-step rounding (nearest 0.1) for its y-axis, which snapped the upper bound to 10% even when the click rate was a fraction of a percent — making sub-1% bars effectively invisible. This change brackets the y-axis to the actual data: - `< 1%` → `0% – 1%` - `1% – 10%` → `0%` to the next whole percent above the max - `10% – 100%` → `0%` to the next multiple of 10 above the max The avg reference line value is included in the bracket pick, so the dashed avg line is always inside the visible range (previously it could fall outside and be hidden). Examples: | Max value | Old upper | New upper | |-----------|-----------|-----------| | 0.4% | 10% | 1% | | 3.2% | 10% | 4% | | 9.2% | 10% | 10% | | 28% | 30% | 30% | | 36% | 40% | 40% | ## Country names in Web analytics The Locations card used to render the official ISO country names from `i18n-iso-countries` ("Russian Federation", "Taiwan, Province of China", "Korea, Republic of"…), which read awkwardly. We now ask the library for the short/common form via `getName(code, 'en', {select: 'alias'})` and add a few manual overrides for countries where the library has no alias. Net result: - Russian Federation → Russia - People's Republic of China → China - Taiwan, Province of China → Taiwan - Islamic Republic of Iran → Iran - Korea, Republic of → South Korea - United States of America → United States - United Kingdom → UK - Viet Nam → Vietnam - Czech Republic → Czechia - Lao People's Democratic Republic → Laos - Moldova, Republic of → Moldova - Syrian Arab Republic → Syria - …plus a handful of similar tidy-ups ref https://linear.app/tryghost/issue/NY-1365/
1 parent cd4f5fd commit dc7bc33

7 files changed

Lines changed: 37 additions & 24 deletions

File tree

‎apps/posts/src/utils/constants.ts‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ export const STATS_RANGES = {
1414
} as const;
1515

1616
export const STATS_LABEL_MAPPINGS = {
17-
// Countries
18-
US: 'United States',
19-
TWN: 'Taiwan',
20-
TW: 'Taiwan',
21-
CN: 'China',
17+
// Countries — overrides where i18n-iso-countries has no friendly alias,
18+
// or where the alias-mode pick isn't the common short form.
19+
GB: 'United Kingdom',
20+
KR: 'South Korea',
21+
LA: 'Laos',
22+
MD: 'Moldova',
23+
SY: 'Syria',
2224

2325
// Technical
2426
'mobile-ios': 'iOS',

‎apps/posts/src/views/PostAnalytics/Web/components/locations.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {STATS_LABEL_MAPPINGS} from '@src/utils/constants';
88

99
countries.registerLocale(enLocale);
1010
const getCountryName = (label: string) => {
11-
return STATS_LABEL_MAPPINGS[label as keyof typeof STATS_LABEL_MAPPINGS] || countries.getName(label, 'en') || 'Unknown';
11+
return STATS_LABEL_MAPPINGS[label as keyof typeof STATS_LABEL_MAPPINGS] || countries.getName(label, 'en', {select: 'alias'}) || 'Unknown';
1212
};
1313

1414
interface ProcessedLocationData {

‎apps/posts/src/views/PostAnalytics/components/stats-filter.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ interface StatsFilterProps extends Omit<React.ComponentProps<typeof Filters>, 'f
2020

2121
// Helper to get country name from code
2222
const getCountryName = (code: string): string => {
23-
return STATS_LABEL_MAPPINGS[code as keyof typeof STATS_LABEL_MAPPINGS] || countries.getName(code, 'en') || code;
23+
return STATS_LABEL_MAPPINGS[code as keyof typeof STATS_LABEL_MAPPINGS] || countries.getName(code, 'en', {select: 'alias'}) || code;
2424
};
2525

2626
// Helper component for visit count badge - used by all filter options

‎apps/stats/src/utils/constants.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@ export const STATS_RANGE_OPTIONS = Object.values(STATS_RANGES);
3434
export const STATS_DEFAULT_RANGE_KEY = 2;
3535

3636
export const STATS_LABEL_MAPPINGS = {
37-
// Countries
38-
US: 'United States',
39-
TWN: 'Taiwan',
40-
TW: 'Taiwan',
37+
// Countries — overrides where i18n-iso-countries has no friendly alias,
38+
// or where the alias-mode pick isn't the common short form.
39+
GB: 'United Kingdom',
40+
KR: 'South Korea',
41+
LA: 'Laos',
42+
MD: 'Moldova',
43+
SY: 'Syria',
4144

4245
// Technical
4346
'mobile-ios': 'iOS',

‎apps/stats/src/views/Stats/Locations/components/locations-card.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {getPeriodText} from '@src/utils/chart-helpers';
1010

1111
countries.registerLocale(enLocale);
1212
const getCountryName = (label: string) => {
13-
return STATS_LABEL_MAPPINGS[label as keyof typeof STATS_LABEL_MAPPINGS] || countries.getName(label, 'en') || 'Unknown';
13+
return STATS_LABEL_MAPPINGS[label as keyof typeof STATS_LABEL_MAPPINGS] || countries.getName(label, 'en', {select: 'alias'}) || 'Unknown';
1414
};
1515

1616
// Normalize country code for flag display

‎apps/stats/src/views/Stats/Newsletters/components/newsletters-kpis.tsx‎

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -201,22 +201,30 @@ const NewsletterKPIs: React.FC<{
201201
return {barDomain: [0, 1], barTicks: [0, 1]};
202202
}
203203

204-
const minValue = Math.min(...values);
205-
const maxValue = Math.max(...values);
206-
207-
// Round to nearest 0.1
208-
const roundedMin = Math.floor(minValue * 10) / 10;
209-
const roundedMax = Math.ceil(maxValue * 10) / 10;
210-
211-
// Ensure we have some padding and don't have the same min/max
212-
const finalMin = Math.max(0, roundedMin);
213-
const finalMax = roundedMax === finalMin ? finalMin + 0.1 : roundedMax;
204+
// Include the avg line value so the y-axis always contains both the
205+
// tallest bar and the avg reference line.
206+
const avgForTab = currentTab === 'avg-open-rate' ? avgOpenRate : avgClickRate;
207+
const bucketValue = Math.max(Math.max(...values), avgForTab);
208+
209+
// Min is always 0. Upper limit:
210+
// < 1% → 1%
211+
// 1% – 10% → next whole percent above
212+
// 10% – 100% → next multiple of 10 above
213+
const finalMin = 0;
214+
let finalMax;
215+
if (bucketValue < 0.01) {
216+
finalMax = 0.01;
217+
} else if (bucketValue < 0.1) {
218+
finalMax = (Math.floor(bucketValue * 100) + 1) / 100;
219+
} else {
220+
finalMax = (Math.floor(bucketValue * 10) + 1) / 10;
221+
}
214222

215223
return {
216224
barDomain: [finalMin, finalMax],
217225
barTicks: [finalMin, finalMax]
218226
};
219-
}, [avgsData, currentTab, tabConfig]);
227+
}, [avgsData, currentTab, tabConfig, avgOpenRate, avgClickRate]);
220228

221229
if (isLoading) {
222230
return (

‎apps/stats/src/views/Stats/components/stats-filter.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ interface StatsFilterProps extends Omit<React.ComponentProps<typeof Filters>, 'f
2121

2222
// Helper to get country name from code
2323
const getCountryName = (code: string): string => {
24-
return STATS_LABEL_MAPPINGS[code as keyof typeof STATS_LABEL_MAPPINGS] || countries.getName(code, 'en') || code;
24+
return STATS_LABEL_MAPPINGS[code as keyof typeof STATS_LABEL_MAPPINGS] || countries.getName(code, 'en', {select: 'alias'}) || code;
2525
};
2626

2727
// Helper component for visit count badge - used by all filter options

0 commit comments

Comments
 (0)