Compare commits

...

8 Commits

Author SHA1 Message Date
github-actions[bot]
3aac4dabf9 Version Packages (#1087)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-22 10:42:53 -03:00
Luccas Mateus
a044f56e3c Changeset 2024-03-22 10:32:11 -03:00
Luccas Mateus
1b58c311eb Plotly components 2024-03-22 10:31:30 -03:00
Rufus Pollock
ed9ac2c263 Delete tools/tsconfig.tools.json
Remove as tools is empty folder.
2024-02-28 16:49:24 +01:00
Rufus Pollock
42c72e5afd Delete tools/generators/.gitkeep
empty directory
2024-02-28 16:48:34 +01:00
Rufus Pollock
9e1a324fa1 [examples/fivethirtyeight][xs]: link to blog post we wrote about it. 2024-02-13 13:31:39 +01:00
Rufus Pollock
90178af8f2 [examples/fivethirtyeight][s]: add link to demo site to README. 2024-02-13 13:30:57 +01:00
Rufus Pollock
00e61e104c [site/config][xs]: change discord server to datahub. 2024-02-09 10:27:06 +01:00
14 changed files with 3851 additions and 210 deletions

View File

@@ -1,3 +1,9 @@
# PortalJS Demo replicating the FiveThirtyEight data portal
## 👉 https://fivethirtyeight.portaljs.org 👈
Here's a blog post we wrote about it: https://www.datopian.com/blog/fivethirtyeight-replica
This is a replica of the awesome data.fivethirtyeight.com using PortalJS.
You might be asking why we did that, there are three main reasons:

3518
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,11 @@
# @portaljs/components
## 0.6.0
### Minor Changes
- [`a044f56e`](https://github.com/datopian/portaljs/commit/a044f56e3cbe0519ddf9d24d78b0bb7eac917e1c) Thanks [@luccasmmg](https://github.com/luccasmmg)! - Added plotly components
## 0.5.10
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@portaljs/components",
"version": "0.5.10",
"version": "0.6.0",
"type": "module",
"description": "https://portaljs.org",
"keywords": [
@@ -40,11 +40,13 @@
"ol": "^7.4.0",
"papaparse": "^5.4.1",
"pdfjs-dist": "2.15.349",
"plotly.js": "^2.30.1",
"postcss-url": "^10.1.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.9",
"react-leaflet": "^4.2.1",
"react-plotly.js": "^2.6.0",
"react-query": "^3.39.3",
"react-vega": "^7.6.0",
"vega": "5.25.0",

View File

@@ -0,0 +1,9 @@
import Plot, { PlotParams } from "react-plotly.js";
export const Plotly: React.FC<PlotParams> = (props) => {
return (
<div>
<Plot {...props} />
</div>
);
};

View File

@@ -0,0 +1,157 @@
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
import { Plotly } from "./Plotly";
import Papa, { ParseConfig } from "papaparse";
import LoadingSpinner from "./LoadingSpinner";
const queryClient = new QueryClient();
async function getCsv(url: string, bytes: number) {
const response = await fetch(url, {
headers: {
Range: `bytes=0-${bytes}`,
},
});
const data = await response.text();
return data;
}
async function parseCsv(
file: string,
parsingConfig: ParseConfig,
): Promise<any> {
return new Promise((resolve, reject) => {
Papa.parse(file, {
...parsingConfig,
header: true,
dynamicTyping: true,
skipEmptyLines: true,
transform: (value: string): string => {
return value.trim();
},
complete: (results: any) => {
return resolve(results);
},
error: (error: any) => {
return reject(error);
},
});
});
}
export interface PlotlyBarChartProps {
url?: string;
data?: { [key: string]: number | string }[];
rawCsv?: string;
randomId?: number;
bytes?: number;
parsingConfig?: ParseConfig;
xAxis: string;
yAxis: string;
lineLabel?: string;
title?: string;
}
export const PlotlyBarChart: React.FC<PlotlyBarChartProps> = ({
url,
data,
rawCsv,
bytes = 5132288,
parsingConfig = {},
xAxis,
yAxis,
lineLabel,
title = "",
}) => {
const randomId = Math.random();
return (
// Provide the client to your App
<QueryClientProvider client={queryClient}>
<PlotlyBarChartInner
url={url}
data={data}
rawCsv={rawCsv}
randomId={randomId}
bytes={bytes}
parsingConfig={parsingConfig}
xAxis={xAxis}
yAxis={yAxis}
lineLabel={lineLabel ?? yAxis}
title={title}
/>
</QueryClientProvider>
);
};
const PlotlyBarChartInner: React.FC<PlotlyBarChartProps> = ({
url,
data,
rawCsv,
randomId,
bytes,
parsingConfig,
xAxis,
yAxis,
lineLabel,
title,
}) => {
if (data) {
return (
<div className="w-full" style={{ height: "500px" }}>
<Plotly
layout={{
title,
}}
data={[
{
x: data.map((d) => d[xAxis]),
y: data.map((d) => d[yAxis]),
type: "bar",
name: lineLabel,
},
]}
/>
</div>
);
}
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
["dataCsv", url, randomId],
() => getCsv(url as string, bytes ?? 5132288),
{ enabled: !!url },
);
const { data: parsedData, isLoading: isParsing } = useQuery(
["dataPreview", csvString, randomId],
() =>
parseCsv(
rawCsv ? (rawCsv as string) : (csvString as string),
parsingConfig ?? {},
),
{ enabled: rawCsv ? true : !!csvString },
);
if (isParsing || isDownloadingCSV)
<div className="w-full flex justify-center items-center h-[500px]">
<LoadingSpinner />
</div>;
if (parsedData)
return (
<div className="w-full" style={{ height: "500px" }}>
<Plotly
layout={{
title,
}}
data={[
{
x: parsedData.data.map((d: any) => d[xAxis]),
y: parsedData.data.map((d: any) => d[yAxis]),
type: "bar",
name: lineLabel,
},
]}
/>
</div>
);
return (
<div className="w-full flex justify-center items-center h-[500px]">
<LoadingSpinner />
</div>
);
};

View File

@@ -0,0 +1,157 @@
import { QueryClient, QueryClientProvider, useQuery } from "react-query";
import { Plotly } from "./Plotly";
import Papa, { ParseConfig } from "papaparse";
import LoadingSpinner from "./LoadingSpinner";
const queryClient = new QueryClient();
async function getCsv(url: string, bytes: number) {
const response = await fetch(url, {
headers: {
Range: `bytes=0-${bytes}`,
},
});
const data = await response.text();
return data;
}
async function parseCsv(
file: string,
parsingConfig: ParseConfig,
): Promise<any> {
return new Promise((resolve, reject) => {
Papa.parse(file, {
...parsingConfig,
header: true,
dynamicTyping: true,
skipEmptyLines: true,
transform: (value: string): string => {
return value.trim();
},
complete: (results: any) => {
return resolve(results);
},
error: (error: any) => {
return reject(error);
},
});
});
}
export interface PlotlyLineChartProps {
url?: string;
data?: { [key: string]: number | string }[];
rawCsv?: string;
randomId?: number;
bytes?: number;
parsingConfig?: ParseConfig;
xAxis: string;
yAxis: string;
lineLabel?: string;
title?: string;
}
export const PlotlyLineChart: React.FC<PlotlyLineChartProps> = ({
url,
data,
rawCsv,
bytes = 5132288,
parsingConfig = {},
xAxis,
yAxis,
lineLabel,
title = "",
}) => {
const randomId = Math.random();
return (
// Provide the client to your App
<QueryClientProvider client={queryClient}>
<LineChartInner
url={url}
data={data}
rawCsv={rawCsv}
randomId={randomId}
bytes={bytes}
parsingConfig={parsingConfig}
xAxis={xAxis}
yAxis={yAxis}
lineLabel={lineLabel ?? yAxis}
title={title}
/>
</QueryClientProvider>
);
};
const LineChartInner: React.FC<PlotlyLineChartProps> = ({
url,
data,
rawCsv,
randomId,
bytes,
parsingConfig,
xAxis,
yAxis,
lineLabel,
title,
}) => {
if (data) {
return (
<div className="w-full" style={{ height: "500px" }}>
<Plotly
layout={{
title,
}}
data={[
{
x: data.map((d) => d[xAxis]),
y: data.map((d) => d[yAxis]),
mode: "lines",
name: lineLabel,
},
]}
/>
</div>
);
}
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
["dataCsv", url, randomId],
() => getCsv(url as string, bytes ?? 5132288),
{ enabled: !!url },
);
const { data: parsedData, isLoading: isParsing } = useQuery(
["dataPreview", csvString, randomId],
() =>
parseCsv(
rawCsv ? (rawCsv as string) : (csvString as string),
parsingConfig ?? {},
),
{ enabled: rawCsv ? true : !!csvString },
);
if (isParsing || isDownloadingCSV)
<div className="w-full flex justify-center items-center h-[500px]">
<LoadingSpinner />
</div>;
if (parsedData)
return (
<div className="w-full" style={{ height: "500px" }}>
<Plotly
layout={{
title,
}}
data={[
{
x: parsedData.data.map((d: any) => d[xAxis]),
y: parsedData.data.map((d: any) => d[yAxis]),
mode: "lines",
name: lineLabel,
},
]}
/>
</div>
);
return (
<div className="w-full flex justify-center items-center h-[500px]">
<LoadingSpinner />
</div>
);
};

View File

@@ -10,3 +10,6 @@ export * from './components/PdfViewer';
export * from "./components/Excel";
export * from "./components/BucketViewer";
export * from "./components/Iframe";
export * from "./components/Plotly";
export * from "./components/PlotlyLineChart";
export * from "./components/PlotlyBarChart";

View File

@@ -0,0 +1,74 @@
import type { Meta, StoryObj } from '@storybook/react';
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 = {
title: 'Components/PlotlyBarChart',
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',
},
bytes: {
description:
'How many bytes to read from the url',
},
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',
},
title: {
description: 'Title to display on the chart. Optional.',
},
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.',
},
yAxis: {
description:
'Name of the Y axis on the data. Required when the "data" parameter is an URL.',
},
},
};
export default meta;
type Story = StoryObj<PlotlyBarChartProps>;
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},
],
xAxis: 'year',
yAxis: 'temperature',
},
};
export const FromURL: Story = {
name: 'Line chart from URL',
args: {
title: 'Apple Stock Prices',
url: 'https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv',
xAxis: 'Date',
yAxis: 'AAPL.Open',
},
};

