From 88f6199d18b5f3024f480258b80c8d51e73a539e Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 15:21:08 -0300 Subject: [PATCH 01/18] feat: implement new Data interface + review PlotlyBarChart API and docs + hide BucketViewer and OpenLayers --- .../src/components/PlotlyBarChart.tsx | 74 ++++++++--------- packages/components/src/index.ts | 3 +- packages/components/src/types/properties.ts | 11 +++ .../stories/BarChartPlotly.stories.ts | 82 +++++++++++++------ ...stories.ts => BucketViewer.bkp.stories.ts} | 3 + ...stories.tsx => OpenLayers.bkp.stories.tsx} | 3 + 6 files changed, 109 insertions(+), 67 deletions(-) create mode 100644 packages/components/src/types/properties.ts rename packages/components/stories/{BucketViewer.stories.ts => BucketViewer.bkp.stories.ts} (96%) rename packages/components/stories/{OpenLayers.stories.tsx => OpenLayers.bkp.stories.tsx} (96%) diff --git a/packages/components/src/components/PlotlyBarChart.tsx b/packages/components/src/components/PlotlyBarChart.tsx index bcb7f1da1..6f2e02e8f 100644 --- a/packages/components/src/components/PlotlyBarChart.tsx +++ b/packages/components/src/components/PlotlyBarChart.tsx @@ -1,7 +1,8 @@ -import { QueryClient, QueryClientProvider, useQuery } from "react-query"; -import { Plotly } from "./Plotly"; -import Papa, { ParseConfig } from "papaparse"; -import LoadingSpinner from "./LoadingSpinner"; +import { QueryClient, QueryClientProvider, useQuery } from 'react-query'; +import { Plotly } from './Plotly'; +import Papa, { ParseConfig } from 'papaparse'; +import LoadingSpinner from './LoadingSpinner'; +import { Data } from '../types/properties'; const queryClient = new QueryClient(); @@ -17,7 +18,7 @@ async function getCsv(url: string, bytes: number) { async function parseCsv( file: string, - parsingConfig: ParseConfig, + parsingConfig: ParseConfig ): Promise { return new Promise((resolve, reject) => { Papa.parse(file, { @@ -39,43 +40,40 @@ async function parseCsv( } export interface PlotlyBarChartProps { - url?: string; - data?: { [key: string]: number | string }[]; - rawCsv?: string; - randomId?: number; + data: Data; + uniqueId?: number; bytes?: number; parsingConfig?: ParseConfig; xAxis: string; yAxis: string; - lineLabel?: string; + // TODO: commented out because this doesn't work. I believe + // this would only make any difference on charts with multiple + // traces. + // lineLabel?: string; title?: string; } export const PlotlyBarChart: React.FC = ({ - url, data, - rawCsv, bytes = 5132288, parsingConfig = {}, xAxis, yAxis, - lineLabel, - title = "", + // lineLabel, + title = '', }) => { - const randomId = Math.random(); + const uniqueId = Math.random(); return ( // Provide the client to your App @@ -83,30 +81,28 @@ export const PlotlyBarChart: React.FC = ({ }; const PlotlyBarChartInner: React.FC = ({ - url, data, - rawCsv, - randomId, + uniqueId, bytes, parsingConfig, xAxis, yAxis, - lineLabel, + // lineLabel, title, }) => { - if (data) { + if (data.values) { return ( -
+
d[xAxis]), - y: data.map((d) => d[yAxis]), - type: "bar", - name: lineLabel, + x: data.values.map((d) => d[xAxis]), + y: data.values.map((d) => d[yAxis]), + type: 'bar', + // name: lineLabel, }, ]} /> @@ -114,18 +110,18 @@ const PlotlyBarChartInner: React.FC = ({ ); } const { data: csvString, isLoading: isDownloadingCSV } = useQuery( - ["dataCsv", url, randomId], - () => getCsv(url as string, bytes ?? 5132288), - { enabled: !!url }, + ['dataCsv', data.url, uniqueId], + () => getCsv(data.url as string, bytes ?? 5132288), + { enabled: !!data.url } ); const { data: parsedData, isLoading: isParsing } = useQuery( - ["dataPreview", csvString, randomId], + ['dataPreview', csvString, uniqueId], () => parseCsv( - rawCsv ? (rawCsv as string) : (csvString as string), - parsingConfig ?? {}, + data.csv ? (data.csv as string) : (csvString as string), + parsingConfig ?? {} ), - { enabled: rawCsv ? true : !!csvString }, + { enabled: data.csv ? true : !!csvString } ); if (isParsing || isDownloadingCSV)
@@ -133,7 +129,7 @@ const PlotlyBarChartInner: React.FC = ({
; if (parsedData) return ( -
+
= ({ { x: parsedData.data.map((d: any) => d[xAxis]), y: parsedData.data.map((d: any) => d[yAxis]), - type: "bar", - name: lineLabel, + type: 'bar', + // name: lineLabel, TODO: commented out because this doesn't work }, ]} /> diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 154290f2d..a5c5a5c7e 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -8,7 +8,8 @@ export * from './components/OpenLayers/OpenLayers'; export * from './components/Map'; export * from './components/PdfViewer'; export * from "./components/Excel"; -export * from "./components/BucketViewer"; +// NOTE: hidden for now +// export * from "./components/BucketViewer"; export * from "./components/Iframe"; export * from "./components/Plotly"; export * from "./components/PlotlyLineChart"; diff --git a/packages/components/src/types/properties.ts b/packages/components/src/types/properties.ts new file mode 100644 index 000000000..35dc93e08 --- /dev/null +++ b/packages/components/src/types/properties.ts @@ -0,0 +1,11 @@ +/* + * All components should use this interface for + * its data property. + * Based on vega. + * + */ +export interface Data { + url?: string; + values?: { [key: string]: number | string }[]; + csv?: string; +} diff --git a/packages/components/stories/BarChartPlotly.stories.ts b/packages/components/stories/BarChartPlotly.stories.ts index 879312e62..a7b3d4a91 100644 --- a/packages/components/stories/BarChartPlotly.stories.ts +++ b/packages/components/stories/BarChartPlotly.stories.ts @@ -1,6 +1,9 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { PlotlyBarChart, PlotlyBarChartProps } from '../src/components/PlotlyBarChart'; +import { + PlotlyBarChart, + PlotlyBarChartProps, +} from '../src/components/PlotlyBarChart'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { @@ -8,39 +11,44 @@ const meta: Meta = { component: PlotlyBarChart, tags: ['autodocs'], argTypes: { - url: { - description: - 'CSV Url to be parsed and used as data source', - }, data: { description: - 'Data to be displayed. as an array of key value pairs \n\n E.g.: [{ year: 1850, temperature: -0.41765878 }, { year: 1851, temperature: -0.2333498 }, ...]', - }, - rawCsv: { - description: - 'Raw csv data to be parsed and used as data source', + 'Data to be displayed. \n\n \ +Must be an object with one of the following properties: `url`, `values` or `csv` \n\n \ +`url`: URL pointing to a CSV file. \n\n \ +`values`: array of objects (check out [this example](/?path=/story/components-plotlybarchart--from-data-points)) \n\n \ +`csv`: string with valid CSV (check out [this example](/?path=/story/components-plotlybarchart--from-inline-csv)) \n\n \ +', }, bytes: { + // TODO: likely this should be an extra option on the data parameter, + // specific to URLs description: - 'How many bytes to read from the url', + "How many bytes to read from the url so that the entire file doesn's have to be fetched.", }, parsingConfig: { - description: 'If using url or rawCsv, this parsing config will be used to parse the data. Optional, check https://www.papaparse.com/ for more info', + description: + 'If using URL or CSV, this parsing config will be used to parse the data. Optional, check https://www.papaparse.com/ for more info.', }, title: { description: 'Title to display on the chart. Optional.', }, - lineLabel: { - description: 'Label to display on the line, Optional, will use yAxis if not provided', - }, + // TODO: commented out because this doesn't work + // lineLabel: { + // description: + // 'Label to display on the line, Optional, will use yAxis if not provided', + // }, xAxis: { description: - 'Name of the X axis on the data. Required when the "data" parameter is an URL.', + 'Name of the column header or object property that represents the X-axis on the data.', }, yAxis: { description: - 'Name of the Y axis on the data. Required when the "data" parameter is an URL.', + 'Name of the column header or object property that represents the Y-axis on the data.', }, + uniqueId: { + description: 'Provide a unique ID to help with cache revalidation of the fetched data.' + } }, }; @@ -49,25 +57,45 @@ export default meta; type Story = StoryObj; export const FromDataPoints: Story = { - name: 'Line chart from array of data points', + name: 'Bar chart from array of data points', args: { - data: [ - {year: '1850', temperature: -0.41765878}, - {year: '1851', temperature: -0.2333498}, - {year: '1852', temperature: -0.22939907}, - {year: '1853', temperature: -0.27035445}, - {year: '1854', temperature: -0.29163003}, - ], + data: { + values: [ + { year: '1850', temperature: -0.41765878 }, + { year: '1851', temperature: -0.2333498 }, + { year: '1852', temperature: -0.22939907 }, + { year: '1853', temperature: -0.27035445 }, + { year: '1854', temperature: -0.29163003 }, + ], + }, xAxis: 'year', yAxis: 'temperature', }, }; export const FromURL: Story = { - name: 'Line chart from URL', + name: 'Bar chart from URL', args: { title: 'Apple Stock Prices', - url: 'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv', + data: { + url: 'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv', + }, + xAxis: 'Date', + yAxis: 'AAPL.Open', + }, +}; + +export const FromInlineCSV: Story = { + name: 'Bar chart from inline CSV', + args: { + title: 'Apple Stock Prices', + data: { + csv: `Date,AAPL.Open,AAPL.High,AAPL.Low,AAPL.Close,AAPL.Volume,AAPL.Adjusted,dn,mavg,up,direction +2015-02-17,127.489998,128.880005,126.919998,127.830002,63152400,122.905254,106.7410523,117.9276669,129.1142814,Increasing +2015-02-18,127.629997,128.779999,127.449997,128.720001,44891700,123.760965,107.842423,118.9403335,130.0382439,Increasing +2015-02-19,128.479996,129.029999,128.330002,128.449997,37362400,123.501363,108.8942449,119.8891668,130.8840887,Decreasing +2015-02-20,128.619995,129.5,128.050003,129.5,48948400,124.510914,109.7854494,120.7635001,131.7415509,Increasing`, + }, xAxis: 'Date', yAxis: 'AAPL.Open', }, diff --git a/packages/components/stories/BucketViewer.stories.ts b/packages/components/stories/BucketViewer.bkp.stories.ts similarity index 96% rename from packages/components/stories/BucketViewer.stories.ts rename to packages/components/stories/BucketViewer.bkp.stories.ts index 893fbb2e4..669acab4e 100644 --- a/packages/components/stories/BucketViewer.stories.ts +++ b/packages/components/stories/BucketViewer.bkp.stories.ts @@ -1,3 +1,6 @@ +// NOTE: this component was renamed with .bkp so that it's hidden +// from the Storybook app + import { type Meta, type StoryObj } from '@storybook/react'; import { diff --git a/packages/components/stories/OpenLayers.stories.tsx b/packages/components/stories/OpenLayers.bkp.stories.tsx similarity index 96% rename from packages/components/stories/OpenLayers.stories.tsx rename to packages/components/stories/OpenLayers.bkp.stories.tsx index 101d47407..d9eabbd0c 100644 --- a/packages/components/stories/OpenLayers.stories.tsx +++ b/packages/components/stories/OpenLayers.bkp.stories.tsx @@ -1,3 +1,6 @@ +// NOTE: this component was renamed with .bkp so that it's hidden +// from the Storybook app + import type { Meta, StoryObj } from '@storybook/react'; import React from 'react'; import OpenLayers from '../src/components/OpenLayers/OpenLayers'; From fe97cc87f46a5222693102df96e162655d500909 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 15:22:55 -0300 Subject: [PATCH 02/18] fix: OpenLayers and BucketViewer were still showing up --- .../stories/{BucketViewer.bkp.stories.ts => BucketViewer.bkp.ts} | 0 .../stories/{OpenLayers.bkp.stories.tsx => OpenLayers.bkp.tsx} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename packages/components/stories/{BucketViewer.bkp.stories.ts => BucketViewer.bkp.ts} (100%) rename packages/components/stories/{OpenLayers.bkp.stories.tsx => OpenLayers.bkp.tsx} (100%) diff --git a/packages/components/stories/BucketViewer.bkp.stories.ts b/packages/components/stories/BucketViewer.bkp.ts similarity index 100% rename from packages/components/stories/BucketViewer.bkp.stories.ts rename to packages/components/stories/BucketViewer.bkp.ts diff --git a/packages/components/stories/OpenLayers.bkp.stories.tsx b/packages/components/stories/OpenLayers.bkp.tsx similarity index 100% rename from packages/components/stories/OpenLayers.bkp.stories.tsx rename to packages/components/stories/OpenLayers.bkp.tsx From cda3d335f15f0c07d50656308c18c0bced86d2c2 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 15:25:14 -0300 Subject: [PATCH 03/18] feat: rename Plotly components stories so that they show up together on the storybook sidebar --- .../{BarChartPlotly.stories.ts => PlotlyBarChart.stories.ts} | 0 .../{LineChartPlotly.stories.ts => PlotlyLineChart.stories.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename packages/components/stories/{BarChartPlotly.stories.ts => PlotlyBarChart.stories.ts} (100%) rename packages/components/stories/{LineChartPlotly.stories.ts => PlotlyLineChart.stories.ts} (100%) diff --git a/packages/components/stories/BarChartPlotly.stories.ts b/packages/components/stories/PlotlyBarChart.stories.ts similarity index 100% rename from packages/components/stories/BarChartPlotly.stories.ts rename to packages/components/stories/PlotlyBarChart.stories.ts diff --git a/packages/components/stories/LineChartPlotly.stories.ts b/packages/components/stories/PlotlyLineChart.stories.ts similarity index 100% rename from packages/components/stories/LineChartPlotly.stories.ts rename to packages/components/stories/PlotlyLineChart.stories.ts From 8b292a9bf2d013539fd07fa377c1c6e3034cd798 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 15:36:48 -0300 Subject: [PATCH 04/18] feat: group stories in different categories --- packages/components/stories/Excel.stories.ts | 2 +- packages/components/stories/FlatUiTable.stories.ts | 2 +- packages/components/stories/Iframe.stories.ts | 2 +- packages/components/stories/LineChart.stories.ts | 2 +- packages/components/stories/Map.stories.ts | 2 +- packages/components/stories/PdfViewer.stories.ts | 2 +- packages/components/stories/Plotly.stories.ts | 2 +- packages/components/stories/PlotlyBarChart.stories.ts | 2 +- packages/components/stories/PlotlyLineChart.stories.ts | 2 +- packages/components/stories/Table.stories.ts | 2 +- packages/components/stories/Vega.stories.ts | 2 +- packages/components/stories/VegaLite.stories.ts | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/components/stories/Excel.stories.ts b/packages/components/stories/Excel.stories.ts index bda3ba974..c242b5021 100644 --- a/packages/components/stories/Excel.stories.ts +++ b/packages/components/stories/Excel.stories.ts @@ -4,7 +4,7 @@ import { Excel, ExcelProps } from '../src/components/Excel'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: 'Components/Excel', + title: 'Components/Tabular/Excel', component: Excel, tags: ['autodocs'], argTypes: { diff --git a/packages/components/stories/FlatUiTable.stories.ts b/packages/components/stories/FlatUiTable.stories.ts index 5546fa347..1baf616b5 100644 --- a/packages/components/stories/FlatUiTable.stories.ts +++ b/packages/components/stories/FlatUiTable.stories.ts @@ -4,7 +4,7 @@ import { FlatUiTable, FlatUiTableProps } from '../src/components/FlatUiTable'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: 'Components/FlatUiTable', + title: 'Components/Tabular/FlatUiTable', component: FlatUiTable, tags: ['autodocs'], argTypes: { diff --git a/packages/components/stories/Iframe.stories.ts b/packages/components/stories/Iframe.stories.ts index 6d5884a88..759acfe4c 100644 --- a/packages/components/stories/Iframe.stories.ts +++ b/packages/components/stories/Iframe.stories.ts @@ -3,7 +3,7 @@ import { type Meta, type StoryObj } from '@storybook/react'; import { Iframe, IframeProps } from '../src/components/Iframe'; const meta: Meta = { - title: 'Components/Iframe', + title: 'Components/Embedding/Iframe', component: Iframe, tags: ['autodocs'], argTypes: { diff --git a/packages/components/stories/LineChart.stories.ts b/packages/components/stories/LineChart.stories.ts index 8428ee8d7..9b17c2c90 100644 --- a/packages/components/stories/LineChart.stories.ts +++ b/packages/components/stories/LineChart.stories.ts @@ -4,7 +4,7 @@ import { LineChart, LineChartProps } from '../src/components/LineChart'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: 'Components/LineChart', + title: 'Components/Charts/LineChart', component: LineChart, tags: ['autodocs'], argTypes: { diff --git a/packages/components/stories/Map.stories.ts b/packages/components/stories/Map.stories.ts index 7358fa624..85c7d20f7 100644 --- a/packages/components/stories/Map.stories.ts +++ b/packages/components/stories/Map.stories.ts @@ -4,7 +4,7 @@ import { Map, MapProps } from '../src/components/Map'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: 'Components/Map', + title: 'Components/Geospatial/Map', component: Map, tags: ['autodocs'], argTypes: { diff --git a/packages/components/stories/PdfViewer.stories.ts b/packages/components/stories/PdfViewer.stories.ts index 352109f15..0abaf05f8 100644 --- a/packages/components/stories/PdfViewer.stories.ts +++ b/packages/components/stories/PdfViewer.stories.ts @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { PdfViewer, PdfViewerProps } from '../src/components/PdfViewer'; const meta: Meta = { - title: 'Components/PdfViewer', + title: 'Components/Embedding/PdfViewer', component: PdfViewer, tags: ['autodocs'], argTypes: { diff --git a/packages/components/stories/Plotly.stories.ts b/packages/components/stories/Plotly.stories.ts index d35559dcb..bd3e8177a 100644 --- a/packages/components/stories/Plotly.stories.ts +++ b/packages/components/stories/Plotly.stories.ts @@ -4,7 +4,7 @@ import { Plotly } from '../src/components/Plotly'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: 'Components/Plotly', + title: 'Components/Charts/Plotly', component: Plotly, tags: ['autodocs'], }; diff --git a/packages/components/stories/PlotlyBarChart.stories.ts b/packages/components/stories/PlotlyBarChart.stories.ts index a7b3d4a91..7bc3c20f5 100644 --- a/packages/components/stories/PlotlyBarChart.stories.ts +++ b/packages/components/stories/PlotlyBarChart.stories.ts @@ -7,7 +7,7 @@ import { // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: 'Components/PlotlyBarChart', + title: 'Components/Charts/PlotlyBarChart', component: PlotlyBarChart, tags: ['autodocs'], argTypes: { diff --git a/packages/components/stories/PlotlyLineChart.stories.ts b/packages/components/stories/PlotlyLineChart.stories.ts index 435b6bb89..f028dbdd6 100644 --- a/packages/components/stories/PlotlyLineChart.stories.ts +++ b/packages/components/stories/PlotlyLineChart.stories.ts @@ -4,7 +4,7 @@ import { PlotlyLineChart, PlotlyLineChartProps } from '../src/components/PlotlyL // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: 'Components/PlotlyLineChart', + title: 'Components/Charts/PlotlyLineChart', component: PlotlyLineChart, tags: ['autodocs'], argTypes: { diff --git a/packages/components/stories/Table.stories.ts b/packages/components/stories/Table.stories.ts index 2f551bd74..d1023c8fd 100644 --- a/packages/components/stories/Table.stories.ts +++ b/packages/components/stories/Table.stories.ts @@ -4,7 +4,7 @@ import { Table, TableProps } from '../src/components/Table'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: 'Components/Table', + title: 'Components/Tabular/Table', component: Table, tags: ['autodocs'], argTypes: { diff --git a/packages/components/stories/Vega.stories.ts b/packages/components/stories/Vega.stories.ts index bbc8f7e3e..6c4eed65b 100644 --- a/packages/components/stories/Vega.stories.ts +++ b/packages/components/stories/Vega.stories.ts @@ -4,7 +4,7 @@ import { Vega } from '../src/components/Vega'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: 'Components/Vega', + title: 'Components/Charts/Vega', component: Vega, tags: ['autodocs'], }; diff --git a/packages/components/stories/VegaLite.stories.ts b/packages/components/stories/VegaLite.stories.ts index c0936720d..28a43771d 100644 --- a/packages/components/stories/VegaLite.stories.ts +++ b/packages/components/stories/VegaLite.stories.ts @@ -4,7 +4,7 @@ import { VegaLite } from '../src/components/VegaLite'; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: 'Components/VegaLite', + title: 'Components/Charts/VegaLite', component: VegaLite, tags: ['autodocs'], argTypes: { From 22038fbd4fedee952279b6b31e691c391f32d6c8 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 15:44:37 -0300 Subject: [PATCH 05/18] feat: Excel component API and docs improvements --- packages/components/src/components/Excel.tsx | 6 ++++-- packages/components/stories/Excel.stories.ts | 12 ++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/components/src/components/Excel.tsx b/packages/components/src/components/Excel.tsx index f49c320d6..0878fc859 100644 --- a/packages/components/src/components/Excel.tsx +++ b/packages/components/src/components/Excel.tsx @@ -4,12 +4,14 @@ import { read, utils } from 'xlsx'; import { AgGridReact } from 'ag-grid-react'; import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-theme-alpine.css'; +import { Data } from '../types/properties'; export type ExcelProps = { - url: string; + data: Pick; }; -export function Excel({ url }: ExcelProps) { +export function Excel({ data }: ExcelProps) { + const url = data.url; const [isLoading, setIsLoading] = useState(false); const [activeSheetName, setActiveSheetName] = useState(); const [workbook, setWorkbook] = useState(); diff --git a/packages/components/stories/Excel.stories.ts b/packages/components/stories/Excel.stories.ts index c242b5021..c5c6c8092 100644 --- a/packages/components/stories/Excel.stories.ts +++ b/packages/components/stories/Excel.stories.ts @@ -8,9 +8,9 @@ const meta: Meta = { component: Excel, tags: ['autodocs'], argTypes: { - url: { + data: { description: - 'Url of the file to be displayed e.g.: "https://url.to/data.csv"', + 'Object with a `url` property pointing to the Excel file to be displayed, e.g.: `{ url: "https://url.to/data.csv" }`', }, }, }; @@ -22,13 +22,17 @@ type Story = StoryObj; export const SingleSheet: Story = { name: 'Excel file with just one sheet', args: { - url: 'https://sheetjs.com/pres.xlsx', + data: { + url: 'https://sheetjs.com/pres.xlsx', + }, }, }; export const MultipleSheet: Story = { name: 'Excel file with multiple sheets', args: { - url: 'https://storage.portaljs.org/IC-Gantt-Chart-Project-Template-8857.xlsx', + data: { + url: 'https://storage.portaljs.org/IC-Gantt-Chart-Project-Template-8857.xlsx', + }, }, }; From e6f0ab4ec8004ae4c0db24805114925b7a409abf Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 15:54:03 -0300 Subject: [PATCH 06/18] feat: FlatUiTable component API and docs improvements --- .../components/src/components/FlatUiTable.tsx | 39 ++++++-------- .../components/stories/FlatUiTable.stories.ts | 52 +++++++++++-------- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/packages/components/src/components/FlatUiTable.tsx b/packages/components/src/components/FlatUiTable.tsx index 6b34e1415..cb4dd6772 100644 --- a/packages/components/src/components/FlatUiTable.tsx +++ b/packages/components/src/components/FlatUiTable.tsx @@ -2,6 +2,7 @@ import { QueryClient, QueryClientProvider, useQuery } from 'react-query'; import Papa from 'papaparse'; import { Grid } from '@githubocto/flat-ui'; import LoadingSpinner from './LoadingSpinner'; +import { Data } from '../types/properties'; const queryClient = new QueryClient(); @@ -36,30 +37,25 @@ export async function parseCsv(file: string, parsingConfig): Promise { } export interface FlatUiTableProps { - url?: string; - data?: { [key: string]: number | string }[]; - rawCsv?: string; - randomId?: number; + data: Data; + uniqueId?: number; bytes: number; parsingConfig: any; } export const FlatUiTable: React.FC = ({ - url, data, - rawCsv, + uniqueId, bytes = 5132288, parsingConfig = {}, }) => { - const randomId = Math.random(); + uniqueId = uniqueId ?? Math.random(); return ( // Provide the client to your App @@ -67,33 +63,32 @@ export const FlatUiTable: React.FC = ({ }; const TableInner: React.FC = ({ - url, data, - rawCsv, - randomId, + uniqueId, bytes, parsingConfig, }) => { - if (data) { + const url = data.url; + const csv = data.csv; + const values = data.values; + + if (values) { return (
- +
); } const { data: csvString, isLoading: isDownloadingCSV } = useQuery( - ['dataCsv', url, randomId], + ['dataCsv', url, uniqueId], () => getCsv(url as string, bytes), { enabled: !!url } ); const { data: parsedData, isLoading: isParsing } = useQuery( - ['dataPreview', csvString, randomId], + ['dataPreview', csvString, uniqueId], () => - parseCsv( - rawCsv ? (rawCsv as string) : (csvString as string), - parsingConfig - ), - { enabled: rawCsv ? true : !!csvString } + parseCsv(csv ? (csv as string) : (csvString as string), parsingConfig), + { enabled: csv ? true : !!csvString } ); if (isParsing || isDownloadingCSV)
diff --git a/packages/components/stories/FlatUiTable.stories.ts b/packages/components/stories/FlatUiTable.stories.ts index 1baf616b5..852e07fbd 100644 --- a/packages/components/stories/FlatUiTable.stories.ts +++ b/packages/components/stories/FlatUiTable.stories.ts @@ -10,23 +10,25 @@ const meta: Meta = { argTypes: { data: { description: - 'Data to be displayed in the table, must be setup as an array of key value pairs', - }, - csv: { - description: 'CSV data as string.', - }, - url: { - description: - 'Fetch the data from a CSV file remotely. only the first 5MB of data will be displayed', + 'Data to be displayed. \n\n \ +Must be an object with one of the following properties: `url`, `values` or `csv` \n\n \ +`url`: URL pointing to a CSV file. \n\n \ +`values`: array of objects. \n\n \ +`csv`: string with valid CSV. \n\n \ +', }, bytes: { description: - 'Fetch the data from a CSV file remotely. only the first of data will be displayed', + 'Fetch the data from a CSV file remotely. Only the first of data will be displayed. Defaults to 5MB.', }, parsingConfig: { description: 'Configuration for parsing the CSV data. See https://www.papaparse.com/docs#config for more details', }, + uniqueId: { + description: + 'Provide a unique ID to help with cache revalidation of the fetched data.', + }, }, }; @@ -36,34 +38,40 @@ type Story = StoryObj; // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args export const FromColumnsAndData: Story = { - name: 'Table data', + name: 'Table from array or objects', args: { - data: [ - { id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 }, - { id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 }, - { id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 }, - { id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 }, - { id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 }, - { id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 }, - { id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 }, - ], + data: { + values: [ + { id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 }, + { id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 }, + { id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 }, + { id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 }, + { id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 }, + { id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 }, + { id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 }, + ], + }, }, }; export const FromRawCSV: Story = { - name: 'Table from raw CSV', + name: 'Table from inline CSV', args: { - rawCsv: ` + data: { + csv: ` Year,Temp Anomaly 1850,-0.418 2020,0.923 `, + }, }, }; export const FromURL: Story = { name: 'Table from URL', args: { - url: 'https://storage.openspending.org/alberta-budget/__os_imported__alberta_total.csv', + data: { + url: 'https://storage.openspending.org/alberta-budget/__os_imported__alberta_total.csv', + }, }, }; From 4b5d549190e450abfc95eaea5a37d6986766064a Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 15:58:33 -0300 Subject: [PATCH 07/18] feat: comment out the Table component for now --- packages/components/src/index.ts | 9 +++++---- .../stories/{Table.stories.ts => Table.bkp.ts} | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) rename packages/components/stories/{Table.stories.ts => Table.bkp.ts} (96%) diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index a5c5a5c7e..4e06f8237 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -1,16 +1,17 @@ -export * from './components/Table'; export * from './components/Catalog'; export * from './components/LineChart'; export * from './components/Vega'; export * from './components/VegaLite'; export * from './components/FlatUiTable'; -export * from './components/OpenLayers/OpenLayers'; export * from './components/Map'; export * from './components/PdfViewer'; export * from "./components/Excel"; -// NOTE: hidden for now -// export * from "./components/BucketViewer"; export * from "./components/Iframe"; export * from "./components/Plotly"; export * from "./components/PlotlyLineChart"; export * from "./components/PlotlyBarChart"; +// NOTE: components that are hidden for now +// TODO: deprecate those components? +// export * from './components/Table'; +// export * from "./components/BucketViewer"; +// export * from './components/OpenLayers/OpenLayers'; diff --git a/packages/components/stories/Table.stories.ts b/packages/components/stories/Table.bkp.ts similarity index 96% rename from packages/components/stories/Table.stories.ts rename to packages/components/stories/Table.bkp.ts index d1023c8fd..fc567c710 100644 --- a/packages/components/stories/Table.stories.ts +++ b/packages/components/stories/Table.bkp.ts @@ -1,3 +1,6 @@ +// NOTE: this component was renamed with .bkp so that it's hidden +// from the Storybook app + import type { Meta, StoryObj } from '@storybook/react'; import { Table, TableProps } from '../src/components/Table'; From b7ee5a1869fa1501a7e5bcb7b0dca3a7e81e2368 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 16:07:12 -0300 Subject: [PATCH 08/18] feat: Iframe component API and docs improvements --- packages/components/src/components/Excel.tsx | 2 +- packages/components/src/components/Iframe.tsx | 15 +++++++++------ packages/components/stories/Iframe.stories.ts | 12 +++++++----- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/components/src/components/Excel.tsx b/packages/components/src/components/Excel.tsx index 0878fc859..f82e08400 100644 --- a/packages/components/src/components/Excel.tsx +++ b/packages/components/src/components/Excel.tsx @@ -7,7 +7,7 @@ import 'ag-grid-community/styles/ag-theme-alpine.css'; import { Data } from '../types/properties'; export type ExcelProps = { - data: Pick; + data: Required>; }; export function Excel({ data }: ExcelProps) { diff --git a/packages/components/src/components/Iframe.tsx b/packages/components/src/components/Iframe.tsx index 9cdb65a56..c85bd6307 100644 --- a/packages/components/src/components/Iframe.tsx +++ b/packages/components/src/components/Iframe.tsx @@ -1,14 +1,17 @@ -import { CSSProperties } from "react"; +import { CSSProperties } from 'react'; +import { Data } from '../types/properties'; export interface IframeProps { - url: string; + data: Required>; style?: CSSProperties; } -export function Iframe({ - url, style -}: IframeProps) { +export function Iframe({ data, style }: IframeProps) { + const url = data.url; return ( - + ); } diff --git a/packages/components/stories/Iframe.stories.ts b/packages/components/stories/Iframe.stories.ts index 759acfe4c..04b8ec35b 100644 --- a/packages/components/stories/Iframe.stories.ts +++ b/packages/components/stories/Iframe.stories.ts @@ -7,13 +7,13 @@ const meta: Meta = { component: Iframe, tags: ['autodocs'], argTypes: { - url: { + data: { description: - 'Page to display inside of the component', + 'Object with a `url` property pointing to the page to be embeded.', }, style: { description: - 'Style of the component', + 'Style object of the component. See example at https://react.dev/learn#displaying-data. Defaults to `{ width: "100%", height: "100%" }`', }, }, }; @@ -25,7 +25,9 @@ type Story = StoryObj; export const Normal: Story = { name: 'Iframe', args: { - url: 'https://app.powerbi.com/view?r=eyJrIjoiYzBmN2Q2MzYtYzE3MS00ODkxLWE5OWMtZTQ2MjBlMDljMDk4IiwidCI6Ijk1M2IwZjgzLTFjZTYtNDVjMy04MmM5LTFkODQ3ZTM3MjMzOSIsImMiOjh9', - style: {width: `100%`, height: `100%`} + data: { + url: 'https://app.powerbi.com/view?r=eyJrIjoiYzBmN2Q2MzYtYzE3MS00ODkxLWE5OWMtZTQ2MjBlMDljMDk4IiwidCI6Ijk1M2IwZjgzLTFjZTYtNDVjMy04MmM5LTFkODQ3ZTM3MjMzOSIsImMiOjh9', + }, + style: { width: `100%`, height: `100%` }, }, }; From d9c20528c5974ce3b8f186a311b45818bfe66041 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 16:20:01 -0300 Subject: [PATCH 09/18] feat: PdfViewer component API and docs improvements --- .../components/src/components/PdfViewer.tsx | 8 ++-- .../components/stories/PdfViewer.stories.ts | 37 +++++++++---------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/packages/components/src/components/PdfViewer.tsx b/packages/components/src/components/PdfViewer.tsx index a4b745b4b..80bbad503 100644 --- a/packages/components/src/components/PdfViewer.tsx +++ b/packages/components/src/components/PdfViewer.tsx @@ -1,22 +1,24 @@ // Core viewer import { Viewer, Worker, SpecialZoomLevel } from '@react-pdf-viewer/core'; import { defaultLayoutPlugin } from '@react-pdf-viewer/default-layout'; +import { Data } from '../types/properties'; // Import styles import '@react-pdf-viewer/core/lib/styles/index.css'; import '@react-pdf-viewer/default-layout/lib/styles/index.css'; export interface PdfViewerProps { - url: string; + data: Required>; layout: boolean; parentClassName?: string; } export function PdfViewer({ - url, + data, layout = false, - parentClassName, + parentClassName = 'h-screen', }: PdfViewerProps) { + const url = data.url; const defaultLayoutPluginInstance = defaultLayoutPlugin(); return ( diff --git a/packages/components/stories/PdfViewer.stories.ts b/packages/components/stories/PdfViewer.stories.ts index 0abaf05f8..e5a812af6 100644 --- a/packages/components/stories/PdfViewer.stories.ts +++ b/packages/components/stories/PdfViewer.stories.ts @@ -7,15 +7,17 @@ const meta: Meta = { component: PdfViewer, tags: ['autodocs'], argTypes: { - url: { - description: 'URL to PDF file', + data: { + description: + 'Object with a `url` property pointing to the PDF file to be displayed, e.g.: `{ url: "https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK" }`.', }, parentClassName: { - description: 'Classname for the parent div of the pdf viewer', + description: + 'HTML classes to be applied to the container of the PDF viewer. [Tailwind](https://tailwindcss.com/) classes, such as `h-96` to define the height of the component, can be used on this field.', }, - layour: { + layout: { description: - 'Set to true if you want to have a layout with zoom level, page count, printing button etc', + 'Set to `true` if you want to display a layout with zoom level, page count, printing button and other controls.', defaultValue: false, }, }, @@ -25,26 +27,23 @@ export default meta; type Story = StoryObj; -export const PdfViewerStory: Story = { - name: 'PdfViewer', +export const PdfViewerStoryWithoutControlsLayout: Story = { + name: 'PDF Viewer without controls layout', args: { - url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK', + data: { + url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK', + }, + parentClassName: 'h-96', }, }; -export const PdfViewerStoryWithLayout: Story = { - name: 'PdfViewer with the default layout', +export const PdfViewerStoryWithControlsLayout: Story = { + name: 'PdfViewer with controls layout', args: { - url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK', + data: { + url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK', + }, layout: true, - }, -}; - -export const PdfViewerStoryWithHeight: Story = { - name: 'PdfViewer with a custom height', - args: { - url: 'https://cdn.filestackcontent.com/wcrjf9qPTCKXV3hMXDwK', parentClassName: 'h-96', - layout: true, }, }; From c202d6cfc4cfa2574cd5da1dabad8d0bfa6fb872 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 16:50:49 -0300 Subject: [PATCH 10/18] feat: LineChart component API and docs improvements --- .../components/src/components/LineChart.tsx | 30 +++++++------- .../components/stories/LineChart.stories.ts | 39 ++++++++++++------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/packages/components/src/components/LineChart.tsx b/packages/components/src/components/LineChart.tsx index 57e9b0984..deb68f6eb 100644 --- a/packages/components/src/components/LineChart.tsx +++ b/packages/components/src/components/LineChart.tsx @@ -2,31 +2,34 @@ import { useEffect, useState } from 'react'; import LoadingSpinner from './LoadingSpinner'; import { VegaLite } from './VegaLite'; import loadData from '../lib/loadData'; +import { Data } from '../types/properties'; type AxisType = 'quantitative' | 'temporal'; type TimeUnit = 'year' | undefined; // or ... export type LineChartProps = { - data: Array> | string | { x: string; y: number }[]; + data: Omit; title?: string; - xAxis?: string; + xAxis: string; xAxisType?: AxisType; - xAxisTimeUnit: TimeUnit; - yAxis?: string; + xAxisTimeUnit?: TimeUnit; + yAxis: string; yAxisType?: AxisType; fullWidth?: boolean; }; export function LineChart({ - data = [], + data, fullWidth = false, title = '', - xAxis = 'x', + xAxis, xAxisType = 'temporal', xAxisTimeUnit = 'year', // TODO: defaults to undefined would probably work better... keeping it as it's for compatibility purposes - yAxis = 'y', + yAxis, yAxisType = 'quantitative', }: LineChartProps) { + const url = data.url; + const values = data.values; const [isLoading, setIsLoading] = useState(false); // By default, assumes data is an Array... @@ -64,13 +67,12 @@ export function LineChart({ } as any; useEffect(() => { - // If data is string, assume it's a URL - if (typeof data === 'string') { + if (url) { setIsLoading(true); // Manualy loading the data allows us to do other kinds // of stuff later e.g. load a file partially - loadData(data).then((res: any) => { + loadData(url).then((res: any) => { setSpecData({ values: res, format: { type: 'csv' } }); setIsLoading(false); }); @@ -78,12 +80,8 @@ export function LineChart({ }, []); var vegaData = {}; - if (Array.isArray(data)) { - var dataObj; - dataObj = data.map((r) => { - return { x: r[0], y: r[1] }; - }); - vegaData = { table: dataObj }; + if (values) { + vegaData = { table: values }; } return isLoading ? ( diff --git a/packages/components/stories/LineChart.stories.ts b/packages/components/stories/LineChart.stories.ts index 9b17c2c90..62b7650c3 100644 --- a/packages/components/stories/LineChart.stories.ts +++ b/packages/components/stories/LineChart.stories.ts @@ -10,27 +10,30 @@ const meta: Meta = { argTypes: { data: { description: - 'Data to be displayed.\n\n E.g.: [["1990", 1], ["1991", 2]] \n\nOR\n\n "https://url.to/data.csv"', + 'Data to be displayed. \n\n \ +Must be an object with one of the following properties: `url` or `values` \n\n \ +`url`: URL pointing to a CSV file. \n\n \ +`values`: array of objects \n\n', }, title: { - description: 'Title to display on the chart. Optional.', + description: 'Title to display on the chart.', }, xAxis: { description: - 'Name of the X axis on the data. Required when the "data" parameter is an URL.', + 'Name of the column header or object property that represents the X-axis on the data.', }, xAxisType: { - description: 'Type of the X axis', + description: 'Type of the X-axis.', }, xAxisTimeUnit: { - description: 'Time unit of the X axis (optional)', + description: 'Time unit of the X-axis, in case its type is `temporal.`', }, yAxis: { description: - 'Name of the Y axis on the data. Required when the "data" parameter is an URL.', + 'Name of the column header or object property that represents the Y-axis on the data.', }, yAxisType: { - description: 'Type of the Y axis', + description: 'Type of the Y-axis', }, fullWidth: { description: @@ -47,21 +50,27 @@ type Story = StoryObj; export const FromDataPoints: Story = { name: 'Line chart from array of data points', args: { - data: [ - ['1850', -0.41765878], - ['1851', -0.2333498], - ['1852', -0.22939907], - ['1853', -0.27035445], - ['1854', -0.29163003], - ], + data: { + values: [ + { year: '1850', value: -0.41765878 }, + { year: '1851', value: -0.2333498 }, + { year: '1852', value: -0.22939907 }, + { year: '1853', value: -0.27035445 }, + { year: '1854', value: -0.29163003 }, + ], + }, + xAxis: 'year', + yAxis: 'value', }, }; export const FromURL: Story = { name: 'Line chart from URL', args: { + data: { + url: 'https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv', + }, title: 'Oil Price x Year', - data: 'https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv', xAxis: 'Date', yAxis: 'Price', }, From 0aed7dce774087a2bb3d1f8f025827d6bfb3dce8 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 16:57:23 -0300 Subject: [PATCH 11/18] feat: Plotly component docs improvements --- packages/components/stories/Plotly.stories.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/components/stories/Plotly.stories.ts b/packages/components/stories/Plotly.stories.ts index bd3e8177a..7824f44ff 100644 --- a/packages/components/stories/Plotly.stories.ts +++ b/packages/components/stories/Plotly.stories.ts @@ -7,6 +7,16 @@ const meta: Meta = { title: 'Components/Charts/Plotly', component: Plotly, tags: ['autodocs'], + argTypes: { + data: { + description: + "Plotly's `data` prop. You can find references on how to use these props at https://github.com/plotly/react-plotly.js/#basic-props.", + }, + layout: { + description: + "Plotly's `layout` prop. You can find references on how to use these props at https://github.com/plotly/react-plotly.js/#basic-props.", + }, + }, }; export default meta; @@ -15,7 +25,7 @@ type Story = StoryObj; // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args export const Primary: Story = { - name: 'Chart built with Plotly', + name: 'Line chart', args: { data: [ { From 059ffe4e34fbcd28ee96de550e070b07a82f3687 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 17:08:50 -0300 Subject: [PATCH 12/18] feat: PlotlyLineChart component API and docs improvements --- .../src/components/PlotlyLineChart.tsx | 62 ++++++++------- packages/components/stories/Map.stories.ts | 2 +- .../stories/PlotlyBarChart.stories.ts | 4 +- .../stories/PlotlyLineChart.stories.ts | 77 +++++++++++++------ 4 files changed, 85 insertions(+), 60 deletions(-) diff --git a/packages/components/src/components/PlotlyLineChart.tsx b/packages/components/src/components/PlotlyLineChart.tsx index 2d8323f30..1732b8260 100644 --- a/packages/components/src/components/PlotlyLineChart.tsx +++ b/packages/components/src/components/PlotlyLineChart.tsx @@ -1,7 +1,8 @@ -import { QueryClient, QueryClientProvider, useQuery } from "react-query"; -import { Plotly } from "./Plotly"; -import Papa, { ParseConfig } from "papaparse"; -import LoadingSpinner from "./LoadingSpinner"; +import { QueryClient, QueryClientProvider, useQuery } from 'react-query'; +import { Plotly } from './Plotly'; +import Papa, { ParseConfig } from 'papaparse'; +import LoadingSpinner from './LoadingSpinner'; +import { Data } from '../types/properties'; const queryClient = new QueryClient(); @@ -17,7 +18,7 @@ async function getCsv(url: string, bytes: number) { async function parseCsv( file: string, - parsingConfig: ParseConfig, + parsingConfig: ParseConfig ): Promise { return new Promise((resolve, reject) => { Papa.parse(file, { @@ -39,38 +40,33 @@ async function parseCsv( } export interface PlotlyLineChartProps { - url?: string; - data?: { [key: string]: number | string }[]; - rawCsv?: string; - randomId?: number; + data: Data; bytes?: number; parsingConfig?: ParseConfig; xAxis: string; yAxis: string; lineLabel?: string; title?: string; + uniqueId?: number; } export const PlotlyLineChart: React.FC = ({ - url, data, - rawCsv, bytes = 5132288, parsingConfig = {}, xAxis, yAxis, lineLabel, - title = "", + title = '', + uniqueId, }) => { - const randomId = Math.random(); + uniqueId = uniqueId ?? Math.random(); return ( // Provide the client to your App = ({ }; const LineChartInner: React.FC = ({ - url, data, - rawCsv, - randomId, + uniqueId, bytes, parsingConfig, xAxis, @@ -94,18 +88,22 @@ const LineChartInner: React.FC = ({ lineLabel, title, }) => { - if (data) { + const values = data.values; + const url = data.url; + const csv = data.csv; + + if (values) { return ( -
+
d[xAxis]), - y: data.map((d) => d[yAxis]), - mode: "lines", + x: values.map((d) => d[xAxis]), + y: values.map((d) => d[yAxis]), + mode: 'lines', name: lineLabel, }, ]} @@ -114,18 +112,18 @@ const LineChartInner: React.FC = ({ ); } const { data: csvString, isLoading: isDownloadingCSV } = useQuery( - ["dataCsv", url, randomId], + ['dataCsv', url, uniqueId], () => getCsv(url as string, bytes ?? 5132288), - { enabled: !!url }, + { enabled: !!url } ); const { data: parsedData, isLoading: isParsing } = useQuery( - ["dataPreview", csvString, randomId], + ['dataPreview', csvString, uniqueId], () => parseCsv( - rawCsv ? (rawCsv as string) : (csvString as string), - parsingConfig ?? {}, + csv ? (csv as string) : (csvString as string), + parsingConfig ?? {} ), - { enabled: rawCsv ? true : !!csvString }, + { enabled: csv ? true : !!csvString } ); if (isParsing || isDownloadingCSV)
@@ -133,7 +131,7 @@ const LineChartInner: React.FC = ({
; if (parsedData) return ( -
+
= ({ { x: parsedData.data.map((d: any) => d[xAxis]), y: parsedData.data.map((d: any) => d[yAxis]), - mode: "lines", + mode: 'lines', name: lineLabel, }, ]} diff --git a/packages/components/stories/Map.stories.ts b/packages/components/stories/Map.stories.ts index 85c7d20f7..0cdbd8a64 100644 --- a/packages/components/stories/Map.stories.ts +++ b/packages/components/stories/Map.stories.ts @@ -13,7 +13,7 @@ const meta: Meta = { 'Data to be displayed.\n\n GeoJSON Object \n\nOR\n\n URL to GeoJSON Object', }, title: { - description: 'Title to display on the map. Optional.', + description: 'Title to display on the map.', }, center: { description: 'Initial coordinates of the center of the map', diff --git a/packages/components/stories/PlotlyBarChart.stories.ts b/packages/components/stories/PlotlyBarChart.stories.ts index 7bc3c20f5..0acb092dc 100644 --- a/packages/components/stories/PlotlyBarChart.stories.ts +++ b/packages/components/stories/PlotlyBarChart.stories.ts @@ -28,10 +28,10 @@ Must be an object with one of the following properties: `url`, `values` or `csv` }, parsingConfig: { description: - 'If using URL or CSV, this parsing config will be used to parse the data. Optional, check https://www.papaparse.com/ for more info.', + 'If using URL or CSV, this parsing config will be used to parse the data. Check https://www.papaparse.com/ for more info.', }, title: { - description: 'Title to display on the chart. Optional.', + description: 'Title to display on the chart.', }, // TODO: commented out because this doesn't work // lineLabel: { diff --git a/packages/components/stories/PlotlyLineChart.stories.ts b/packages/components/stories/PlotlyLineChart.stories.ts index f028dbdd6..9ebfd15e5 100644 --- a/packages/components/stories/PlotlyLineChart.stories.ts +++ b/packages/components/stories/PlotlyLineChart.stories.ts @@ -1,45 +1,52 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { PlotlyLineChart, PlotlyLineChartProps } from '../src/components/PlotlyLineChart'; +import { + PlotlyLineChart, + PlotlyLineChartProps, +} from '../src/components/PlotlyLineChart'; -// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { title: 'Components/Charts/PlotlyLineChart', component: PlotlyLineChart, tags: ['autodocs'], argTypes: { - url: { - description: - 'CSV Url to be parsed and used as data source', - }, data: { description: - 'Data to be displayed. as an array of key value pairs \n\n E.g.: [{ year: 1850, temperature: -0.41765878 }, { year: 1851, temperature: -0.2333498 }, ...]', - }, - rawCsv: { - description: - 'Raw csv data to be parsed and used as data source', + 'Data to be displayed. \n\n \ +Must be an object with one of the following properties: `url`, `values` or `csv` \n\n \ +`url`: URL pointing to a CSV file. \n\n \ +`values`: array of objects. \n\n \ +`csv`: string with valid CSV. \n\n \ +', }, bytes: { + // TODO: likely this should be an extra option on the data parameter, + // specific to URLs description: - 'How many bytes to read from the url', + "How many bytes to read from the url so that the entire file doesn's have to be fetched.", }, parsingConfig: { - description: 'If using url or rawCsv, this parsing config will be used to parse the data. Optional, check https://www.papaparse.com/ for more info', + description: + 'If using URL or CSV, this parsing config will be used to parse the data. Check https://www.papaparse.com/ for more info', }, title: { - description: 'Title to display on the chart. Optional.', + description: 'Title to display on the chart.', }, lineLabel: { - description: 'Label to display on the line, Optional, will use yAxis if not provided', + description: + 'Label to display on the line, will use yAxis if not provided', }, xAxis: { description: - 'Name of the X axis on the data. Required when the "data" parameter is an URL.', + 'Name of the column header or object property that represents the X-axis on the data.', }, yAxis: { description: - 'Name of the Y axis on the data. Required when the "data" parameter is an URL.', + 'Name of the column header or object property that represents the Y-axis on the data.', + }, + uniqueId: { + description: + 'Provide a unique ID to help with cache revalidation of the fetched data.', }, }, }; @@ -51,13 +58,15 @@ type Story = StoryObj; export const FromDataPoints: Story = { name: 'Line chart from array of data points', args: { - data: [ - {year: '1850', temperature: -0.41765878}, - {year: '1851', temperature: -0.2333498}, - {year: '1852', temperature: -0.22939907}, - {year: '1853', temperature: -0.27035445}, - {year: '1854', temperature: -0.29163003}, - ], + data: { + values: [ + { year: '1850', temperature: -0.41765878 }, + { year: '1851', temperature: -0.2333498 }, + { year: '1852', temperature: -0.22939907 }, + { year: '1853', temperature: -0.27035445 }, + { year: '1854', temperature: -0.29163003 }, + ], + }, xAxis: 'year', yAxis: 'temperature', }, @@ -67,8 +76,26 @@ export const FromURL: Story = { name: 'Line chart from URL', args: { title: 'Oil Price x Year', - url: 'https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv', + data: { + url: 'https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv', + }, xAxis: 'Date', yAxis: 'Price', }, }; + +export const FromInlineCSV: Story = { + name: 'Bar chart from inline CSV', + args: { + title: 'Apple Stock Prices', + data: { + csv: `Date,AAPL.Open,AAPL.High,AAPL.Low,AAPL.Close,AAPL.Volume,AAPL.Adjusted,dn,mavg,up,direction +2015-02-17,127.489998,128.880005,126.919998,127.830002,63152400,122.905254,106.7410523,117.9276669,129.1142814,Increasing +2015-02-18,127.629997,128.779999,127.449997,128.720001,44891700,123.760965,107.842423,118.9403335,130.0382439,Increasing +2015-02-19,128.479996,129.029999,128.330002,128.449997,37362400,123.501363,108.8942449,119.8891668,130.8840887,Decreasing +2015-02-20,128.619995,129.5,128.050003,129.5,48948400,124.510914,109.7854494,120.7635001,131.7415509,Increasing`, + }, + xAxis: 'Date', + yAxis: 'AAPL.Open', + }, +}; From 3d73ac422e33bfd5b1f9a842e626271e20cff263 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 17:13:05 -0300 Subject: [PATCH 13/18] feat: Vega and Vega Lite components API and docs improvements --- packages/components/src/components/Vega.tsx | 3 ++- packages/components/src/components/VegaLite.tsx | 7 ++++--- packages/components/stories/Vega.stories.ts | 12 +++++++++++- packages/components/stories/VegaLite.stories.ts | 2 +- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/components/src/components/Vega.tsx b/packages/components/src/components/Vega.tsx index 3a66d8aa0..a51f053fd 100644 --- a/packages/components/src/components/Vega.tsx +++ b/packages/components/src/components/Vega.tsx @@ -1,6 +1,7 @@ // Wrapper for the Vega component import { Vega as VegaOg } from "react-vega"; +import { VegaProps } from "react-vega/lib/Vega"; -export function Vega(props) { +export function Vega(props: VegaProps) { return ; } diff --git a/packages/components/src/components/VegaLite.tsx b/packages/components/src/components/VegaLite.tsx index 7c311966e..3b48d142f 100644 --- a/packages/components/src/components/VegaLite.tsx +++ b/packages/components/src/components/VegaLite.tsx @@ -1,8 +1,9 @@ // Wrapper for the Vega Lite component -import { VegaLite as VegaLiteOg } from "react-vega"; -import applyFullWidthDirective from "../lib/applyFullWidthDirective"; +import { VegaLite as VegaLiteOg } from 'react-vega'; +import { VegaLiteProps } from 'react-vega/lib/VegaLite'; +import applyFullWidthDirective from '../lib/applyFullWidthDirective'; -export function VegaLite(props) { +export function VegaLite(props: VegaLiteProps) { const Component = applyFullWidthDirective({ Component: VegaLiteOg }); return ; diff --git a/packages/components/stories/Vega.stories.ts b/packages/components/stories/Vega.stories.ts index 6c4eed65b..95d859593 100644 --- a/packages/components/stories/Vega.stories.ts +++ b/packages/components/stories/Vega.stories.ts @@ -7,6 +7,16 @@ const meta: Meta = { title: 'Components/Charts/Vega', component: Vega, tags: ['autodocs'], + argTypes: { + data: { + description: + "Vega's `data` prop. You can find references on how to use this prop at https://vega.github.io/vega/docs/data/", + }, + spec: { + description: + "Vega's `spec` prop. You can find references on how to use this prop at https://vega.github.io/vega/docs/specification/", + }, + }, }; export default meta; @@ -15,7 +25,7 @@ type Story = StoryObj; // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args export const Primary: Story = { - name: 'Chart built with Vega', + name: 'Bar chart', args: { data: { table: [ diff --git a/packages/components/stories/VegaLite.stories.ts b/packages/components/stories/VegaLite.stories.ts index 28a43771d..929d111b2 100644 --- a/packages/components/stories/VegaLite.stories.ts +++ b/packages/components/stories/VegaLite.stories.ts @@ -25,7 +25,7 @@ type Story = StoryObj; // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args export const Primary: Story = { - name: 'Chart built with Vega Lite', + name: 'Bar chart', args: { data: { table: [ From b859d48f179b92c4e5d55f8e38e3ae0dfb25d026 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 17:30:45 -0300 Subject: [PATCH 14/18] feat: Map component API and docs improvements --- packages/components/src/components/Map.tsx | 32 ++++++++------- packages/components/src/types/properties.ts | 9 ++++- packages/components/stories/Map.stories.ts | 45 ++++++++++++++------- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/packages/components/src/components/Map.tsx b/packages/components/src/components/Map.tsx index fcc921f09..722740f71 100644 --- a/packages/components/src/components/Map.tsx +++ b/packages/components/src/components/Map.tsx @@ -2,6 +2,7 @@ import { CSSProperties, useEffect, useState } from 'react'; import LoadingSpinner from './LoadingSpinner'; import loadData from '../lib/loadData'; import chroma from 'chroma-js'; +import { GeospatialData } from '../types/properties'; import { MapContainer, TileLayer, @@ -14,26 +15,25 @@ import * as L from 'leaflet'; export type MapProps = { layers: { - data: string | GeoJSON.GeoJSON; + data: GeospatialData; name: string; colorScale?: { starting: string; ending: string; }; tooltip?: - | { - propNames: string[]; - } - | boolean; - _id?: number; + | { + propNames: string[]; + } + | boolean; }[]; title?: string; center?: { latitude: number | undefined; longitude: number | undefined }; zoom?: number; style?: CSSProperties; autoZoomConfiguration?: { - layerName: string - } + layerName: string; + }; }; export function Map({ @@ -56,17 +56,19 @@ export function Map({ useEffect(() => { const loadDataPromises = layers.map(async (layer) => { + const url = layer.data.url; + const geojson = layer.data.geojson; let layerData: any; - if (typeof layer.data === 'string') { + if (url) { // If "data" is string, assume it's a URL setIsLoading(true); - layerData = await loadData(layer.data).then((res: any) => { + layerData = await loadData(url).then((res: any) => { return JSON.parse(res); }); } else { // Else, expect raw GeoJSON - layerData = layer.data; + layerData = geojson; } if (layer.colorScale) { @@ -111,23 +113,23 @@ export function Map({ // Create the title box var info = new L.Control() as any; - info.onAdd = function() { + info.onAdd = function () { this._div = L.DomUtil.create('div', 'info'); this.update(); return this._div; }; - info.update = function() { + info.update = function () { this._div.innerHTML = `

${title}

`; }; if (title) info.addTo(map.target); - if(!autoZoomConfiguration) return; + if (!autoZoomConfiguration) return; let layerToZoomBounds = L.latLngBounds(L.latLng(0, 0), L.latLng(0, 0)); layers.forEach((layer) => { - if(layer.name === autoZoomConfiguration.layerName) { + if (layer.name === autoZoomConfiguration.layerName) { const data = layersData.find( (layerData) => layerData.name === layer.name )?.data; diff --git a/packages/components/src/types/properties.ts b/packages/components/src/types/properties.ts index 35dc93e08..8805e3bba 100644 --- a/packages/components/src/types/properties.ts +++ b/packages/components/src/types/properties.ts @@ -4,8 +4,15 @@ * Based on vega. * */ + +type URL = string; // Just in case we want to transform it into an object with configurations export interface Data { - url?: string; + url?: URL; values?: { [key: string]: number | string }[]; csv?: string; } + +export interface GeospatialData { + url?: URL; + geojson?: GeoJSON.GeoJSON; +} diff --git a/packages/components/stories/Map.stories.ts b/packages/components/stories/Map.stories.ts index 0cdbd8a64..c7bc80818 100644 --- a/packages/components/stories/Map.stories.ts +++ b/packages/components/stories/Map.stories.ts @@ -10,7 +10,11 @@ const meta: Meta = { argTypes: { layers: { description: - 'Data to be displayed.\n\n GeoJSON Object \n\nOR\n\n URL to GeoJSON Object', + 'Array of layers to be displayed on the map. Should be an object with: \n\n \ +`data`: object with either a `url` property pointing to a GeoJSON file or a `geojson` property with a GeoJSON object. \n\n \ +`name`: name of the layer. \n\n \ +`colorscale`: object with a `starting` and `ending` colors that will be used to create a gradient and color the map. \n\n \ +`tooltip`: `true` to show all available features on the tooltip, object with a `propNames` property as an array of strings to choose which features to display. \n\n', }, title: { description: 'Title to display on the map.', @@ -19,14 +23,15 @@ const meta: Meta = { description: 'Initial coordinates of the center of the map', }, zoom: { - description: 'Zoom level', + description: 'Initial zoom level', }, style: { - description: "Styles for the container" + description: "CSS styles to be applied to the map's container.", }, autoZoomConfiguration: { - description: "Configuration to auto zoom in the specified layer data" - } + description: + "Pass a layer's name to automatically zoom to the bounding area of a layer.", + }, }, }; @@ -40,7 +45,9 @@ export const GeoJSONPolygons: Story = { args: { layers: [ { - data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson', + data: { + url: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson', + }, name: 'Polygons', tooltip: { propNames: ['name'] }, colorScale: { @@ -60,7 +67,9 @@ export const GeoJSONPoints: Story = { args: { layers: [ { - data: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson', + data: { + url: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson', + }, name: 'Points', tooltip: { propNames: ['Location'] }, }, @@ -76,12 +85,16 @@ export const GeoJSONMultipleLayers: Story = { args: { layers: [ { - data: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson', + data: { + url: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson', + }, name: 'Points', tooltip: true, }, { - data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson', + data: { + url: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson', + }, name: 'Polygons', tooltip: true, colorScale: { @@ -94,19 +107,23 @@ export const GeoJSONMultipleLayers: Story = { center: { latitude: 45, longitude: 0 }, zoom: 2, }, -} +}; export const GeoJSONMultipleLayersWithAutoZoomInSpecifiedLayer: Story = { name: 'GeoJSON polygons and points map with auto zoom in the points layer', args: { layers: [ { - data: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson', + data: { + url: 'https://opendata.arcgis.com/datasets/9c58741995174fbcb017cf46c8a42f4b_25.geojson', + }, name: 'Points', tooltip: true, }, { - data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson', + data: { + url: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_geography_marine_polys.geojson', + }, name: 'Polygons', tooltip: true, colorScale: { @@ -119,7 +136,7 @@ export const GeoJSONMultipleLayersWithAutoZoomInSpecifiedLayer: Story = { center: { latitude: 45, longitude: 0 }, zoom: 2, autoZoomConfiguration: { - layerName: 'Points' - } + layerName: 'Points', + }, }, }; From 2ea185b710959ff83331da1ae4c9b158c8ef2cb9 Mon Sep 17 00:00:00 2001 From: Demenech Date: Tue, 9 Apr 2024 17:41:01 -0300 Subject: [PATCH 15/18] feat: Catalog component API and docs improvements --- .../components/src/components/Catalog.tsx | 20 ++--- .../components/stories/Catalog.stories.ts | 75 ++----------------- 2 files changed, 17 insertions(+), 78 deletions(-) diff --git a/packages/components/src/components/Catalog.tsx b/packages/components/src/components/Catalog.tsx index 83899ec2e..a7b63dc55 100644 --- a/packages/components/src/components/Catalog.tsx +++ b/packages/components/src/components/Catalog.tsx @@ -7,7 +7,12 @@ export function Catalog({ datasets, facets, }: { - datasets: any[]; + datasets: { + _id: string | number; + metadata: { title: string; [k: string]: string | number }; + url_path: string; + [k: string]: any; + }[]; facets: string[]; }) { const [indexFilter, setIndexFilter] = useState(''); @@ -56,7 +61,7 @@ export function Catalog({ //Then check if the selectedValue for the given facet is included in the dataset metadata .filter((dataset) => { //Avoids a server rendering breakage - if (!watch() || Object.keys(watch()).length === 0) return true + if (!watch() || Object.keys(watch()).length === 0) return true; //This will filter only the key pairs of the metadata values that were selected as facets const datasetFacets = Object.entries(dataset.metadata).filter((entry) => facets.includes(entry[0]) @@ -86,9 +91,7 @@ export function Catalog({ className="p-2 ml-1 text-sm shadow border border-block" {...register(elem[0] + '.selectedValue')} > - + {(elem[1] as { possibleValues: string[] }).possibleValues.map( (val) => (
) : ( - + ); } diff --git a/packages/components/stories/LineChart.stories.ts b/packages/components/stories/LineChart.stories.ts index 62b7650c3..27a8b3f8f 100644 --- a/packages/components/stories/LineChart.stories.ts +++ b/packages/components/stories/LineChart.stories.ts @@ -35,10 +35,6 @@ Must be an object with one of the following properties: `url` or `values` \n\n \ yAxisType: { description: 'Type of the Y-axis', }, - fullWidth: { - description: - 'Whether the component should be rendered as full bleed or not', - }, }, }; From 7bba10714d86b737cf628c84b54a41cc1b029931 Mon Sep 17 00:00:00 2001 From: Ola Rubaj <52197250+olayway@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:13:47 +0200 Subject: [PATCH 17/18] refresh package-lock file --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 8690c7e5b..ce1b36ecb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49897,7 +49897,7 @@ }, "packages/components": { "name": "@portaljs/components", - "version": "0.5.10", + "version": "0.6.0", "dependencies": { "@githubocto/flat-ui": "^0.14.1", "@heroicons/react": "^2.0.17", From 48cd812a488a069a419d8ecc67f24f94d4d1d1d6 Mon Sep 17 00:00:00 2001 From: Ola Rubaj <52197250+olayway@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:14:00 +0200 Subject: [PATCH 18/18] add changeset file --- .changeset/rotten-chefs-shout.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rotten-chefs-shout.md diff --git a/.changeset/rotten-chefs-shout.md b/.changeset/rotten-chefs-shout.md new file mode 100644 index 000000000..313d1203e --- /dev/null +++ b/.changeset/rotten-chefs-shout.md @@ -0,0 +1,5 @@ +--- +'@portaljs/components': major +--- + +Components API tidying up and storybook docs improvements.