diff --git a/.storybook/preview.ts b/.storybook/preview.ts index e4f4d1e..41ac159 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -15,11 +15,43 @@ const preview: Preview = { name: 'Website', styles: { width: '766px', - height: '600px', + height: '635px', + }, + type: 'desktop', + }, + small: { + name: 'Small screen', + styles: { + width: '383px', + height: '300px', + }, + type: 'desktop', + }, + large: { + name: 'Large screen', + styles: { + width: '1150px', + height: '900px' }, type: 'desktop', }, - }, + mobile: { + name: 'Mobile', + styles: { + width: '540px', + height: '960px', + }, + type: 'mobile', + }, + tablet: { + name: 'Tablet', + styles: { + width: '900px', + height: '600px', + }, + type: 'tablet', + }, + } }, }, tags: ['autodocs'] diff --git a/Changelog.md b/Changelog.md index 59d79f3..81946af 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,2 +1,9 @@ v1.3.0 (24 September 2025): -- component optional property 'showTableTitles' renamed to 'showTitles' as it now applies to both chart and table titles. Default value is true. \ No newline at end of file +- Chart component optional property 'showTableTitles' renamed to 'showTitles' as it now applies to both chart and table titles. Default value is true. + +v1.4.0 (10 November 2025): +- Chart component's optional properties' default values updated: + - 'showTitles' default changed to true. + - 'showSources' default changed to true. + - NEW: showLastUpdated defaults to false. +- Chart component's new optional property 'showLastUpdated' (boolean) added to control the visibility of the last updated information above the source information. \ No newline at end of file diff --git a/package.json b/package.json index e1e7903..03875be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@statisticsfinland/pxvisualizer", - "version": "1.3.2", + "version": "1.4.0", "description": "Component library for visualizing PxGraf data", "main": "./dist/pxv.cjs", "jestSonar": { diff --git a/src/core/chartOptions/Utility/formatters.test.ts b/src/core/chartOptions/Utility/formatters.test.ts index faa118a..405c1f2 100644 --- a/src/core/chartOptions/Utility/formatters.test.ts +++ b/src/core/chartOptions/Utility/formatters.test.ts @@ -1,5 +1,5 @@ import { DataLabelsOptions, Point, Tooltip } from "highcharts"; -import { formatLocale, getDataFormattedForChartType, getDataLabelFormatterFunction, getLineChartToolTipFormatterFunction, getToolTipFormatterFunction, parseScreenReaderFriendlyTimePeriods, shortenStringValue } from "./formatters"; +import { formatLocale, getDataFormattedForChartType, getDataLabelFormatterFunction, getLineChartToolTipFormatterFunction, getToolTipFormatterFunction, parseScreenReaderFriendlyTimePeriods, shortenStringValue, getFormattedLastUpdatedText } from "./formatters"; import { combinationValuesLinechartViewFixture, multiselectableLineChartViewFixture, simpleQuarterLinechartViewFixture } from "./fixtures/linechartViews"; import { simpleHorizontalBarchartViewFixture } from "./fixtures/horizontalbarchartViews"; import { simpleGroupHorizontalBarchartViewFixture } from "./fixtures/grouphorizontalbarchartViews"; @@ -421,4 +421,31 @@ describe('getDataFormattedForChartType tests', () => { const result: string = getDataFormattedForChartType(view, point, 'en', 2); expect(result).toBe('12.0% (123.46 kg)'); }); +}); + +describe('getFormattedLastUpdatedText tests', () => { + it('should format last updated text correctly for Finnish locale', () => { + const result = getFormattedLastUpdatedText('2025-09-23', 'fi'); + expect(result).toBe('Päivitetty: 23.9.2025'); + }); + + it('should format last updated text correctly for Swedish locale', () => { + const result = getFormattedLastUpdatedText('2025-09-23', 'sv'); + expect(result).toBe('Uppdaterad: 2025-09-23'); + }); + + it('should format last updated text correctly for English locale to British English', () => { + const result = getFormattedLastUpdatedText('2025-09-23', 'en'); + expect(result).toBe('Updated: 23/09/2025'); + }); + + it('should return undefined for undefined input', () => { + const result = getFormattedLastUpdatedText(undefined, 'fi'); + expect(result).toBeUndefined(); + }); + + it('should return undefined for invalid date', () => { + const result = getFormattedLastUpdatedText('invalid-date', 'fi'); + expect(result).toBeUndefined(); + }); }); \ No newline at end of file diff --git a/src/core/chartOptions/Utility/formatters.ts b/src/core/chartOptions/Utility/formatters.ts index 53828bb..509da58 100644 --- a/src/core/chartOptions/Utility/formatters.ts +++ b/src/core/chartOptions/Utility/formatters.ts @@ -256,4 +256,27 @@ function parsePlacementSuffix(value: string, locale: string) { } else { return `${value}.`; } +} + +/** + * Formats the last updated text based on the provided date string and locale. + * @param lastUpdated Date string representing the last updated date. + * @param locale Locale string for formatting the date. + * @returns Formatted last updated text or undefined if input is invalid. + */ +export function getFormattedLastUpdatedText(lastUpdated: string | undefined, locale: string): string | undefined { + if (!lastUpdated) return undefined; + + try { + const date = new Date(lastUpdated); + if (Number.isNaN(date.getTime())) return undefined; + + const dateLocale = locale === 'en' ? 'en-GB' : locale; // Use en-GB for English to get DD/MM/YYYY format if not specified + const formattedDate: string = Intl.DateTimeFormat(dateLocale).format(date); + + return `${Translations.lastUpdated[locale]}: ${formattedDate}`; + } catch (error) { + console.error('Error formatting date:', error); + return undefined; + } } \ No newline at end of file diff --git a/src/core/chartOptions/chartOptions.ts b/src/core/chartOptions/chartOptions.ts index 67a5e2e..7c81414 100644 --- a/src/core/chartOptions/chartOptions.ts +++ b/src/core/chartOptions/chartOptions.ts @@ -1,23 +1,39 @@ import { LegendOptions, Options, PlotSeriesDataLabelsOptions, YAxisOptions } from 'highcharts'; import { View } from "../types/view"; -import { getAxisLabelShorteningFunction, getFormattedUnits, getToolTipFormatterFunction, getScreenReaderFormatterCallbackFunction, getDataLabelFormatterFunction } from './Utility/formatters'; -import { Translations } from '../conversion/translations'; +import { getAxisLabelShorteningFunction, getFormattedUnits, getToolTipFormatterFunction, getScreenReaderFormatterCallbackFunction, getDataLabelFormatterFunction, getFormattedLastUpdatedText } from './Utility/formatters'; import { getXAxisOptions } from './Utility/timeIntervals'; import { getLinearAxisTickPositionerFunction } from './Utility/tickPositioners'; import { IChartOptions } from '../types/chartOptions'; import { buildBarChartSeries, buildColumnChartSeries } from './Utility/seriesDataBuilder'; +import { Translations } from "../conversion/translations"; export const commonChartOptions = (view: View, locale: string, options?: IChartOptions): Options => { - const showTitle: boolean = options?.showTitle ?? true; + const showTitles: boolean = options?.showTitles ?? true; + + const sourceText = Translations.source[locale]; + let creditsText = `${sourceText}: ${view.sources.map(s => s[locale]).join(', ')}`; + const hasLastUpdated = options?.showLastUpdated && view.lastUpdated; + + if (hasLastUpdated) { + const lastUpdatedText = getFormattedLastUpdatedText(view.lastUpdated, locale); + if (lastUpdatedText) { + creditsText = `${lastUpdatedText}
${sourceText}: ${view.sources.map(s => s[locale]).join(', ')}`; + } + } + return { + chart: { + spacingBottom: hasLastUpdated ? 50 : 30 // Conditional spacing based on lastUpdated presence + }, accessibility: { point: { descriptionFormatter: getScreenReaderFormatterCallbackFunction(view, locale) } }, - title: { text: showTitle ? view.header[locale] : undefined }, + title: { text: showTitles ? view.header[locale] : undefined }, subtitle: { text: view.subheaderValues.map(sv => sv[locale]).join(' | ') }, - credits: { text: `${Translations.source[locale]}: ${view.sources.map(s => s[locale]).join(', ')}` }, + credits: { enabled: false }, + caption: { text: creditsText }, tooltip: { formatter: getToolTipFormatterFunction(view, locale) }, diff --git a/src/core/chartOptions/pieChartOptions.ts b/src/core/chartOptions/pieChartOptions.ts index 090384c..2bf607d 100644 --- a/src/core/chartOptions/pieChartOptions.ts +++ b/src/core/chartOptions/pieChartOptions.ts @@ -7,7 +7,7 @@ import { buildPatternObject } from './Utility/patternFill'; export const pieChartOptions = (view: View, locale: string, options?: IChartOptions): Options => { return { - ...commonChartOptions(view, locale), + ...commonChartOptions(view, locale, options), chart: { type: 'pie' }, plotOptions: { pie: { diff --git a/src/core/conversion/fixtures/percentHorizontalBarChart.ts b/src/core/conversion/fixtures/percentHorizontalBarChart.ts index 4916be9..5d9412e 100644 --- a/src/core/conversion/fixtures/percentHorizontalBarChart.ts +++ b/src/core/conversion/fixtures/percentHorizontalBarChart.ts @@ -222,6 +222,7 @@ export const PERCENT_HORIZONTAL_BAR_CHART_VIEW: View = { "sv": "Antal, Enrumslägenhet 2022Q4 efter Område, Finansieringssätt", "en": "Number, One-room flat 2022Q4 by Region, Type of funding" }, + lastUpdated: "2023-01-19T06:00:00Z", subheaderValues: [], units: [ { @@ -595,6 +596,7 @@ export const PERCENT_HORIZONTAL_BAR_CHART_WITH_SELECTABLES_VIEW: View = { "sv": "Antal 2022Q4 efter Område, Antal rum, Finansieringssätt", "en": "Number 2022Q4 by Region, Number of rooms, Type of funding" }, + "lastUpdated": "2023-01-19T06:00:00Z", subheaderValues: [ { "fi": "Kaksiot", diff --git a/src/core/conversion/fixtures/percentVerticalBarChart.ts b/src/core/conversion/fixtures/percentVerticalBarChart.ts index 186533d..7db1def 100644 --- a/src/core/conversion/fixtures/percentVerticalBarChart.ts +++ b/src/core/conversion/fixtures/percentVerticalBarChart.ts @@ -280,6 +280,7 @@ export const PERCENT_VERTICAL_BAR_CHART_VIEW: View = { "sv": "Antal, Helsingfors, Fri finansierad 2021Q2-2022Q4 efter Antal rum", "en": "Number, Helsinki, Non subsidised 2021Q2-2022Q4 by Number of rooms" }, + lastUpdated: "2023-01-19T06:00:00Z", subheaderValues: [], units: [{ name: { @@ -843,6 +844,7 @@ export const PERCENT_VERTICAL_BAR_CHART_WITH_SELECTABLES_VIEW: View = { "sv": "Antal 2021Q4 efter Område, Antal rum, Finansieringssätt", "en": "Number 2021Q4 by Region, Number of rooms, Type of funding" }, + lastUpdated: "2023-01-19T06:00:00Z", subheaderValues: [ { "fi": "2022Q1", @@ -1241,6 +1243,7 @@ export const PERCENT_VERTICAL_BAR_CHART_PIVOTED_WITH_SELECTABLES_VIEW: View = { "sv": "Antal 2022Q4 efter Område, Antal rum, Finansieringssätt", "en": "Number 2022Q4 by Region, Number of rooms, Type of funding" }, + lastUpdated: "2023-01-19T06:00:00Z", subheaderValues: [ { "fi": "Vapaarahoitteinen", diff --git a/src/core/conversion/pxGrafDataConverter.test.ts b/src/core/conversion/pxGrafDataConverter.test.ts index a3db30b..cbef156 100644 --- a/src/core/conversion/pxGrafDataConverter.test.ts +++ b/src/core/conversion/pxGrafDataConverter.test.ts @@ -207,7 +207,7 @@ describe('PxGrafDataConverter tests, VerticalBarChart', () => { const result = convertPxGraphDataToChartOptions('fi', mockView); expect(result.chart?.type).toBe('column'); expect(result.title?.text).toBe('Lukumäärä 2015Q1 muuttujina Alue, Huoneluku, Rahoitusmuoto'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe('2015Q1 | Pääkaupunkiseutu (PKS) | Vapaarahoitteinen'); }); @@ -217,7 +217,7 @@ describe('PxGrafDataConverter tests, VerticalBarChart', () => { const result = convertPxGraphDataToChartOptions('sv', mockView); expect(result.chart?.type).toBe('column'); expect(result.title?.text).toBe('Antal 2015Q1 efter Område, Antal rum, Finansieringssätt'); - expect(result.credits?.text).toBe('Källa: PxVisualizer-sv'); + expect(result.caption?.text).toBe('Källa: PxVisualizer-sv'); expect(result.subtitle?.text).toBe('2015Q1 | Huvudstadsregionen | Fri finansierad'); }); }); @@ -229,7 +229,7 @@ describe('PxGrafDataConverter tests, BasicHorizontalBarChart', () => { const result = convertPxGraphDataToChartOptions('fi', mockView); expect(result.chart?.type).toBe('bar'); expect(result.title?.text).toBe('Tiedot 2022Q4 muuttujina Tiedot, Alue, Huoneluku'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe('Yksiöt | Lukumäärä'); }); @@ -239,7 +239,7 @@ describe('PxGrafDataConverter tests, BasicHorizontalBarChart', () => { const result = convertPxGraphDataToChartOptions('en', mockView); expect(result.chart?.type).toBe('bar'); expect(result.title?.text).toBe('Information 2022Q4 by Information, Region, Number of rooms'); - expect(result.credits?.text).toBe('Source: PxVisualizer-en'); + expect(result.caption?.text).toBe('Source: PxVisualizer-en'); expect(result.subtitle?.text).toBe('One-room flat | Number'); }); }); @@ -251,7 +251,7 @@ describe('PxGrafDataConverter tests, GroupHorizontalBarChart', () => { const result = convertPxGraphDataToChartOptions('fi', mockView); expect(result.chart?.type).toBe('bar'); expect(result.title?.text).toBe('Tiedot 2015Q1-2015Q2 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe('Pääkaupunkiseutu (PKS) | Yksiöt | Lukumäärä'); }); @@ -261,7 +261,7 @@ describe('PxGrafDataConverter tests, GroupHorizontalBarChart', () => { const result = convertPxGraphDataToChartOptions('en', mockView); expect(result.chart?.type).toBe('bar'); expect(result.title?.text).toBe('Information 2015Q1-2015Q2 by Information, Region, Number of rooms, Type of funding'); - expect(result.credits?.text).toBe('Source: PxVisualizer-en'); + expect(result.caption?.text).toBe('Source: PxVisualizer-en'); expect(result.subtitle?.text).toBe('Greater Helsinki | One-room flat | Number'); }); }); @@ -273,7 +273,7 @@ describe('PxGrafDataConverter tests, PieBarChart', () => { const result = convertPxGraphDataToChartOptions('fi', mockView); expect(result.chart?.type).toBe('pie'); expect(result.title?.text).toBe('Lukumäärä, Yksiöt 2022Q4 muuttujana Alue'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe(''); }); @@ -283,7 +283,7 @@ describe('PxGrafDataConverter tests, PieBarChart', () => { const result = convertPxGraphDataToChartOptions('en', mockView); expect(result.chart?.type).toBe('pie'); expect(result.title?.text).toBe('Number, One-room flat 2022Q4 by Region'); - expect(result.credits?.text).toBe('Source: PxVisualizer-en'); + expect(result.caption?.text).toBe('Source: PxVisualizer-en'); expect(result.subtitle?.text).toBe(''); }); }); @@ -295,7 +295,7 @@ describe('PxGrafDataConverter tests, GroupVerticalBarChart', () => { const result = convertPxGraphDataToChartOptions('fi', mockView); expect(result.chart?.type).toBe('column'); expect(result.title?.text).toBe('Tiedot 2015Q1-2015Q2 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe('Pääkaupunkiseutu (PKS) | Yksiöt | Lukumäärä'); }); @@ -305,7 +305,7 @@ describe('PxGrafDataConverter tests, GroupVerticalBarChart', () => { const result = convertPxGraphDataToChartOptions('sv', mockView); expect(result.chart?.type).toBe('column'); expect(result.title?.text).toBe('Uppgifter 2015Q1-2015Q2 efter Uppgifter, Område, Antal rum, Finansieringssätt'); - expect(result.credits?.text).toBe('Källa: PxVisualizer-sv'); + expect(result.caption?.text).toBe('Källa: PxVisualizer-sv'); expect(result.subtitle?.text).toBe('Huvudstadsregionen | Enrumslägenhet | Antal'); }); }); @@ -318,7 +318,7 @@ describe('PxGrafDataConverter tests, StackedVerticalBarChart', () => { expect(result.chart?.type).toBe('column'); expect(result.plotOptions?.column?.stacking).toBe('normal'); expect(result.title?.text).toBe('Tiedot 2015Q1-2015Q2 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe('Pääkaupunkiseutu (PKS) | Yksiöt | Lukumäärä'); }); @@ -329,7 +329,7 @@ describe('PxGrafDataConverter tests, StackedVerticalBarChart', () => { expect(result.chart?.type).toBe('column'); expect(result.plotOptions?.column?.stacking).toBe('normal'); expect(result.title?.text).toBe('Uppgifter 2015Q1-2015Q2 efter Uppgifter, Område, Antal rum, Finansieringssätt'); - expect(result.credits?.text).toBe('Källa: PxVisualizer-sv'); + expect(result.caption?.text).toBe('Källa: PxVisualizer-sv'); expect(result.subtitle?.text).toBe('Huvudstadsregionen | Enrumslägenhet | Antal'); }); }); @@ -342,7 +342,7 @@ describe('PxGrafDataConverter tests, StackedHorizontalBarChart', () => { expect(result.chart?.type).toBe('bar'); expect(result.plotOptions?.bar?.stacking).toBe('normal'); expect(result.title?.text).toBe('Tiedot 2015Q1 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe('2015Q1 | Pääkaupunkiseutu (PKS) | Yksiöt'); }); @@ -353,7 +353,7 @@ describe('PxGrafDataConverter tests, StackedHorizontalBarChart', () => { expect(result.chart?.type).toBe('bar'); expect(result.plotOptions?.bar?.stacking).toBe('normal'); expect(result.title?.text).toBe('Uppgifter 2015Q1 efter Uppgifter, Område, Antal rum, Finansieringssätt'); - expect(result.credits?.text).toBe('Källa: PxVisualizer-sv'); + expect(result.caption?.text).toBe('Källa: PxVisualizer-sv'); expect(result.subtitle?.text).toBe('2015Q1 | Huvudstadsregionen | Enrumslägenhet'); }); }); @@ -371,7 +371,7 @@ describe('PxGrafDataConverter tests, PercentVerticalBarChart', () => { expect(result.chart?.type).toBe('column'); expect(result.plotOptions?.column?.stacking).toBe('percent'); expect(result.title?.text).toBe('Lukumäärä 2021Q4 muuttujina Alue, Huoneluku, Rahoitusmuoto'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe('2022Q1 | Yksiöt'); }); @@ -387,7 +387,7 @@ describe('PxGrafDataConverter tests, PercentVerticalBarChart', () => { expect(result.chart?.type).toBe('column'); expect(result.plotOptions?.column?.stacking).toBe('percent'); expect(result.title?.text).toBe('Antal 2021Q4 efter Område, Antal rum, Finansieringssätt'); - expect(result.credits?.text).toBe('Källa: PxVisualizer-sv'); + expect(result.caption?.text).toBe('Källa: PxVisualizer-sv'); expect(result.subtitle?.text).toBe('2022Q1 | Enrumslägenhet'); }); }); @@ -405,7 +405,7 @@ describe('PxGrafDataConverter tests, PercentHorizontalBarChart', () => { expect(result.chart?.type).toBe('bar'); expect(result.plotOptions?.bar?.stacking).toBe('percent'); expect(result.title?.text).toBe('Lukumäärä 2022Q4 muuttujina Alue, Huoneluku, Rahoitusmuoto'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe('Kaksiot'); }); @@ -421,7 +421,7 @@ describe('PxGrafDataConverter tests, PercentHorizontalBarChart', () => { expect(result.chart?.type).toBe('bar'); expect(result.plotOptions?.bar?.stacking).toBe('percent'); expect(result.title?.text).toBe('Antal 2022Q4 efter Område, Antal rum, Finansieringssätt'); - expect(result.credits?.text).toBe('Källa: PxVisualizer-sv'); + expect(result.caption?.text).toBe('Källa: PxVisualizer-sv'); expect(result.subtitle?.text).toBe('Tvårumslägenhet'); }); }); @@ -434,7 +434,7 @@ describe('PxGrafDataConverter tests, PyramidChart', () => { expect(result.chart?.type).toBe('bar'); expect(result.plotOptions?.series?.stacking).toBe('normal'); expect(result.title?.text).toBe('Väestö 31.12. 2020 muuttujina Alue, Ikä, Sukupuoli'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe('KOKO MAA | 2020'); }); @@ -445,7 +445,7 @@ describe('PxGrafDataConverter tests, PyramidChart', () => { expect(result.chart?.type).toBe('bar'); expect(result.plotOptions?.series?.stacking).toBe('normal'); expect(result.title?.text).toBe('Befolkning 31.12. 2020 efter Område, Ålder, Kön'); - expect(result.credits?.text).toBe('Källa: PxVisualizer-sv'); + expect(result.caption?.text).toBe('Källa: PxVisualizer-sv'); expect(result.subtitle?.text).toBe('HELA LANDET | 2020'); }); }); @@ -457,7 +457,7 @@ describe('PxGrafDataConverter tests, ScatterPlot', () => { const result = convertPxGraphDataToChartOptions('fi', mockView); expect(result.chart?.type).toBe('scatter'); expect(result.title?.text).toBe('Tiedot 2015Q1-2022Q4 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe('Pääkaupunkiseutu (PKS) | Yksiöt | Vapaarahoitteinen'); }); @@ -467,7 +467,7 @@ describe('PxGrafDataConverter tests, ScatterPlot', () => { const result = convertPxGraphDataToChartOptions('sv', mockView); expect(result.chart?.type).toBe('scatter'); expect(result.title?.text).toBe('Uppgifter 2015Q1-2022Q4 efter Uppgifter, Område, Antal rum, Finansieringssätt'); - expect(result.credits?.text).toBe('Källa: PxVisualizer-sv'); + expect(result.caption?.text).toBe('Källa: PxVisualizer-sv'); expect(result.subtitle?.text).toBe('Huvudstadsregionen | Enrumslägenhet | Fri finansierad'); }); }); @@ -479,7 +479,7 @@ describe('PxGrafDataConverter tests, LineChart', () => { const result = convertPxGraphDataToChartOptions('fi', mockView); expect(result.chart?.type).toBe('line'); expect(result.title?.text).toBe('Neliövuokra (eur/m2), Yksiöt, Vapaarahoitteinen 2015Q1-2022Q4 muuttujana Alue'); - expect(result.credits?.text).toBe('Lähde: PxVisualizer-fi'); + expect(result.caption?.text).toBe('Lähde: PxVisualizer-fi'); expect(result.subtitle?.text).toBe(''); }); @@ -489,7 +489,7 @@ describe('PxGrafDataConverter tests, LineChart', () => { const result = convertPxGraphDataToChartOptions('sv', mockView); expect(result.chart?.type).toBe('line'); expect(result.title?.text).toBe('Kvadratmeterspris (eur/m2), Enrumslägenhet, Fri finansierad 2015Q1-2022Q4 efter Område'); - expect(result.credits?.text).toBe('Källa: PxVisualizer-sv'); + expect(result.caption?.text).toBe('Källa: PxVisualizer-sv'); expect(result.subtitle?.text).toBe(''); }); }); \ No newline at end of file diff --git a/src/core/conversion/translations/defaultTranslations.ts b/src/core/conversion/translations/defaultTranslations.ts index f8ba659..3d8b0a6 100644 --- a/src/core/conversion/translations/defaultTranslations.ts +++ b/src/core/conversion/translations/defaultTranslations.ts @@ -330,6 +330,11 @@ export const DefaultTranslations: TTranslations = { 'fi': 'Poista kuviosta symbolit', 'sv': 'Ta bort symbolerna från diagrammet', 'en': 'Remove symbols from the figure' + }, + lastUpdated: { + 'fi': 'Päivitetty', + 'sv': 'Uppdaterad', + 'en': 'Updated' } } diff --git a/src/core/conversion/translations/translationManager.test.ts b/src/core/conversion/translations/translationManager.test.ts index 2e138ee..2832461 100644 --- a/src/core/conversion/translations/translationManager.test.ts +++ b/src/core/conversion/translations/translationManager.test.ts @@ -199,6 +199,9 @@ const mockTranslationPackage: TTranslationPackage = { }, toggleAccessibilityModeOff: { 'foo': 'Piilota kuviosta symbolit', + }, + lastUpdated: { + 'foo': 'Päivitetty: {date}', } }, ArrayTranslations: { diff --git a/src/core/conversion/translations/translationTypes.ts b/src/core/conversion/translations/translationTypes.ts index aa8e56a..55186e4 100644 --- a/src/core/conversion/translations/translationTypes.ts +++ b/src/core/conversion/translations/translationTypes.ts @@ -67,6 +67,7 @@ export type TTranslations = { dataMissing: TMultiLanguageString; toggleAccessibilityModeOn: TMultiLanguageString; toggleAccessibilityModeOff: TMultiLanguageString; + lastUpdated: TMultiLanguageString; } export type TArrayTranslations = { diff --git a/src/core/conversion/viewUtils.test.ts b/src/core/conversion/viewUtils.test.ts index b655b7c..eb7fc66 100644 --- a/src/core/conversion/viewUtils.test.ts +++ b/src/core/conversion/viewUtils.test.ts @@ -15,7 +15,7 @@ import { } from './fixtures/percentVerticalBarChart'; import { SELECTABLE_TABLE_WITH_MISSING_DATA } from './fixtures/tableChart'; import { ASCENDING, DESCENDING, SUM, REVERSED, NO_SORTING } from './viewSorting'; -import { buildSeries, convertPxGrafResponseToView, convertToRelative } from './viewUtils'; +import { buildSeries, convertPxGrafResponseToView, convertToRelative, getLastUpdated } from './viewUtils'; const createContentComponent = (overrides?: Partial) => { const id = Math.random().toString(36).substring(2, 15); @@ -912,6 +912,7 @@ describe('horizontal bar chart view conversion', () => { sv: 'PxVisualizer-sv' } ], + "lastUpdated": "2023-01-19T06:00:00Z", subheaderValues: [], units: [ { @@ -996,6 +997,7 @@ describe('horizontal bar chart view conversion', () => { fi: 'Tiedot 2022Q4 muuttujina Tiedot, Alue, Huoneluku', sv: 'Uppgifter 2022Q4 efter Uppgifter, Område, Antal rum' }, + "lastUpdated": "2023-01-19T06:00:00Z", rowVarNames: [], series: [ { @@ -1147,7 +1149,8 @@ describe('horizontal bar chart view conversion', () => { "fi": "PxVisualizer-fi", "sv": "PxVisualizer-sv", }, - ], + ], + lastUpdated: "2022-03-17T06:00:00Z", subheaderValues: [], units: [ { @@ -1214,6 +1217,7 @@ describe('horizontal bar chart view conversion', () => { "fi": "Päästö, tuhatta tonnia CO2-ekv. 2020 muuttujina Päästöluokka, Kasvihuonekaasu", "sv": "Utsläpp, tusen ton CO2-ekv. 2020 efter Utsläppsklass, Växthusgas", }, + lastUpdated: "2022-03-17T06:00:00Z", rowVarNames: [ { "en": "Emission category", @@ -1302,7 +1306,6 @@ describe('line chart view conversion', () => { it('returns multiline linechart view', () => { const resultView: View = convertPxGrafResponseToView(LINE_CHART_WITH_QUARTER_SERIES.pxGraphData, {}); const expectedView: View = { - tableReferenceName: "table.px", seriesType: ESeriesType.Time, visualizationSettings: { @@ -1560,6 +1563,7 @@ describe('line chart view conversion', () => { "fi": "Neliövuokra (eur/m2), Yksiöt, Vapaarahoitteinen 2015Q1-2022Q4 muuttujana Alue", "sv": "Kvadratmeterspris (eur/m2), Enrumslägenhet, Fri finansierad 2015Q1-2022Q4 efter Område", }, + lastUpdated: "2023-01-19T06:00:00Z", "series": [ { "rowNameGroup": [ @@ -1983,6 +1987,7 @@ describe('line chart view conversion', () => { "fi": "Neliövuokra (eur/m2), Vapaarahoitteinen 2015Q1-2022Q4 muuttujina Alue, Huoneluku", "sv": "Kvadratmeterspris (eur/m2), Fri finansierad 2015Q1-2022Q4 efter Område, Antal rum", }, + lastUpdated: "2023-01-19T06:00:00Z", series: [ { rowNameGroup: [ @@ -2707,6 +2712,7 @@ describe('line chart view conversion', () => { "fi": "Neliövuokra (eur/m2), Vapaarahoitteinen 2015Q1-2022Q4 muuttujina Alue, Huoneluku", "sv": "Kvadratmeterspris (eur/m2), Fri finansierad 2015Q1-2022Q4 efter Område, Antal rum", }, + lastUpdated: "2023-01-19T06:00:00Z", series: [ { rowNameGroup: [ @@ -3599,3 +3605,64 @@ describe('table conversion', () => { })).toThrow("Provided selected value code can not be found from the metadata"); }); }); + +describe('getLastUpdated', () => { + it('returns the most recent last updated date from multiple content dimension values', () => { + const expectedLastUpdated: string = "2024-05-10T12:00:00"; + const contentVar: IVariableMeta = { + name: { + "fi": "Tiedot", + "en": "Information", + }, + type: EVariableType.Content, + note: null, + code: "Tiedot", + values: [ + { + code: "tiedot-1", + name: { + "fi": "Tiedot 1", + "en": "Tiedot 1 en" + }, + isSum: false, + note: null, + contentComponent: { + source: { + "fi": "Source 1", + "en": "Source 1 en" + }, + unit: { + "fi": "unit 1", + "en": "unit 1 en", + }, + numberOfDecimals: 2, + lastUpdated: "2023-05-10T12:00:00" // Older value + } + }, + { + code: "tiedot-2", + name: { + "fi": "Tiedot 2", + "en": "Tiedot 2 en" + }, + isSum: false, + note: null, + contentComponent: { + source: { + "fi": "Source 2", + "en": "Source 12 en" + }, + unit: { + "fi": "unit 2", + "en": "unit 2 en", + }, + numberOfDecimals: 1, + lastUpdated: expectedLastUpdated // Newer, expected value + } + } + ] + }; + const lastUpdated = getLastUpdated(contentVar, {}); + expect(lastUpdated).toBe(expectedLastUpdated); + }); +}); diff --git a/src/core/conversion/viewUtils.ts b/src/core/conversion/viewUtils.ts index e33eea2..61dd2c5 100644 --- a/src/core/conversion/viewUtils.ts +++ b/src/core/conversion/viewUtils.ts @@ -72,12 +72,15 @@ function convert(responseObj: IQueryVisualizationResponse, selectedValueCodes: T const rowVarNames = getVariableNames(directionlessMultiselectVarNames, metaData) .concat(getVariableNames(responseObj.rowVariableCodes, responseObj.metaData)); + const lastUpdated = getLastUpdated(contentVar, selectedValueCodes); + return { header: responseObj.header, tableReferenceName: responseObj.tableReference.name, subheaderValues: getSubheaderValues(selectableVariables, selectedValueCodes, selectedValueAmounts), units: getUnitInformation(contentVar, selectedValueCodes), sources: getContentProperty(contentVar, selectedValueCodes, (cc) => cc?.source ?? Translations.empty), + lastUpdated: lastUpdated, columnNameGroups: unsortedSeries.columnNameGroups, series: unsortedSeries.series, rowVarNames: rowVarNames, @@ -102,6 +105,48 @@ export function buildSeries(responseObj: IQueryVisualizationResponse, selectedVa }; } +/** + * Get the last updated date from the content variable values. + * @param contentVar Content variable metadata + * @param selectedValueCodes Selected selectable value codes if any + * @returns Last updated date as a string, or undefined if no valid dates are found + */ +export function getLastUpdated( + contentVar: IVariableMeta, + selectedValueCodes: TVariableSelections +): string | undefined { + let dates: (string | undefined)[]; + + if (contentVar.code in selectedValueCodes) { + dates = contentVar.values + .filter(v => selectedValueCodes[contentVar.code].includes(v.code)) + .map(cvv => cvv.contentComponent?.lastUpdated) + .filter(onlyUnique); + } else { + dates = contentVar.values + .map(cvv => cvv.contentComponent?.lastUpdated) + .filter(onlyUnique); + } + + // Filter out undefined values and get the most recent date + const validDates = dates.filter((date): date is string => date !== undefined); + + if (validDates.length === 0) { + return undefined; + } + + if (validDates.length === 1) { + return validDates[0]; + } + + // Find the most recent date + const dateObjects = validDates.map(dateStr => new Date(dateStr)); + const latestIndex = dateObjects.reduce((latestIdx, currentDate, currentIdx, arr) => { + return currentDate > arr[latestIdx] ? currentIdx : latestIdx; + }, 0); + return validDates[latestIndex]; +} + function getVariableNames(varCodes: string[], meta: IVariableMeta[]): TMultiLanguageString[] { return varCodes.reduce((acc: TMultiLanguageString[], code: string) => { const name = meta.find(v => v.code === code)?.name; @@ -167,7 +212,7 @@ function getContentProperty( } } -function getSeriesType (varCodes: string[], meta: IVariableMeta[]) { +function getSeriesType(varCodes: string[], meta: IVariableMeta[]) { if (varCodes.length > 1 || varCodes.length === 0) { return ESeriesType.Nominal; } diff --git a/src/core/highcharts/drawChart.test.tsx b/src/core/highcharts/drawChart.test.tsx index af07288..18594b2 100644 --- a/src/core/highcharts/drawChart.test.tsx +++ b/src/core/highcharts/drawChart.test.tsx @@ -20,7 +20,7 @@ describe('drawChart tests', () => { it('calls Highcharts.chart with correct parameters', () => { // Arrange - const customOptions = { accessibilityMode: true }; + const customOptions = { accessibilityMode: true, showUnits: false, showSources: false }; const selectedVariableCodes = { 'variableCode1': ['value1', 'value2'] }; // Act diff --git a/src/core/highcharts/drawChart.ts b/src/core/highcharts/drawChart.ts index 24dcbd9..7c1114d 100644 --- a/src/core/highcharts/drawChart.ts +++ b/src/core/highcharts/drawChart.ts @@ -35,6 +35,5 @@ export const drawChart = ( const variableSelections = extractSelectableVariableValues(pxGraphData.selectableVariableCodes, pxGraphData.metaData, pxGraphData.visualizationSettings.defaultSelectableVariableCodes, selectedVariableCodes); const view = convertPxGrafResponseToView(pxGraphData, variableSelections); const highChartOptions = convertPxGraphDataToChartOptions(validLocale, view, options); - return Highcharts.chart(container, highChartOptions); } \ No newline at end of file diff --git a/src/core/highcharts/themes.ts b/src/core/highcharts/themes.ts index 9025cdb..846c768 100644 --- a/src/core/highcharts/themes.ts +++ b/src/core/highcharts/themes.ts @@ -42,6 +42,7 @@ export const defaultTheme: (locale: string, fontFamily?: string) => Highcharts.O accessibility: { thousandsSep: Translations.thousandsSepAccessibility[locale], credits: Translations.credits[locale], + caption: Translations.credits[locale], chartTypes: { barMultiple: Translations.barMultiple[locale], barSingle: Translations.barSingle[locale], @@ -117,7 +118,6 @@ export const defaultTheme: (locale: string, fontFamily?: string) => Highcharts.O style: { fontFamily: fontFamily ?? '"Barlow Semi Condensed", Verdana, sans-serif' }, - spacingBottom: 30, spacingLeft: 20, // for tilted labels to fit in the x axis with ellipsis overflow height: (9 / 16 * 100) + '%' }, @@ -155,7 +155,7 @@ export const defaultTheme: (locale: string, fontFamily?: string) => Highcharts.O }, align: 'left', }, - credits: { + caption: { position: { align: "left", x: 5 diff --git a/src/core/tables/__snapshots__/htmlTable.test.ts.snap b/src/core/tables/__snapshots__/htmlTable.test.ts.snap index 1bf7786..0bb1359 100644 --- a/src/core/tables/__snapshots__/htmlTable.test.ts.snap +++ b/src/core/tables/__snapshots__/htmlTable.test.ts.snap @@ -4,14 +4,16 @@ exports[`Html table render tests should match snapshot: Table with column variab " +  + Adoptiot 1987-1989 muuttujina Syntymävaltio, Ikä + 

  -  - Adoptiot 1987-1989 muuttujina Syntymävaltio, Ikä -     +  + Neliöhinta (EUR/m2), Helsinki 2020Q1-2023Q2* muuttujina Talotyyppi, Huoneluku + 
 + Rivitalot | Kaksiot + 

  -  - Neliöhinta (EUR/m2), Helsinki 2020Q1-2023Q2* muuttujina Talotyyppi, Huoneluku - 
 - Rivitalot | Kaksiot -     +  + Lukumäärä, Vantaa, Yksiöt, Vapaarahoitteinen 2022Q4 + 

  -  - Lukumäärä, Vantaa, Yksiöt, Vapaarahoitteinen 2022Q4 -     @@ -469,14 +475,16 @@ exports[`Html table render tests should match snapshot: Table with row and colum " +  + Tiedot 2022Q1-2022Q4 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto + 

  -  - Tiedot 2022Q1-2022Q4 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto -     +  + Tiedot 2022Q1-2022Q4 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto + 

  -  - Tiedot 2022Q1-2022Q4 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto -     +  + Tiedot 2022Q1-2022Q4 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto + 

  -  - Tiedot 2022Q1-2022Q4 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto -     +  + Lukumäärä 2022Q1-2022Q4 muuttujina Alue, Huoneluku, Rahoitusmuoto + 

  -  - Lukumäärä 2022Q1-2022Q4 muuttujina Alue, Huoneluku, Rahoitusmuoto -     +  + Lukumäärä, Vantaa, Yksiöt, Vapaarahoitteinen 2022Q4 + 

  -  - Lukumäärä, Vantaa, Yksiöt, Vapaarahoitteinen 2022Q4 -     diff --git a/src/core/tables/htmlTable.test.ts b/src/core/tables/htmlTable.test.ts index 68ab874..8c89ed8 100644 --- a/src/core/tables/htmlTable.test.ts +++ b/src/core/tables/htmlTable.test.ts @@ -5,6 +5,7 @@ import { extractSelectableVariableValues } from "../conversion/helpers"; import { convertPxGrafResponseToView } from "../conversion/viewUtils"; import { renderHtmlTable } from "./htmlTable"; import { SELECTABLE_TABLE_WITH_INVALID_MISSING_DATA } from "./fixtures/pxGrafResponses"; +import { IChartOptions } from "../types/chartOptions"; describe('Html table render tests', () => { it('should match snapshot: Table with column variables only', () => { @@ -22,7 +23,14 @@ describe('Html table render tests', () => { div.id = testId; document.body.appendChild(div); - renderHtmlTable(mockView, locale, true, true, true, testId); + const settings: IChartOptions = { + showTitles: true, + showUnits: true, + showSources: true, + showLastUpdated: false + }; + + renderHtmlTable(mockView, locale, settings, testId); const renderedOutput = prettyDOM(div); expect(renderedOutput).toMatchSnapshot(); @@ -45,7 +53,14 @@ describe('Html table render tests', () => { div.id = testId; document.body.appendChild(div); - renderHtmlTable(mockView, locale, true, true, true, testId); + const settings: IChartOptions = { + showTitles: true, + showUnits: true, + showSources: true, + showLastUpdated: false + }; + + renderHtmlTable(mockView, locale, settings, testId); const renderedOutput = prettyDOM(div); expect(renderedOutput).toMatchSnapshot(); @@ -68,7 +83,14 @@ describe('Html table render tests', () => { div.id = testId; document.body.appendChild(div); - renderHtmlTable(mockView, locale, true, true, true, testId); + const settings: IChartOptions = { + showTitles: true, + showUnits: true, + showSources: true, + showLastUpdated: false + }; + + renderHtmlTable(mockView, locale, settings, testId); const renderedOutput = prettyDOM(div); expect(renderedOutput).toMatchSnapshot(); @@ -91,7 +113,14 @@ describe('Html table render tests', () => { div.id = testId; document.body.appendChild(div); - renderHtmlTable(mockView, locale, false, true, true, testId); + const settings: IChartOptions = { + showTitles: false, + showUnits: true, + showSources: true, + showLastUpdated: false + }; + + renderHtmlTable(mockView, locale, settings, testId); const renderedOutput = prettyDOM(div); expect(renderedOutput).toMatchSnapshot(); @@ -114,7 +143,14 @@ describe('Html table render tests', () => { div.id = testId; document.body.appendChild(div); - renderHtmlTable(mockView, locale, true, false, false, testId); + const settings: IChartOptions = { + showTitles: true, + showUnits: false, + showSources: false, + showLastUpdated: false + }; + + renderHtmlTable(mockView, locale, settings, testId); const renderedOutput = prettyDOM(div); expect(renderedOutput).toMatchSnapshot(); @@ -137,7 +173,14 @@ describe('Html table render tests', () => { div.id = testId; document.body.appendChild(div); - renderHtmlTable(mockView, locale, true, false, false, testId, 'Test footnote'); + const settings: IChartOptions = { + showTitles: true, + showUnits: false, + showSources: false, + showLastUpdated: false + }; + + renderHtmlTable(mockView, locale, settings, testId, 'Test footnote'); const renderedOutput = prettyDOM(div); expect(renderedOutput).toMatchSnapshot(); @@ -160,7 +203,14 @@ describe('Html table render tests', () => { div.id = testId; document.body.appendChild(div); - renderHtmlTable(mockView, locale, true, true, true, testId); + const settings: IChartOptions = { + showTitles: true, + showUnits: true, + showSources: true, + showLastUpdated: false + }; + + renderHtmlTable(mockView, locale, settings, testId); const renderedOutput = prettyDOM(div); expect(renderedOutput).toMatchSnapshot(); @@ -183,7 +233,14 @@ describe('Html table render tests', () => { div.id = testId; document.body.appendChild(div); - renderHtmlTable(mockView, locale, true, true, true, testId, 'Test footnote'); + const settings: IChartOptions = { + showTitles: true, + showUnits: true, + showSources: true, + showLastUpdated: false + }; + + renderHtmlTable(mockView, locale, settings, testId, 'Test footnote'); const renderedOutput = prettyDOM(div); expect(renderedOutput).toMatchSnapshot(); @@ -209,7 +266,14 @@ describe('Html table render tests', () => { div.id = testId; document.body.appendChild(div); - renderHtmlTable(mockView, locale, true, true, true, testId); + const settings: IChartOptions = { + showTitles: true, + showUnits: true, + showSources: true, + showLastUpdated: false + }; + + renderHtmlTable(mockView, locale, settings, testId); const renderedOutput = prettyDOM(div); expect(renderedOutput).toMatchSnapshot(); @@ -237,7 +301,14 @@ describe('Html table render tests', () => { div.id = testId; document.body.appendChild(div); - renderHtmlTable(mockView, locale, true, true, true, testId); + const settings: IChartOptions = { + showTitles: true, + showUnits: true, + showSources: true, + showLastUpdated: false + }; + + renderHtmlTable(mockView, locale, settings, testId); const renderedOutput = prettyDOM(div); expect(renderedOutput).toMatchSnapshot(); diff --git a/src/core/tables/htmlTable.ts b/src/core/tables/htmlTable.ts index d8c4b2c..f62f03f 100644 --- a/src/core/tables/htmlTable.ts +++ b/src/core/tables/htmlTable.ts @@ -1,10 +1,11 @@ -import { getFormattedUnits } from "../chartOptions/Utility/formatters"; +import { getFormattedUnits, getFormattedLastUpdatedText } from "../chartOptions/Utility/formatters"; import { Translations } from "../conversion/translations"; import { TMultiLanguageString } from "../types/queryVisualizationResponse"; import { IDataSeries, View } from "../types/view"; +import { IChartOptions } from "../types/chartOptions"; import { formatMissingData, formatNumericValue } from "./tableUtils"; -export function renderHtmlTable(view: View, locale: string, showTitles: boolean, showUnits: boolean, showSources: boolean, containerId: string, footnote?: string): void { +export function renderHtmlTable(view: View, locale: string, options: IChartOptions, containerId: string, footnote?: string): void { const container = document.getElementById(containerId); if (!container) throw new Error("No container with matching id found in the DOM tree"); @@ -13,22 +14,27 @@ export function renderHtmlTable(view: View, locale: string, showTitles: boolean, // Table content const table = generateTable(view, locale); - if (showTitles) { + if (options.showTitles) { + const titleId = `${containerId}-title`; + const titleElement = document.createElement('p'); + titleElement.id = titleId; + titleElement.textContent = view.header[locale]; + titleElement.className = 'tableChart-title'; - const caption = document.createElement('caption'); - caption.textContent = view.header[locale]; if (view.subheaderValues.length > 0) { const subtitle: string = view.subheaderValues.map(value => value[locale]).join(' | '); - caption.append(document.createElement('br'), subtitle); + titleElement.append(document.createElement('br'), subtitle); } - caption.className = 'tableChart-caption'; - table.prepend(caption); + // Set aria-labelledby on the table to reference the title + table.setAttribute('aria-labelledby', titleId); + + container.append(titleElement); } container.append(table); // Units - if (showUnits) { + if (options.showUnits) { const pUnits = document.createElement('p'); const unitName = getFormattedUnits(view.units, locale); const units: string = `${Translations.unit[locale]}: ${unitName}`; @@ -43,8 +49,18 @@ export function renderHtmlTable(view: View, locale: string, showTitles: boolean, container.append(pFootnote); } + // Last Updated + if (options.showLastUpdated && view.lastUpdated) { + const pLastUpdated = document.createElement('p'); + const lastUpdatedText = getFormattedLastUpdatedText(view.lastUpdated, locale); + if (lastUpdatedText) { + pLastUpdated.append(lastUpdatedText); + container.append(pLastUpdated); + } + } + // Sources - if (showSources) { + if (options.showSources) { const pSources = document.createElement('p'); const sources: string = `${Translations.source[locale]}: ${view.sources.map(source => source[locale]).join(', ')}`; pSources.append(sources); @@ -160,10 +176,10 @@ const calculateColSpans = (columnNameGroups: TMultiLanguageString[][]): number[] const colSpans: number[] = Array(columnNameGroups[0].length).fill(1); for (let row = 0; row < columnNameGroups[0].length; row++) { - for (let col = 0; col < columnNameGroups.length - 1; col++) { - if (compare(columnNameGroups[col][row], columnNameGroups[col + 1][row])) colSpans[row]++; - else break; - } + for (let col = 0; col < columnNameGroups.length - 1; col++) { + if (compare(columnNameGroups[col][row], columnNameGroups[col + 1][row])) colSpans[row]++; + else break; + } } return colSpans; } \ No newline at end of file diff --git a/src/core/types/chartOptions.ts b/src/core/types/chartOptions.ts index 7286763..02c1252 100644 --- a/src/core/types/chartOptions.ts +++ b/src/core/types/chartOptions.ts @@ -3,6 +3,9 @@ */ export interface IChartOptions { accessibilityMode?: boolean; - showTitle?: boolean; + showTitles?: boolean; fontFamily?: string; + showLastUpdated?: boolean; + showUnits: boolean; + showSources: boolean; } \ No newline at end of file diff --git a/src/core/types/view.ts b/src/core/types/view.ts index e6bbfc3..0c6f331 100644 --- a/src/core/types/view.ts +++ b/src/core/types/view.ts @@ -17,6 +17,7 @@ export interface View { subheaderValues: TMultiLanguageString[], units: IUnitInfo[], sources: TMultiLanguageString[], + lastUpdated?: string, columnNameGroups: TMultiLanguageString[][], series: IDataSeries[], colVarNames: TMultiLanguageString[], diff --git a/src/react/components/chart/__snapshots__/chart.test.tsx.snap b/src/react/components/chart/__snapshots__/chart.test.tsx.snap index a392720..959f910 100644 --- a/src/react/components/chart/__snapshots__/chart.test.tsx.snap +++ b/src/react/components/chart/__snapshots__/chart.test.tsx.snap @@ -3,10 +3,10 @@ exports[`Rendering test renders chart data correctly 1`] = `
+

+ Lukumäärä, Pääkaupunkiseutu (PKS), Yksiöt 2015Q1-2015Q2 muuttujana Rahoitusmuoto +

+ + + + + + + + + + + + + + + + + + +
+ + 2015Q1 + + 2015Q2 +
+ Vapaarahoitteinen + + 11 096 + + 11 625 +
+ ARA + + 4 845 + + 5 174 +
+

+ Lähde: PxVisualizer-fi +

+
+
+
+ +`; + +exports[`Rendering test renders chart data correctly with hidden context menu 1`] = ` + +
+
+
+
+
+
+

+ Lukumäärä, Pääkaupunkiseutu (PKS), Yksiöt 2015Q1-2015Q2 muuttujana Rahoitusmuoto +

+ -
- Lukumäärä, Pääkaupunkiseutu (PKS), Yksiöt 2015Q1-2015Q2 muuttujana Rahoitusmuoto -
- Tiedot 2022Q1-2022Q4 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto -
`; -exports[`Rendering test renders table data correctly when titles are forced on 1`] = ` +exports[`Rendering test renders table data correctly with hidden titles and hidden context menu 1`] = `
-
-
- -
-
-
-
- Tiedot 2022Q1-2022Q4 muuttujina Tiedot, Alue, Huoneluku, Rahoitusmuoto -
`; -exports[`Rendering test renders table data correctly when units and footnote are on 1`] = ` +exports[`Rendering test renders table data correctly with last updated date 1`] = `