View File

@@ -0,0 +1,74 @@
import type { Meta, StoryObj } from '@storybook/react';
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/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',
},
bytes: {
description:
'How many bytes to read from the url',
},
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',
},
title: {
description: 'Title to display on the chart. Optional.',
},
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.',
},
yAxis: {
description:
'Name of the Y axis on the data. Required when the "data" parameter is an URL.',
},
},
};
export default meta;
type Story = StoryObj<PlotlyLineChartProps>;
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},
],
xAxis: 'year',
yAxis: 'temperature',
},
};
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',
xAxis: 'Date',
yAxis: 'Price',
},
};

View File

@@ -0,0 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react';
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',
component: Plotly,
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<any>;
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
export const Primary: Story = {
name: 'Chart built with Plotly',
args: {
data: [
{
x: [1, 2, 3],
y: [2, 6, 3],
type: 'scatter',
mode: 'lines+markers',
marker: { color: 'red' },
},
],
layout: {
title: 'Chart built with Plotly',
xaxis: {
title: 'x Axis',
},
yaxis: {
title: 'y Axis',
},
},
},
};

View File

@@ -69,7 +69,7 @@ const config = {
},
},
github: 'https://github.com/datopian/portaljs',
discord: 'https://discord.gg/EeyfGrGu4U',
discord: 'https://discord.gg/xfFDMPU9dC',
tableOfContents: true,
analytics: 'G-96GWZHMH57',
// editLinkShow: true,

View File

@@ -1,12 +0,0 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "../dist/out-tsc/tools",
"rootDir": ".",
"module": "commonjs",
"target": "es5",
"types": ["node"],
"importHelpers": false
},
"include": ["**/*.ts"]
}