diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 0000000..59d79f3 --- /dev/null +++ b/Changelog.md @@ -0,0 +1,2 @@ +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 diff --git a/README.md b/README.md index f3eda23..e659707 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ drawChart(container, pxGraphData, locale);> - showContextMenu: boolean - Flag to show or hide the context menu in the visualization. - menuItemDefinitions: object[] - Definitions for custom context menu items. These can be either links or run custom functions. The provided object must implement either the IFunctionalMenuItem or ILinkMenuItem interface. - menuIconInheritColor: boolean - Flag to inherit the color of the context menu icons from the parent element. -- showTableTitles: boolean - Flag to show or hide the titles in the table view. +- showTitles: boolean - Flag to show or hide the titles in the tables and charts. - showTableUnits: boolean - Flag to show or hide the units in the table view. - showTableSources: boolean - Flag to show or hide the sources in the table view. - footnote: string - Custom footnote to be displayed in the visualization. diff --git a/package-lock.json b/package-lock.json index aa5de19..b2305a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,16 @@ { "name": "@statisticsfinland/pxvisualizer", - "version": "1.2.2", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@statisticsfinland/pxvisualizer", - "version": "1.2.2", + "version": "1.3.0", "license": "Apache-2.0", "dependencies": { "decimal.js": "^10.5.0", - "fflate": "^0.8.2", - "highcharts": "^12.1.2", - "highcharts-react-official": "^3.2.1" + "fflate": "^0.8.2" }, "devDependencies": { "@babel/core": "^7.26.7", @@ -37,8 +35,8 @@ "babel-loader": "^10.0.0", "clean-publish": "^5.1.0", "decimal.js": "^10.5.0", - "highcharts": "^12.1.2", - "highcharts-react-official": "^3.2.1", + "highcharts": "^12.4.0", + "highcharts-react-official": "^3.2.2", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-junit": "^16.0.0", @@ -8021,16 +8019,16 @@ } }, "node_modules/highcharts": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-12.2.0.tgz", - "integrity": "sha512-UUN+osTP3aeGc4KmoMuWAjzpKif8GYHFozzYI4O8h1ILGof25M/ZGBpXLvgqf1z0LVh7N9eG7i0HnzMfjcR4nA==", + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-12.4.0.tgz", + "integrity": "sha512-o6UxxfChSUrvrZUbWrAuqL1HO/+exhAUPcZY6nnqLsadZQlnP16d082sg7DnXKZCk1gtfkyfkp6g3qkIZ9miZg==", "dev": true, "license": "https://www.highcharts.com/license" }, "node_modules/highcharts-react-official": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.1.tgz", - "integrity": "sha512-hyQTX7ezCxl7JqumaWiGsroGWalzh24GedQIgO3vJbkGOZ6ySRAltIYjfxhrq4HszJOySZegotEF7v+haQ75UA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.2.tgz", + "integrity": "sha512-2kkWOB6RpdR26fmAJkrtJFG9xWFUDGKWyat88tW3fa/3l/Jc7D5ZfwTng2MZsdiKIH32AFy0Pr75udUe7uN6LA==", "dev": true, "license": "MIT", "peerDependencies": { diff --git a/package.json b/package.json index 31d3478..5a6a102 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@statisticsfinland/pxvisualizer", - "version": "1.2.4", + "version": "1.3.1", "description": "Component library for visualizing PxGraf data", "main": "./dist/pxv.cjs", "jestSonar": { @@ -68,8 +68,6 @@ "babel-loader": "^10.0.0", "clean-publish": "^5.1.0", "decimal.js": "^10.5.0", - "highcharts": "^12.1.2", - "highcharts-react-official": "^3.2.1", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-junit": "^16.0.0", @@ -90,8 +88,8 @@ "dependencies": { "decimal.js": "^10.5.0", "fflate": "^0.8.2", - "highcharts": "^12.1.2", - "highcharts-react-official": "^3.2.1" + "highcharts": "^12.4.0", + "highcharts-react-official": "^3.2.2" }, "peerDependencies": { "react": "^18.2.0 || ^19.1.0", diff --git a/src/core/chartOptions/basicHorizontalBarchartOptions.ts b/src/core/chartOptions/basicHorizontalBarchartOptions.ts index 6aa231b..8a29168 100644 --- a/src/core/chartOptions/basicHorizontalBarchartOptions.ts +++ b/src/core/chartOptions/basicHorizontalBarchartOptions.ts @@ -6,7 +6,7 @@ import { IChartOptions } from '../types/chartOptions'; export const basicHorizontalBarChartOptions = (view: View, locale: string, options?: IChartOptions): Options => { return { - ...commonHorizontalBarChartOptions(view, locale), + ...commonHorizontalBarChartOptions(view, locale, options), series: buildBarChartSeries(view, locale, false, options?.accessibilityMode), chart: { type: 'bar', spacingBottom: 60 }, yAxis: { diff --git a/src/core/chartOptions/chartOptions.ts b/src/core/chartOptions/chartOptions.ts index 3f3c434..67a5e2e 100644 --- a/src/core/chartOptions/chartOptions.ts +++ b/src/core/chartOptions/chartOptions.ts @@ -7,14 +7,15 @@ import { getLinearAxisTickPositionerFunction } from './Utility/tickPositioners'; import { IChartOptions } from '../types/chartOptions'; import { buildBarChartSeries, buildColumnChartSeries } from './Utility/seriesDataBuilder'; -export const commonChartOptions = (view: View, locale: string): Options => { +export const commonChartOptions = (view: View, locale: string, options?: IChartOptions): Options => { + const showTitle: boolean = options?.showTitle ?? true; return { accessibility: { point: { descriptionFormatter: getScreenReaderFormatterCallbackFunction(view, locale) } }, - title: { text: view.header[locale] }, + title: { text: showTitle ? 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(', ')}` }, tooltip: { @@ -65,9 +66,9 @@ export const commonBasicHorizontalBarChartYAxisOptions = (view: View, locale: st return yAxisOptions; } -export const commonHorizontalBarChartOptions = (view: View, locale: string): Options => { +export const commonHorizontalBarChartOptions = (view: View, locale: string, options?: IChartOptions): Options => { return { - ...commonChartOptions(view, locale), + ...commonChartOptions(view, locale, options), chart: { type: 'bar' }, xAxis: { categories: view.columnNameGroups.map(cng => cng.map(n => n[locale]).join(', ')), @@ -83,7 +84,7 @@ export const commonHorizontalBarChartOptions = (view: View, locale: string): Opt export const commonStackedHorizontalBarChartOptions = (view: View, locale: string, options?: IChartOptions): Options => { return { - ...commonHorizontalBarChartOptions(view, locale), + ...commonHorizontalBarChartOptions(view, locale, options), series: buildBarChartSeries(view, locale, true, options?.accessibilityMode), legend: { ...commonLegendStyleOptions, @@ -95,9 +96,9 @@ export const commonStackedHorizontalBarChartOptions = (view: View, locale: strin }; } -export const commonVerticalBarChartOptions = (view: View, locale: string): Options => { +export const commonVerticalBarChartOptions = (view: View, locale: string, options?: IChartOptions): Options => { const result = { - ...commonChartOptions(view, locale), + ...commonChartOptions(view, locale, options), chart: { type: 'column' }, xAxis: getXAxisOptions(view, locale), }; @@ -106,7 +107,7 @@ export const commonVerticalBarChartOptions = (view: View, locale: string): Optio export const commonBasicVerticalBarChartOptions = (view: View, locale: string, options?: IChartOptions): Options => { return { - ...commonVerticalBarChartOptions(view, locale), + ...commonVerticalBarChartOptions(view, locale, options), series: buildColumnChartSeries(view, locale, false, options?.accessibilityMode), yAxis: { softMin: 0, @@ -120,7 +121,7 @@ export const commonBasicVerticalBarChartOptions = (view: View, locale: string, o export const commonStackedVerticalBarChartOptions = (view: View, locale: string, options?: IChartOptions): Options => { return { - ...commonVerticalBarChartOptions(view, locale), + ...commonVerticalBarChartOptions(view, locale, options), series: buildColumnChartSeries(view, locale, true, options?.accessibilityMode), legend: { ...commonLegendStyleOptions, diff --git a/src/core/chartOptions/groupHorizontalBarChartOptions.ts b/src/core/chartOptions/groupHorizontalBarChartOptions.ts index 5201bab..66720e5 100644 --- a/src/core/chartOptions/groupHorizontalBarChartOptions.ts +++ b/src/core/chartOptions/groupHorizontalBarChartOptions.ts @@ -6,7 +6,7 @@ import { IChartOptions } from '../types/chartOptions'; export const groupHorizontalBarChartOptions = (view: View, locale: string, options?: IChartOptions): Options => { return { - ...commonHorizontalBarChartOptions(view, locale), + ...commonHorizontalBarChartOptions(view, locale, options), series: buildBarChartSeries(view, locale, false, options?.accessibilityMode), yAxis: { ...commonBasicHorizontalBarChartYAxisOptions(view, locale), diff --git a/src/core/chartOptions/lineChartOptions.ts b/src/core/chartOptions/lineChartOptions.ts index 8ff19cd..89a886f 100644 --- a/src/core/chartOptions/lineChartOptions.ts +++ b/src/core/chartOptions/lineChartOptions.ts @@ -10,7 +10,7 @@ export const lineChartOptions = (view: View, locale: string, options?: IChartOpt const cutValueAxis = !view.visualizationSettings?.cutValueAxis ? 0 : undefined; const markerSettings = options?.accessibilityMode ? { enabledThreshold: 3 } : { enabled: false }; return { - ...commonChartOptions(view, locale), + ...commonChartOptions(view, locale, options), chart: { type: 'line' }, tooltip: { formatter: getLineChartToolTipFormatterFunction(view, locale) diff --git a/src/core/chartOptions/pyramidChartOptions.ts b/src/core/chartOptions/pyramidChartOptions.ts index c6bb484..26990a9 100644 --- a/src/core/chartOptions/pyramidChartOptions.ts +++ b/src/core/chartOptions/pyramidChartOptions.ts @@ -9,7 +9,7 @@ export const pyramidChartOptions = (view: View, locale: string, options?: IChart const categories = view.columnNameGroups.map(cng => cng.map(n => n[locale]).join(', ')); const maxValue = Math.max(...view.series.map(s => Math.max(...s.series.map(dataCell => dataCell.value ?? 0)))); return { - ...commonChartOptions(view, locale), + ...commonChartOptions(view, locale, options), chart: { type: 'bar' }, xAxis: { categories: categories, diff --git a/src/core/chartOptions/scatterPlotOptions.ts b/src/core/chartOptions/scatterPlotOptions.ts index 7e4a692..72f214c 100644 --- a/src/core/chartOptions/scatterPlotOptions.ts +++ b/src/core/chartOptions/scatterPlotOptions.ts @@ -2,12 +2,13 @@ import { Options } from 'highcharts'; import { IDataSeries, View } from "../types/view"; import { getScatterPlotTooltipFormatterFunction } from './Utility/formatters'; import { commonChartOptions, commonYAxisOptions } from './chartOptions'; +import { IChartOptions } from '../types/chartOptions'; -export const scatterPlotOptions = (view: View, locale: string): Options => { +export const scatterPlotOptions = (view: View, locale: string, options?: IChartOptions): Options => { const X_INDEX = 1; const Y_INDEX = 0; const cutValueAxis = !view.visualizationSettings?.cutValueAxis ? 0 : undefined; return { - ...commonChartOptions(view, locale), + ...commonChartOptions(view, locale, options), chart: { type: 'scatter' }, xAxis: { softMin: 0, diff --git a/src/core/conversion/pxGrafDataConverter.ts b/src/core/conversion/pxGrafDataConverter.ts index 37d87ac..5543306 100644 --- a/src/core/conversion/pxGrafDataConverter.ts +++ b/src/core/conversion/pxGrafDataConverter.ts @@ -41,7 +41,7 @@ export const convertPxGraphDataToChartOptions = (locale: string, view: View, opt case EVisualizationType.PyramidChart: return pyramidChartOptions(view, locale, options); case EVisualizationType.ScatterPlot: - return scatterPlotOptions(view, locale); + return scatterPlotOptions(view, locale, options); default: throw new Error('Unsupported chart type'); } diff --git a/src/core/tables/__snapshots__/htmlTable.test.ts.snap b/src/core/tables/__snapshots__/htmlTable.test.ts.snap index 1d60f94..9562609 100644 --- a/src/core/tables/__snapshots__/htmlTable.test.ts.snap +++ b/src/core/tables/__snapshots__/htmlTable.test.ts.snap @@ -3542,3 +3542,35 @@ exports[`Html table render tests should match snapshot: Table with row variables 

 " `; + +exports[`Html table render tests should match snapshot: Table with source and footnote 1`] = ` +" +  +  + Lukumäärä, Vantaa, Yksiöt, Vapaarahoitteinen 2022Q4 +  +  +  +  + 2 548 +  +  +  +  + 

 + Yksikkö: lukumäärä + 

 + 

 + Test footnote + 

 + 

 + Lähde: PxVisualizer-fi + 

 +" +`; diff --git a/src/core/tables/htmlTable.test.ts b/src/core/tables/htmlTable.test.ts index 3d4b4ee..68ab874 100644 --- a/src/core/tables/htmlTable.test.ts +++ b/src/core/tables/htmlTable.test.ts @@ -168,6 +168,29 @@ describe('Html table render tests', () => { document.body.removeChild(div); }); + it('should match snapshot: Table with source and footnote', () => { + const mockVarSelections = extractSelectableVariableValues( + TABLE_WITH_ONE_CELL.pxGraphData.selectableVariableCodes, + TABLE_WITH_ONE_CELL.pxGraphData.metaData, + TABLE_WITH_ONE_CELL.pxGraphData.visualizationSettings.defaultSelectableVariableCodes, + TABLE_WITH_ONE_CELL.selectedVariableCodes); + const mockView = convertPxGrafResponseToView(TABLE_WITH_ONE_CELL.pxGraphData, mockVarSelections); + const locale = 'fi'; + + const testId = 'test-6895638450983059889'; + + const div = document.createElement('div'); + div.id = testId; + document.body.appendChild(div); + + renderHtmlTable(mockView, locale, true, true, true, testId, 'Test footnote'); + + const renderedOutput = prettyDOM(div); + expect(renderedOutput).toMatchSnapshot(); + + document.body.removeChild(div); + }); + it('should match snapshot: Table with missing data and selectable values', () => { const mockVarSelections = extractSelectableVariableValues( SELECTABLE_TABLE_WITH_MISSING_DATA.selectableVariableCodes, diff --git a/src/core/tables/htmlTable.ts b/src/core/tables/htmlTable.ts index 30f0d5d..d8c4b2c 100644 --- a/src/core/tables/htmlTable.ts +++ b/src/core/tables/htmlTable.ts @@ -36,6 +36,13 @@ export function renderHtmlTable(view: View, locale: string, showTitles: boolean, container.append(pUnits); } + // Footnote + if (footnote) { + const pFootnote = document.createElement('p'); + pFootnote.append(footnote); + container.append(pFootnote); + } + // Sources if (showSources) { const pSources = document.createElement('p'); @@ -44,13 +51,6 @@ export function renderHtmlTable(view: View, locale: string, showTitles: boolean, container.append(pSources); } - // Footnote - if (footnote) { - const pFootnote = document.createElement('p'); - pFootnote.append(footnote); - container.append(pFootnote); - } - } catch (error) { console.error(error); container.replaceChildren(); diff --git a/src/core/types/chartOptions.ts b/src/core/types/chartOptions.ts index c9881e2..178f38a 100644 --- a/src/core/types/chartOptions.ts +++ b/src/core/types/chartOptions.ts @@ -3,4 +3,5 @@ */ export interface IChartOptions { accessibilityMode?: boolean; + showTitle?: boolean; } \ No newline at end of file diff --git a/src/react/components/burgerMenu/burgerMenu.test.tsx b/src/react/components/burgerMenu/burgerMenu.test.tsx index 5f867f8..7460f70 100644 --- a/src/react/components/burgerMenu/burgerMenu.test.tsx +++ b/src/react/components/burgerMenu/burgerMenu.test.tsx @@ -18,9 +18,9 @@ describe('burgerMenu, rendering tests', () => { }); const viewData = { tableReference: "testTable" } as unknown as View; -const mockExportChartLocal = jest.fn(); +const mockExportChart = jest.fn(); beforeEach(() => { - mockExportChartLocal.mockClear(); + mockExportChart.mockClear(); }); const mockTableToggle = { @@ -221,10 +221,12 @@ describe('burgerMenu, functional tests', () => { expect(finalHeight).toEqual(3000); }); - it('should call exportChartLocal with correct parameters for SVG export', () => { + it('should call export chart with correct parameters for SVG export', () => { const mockRef = { chart: { - exportChartLocal: mockExportChartLocal, + exporting: { + exportChart: mockExportChart, + }, chartWidth: 800, chartHeight: 400 } @@ -235,7 +237,7 @@ describe('burgerMenu, functional tests', () => { fireEvent.click(screen.getByText(Translations.downloadSVG["fi"])); const { finalWidth, finalHeight } = calculateExportDimensions(mockRef); - expect(mockExportChartLocal).toHaveBeenCalledWith({ + expect(mockExportChart).toHaveBeenCalledWith({ filename: generateFilename(viewData.tableReferenceName), type: "image/svg+xml", sourceWidth: finalWidth, @@ -244,10 +246,12 @@ describe('burgerMenu, functional tests', () => { }, {}); }); - it('should call exportChartLocal with correct parameters for PNG export', () => { + it('should call export chart with correct parameters for PNG export', () => { const mockRef = { chart: { - exportChartLocal: mockExportChartLocal, + exporting: { + exportChart: mockExportChart, + }, chartWidth: 800, chartHeight: 400 } @@ -258,7 +262,7 @@ describe('burgerMenu, functional tests', () => { fireEvent.click(screen.getByText(Translations.downloadPNG["fi"])); const { finalWidth, finalHeight } = calculateExportDimensions(mockRef); - expect(mockExportChartLocal).toHaveBeenCalledWith({ + expect(mockExportChart).toHaveBeenCalledWith({ filename: generateFilename(viewData.tableReferenceName), sourceWidth: finalWidth, sourceHeight: finalHeight, diff --git a/src/react/components/burgerMenu/burgerMenu.tsx b/src/react/components/burgerMenu/burgerMenu.tsx index 044b36b..40fa4e2 100644 --- a/src/react/components/burgerMenu/burgerMenu.tsx +++ b/src/react/components/burgerMenu/burgerMenu.tsx @@ -229,7 +229,7 @@ export const BurgerMenu: React.FC = ({ viewData, currentChartR prefixIcon={'Download'} text={Translations.downloadSVG[locale]} onClick={() => handleMenuItemClick(() => - currentChartRef.chart.exportChartLocal({ + currentChartRef.chart.exporting.exportChart({ filename: `${generateFilename(viewData.tableReferenceName)}`, type: "image/svg+xml", sourceWidth: calculateExportDimensions(currentChartRef).finalWidth, @@ -252,7 +252,7 @@ export const BurgerMenu: React.FC = ({ viewData, currentChartR prefixIcon={'Download'} text={Translations.downloadPNG[locale]} onClick={() => handleMenuItemClick(() => - currentChartRef.chart.exportChartLocal({ + currentChartRef.chart.exporting.exportChart({ filename: `${generateFilename(viewData.tableReferenceName)}`, sourceWidth: calculateExportDimensions(currentChartRef).finalWidth, sourceHeight: calculateExportDimensions(currentChartRef).finalHeight, diff --git a/src/react/components/chart/__snapshots__/chart.test.tsx.snap b/src/react/components/chart/__snapshots__/chart.test.tsx.snap index 0fe7d6b..29a6905 100644 --- a/src/react/components/chart/__snapshots__/chart.test.tsx.snap +++ b/src/react/components/chart/__snapshots__/chart.test.tsx.snap @@ -115,6 +115,128 @@ exports[`Rendering test renders chart data correctly 1`] = ` +

+ Lähde: PxVisualizer-fi +

+ + + + +`; + +exports[`Rendering test renders chart data correctly with hidden title 1`] = ` + +
+
+
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+ + 2015Q1 + + 2015Q2 +
+ Vapaarahoitteinen + + 11 096 + + 11 625 +
+ ARA + + 4 845 + + 5 174 +
+

+ Lähde: PxVisualizer-fi +

@@ -145,7 +267,7 @@ exports[`Rendering test renders table data correctly 1`] = ` class="sc-kpOvIu imTYeK" >
@@ -820,7 +950,7 @@ exports[`Rendering test renders table data correctly when given footnote 1`] = ` class="sc-kpOvIu imTYeK" >