Compare commits

..

52 Commits

Author SHA1 Message Date
github-actions[bot]
6aeadd71de Version Packages (#942)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-09 00:16:30 +02:00
olayway
affca05058 [remark-callouts/package.json][xs]: fix export path 2023-06-09 00:12:03 +02:00
github-actions[bot]
f54d238795 Version Packages (#941)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-08 23:57:47 +02:00
olayway
e82e2ae021 [packages][xs]: replace prepublish with prepare 2023-06-08 23:53:42 +02:00
github-actions[bot]
c3246ee7f8 Version Packages (#940)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-08 23:45:12 +02:00
olayway
40d80d2282 [packages/*][s]: add prepublish script to build first 2023-06-08 23:42:17 +02:00
olayway
e0e720338f [components/package.json][xs]: add prepublish script 2023-06-08 23:33:34 +02:00
olayway
4f8b1b1e96 [ckan/package.json][xs]: add prepublish script 2023-06-08 23:32:29 +02:00
olayway
362afcc133 [package.json][xs]: rm faulty prerelease script 2023-06-08 23:29:17 +02:00
João Demenech
c165b3cc44 merge: new data story about ESIF funds
A new data story about ESIF funds
2023-06-08 17:22:27 -03:00
João Demenech
261a2a081e Fix minor typos 2023-06-08 17:21:43 -03:00
Anuar Ustayev (aka Anu)
d27857f490 Added conclusion and final tweaks. 2023-06-08 20:47:59 +07:00
Anuar Ustayev (aka Anu)
b3ba263bd8 Switch from raw csv to remote file in r2. 2023-06-08 20:35:33 +07:00
Anuar Ustayev (aka Anu)
cb774d0ad0 Added bar chart for comparison between countries. 2023-06-08 20:28:05 +07:00
Anuar Ustayev (aka Anu)
b48f71ecef Add updated data with currency consideration. 2023-06-08 20:07:03 +07:00
Anuar Ustayev (aka Anu)
07b3235647 Create where-the-european-structural-and-investment-funds-go.mdx 2023-06-08 18:19:57 +07:00
Luccas Mateus de Medeiros Gomes
d0c2ee1e71 [examples/openspending][xs] - add contributing tab 2023-06-07 15:31:40 -03:00
João Demenech
bc180189cb Update OS sample data story 2023-06-07 11:28:31 -03:00
github-actions[bot]
39c862627d Version Packages (#937)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-07 15:22:12 +02:00
olayway
b7158a5be6 add changeset file 2023-06-07 15:17:46 +02:00
olayway
ee87c4f623 [remark-embed][s]: don't include README in dist
- it will already be included by npm anyway
2023-06-07 15:06:08 +02:00
olayway
4141af0e82 [remark-embed][s]: specify folder to publish 2023-06-07 15:05:29 +02:00
olayway
7d36d22671 [remark-wiki-link][xs]: don't include README in /dist
- it is included automatically by npm, in the parent folder
2023-06-07 15:04:46 +02:00
olayway
eab2d65113 [remark-wiki-link][s]: specify folder to publish 2023-06-07 15:04:46 +02:00
olayway
51d0a7692e [remark-callouts][xs]: don't include README in /dist
- it is included automatically by npm, in the parent folder
2023-06-07 15:04:46 +02:00
olayway
cdd90ac384 [remak-callouts][s]: specify folder to publish in package.json 2023-06-07 15:04:46 +02:00
olayway
dcf6400304 [core][xs]: don't include README in /dist
- it is included automatically by npm, in the parent folder
2023-06-07 15:04:46 +02:00
olayway
247b2412d6 [core][xs]: specify folder to publish 2023-06-07 15:04:41 +02:00
olayway
1ad9b85e02 [package.json][s]: add building to prerelease 2023-06-07 13:56:32 +02:00
Ola Rubaj
af134cac8b Integrate flowershow packages (#923)
* [packages][m]: mv @flowershow/core package here

* [packages/core][xs]: rename to @portaljs/core

* [package.json][xs]: setup npm workspaces

* [packages/core][xs]:replace deprecated rollup executor

* [core/package.json][s]: fix mermaid versions

* [core/tsconfig][xs]: rm extends

* [core/jest.config][xs]: rm coverageDirectory

* [core/package.json][xs]: install core-js

* [packages.json][s]:use same version for all nrwl packages

* [core/.eslintrc][xs]: adjust ignorePatterns

* [core/project.json][xs]: rm publish targets

* [packages][m]: mv @flowershow/remark-wiki-link here

* [packages][m]: mv @flowershow/remark-wiki-link here

* [packages][m]: mv @flowershow/remark-embed here

* [remark-callouts/project.json][xs]: adjst test pattern

* [package.json][s]: install missing deps

* [remark-callouts][xs]: adjst fields in package.json

* [remark-callouts][s]: rm pubish targets and adjst build executor

* [remark-embed/jest.config][xs]: rm unknown option coverageDirectory

* [remark-embed][xs]: rm publish targets

* [remark-embed][s]: rename to @portaljs/remark-embed

* [remark-wiki-link/eslintrc][xs]:adjst ignorePatterns

* [package.json][xs]: install missing deps

* [remark-wiki-link/test][xs]:specify format

- also temporarily force any type on htmlExtension

* [remark-wiki-link/README][xs]: replace @flowershow with @portaljs

* [remark-wiki-link][xs]:rm old changelog

* [remark-wiki-link][xs]: adjst package.json

* [remark-wiki-link/project.json][xs]: rm publish targets

* [core][s]: rm old changelog

* [core/README][xs]:correct scope name

* [remark-callouts/README][xs]: add @portaljs to pckg name

* [remark-embed/README][xs]: add @portaljs to pckg name

* [package-lock.json][xs]: refresh after rebasing on main
2023-06-07 07:21:00 -03:00
github-actions[bot]
0b8c56bcac Version Packages (#934)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-06 22:33:32 -03:00
Luccas Mateus de Medeiros Gomes
20c64222c1 [examples/openspending][xs] - fix styling 2023-06-06 21:59:21 -03:00
João Demenech
683159da02 @portaljs/components improvements (#933)
* [#912,@portaljs/components][s]: create LoadingSpinner component and add that to <Table /> when using the URL parameter

* [#912,@portaljs/components][m]: refactor LineChart and add more params

* [#912,@portaljs/components][m]: possibly fixes 'loading...' size on FlatUiTable on Firefox

* [@portaljs/components][xs]: add storybook-static to .gitignore

* Add changeset
2023-06-06 21:21:31 -03:00
Luccas Mateus de Medeiros Gomes
c0681fdc7f [examples][m] - update examples to use new package version 2023-06-06 13:51:41 -03:00
Luccas Mateus de Medeiros Gomes
ec1910e016 [packages/components][xs] - update version 2023-06-06 13:02:39 -03:00
Luccas Mateus
fc70f6ec66 [examples/openspending][lg] - adding drd support (#928)
* [examples/openspending][lg] - adding drd support

* [examples/openspending][sm] - add data stories to header
2023-06-06 12:58:54 -03:00
João Demenech
8e4428e2f8 github-backed-catalog: improve documentation (#925)
* [examples/github-backed-catalog][m]: improve documentation

* [examples/github-backed-catalog][m]: add info about editing the deployment created on Vercel
2023-06-06 07:28:51 -03:00
github-actions[bot]
1292350aac Version Packages (#926)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-06 07:26:45 -03:00
João Demenech
e4da3ed672 Merge pull request #924 from datopian/fix-redeclaration-of-formal-parameter-error
[packages/components][m] - fix error on build
2023-06-05 16:22:09 -03:00
Luccas Mateus de Medeiros Gomes
959fe5a588 [packages/components][m] - fix error on build 2023-06-05 14:23:57 -03:00
João Demenech
7a46a6732b [examples/openspending,footer,#897][s]: add PortalJS logo to the footer 2023-06-05 13:13:39 -03:00
Ola Rubaj
688db3e6a6 Changesets setup + auto-publish to npm (#922)
Closes #905 

## Summary

This PR sets up changesets with the aim to simplify the process of documenting changes committed to the codebase, as well as automating versioning packages and publishing them to npm.

## Changes
- initialised [changesets](https://github.com/changesets/changesets) in the repository
- (separately from this PR) installed [changesets bot](https://github.com/apps/changeset-bot) in the repository (it will check if a submitted PR includes a changeset file and inform if found or not)
- added `.github/workflows/release.yml`: The workflow creates a PR with version bumps and related changelog
files' updates, each time a commit containing changeset
file(s) is added to the main branch. After merging this auto-generated PR, this workflow will also auto-publish the released packages to npm and add git tags.
- added the following scripts to the root `package.json`:
  - `changeset`: should be run to generate changeset files, that describe changes made to the code,
  - `prerelease`: runs linting and testing on packages affected by the changes in a PR
  - `release`: runs `changeset publish` and is used by the `release` workflow then the above-mentioned auto-generated PR is merged to the main branch
2023-06-05 17:41:37 +02:00
João Demenech
fa642d8914 [examples/learn,docs][xs]: fix typo 2023-06-02 18:59:04 -03:00
João Demenech
c80b283201 [examples/learn,docs,#910][m]: add conclusions at the end of each tutorial, add page title to each tutorial page, fix hydration error re modified metadata field 2023-06-02 18:52:56 -03:00
Luccas Mateus de Medeiros Gomes
95fd8e72df [examples/openspending][sm] - 5mb to 500kb on preview 2023-06-02 09:38:13 -03:00
João Demenech
e50c76090c [examples/openspending][xs]: make year be displayed in UUTC timezone on DatasetCard 2023-06-01 15:39:15 -03:00
Luccas Mateus de Medeiros Gomes
70012d7c03 [examples/538][m] - added GA 2023-06-01 10:50:59 -03:00
Luccas Mateus de Medeiros Gomes
024c06d9cd [examples/538][xs] - change read more link 2023-06-01 10:41:43 -03:00
Luccas Mateus de Medeiros Gomes
35668c069e [examples/538][xs] - change order of datasets 2023-06-01 10:36:04 -03:00
Luccas Mateus de Medeiros Gomes
2500779499 [examples/538][m] - changes requested by anu 2023-06-01 10:30:17 -03:00
João Demenech
fc8eb95dbc Open Spending: minor improvements (#918)
* [examples/openspending][xs]: center align footer and make file previews wider

* Run prettier

---------

Co-authored-by: deme <deme@deme>
2023-06-01 10:19:39 -03:00
Anuar Ustayev (aka Anu)
45089419da Merge pull request #917 from datopian/fivethirtyeight-v0.2
[examples/538][m] - v0.2
2023-06-01 20:17:04 +07:00
360 changed files with 41868 additions and 7104 deletions

8
.changeset/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

14
.changeset/config.json Normal file
View File

@@ -0,0 +1,14 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": [
"@changesets/changelog-github",
{ "repo": "datopian/portaljs" }
],
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}

39
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Release
on:
push:
branches:
- main
concurrency: release-${{ github.ref }}
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v3
- name: Setup Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Install Dependencies
run: npm ci
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1
with:
publish: npm run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# - name: Send a Discord notification if a publish happens
# if: steps.changesets.outputs.published == 'true'
# uses: Ilshidur/action-discord@0.3.2
# with:
# args: 'The project {{ EVENT_PAYLOAD.repository.full_name }} has been deployed.'

View File

@@ -13,7 +13,7 @@ Structure:
- **dataset-frictionless**: Example utilizing a frictionless dataset as an example
- **site**: the website for the project, with a landing page and the docs
- **packages**:
- **portaljs-components**: the library of components for creating a data portal
- **portaljs-components**: the library of components for creating a data portal
## How to contribute
@@ -26,10 +26,11 @@ If you'd like to work on one of the issues you can:
3. Clone the forked repository to your machine.
4. Create a feature branch (e.g. `50-update-readme`, where `50` is the number of the related issue).
5. Commit your changes to the feature branch.
6. Push the feature branch to your forked repository.
7. Create a Pull Request against the original repository.
6. Add changeset file describing the changes. (See section below)
7. Push the feature branch to your forked repository.
8. Create a Pull Request against the original repository.
- add a short description of the changes included in the PR
8. Address review comments if requested by our demanding reviewers 😜.
9. Address review comments if requested by our demanding reviewers 😜.
If you have an idea for improvement, and it doesn't have a corresponding issue yet, simply submit a new one.
@@ -62,6 +63,7 @@ or you can use just:
nx <target> <project>
# e.g. npx nx serve ckan
```
if you have the `nx` binary installed globally in your machine
#### Running multiple tasks
@@ -109,7 +111,7 @@ To check code formatting in selected projects:
```sh
npx nx format:check --projects=<array of projects>
# npx nx format:check --projects=ckan,dataset-frictionless
# npx nx format:check --projects=ckan,dataset-frictionless
```
To check code formatting in all projects:
@@ -124,7 +126,7 @@ To fix code formatting in selected projects:
```sh
npx nx format:write --projects=<array projects>
# npx nx format:write --projects=ckan,dataset-frictionless
# npx nx format:write --projects=ckan,dataset-frictionless
```
To fix formatting in all projects:
@@ -174,3 +176,23 @@ To learn more see this [offical docs page](https://nx.dev/reference/nx-json).
Each project also has it's own configuration file - `project.json`, where you can define and configure it's targets (and more).
To learn more see this [offical docs page](https://nx.dev/reference/project-configuration).
## Changesets and publishing packages
> This monorepo is set up with changesets versioning tool. See their [github repository](https://github.com/changesets/changesets) to learn more.
### What are Changesets?
Changesets are files that describe the intention of a contributor to bump a version of the package according to their changes. Changeset file holds two key bits of information: a version type (following semver), and change information to be added to a changelog.
### Adding changesets
In the root directory of the repo, run:
```
npx changeset
```
Select the package that has been changed, the semver version that should be bumped with it and a description of your changes. Please make sure to add the most accurate but also concise information.
To learn about semantic versioning standards see [this semver doc page](https://semver.org/).

View File

@@ -27,7 +27,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
website.{' '}
<a
className="hover:underline font-bold"
href="https://github.com/datopian/portaljs/tree/main/examples/fivethirtyeight"
href="https://github.com/datopian/portaljs/tree/main/examples/fivethirtyeight#readme"
>
Read more here
</a>{' '}

View File

@@ -1,33 +1,4 @@
[
{
"url": "https://github.com/fivethirtyeight/data/tree/master/polls",
"name": "polls",
"displayName": "<span class=\"lastword\">polls</span>",
"articles": [
{
"date": "2023-05-11T14:35:40.000Z",
"title": "Latest Polls",
"url": "https://projects.fivethirtyeight.com/polls/"
}
],
"files": [
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls_historical.csv",
"https://projects.fivethirtyeight.com/polls-page/data/president_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/president_polls_historical.csv",
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls_historical.csv",
"https://projects.fivethirtyeight.com/polls-page/data/house_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/house_polls_historical.csv",
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls_historical.csv",
"https://projects.fivethirtyeight.com/polls-page/data/president_approval_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls_historical.csv",
"https://projects.fivethirtyeight.com/2020-primary-data/pres_primary_avgs_2020.csv",
"https://projects.fivethirtyeight.com/2020-general-data/presidential_poll_averages_2020.csv"
]
},
{
"url": "https://github.com/fivethirtyeight/data/tree/master/congress-generic-ballot",
"name": "congress-generic-ballot",
@@ -195,6 +166,35 @@
}
]
},
{
"url": "https://github.com/fivethirtyeight/data/tree/master/polls",
"name": "polls",
"displayName": "<span class=\"lastword\">polls</span>",
"articles": [
{
"date": "2023-05-11T14:35:40.000Z",
"title": "Latest Polls",
"url": "https://projects.fivethirtyeight.com/polls/"
}
],
"files": [
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls_historical.csv",
"https://projects.fivethirtyeight.com/polls-page/data/president_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/president_polls_historical.csv",
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls_historical.csv",
"https://projects.fivethirtyeight.com/polls-page/data/house_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/house_polls_historical.csv",
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls_historical.csv",
"https://projects.fivethirtyeight.com/polls-page/data/president_approval_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls.csv",
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls_historical.csv",
"https://projects.fivethirtyeight.com/2020-primary-data/pres_primary_avgs_2020.csv",
"https://projects.fivethirtyeight.com/2020-general-data/presidential_poll_averages_2020.csv"
]
},
{
"url": "https://github.com/fivethirtyeight/data/tree/master/nfl-elo",
"name": "nfl-elo",

File diff suppressed because it is too large Load Diff

View File

@@ -9,9 +9,10 @@
"lint": "next lint"
},
"dependencies": {
"@flowershow/core": "^0.4.13",
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.18",
"@portaljs/components": "^0.1.7",
"@portaljs/components": "^0.1.8",
"@tailwindcss/typography": "^0.5.9",
"@types/node": "20.1.1",
"@types/react": "18.2.6",

View File

@@ -1,8 +1,49 @@
import '@/styles/globals.css'
import '@portaljs/components/styles.css'
import '@/styles/globals.css';
import '@portaljs/components/styles.css';
import { useEffect } from 'react';
import { pageview } from '@flowershow/core';
import Script from 'next/script';
import Head from 'next/head';
import { useRouter } from 'next/router';
import type { AppProps } from 'next/app'
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
const router = useRouter();
useEffect(() => {
const handleRouteChange = (url: any) => {
pageview(url);
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
return (
<>
<Head>
<link rel="shortcut icon" href="/squared_logo.png" />
</Head>
<Script
strategy="afterInteractive"
src="https://www.googletagmanager.com/gtag/js?id=G-3N9SXTC7GS"
/>
<Script
id="gtag-init"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-3N9SXTC7GS', {
page_path: window.location.pathname,
});
`,
}}
/>
<Component {...pageProps} />
</>
);
}

View File

@@ -32,10 +32,10 @@ export default function DatasetPage({
<>
<NextSeo title={`${dataset.name} page`} />
<Layout>
<main className="max-w-5xl px-2 prose mx-auto my-8 prose-thead:border-b-4 prose-table:max-w-5xl prose-table:overflow-scroll prose-thead:overflow-scroll prose-tbody:overflow-scroll prose-thead:pb-2 prose-thead:border-zinc-900 prose-th:uppercase prose-th:text-left prose-th:font-light prose-th:text-xs prose-a:no-underline">
<main className="max-w-5xl px-2 prose mx-auto my-8 pb-8 prose-thead:border-b-4 prose-table:max-w-5xl prose-table:overflow-scroll prose-thead:overflow-scroll prose-tbody:overflow-scroll prose-thead:pb-2 prose-thead:border-zinc-900 prose-th:uppercase prose-th:text-left prose-th:font-light prose-th:text-xs prose-a:no-underline">
<Breadcrumbs links={[{ title: dataset.name, href: '' }]} />
<h1 className="uppercase mb-0 mt-16">{dataset.name}</h1>
<table className="w-full my-10 mb-4 hidden md:table">
<table className="w-full my-10 mb-8 hidden md:table">
<thead className="border-b-4 pb-2 border-zinc-900">
<tr>
<th className="uppercase text-left font-normal text-xs pb-3">
@@ -117,7 +117,7 @@ export default function DatasetPage({
</div>
{dataset.files && dataset.files.length > 0 && (
<>
<h2 className="mb-0 mt-10">Data Previews</h2>
<h2 className="mb-0 mt-8">Data Previews</h2>
{dataset.files?.map((file) => (
<div
key={file}

View File

@@ -5,3 +5,7 @@
.preview-table > div {
overflow-y: hidden;
}
.prose h1 {
font-size: 1.5em !important;
}

View File

@@ -7,13 +7,13 @@ import { Mermaid } from '@flowershow/core';
// to handle import statements. Instead, you must include components in scope
// here.
const components = {
Table: dynamic(() => import('../portaljs/components/Table').then(mod => mod.Table)),
Catalog: dynamic(() => import('../portaljs/components/Catalog').then(mod => mod.Catalog)),
Table: dynamic(() => import('@portaljs/components').then(mod => mod.Table)),
Catalog: dynamic(() => import('@portaljs/components').then(mod => mod.Catalog)),
mermaid: Mermaid,
Vega: dynamic(() => import('../portaljs/components/Vega').then(mod => mod.Vega)),
VegaLite: dynamic(() => import('../portaljs/components/VegaLite').then(mod => mod.VegaLite)),
LineChart: dynamic(() => import('../portaljs/components/LineChart').then(mod => mod.LineChart)),
FlatUiTable: dynamic(() => import('../portaljs/components/FlatUiTable').then(mod => mod.FlatUiTable)),
Vega: dynamic(() => import('@portaljs/components').then(mod => mod.Vega)),
VegaLite: dynamic(() => import('@portaljs/components').then(mod => mod.VegaLite)),
LineChart: dynamic(() => import('@portaljs/components').then(mod => mod.LineChart)),
FlatUiTable: dynamic(() => import('@portaljs/components').then(mod => mod.FlatUiTable)),
} as any;
export default function DRD({ source }: { source: any }) {

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@
"@flowershow/remark-embed": "^1.0.0",
"@flowershow/remark-wiki-link": "^1.1.2",
"@opentelemetry/api": "^1.4.0",
"@portaljs/components": "^0.1.0",
"@portaljs/components": "^0.1.8",
"gray-matter": "^4.0.3",
"hastscript": "^7.2.0",
"mdx-mermaid": "2.0.0-rc7",

View File

@@ -81,7 +81,7 @@ export default function DatasetPage({ mdxSource, frontMatter }) {
<p className="my-0">
<span className="font-semibold">Modified: </span>
<span className="description my-0">
{new Date(frontMatter.modified).toLocaleDateString()}
{new Date(frontMatter.modified).toLocaleDateString("en-US")}
</span>
</p>
)}

View File

@@ -1,119 +0,0 @@
import { Index } from 'flexsearch';
import { useState } from 'react';
import DebouncedInput from './DebouncedInput';
import { useForm } from 'react-hook-form';
export function Catalog({
datasets,
facets,
}: {
datasets: any[];
facets: string[];
}) {
const [indexFilter, setIndexFilter] = useState('');
const index = new Index({ tokenize: 'full' });
datasets.forEach((dataset) =>
index.add(
dataset._id,
//This will join every metadata value + the url_path into one big string and index that
Object.entries(dataset.metadata).reduce(
(acc, curr) => acc + ' ' + curr[1].toString(),
''
) +
' ' +
dataset.url_path
)
);
const facetValues = facets
? facets.reduce((acc, facet) => {
const possibleValues = datasets.reduce((acc, curr) => {
const facetValue = curr.metadata[facet];
if (facetValue) {
return Array.isArray(facetValue)
? acc.concat(facetValue)
: acc.concat([facetValue]);
}
return acc;
}, []);
acc[facet] = {
possibleValues: [...new Set(possibleValues)],
selectedValue: null,
};
return acc;
}, {})
: [];
const { register, watch } = useForm(facetValues);
const filteredDatasets = datasets
// First filter by flex search
.filter((dataset) =>
indexFilter !== ''
? index.search(indexFilter).includes(dataset._id)
: true
)
//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
//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])
);
//Check if the value present is included in the selected value in the form
return datasetFacets.every((elem) =>
watch()[elem[0]].selectedValue
? (elem[1] as string | string[]).includes(
watch()[elem[0]].selectedValue
)
: true
);
});
return (
<>
<DebouncedInput
value={indexFilter ?? ''}
onChange={(value) => setIndexFilter(String(value))}
className="p-2 text-sm shadow border border-block mr-1"
placeholder="Search all datasets..."
/>
{Object.entries(facetValues).map((elem) => (
<select
key={elem[0]}
defaultValue=""
className="p-2 ml-1 text-sm shadow border border-block"
{...register(elem[0] + '.selectedValue')}
>
<option value="">
Filter by {elem[0]}
</option>
{(elem[1] as { possibleValues: string[] }).possibleValues.map(
(val) => (
<option
key={val}
className="dark:bg-white dark:text-black"
value={val}
>
{val}
</option>
)
)}
</select>
))}
<ul className='mb-5 pl-6 mt-5 list-disc'>
{filteredDatasets.map((dataset) => (
<li className='py-2' key={dataset._id}>
<a className='font-medium underline' href={dataset.url_path}>
{dataset.metadata.title
? dataset.metadata.title
: dataset.url_path}
</a>
</li>
))}
</ul>
</>
);
}

View File

@@ -1,32 +0,0 @@
import { useEffect, useState } from "react";
const DebouncedInput = ({
value: initialValue,
onChange,
debounce = 500,
...props
}) => {
const [value, setValue] = useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
useEffect(() => {
const timeout = setTimeout(() => {
onChange(value);
}, debounce);
return () => clearTimeout(timeout);
}, [value]);
return (
<input
{...props}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
};
export default DebouncedInput;

View File

@@ -1,113 +0,0 @@
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import Papa from 'papaparse';
import { Grid } from '@githubocto/flat-ui';
const queryClient = new QueryClient();
export async function getCsv(url: string, corsProxy?: string) {
if (corsProxy) {
url = corsProxy + url
}
const response = await fetch(url, {
headers: {
Range: 'bytes=0-5132288',
},
});
const data = await response.text();
return data;
}
export async function parseCsv(file: string): Promise<any> {
return new Promise((resolve, reject) => {
Papa.parse(file, {
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);
},
});
});
}
const Spinning = () => {
return (
<div role="status w-fit mx-auto">
<svg
aria-hidden="true"
className="w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-emerald-600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
</div>
);
};
export interface FlatUiTableProps {
url?: string;
data?: { [key: string]: number | string }[];
rawCsv?: string;
corsProxy?: string;
}
export const FlatUiTable: React.FC<FlatUiTableProps> = ({
url,
data,
rawCsv,
corsProxy,
}) => {
return (
// Provide the client to your App
<QueryClientProvider client={queryClient}>
<TableInner corsProxy={corsProxy} url={url} data={data} rawCsv={rawCsv} />
</QueryClientProvider>
);
};
const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv, corsProxy }) => {
if (data) {
return (
<div className="w-full" style={{height: '500px'}}>
<Grid data={data} />
</div>
);
}
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
['dataCsv', url],
() => getCsv(url as string, corsProxy),
{ enabled: !!url }
);
const { data: parsedData, isLoading: isParsing } = useQuery(
['dataPreview', csvString],
() => parseCsv(rawCsv ? rawCsv as string : csvString as string),
{ enabled: rawCsv ? true : !!csvString }
);
if (isParsing || isDownloadingCSV)
<div className="w-full">
<Spinning />
</div>;
if (parsedData)
return (
<div className="w-full" style={{height: '500px'}}>
<Grid data={parsedData.data} />
</div>
);
return <Spinning />
};

View File

@@ -1,63 +0,0 @@
import { VegaLite } from './VegaLite';
export type LineChartProps = {
data: Array<Array<string | number>> | string | { x: string; y: number }[];
title?: string;
xAxis?: string;
yAxis?: string;
fullWidth?: boolean;
};
export function LineChart({
data = [],
fullWidth = false,
title = '',
xAxis = 'x',
yAxis = 'y',
}: LineChartProps) {
var tmp = data;
if (Array.isArray(data)) {
tmp = data.map((r) => {
return { x: r[0], y: r[1] };
});
}
const vegaData = { table: tmp };
const spec = {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
title,
width: 600,
height: 300,
mark: {
type: 'line',
color: 'black',
strokeWidth: 1,
tooltip: true,
},
data: {
name: 'table',
},
selection: {
grid: {
type: 'interval',
bind: 'scales',
},
},
encoding: {
x: {
field: xAxis,
timeUnit: 'year',
type: 'temporal',
},
y: {
field: yAxis,
type: 'quantitative',
},
},
};
if (typeof data === 'string') {
spec.data = { url: data } as any;
return <VegaLite fullWidth={fullWidth} spec={spec} />;
}
return <VegaLite fullWidth={fullWidth} data={vegaData} spec={spec} />;
}

View File

@@ -1,195 +0,0 @@
import {
createColumnHelper,
FilterFn,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import {
ArrowDownIcon,
ArrowUpIcon,
ChevronDoubleLeftIcon,
ChevronDoubleRightIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from '@heroicons/react/24/solid';
import React, { useEffect, useMemo, useState } from 'react';
import parseCsv from '../lib/parseCsv';
import DebouncedInput from './DebouncedInput';
import loadData from '../lib/loadData';
export type TableProps = {
data?: Array<{ [key: string]: number | string }>;
cols?: Array<{ [key: string]: string }>;
csv?: string;
url?: string;
fullWidth?: boolean;
};
export const Table = ({
data: ogData = [],
cols: ogCols = [],
csv = '',
url = '',
fullWidth = false,
}: TableProps) => {
if (csv) {
const out = parseCsv(csv);
ogData = out.rows;
ogCols = out.fields;
}
const [data, setData] = React.useState(ogData);
const [cols, setCols] = React.useState(ogCols);
// const [error, setError] = React.useState(""); // TODO: add error handling
const tableCols = useMemo(() => {
const columnHelper = createColumnHelper();
return cols.map((c) =>
columnHelper.accessor<any, string>(c.key, {
header: () => c.name,
cell: (info) => info.getValue(),
})
);
}, [data, cols]);
const [globalFilter, setGlobalFilter] = useState('');
const table = useReactTable({
data,
columns: tableCols,
getCoreRowModel: getCoreRowModel(),
state: {
globalFilter,
},
globalFilterFn: globalFilterFn,
onGlobalFilterChange: setGlobalFilter,
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});
useEffect(() => {
if (url) {
loadData(url).then((data) => {
const { rows, fields } = parseCsv(data);
setData(rows);
setCols(fields);
});
}
}, [url]);
return (
<div className={`${fullWidth ? 'w-[90vw] ml-[calc(50%-45vw)]' : 'w-full'}`}>
<DebouncedInput
value={globalFilter ?? ''}
onChange={(value: any) => setGlobalFilter(String(value))}
className="p-2 text-sm shadow border border-block"
placeholder="Search all columns..."
/>
<table className="w-full mt-10">
<thead className="text-left border-b border-b-slate-300">
{table.getHeaderGroups().map((hg) => (
<tr key={hg.id}>
{hg.headers.map((h) => (
<th key={h.id} className="pr-2 pb-2">
<div
{...{
className: h.column.getCanSort()
? 'cursor-pointer select-none'
: '',
onClick: h.column.getToggleSortingHandler(),
}}
>
{flexRender(h.column.columnDef.header, h.getContext())}
{{
asc: (
<ArrowUpIcon className="inline-block ml-2 h-4 w-4" />
),
desc: (
<ArrowDownIcon className="inline-block ml-2 h-4 w-4" />
),
}[h.column.getIsSorted() as string] ?? (
<div className="inline-block ml-2 h-4 w-4" />
)}
</div>
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((r) => (
<tr key={r.id} className="border-b border-b-slate-200">
{r.getVisibleCells().map((c) => (
<td key={c.id} className="py-2">
{flexRender(c.column.columnDef.cell, c.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
<div className="flex gap-2 items-center justify-center mt-10">
<button
className={`w-6 h-6 ${
!table.getCanPreviousPage() ? 'opacity-25' : 'opacity-100'
}`}
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<ChevronDoubleLeftIcon />
</button>
<button
className={`w-6 h-6 ${
!table.getCanPreviousPage() ? 'opacity-25' : 'opacity-100'
}`}
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeftIcon />
</button>
<span className="flex items-center gap-1">
<div>Page</div>
<strong>
{table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount()}
</strong>
</span>
<button
className={`w-6 h-6 ${
!table.getCanNextPage() ? 'opacity-25' : 'opacity-100'
}`}
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<ChevronRightIcon />
</button>
<button
className={`w-6 h-6 ${
!table.getCanNextPage() ? 'opacity-25' : 'opacity-100'
}`}
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<ChevronDoubleRightIcon />
</button>
</div>
</div>
);
};
const globalFilterFn: FilterFn<any> = (row, columnId, filterValue: string) => {
const search = filterValue.toLowerCase();
let value = row.getValue(columnId) as string;
if (typeof value === 'number') value = String(value);
return value?.toLowerCase().includes(search);
};

View File

@@ -1,6 +0,0 @@
// Wrapper for the Vega component
import { Vega as VegaOg } from "react-vega";
export function Vega(props) {
return <VegaOg {...props} />;
}

View File

@@ -1,9 +0,0 @@
// Wrapper for the Vega Lite component
import { VegaLite as VegaLiteOg } from "react-vega";
import applyFullWidthDirective from "../lib/applyFullWidthDirective";
export function VegaLite(props) {
const Component = applyFullWidthDirective({ Component: VegaLiteOg });
return <Component {...props} />;
}

View File

@@ -1,21 +0,0 @@
export default function applyFullWidthDirective({
Component,
defaultWFull = true,
}) {
return (props) => {
const newProps = { ...props };
let newClassName = newProps.className || "";
if (newProps.fullWidth === true) {
newClassName += " w-[90vw] ml-[calc(50%-45vw)] max-w-none";
} else if (defaultWFull) {
// So that charts and tables will have the
// same width as the text content, but images
// can have its width set using the width prop
newClassName += " w-full";
}
newProps.className = newClassName;
return <Component {...newProps} />;
};
}

View File

@@ -1,5 +0,0 @@
export default async function loadData(url: string) {
const response = await fetch(url)
const data = await response.text()
return data
}

View File

@@ -1,20 +0,0 @@
import papa from "papaparse";
const parseCsv = (csv: string) => {
csv = csv.trim();
const rawdata = papa.parse(csv, { header: true });
let cols: any[] = [];
if(rawdata.meta.fields) {
cols = rawdata.meta.fields.map((r: string) => {
return { key: r, name: r };
});
}
return {
rows: rawdata.data as any,
fields: cols,
};
};
export default parseCsv;

View File

@@ -0,0 +1,21 @@
import { MDXRemote } from 'next-mdx-remote';
import dynamic from 'next/dynamic';
import { Mermaid } from '@flowershow/core';
// Custom components/renderers to pass to MDX.
// Since the MDX files aren't loaded by webpack, they have no knowledge of how
// to handle import statements. Instead, you must include components in scope
// here.
const components = {
Table: dynamic(() => import('@portaljs/components').then(mod => mod.Table)),
Catalog: dynamic(() => import('@portaljs/components').then(mod => mod.Catalog)),
mermaid: Mermaid,
Vega: dynamic(() => import('@portaljs/components').then(mod => mod.Vega)),
VegaLite: dynamic(() => import('@portaljs/components').then(mod => mod.VegaLite)),
LineChart: dynamic(() => import('@portaljs/components').then(mod => mod.LineChart)),
FlatUiTable: dynamic(() => import('@portaljs/components').then(mod => mod.FlatUiTable)),
} as any;
export default function DRD({ source }: { source: any }) {
return <MDXRemote {...source} components={components} />;
}

View File

@@ -45,12 +45,12 @@ export default function DatasetCard({ dataset }: { dataset: Project }) {
<dt className="text-gray-500">Fiscal Period</dt>
<dd className="text-gray-700">
{dataset.fiscalPeriod?.start &&
new Date(dataset.fiscalPeriod.start).getFullYear()}
new Date(dataset.fiscalPeriod.start).getUTCFullYear()}
{dataset.fiscalPeriod?.end &&
dataset.fiscalPeriod?.start !== dataset.fiscalPeriod?.end && (
<>
{' - '}
{new Date(dataset.fiscalPeriod.end).getFullYear()}
{new Date(dataset.fiscalPeriod.end).getUTCFullYear()}
</>
)}
</dd>

View File

@@ -4,10 +4,13 @@ import { Grid } from '@githubocto/flat-ui';
const queryClient = new QueryClient();
export async function getCsv(url: string) {
export async function getCsv(url: string, corsProxy?: string, range?: string) {
if (corsProxy) {
url = corsProxy + url
}
const response = await fetch(url, {
headers: {
Range: 'bytes=0-5132288',
Range: range ? `bytes=0-${range}` : 'bytes=0-512000',
},
});
const data = await response.text();
@@ -61,21 +64,25 @@ export interface FlatUiTableProps {
url?: string;
data?: { [key: string]: number | string }[];
rawCsv?: string;
range?: string;
corsProxy?: string;
}
export const FlatUiTable: React.FC<FlatUiTableProps> = ({
url,
data,
rawCsv,
corsProxy,
range
}) => {
return (
// Provide the client to your App
<QueryClientProvider client={queryClient}>
<TableInner url={url} data={data} rawCsv={rawCsv} />
<TableInner range={range} corsProxy={corsProxy} url={url} data={data} rawCsv={rawCsv} />
</QueryClientProvider>
);
};
const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv }) => {
const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv, corsProxy, range }) => {
if (data) {
return (
<div className="w-full" style={{height: '500px'}}>
@@ -85,12 +92,12 @@ const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv }) => {
}
const { data: csvString, isLoading: isDownloadingCSV } = useQuery(
['dataCsv', url],
() => getCsv(url),
() => getCsv(url as string, corsProxy, range),
{ enabled: !!url }
);
const { data: parsedData, isLoading: isParsing } = useQuery(
['dataPreview', csvString],
() => parseCsv(rawCsv ? rawCsv : csvString),
() => parseCsv(rawCsv ? rawCsv as string : csvString as string),
{ enabled: rawCsv ? true : !!csvString }
);
if (isParsing || isDownloadingCSV)
@@ -103,4 +110,6 @@ const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv }) => {
<Grid data={parsedData.data} />
</div>
);
return <Spinning />
};

View File

@@ -23,6 +23,10 @@ export function Header() {
title: 'Datasets',
href: '/#datasets',
},
{
title: 'Data Stories',
href: '/stories',
},
{
title: 'Blog',
href: '/blog',
@@ -49,6 +53,10 @@ export function Header() {
},
],
},
{
title: 'Contributing',
href: '/contributing',
},
{
title: 'Resources',
href: '/resources',
@@ -157,7 +165,7 @@ function Dropdown({ navItem }: { navItem: any }) {
>
<div className="py-1">
{navItem.children.map((item) => (
<Menu.Item>
<Menu.Item key={item.href}>
{({ active }) => (
<a
key={item.href}

View File

@@ -2,23 +2,23 @@ import Image from 'next/image';
import Link from 'next/link';
export default function Footer() {
return (
<footer>
<footer className="bg-slate-50 border-t border-slate-100 py-10">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 flex flex-col items-center justify-between md:flex-row">
<div className="flex gap-x-2 items-center">
<p className="mt-8 text-base text-slate-500 md:mt-0">Maintained by</p>
<a href="https://www.datopian.com/">
<div className="flex gap-x-2 items-center justify-center mx-auto">
<p className="text-base text-slate-500">Maintained by</p>
<a href="https://www.datopian.com/" target="_blank">
<Image
alt="Datopian logo"
className="mb-2"
className="mb-1"
src="/datopian-logotype.png"
width={160}
height={40}
width={120}
height={30}
/>
</a>
</div>
<p className="mt-6 text-base text-slate-500 md:mt-0">
Copyright © 2023 Datopian, LLC. All rights reserved.
</p>
<div className="flex gap-x-2 items-center mx-auto h-20">
<p className="mt-8 text-base text-slate-500 md:mt-0">Built with <a href="https://portaljs.org" target="_blank" className='text-xl font-medium'>🌀 PortalJS</a></p>
</div>
</div>
</footer>
);

View File

@@ -3,7 +3,7 @@ import Footer from './Footer';
export default function Layout({ children }) {
return (
<div className="bg-white min-h-screen pb-12">
<div className="bg-white min-h-screen">
<Header />
{children}
<Footer />

View File

@@ -0,0 +1,36 @@
# How to contribute
OpenSpending is a project that aims to make public financial data more accessible, understandable, and usable. It is powered by PortalJS, a framework for building data portals that are fast, secure, and easy to customize.
If you have any questions, the best place to get answers is to reach to us on [Discord](https://discord.gg/xJrxCbkP)
We welcome contributions from anyone who is interested in improving OpenSpending and making it more useful for the public. There are many ways you can contribute to the project, such as:
- Submitting datasets for evaluation and inclusion in OpenSpending
- Reporting bugs or issues with the website or the data
- Suggesting new features or enhancements
- Providing feedback or ideas
- Helping other users or answering questions
## Submitting a contribution
The main platform for communication and collaboration for OpenSpending is Github, if you want to interact with us you can o so by submitting an issue.
If you want to submit a dataset for evaluation and inclusion in OpenSpending, you will need to create an issue on Github using this template:
```markdown
Title: [Dataset Submission] Name of the dataset (This will be the name of the repo where your datasets will be stored)
Readme: A description of your data to include in the new repo that will be created
Datapackage: All our datasets require a datapackage following the [frictionless spec](https://specs.frictionlessdata.io/) more specific the [fiscal version](https://specs.frictionlessdata.io/fiscal-data-package/)
```
Please make sure that the dataset meets the following criteria before submitting it:
- The dataset contains public financial data (e.g., budgets, expenditures, revenues, contracts)
- The dataset is open and free to use (e.g., no restrictions on access or reuse)
- The dataset is structured and machine-readable (e.g., no scanned images or PDFs)
- The dataset has a datapackage containing metadata and documentation (e.g., descriptions, definitions, schemas)
If you have any questions or doubts about submitting a dataset, please contact us on Github or join our [Discord server](https://discord.gg/xJrxCbkP), where you can chat with other contributors and get support from the OpenSpending team.
We appreciate your interest and involvement in OpenSpending, and we look forward to working with you on making public financial data more open and transparent. Thank you for your contribution! 😊

View File

@@ -0,0 +1,37 @@
---
title: Sample Data Story
date: 06/06/2023
---
The below chart clearly shows the steep upward trend of oil prices these are largely due to social-political factors and civil unrest:
<LineChart
data="https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv"
title="Oil Price x Year"
xAxis="Date"
yAxis="Price"
/>
But to really understand the different global factors that influence the price of a commodity like oil the long term trends as shown above is not adequate.
When considering the yearly fluctuations it is often beneficial to filter certain time periods.
One can use the below preview to filter year periods. Alternatively, if you want to consider the points in time when the price of oil was the highest the price range can also be filtered.
<FlatUiTable url="https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv" />
Careful data analysis has identified instability albeit economic or political to be the major driver of oil price flatuations
```mermaid
graph LR;
A[Socio-Political Instability]-->B[War & Civil Unrest];
A-->C[Economic Factors];
B-->D[Price Increase];
C-->D;
```
___
**To find the raw data used in this article visit https://github.com/datasets/oil-prices/**

View File

@@ -0,0 +1,158 @@
---
title: Where the European Structural and Investment Funds go?
date: 2023-06-08
authors: ['Anuar Ustayev']
---
European Structural and Investment Funds (ESIF) is a financial instrument used by the European Union (EU) to support regional development and economic cohesion among its member states. The ESIF combines several funds to provide financial assistance to regions and cities in the EU with the aim of reducing economic disparities and promoting sustainable growth.
In this data story, our objective is to determine which country benefits the most from the ESIF funds and identify the region within the EU that receives the highest amount of funding.
To begin our analysis, we are using data on the allocation of ESIF funds across EU member states and their respective regions. These datasets are provided by previous work at OpenSpending project. See available datasets:
- Full dataset: https://www.openspending.org/@os-data/complete-european-esif-funds-beneficiaries-2007-2020
- By country, e.g., this is for Austria: https://www.openspending.org/@os-data/complete-european-esif-funds-beneficiaries-2007-2020-filtered-by-at
The data provide insights into the financial assistance provided by the European Union to support regional development and economic cohesion:
<FlatUiTable url="https://storage.openspending.org/complete-european-esif-funds-beneficiaries-2007-2020/eu-esif-funds-beneficiaries-2007-2020-full.csv" />
After processing the available datasets we have derived an aggregated data resource that groups data by country which enables us to understand where the most funding went between 2007 and 2020. Notice that maximum amounts in each column are highlighted in the table but they might be in local currencies so check the 'currency' field:
<FlatUiTable url="https://storage.openspending.org/eu-esif-funds-beneficiaries-2007-2020-full-aggregated-by-country.csv" />
Quick observations:
- The Czech Republic has the EU cofinancing amount of CZK 2,731,165,279,234 which is approximately EUR 115 billions.
- Poland has the EU cofinancing amount of PLN 302,958,765,476 which is approximately EUR 67 billions.
- Other countries that use local currencies do not have significant amounts when comparing to above two nations.
So let's convert all currencies to ~EUR but only include 'eu_cofinancing_amount' values as we will use it for comparison:
<FlatUiTable rawCsv="beneficiary_country_code,eu_cofinancing_amount
AT,0.0
BE,858388537.42
BG,0.0
CY,0.0
CZ,115591108113
DE,912803359.21
DK,492017672
EE,5419641772.0
EL,0.0
ES,0.0
FI,2858282488.0
FR,15027864492.94
HR,0.0
HU,0.0
IE,0.0
IT,34860253672.98
LT,6750675528.74
LU,30852529.89
LV,0.0
MT,0.0
NL,0.0
PL,67537688711
PT,23800568868.02
RO,19970864938.12
SE,0.0
SI,4576151396.24
SK,12295960193.92
UK,12829255012.10" />
With this data table we can easily see top countries by funding. Note that you can use the table component above to sort values by values in 'eu_cofinancing_amount' column which helps you to quickly see top countries. Let's build a quick visualization to make it even more obvious:
<Vega
data={{
table: [
{
x: "LU",
y: 30852529.89
},
{
x: "DK",
y: 492017672
},
{
x: "BE",
y: 858388537.42
},
{
x: "DE",
y: 912803359.21
},
{
x: "FI",
y: 2858282488
},
{
x: "SI",
y: 4576151396.24
},
{
x: "EE",
y: 5419641772
},
{
x: "LT",
y: 6750675528.74
},
{
x: "SK",
y: 12295960193.92
},
{
x: "UK",
y: 12829255012.1
},
{
x: "FR",
y: 15027864492.94
},
{
x: "RO",
y: 19970864938.12
},
{
x: "PT",
y: 23800568868.02
},
{
x: "IT",
y: 34860253672.98
},
{
x: "PL",
y: 67537688711
},
{
x: "CZ",
y: 115591108113
}
]
}}
spec={{
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
data: {
name: 'table'
},
encoding: {
x: {
field: 'x',
type: 'ordinal'
},
y: {
field: 'y',
type: 'quantitative'
}
},
mark: 'bar'
}}
/>
Based on the bar chart above we can conclude that the following 3 countries have received the most amounts from ESIF fund:
1. Czech Republic - EUR ~116b.
2. Poland - EUR ~68b.
3. Italy - EUR ~35b.
_This data story was created by using Datopian's PortalJS framework. You can learn more about the framework by visiting https://portaljs.org/_

File diff suppressed because it is too large Load Diff

View File

@@ -12,27 +12,15 @@
"test": "vitest"
},
"dependencies": {
"@flowershow/core": "^0.4.13",
"@flowershow/markdowndb": "^0.1.5",
"@heroicons/react": "^2.0.18",
"@octokit/plugin-throttling": "^5.2.2",
"@portaljs/components": "0.1.4",
"@types/flexsearch": "^0.7.3",
"@types/node": "18.16.0",
"@types/react": "18.0.38",
"@types/react-dom": "18.0.11",
"@vitejs/plugin-react": "^4.0.0",
"clsx": "^1.2.1",
"datapackage": "^1.1.10",
"eslint": "8.39.0",
"eslint-config-next": "13.3.1",
"flexsearch": "0.7.21",
"next": "13.3.0",
"next-seo": "^6.0.0",
"octokit": "^2.0.14",
"prettier": "^2.8.8",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.43.9",
"react-markdown": "^8.0.7",
"react-timeago": "^7.1.0",
@@ -40,6 +28,38 @@
"remark": "^14.0.3",
"remark-gfm": "^3.0.1",
"strip-markdown": "^5.0.1",
"@flowershow/core": "^0.4.13",
"@flowershow/markdowndb": "^0.1.5",
"@flowershow/remark-callouts": "^1.0.0",
"@flowershow/remark-embed": "^1.0.0",
"@githubocto/flat-ui": "^0.14.1",
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.18",
"@portaljs/ckan": "^0.0.2",
"@portaljs/components": "^0.1.8",
"@tailwindcss/typography": "^0.5.9",
"@types/node": "20.2.3",
"@types/react": "18.2.6",
"@types/react-dom": "18.2.4",
"autoprefixer": "10.4.14",
"eslint": "8.41.0",
"eslint-config-next": "13.4.3",
"isomorphic-unfetch": "^4.0.2",
"next": "13.4.3",
"next-mdx-remote": "^4.4.1",
"papaparse": "^5.4.1",
"postcss": "8.4.23",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-query": "^3.39.3",
"rehype-autolink-headings": "^6.1.1",
"rehype-katex": "^6.0.3",
"rehype-prism-plus": "^1.5.1",
"rehype-slug": "^5.1.0",
"remark-math": "^5.1.1",
"remark-smartypants": "^2.0.0",
"remark-toc": "^8.0.1",
"tailwindcss": "3.3.2",
"typescript": "5.0.4"
},
"devDependencies": {

View File

@@ -184,22 +184,24 @@ export default function ProjectPage({
return (
<div key={file.name}>
{file.path && (
<>
<div className="lg:ml-[calc(50%-37vw)] lg:w-[74vw]">
<h4>
{file.name}
{file.format ? `.${file.format}` : ''}
</h4>
{file.bytes >= 5132288 && (
<span>Previewing 5MB out of {size}</span>
{file.bytes >= 512000 && (
<span>Previewing 500KB out of {size}</span>
)}
<FlatUiTable
url={
file.path.startsWith('http')
? file.path
: `https://raw.githubusercontent.com/${project.owner.name}/${project.repo.name}/main/${file.path}`
}
/>
</>
<div className="mt-5">
<FlatUiTable
url={
file.path.startsWith('http')
? file.path
: `https://raw.githubusercontent.com/${project.owner.name}/${project.repo.name}/main/${file.path}`
}
/>
</div>
</div>
)}
</div>
);

View File

@@ -53,6 +53,7 @@ export async function getStaticPaths() {
const paths = allDocuments
.filter((page) => page.metadata?.isDraft !== true)
.filter((page) => !page.file_path.startsWith('content/stories/'))
.map((page) => {
const parts = page.url_path!.split('/');
return { params: { slug: parts } };

View File

@@ -57,7 +57,7 @@ function CustomApp({ Component, pageProps }: AppProps) {
<NextSeo title="OpenSpending" />
<Script
strategy="afterInteractive"
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TOKEN}`}
src="https://www.googletagmanager.com/gtag/js?id=G-GXZF7NRXX6"
/>
<Script
id="gtag-init"
@@ -67,7 +67,7 @@ function CustomApp({ Component, pageProps }: AppProps) {
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', ${GA_TOKEN}, {
gtag('config', 'G-GXZF7NRXX6', {
page_path: window.location.pathname,
});
`,

View File

@@ -0,0 +1,62 @@
import fs from 'fs';
import clientPromise from '@/lib/mddb';
import { GetStaticProps } from 'next';
import Layout from '../../components/_shared/Layout';
import { formatDate } from '@/utils/formatDate';
import parse from '../../lib/markdown';
import DataRichDocument from '../../components/DataRichDocument';
export default function Page({ source, meta }) {
return (
<Layout>
<article className="docs prose-a:text-primary dark:prose-a:text-primary-dark prose-strong:text-primary dark:prose-strong:text-primary-dark prose-code:text-primary dark:prose-code:text-primary-dark prose-headings:text-primary dark:prose-headings:text-primary-dark prose text-primary dark:text-primary-dark prose-headings:font-headings dark:prose-invert prose-a:break-words mx-auto p-6">
<header>
<div className="mb-4 flex-col items-center">
{meta.title && (
<h1 className="flex justify-center">{meta.title}</h1>
)}
{meta.date && (
<p className="text-sm text-zinc-400 dark:text-zinc-500 flex justify-center">
<time dateTime={meta.date}>{formatDate(meta.date)}</time>
</p>
)}
</div>
</header>
<section>
<DataRichDocument source={source} />
</section>
</article>
</Layout>
);
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const mddb = await clientPromise;
const dbFile = await mddb.getFileByUrl('stories/' + params?.fileName);
let source = fs.readFileSync(dbFile.file_path, { encoding: 'utf-8' });
let { mdxSource } = await parse(source, '.mdx', {});
return {
props: {
source: mdxSource,
meta: dbFile.metadata,
},
};
};
export async function getStaticPaths() {
const mddb = await clientPromise;
let allDocuments = await mddb.getFiles({ extensions: ['mdx'], folder: 'stories' });
const paths = allDocuments
.filter((page) => page.metadata?.isDraft !== true)
.map((page) => {
const parts = page.url_path!.split('/').slice(-1)[0];
return { params: { fileName: parts } };
});
return {
paths,
fallback: false,
};
}

View File

@@ -0,0 +1,57 @@
import fs from 'fs';
import React from 'react';
import { GetStaticProps } from 'next';
import { BlogsList, SimpleLayout } from '@flowershow/core';
import clientPromise from '../../lib/mddb';
import type { CustomAppProps } from '../_app';
import Layout from '@/components/_shared/Layout';
interface BlogIndexPageProps extends CustomAppProps {
blogs: any[]; // TODO types
}
export default function StoriesList({
stories,
meta: { title, description },
}: BlogIndexPageProps) {
return (
<Layout>
<div className="blog-list">
<SimpleLayout title={title} description={description}>
<BlogsList blogs={stories} />
</SimpleLayout>
</div>
</Layout>
);
}
export const getStaticProps: GetStaticProps = async () => {
const mddb = await clientPromise;
const storiesFiles = await mddb.getFiles({ folder: 'stories' });
const stories = storiesFiles.map((item) => ({
_id: item._id,
file_path: item.file_path,
urlPath: item.url_path,
date: item.metadata.date,
...item.metadata,
}));
return {
props: {
meta: {
title: 'Data Stories',
showSidebar: false,
showToc: false,
showComments: false,
showEditLink: false,
urlPath: '/stories',
},
stories: stories.sort((a, b) => {
const bDate = new Date(b.date);
const aDate = new Date(a.date);
return bDate.getTime() - aDate.getTime();
}),
},
};
};

View File

@@ -82,3 +82,99 @@ pre {
.blog-list button {
color: black;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "@flowershow/remark-callouts/styles.css";
.w-5 {
width: 1.25rem
}
.h-5 {
height: 1.25rem
}
/* mathjax */
.math-inline > mjx-container > svg {
display: inline;
align-items: center;
}
/* smooth scrolling in modern browsers */
html {
scroll-behavior: smooth !important;
}
/* tooltip fade-out clip */
.tooltip-body::after {
content: "";
position: absolute;
right: 0;
top: 3.6rem; /* multiple of $line-height used on the tooltip body (defined in tooltipBodyStyle) */
height: 1.2rem; /* ($top + $height)/$line-height is the number of lines we want to clip tooltip text at*/
width: 10rem;
background: linear-gradient(
to right,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 1) 100%
);
}
:is(h2, h3, h4, h5, h6):not(.blogitem-title) {
margin-left: -2rem !important;
padding-left: 2rem !important;
scroll-margin-top: 4.5rem;
position: relative;
}
.heading-link {
padding: 1px;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
margin: auto 0;
border-radius: 5px;
background: #1e293b;
opacity: 0;
transition: opacity 0.2s;
}
.light .heading-link {
/* border: 1px solid #ab2b65; */
/* background: none; */
background: #e2e8f0;
}
:is(h2, h3, h4, h5, h6):not(.blogitem-title):hover .heading-link {
opacity: 100;
}
.heading-link svg {
transform: scale(0.75);
}
@media screen and (max-width: 640px) {
.heading-link {
visibility: hidden;
}
}
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}

31133
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,24 @@
{
"name": "portaljs",
"workspaces": ["./packages/*"],
"version": "0.0.0",
"license": "MIT",
"scripts": {},
"scripts": {
"changeset": "changeset",
"prerelease": "nx run-many --targets=lint,test,build --projects=tag:test",
"release": "changeset publish"
},
"private": true,
"devDependencies": {
"@babel/preset-react": "^7.14.5",
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.1",
"@nrwl/cypress": "15.9.2",
"@nrwl/eslint-plugin-nx": "^16.0.2",
"@nrwl/eslint-plugin-nx": "15.9.2",
"@nrwl/jest": "15.9.2",
"@nrwl/js": "15.9.2",
"@nrwl/linter": "15.9.2",
"@nrwl/next": "^15.9.2",
"@nrwl/next": "15.9.2",
"@nrwl/react": "15.9.2",
"@nrwl/rollup": "15.9.2",
"@nrwl/workspace": "15.9.2",
@@ -21,13 +28,16 @@
"@swc/helpers": "~0.5.0",
"@swc/jest": "0.2.20",
"@testing-library/react": "14.0.0",
"@types/chai": "^4.3.5",
"@types/jest": "^29.4.0",
"@types/mocha": "^10.0.1",
"@types/node": "18.14.2",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1",
"babel-jest": "^29.4.1",
"chai": "^4.3.7",
"cypress": "^12.2.0",
"eslint": "~8.15.0",
"eslint-config-next": "13.1.1",
@@ -37,14 +47,21 @@
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"htmlparser2": "^9.0.0",
"jest": "^29.4.1",
"jest-environment-jsdom": "^29.4.1",
"micromark": "^3.2.0",
"mocha": "^10.2.0",
"nx": "15.9.2",
"prettier": "^2.6.2",
"react-test-renderer": "18.2.0",
"rehype-stringify": "^9.0.3",
"remark": "^14.0.3",
"swc-loader": "0.1.15",
"ts-jest": "^29.0.5",
"ts-node": "10.9.1",
"typescript": "~4.9.5"
"typescript": "~4.9.5",
"unist-util-select": "^4.0.3",
"unist-util-visit": "^4.1.2"
}
}

View File

@@ -0,0 +1,7 @@
# @portaljs/ckan
## 0.0.3
### Patch Changes
- [`e82e2ae`](https://github.com/datopian/portaljs/commit/e82e2ae0211ea3e4701703d353b44cf1001434ef) Thanks [@olayway](https://github.com/olayway)! - Fix: replace deprecated `prepublish` script with `prepare`

View File

@@ -1,6 +1,6 @@
{
"name": "@portaljs/ckan",
"version": "0.0.2",
"version": "0.0.3",
"type": "module",
"description": "https://portaljs.org",
"keywords": [
@@ -13,7 +13,8 @@
"scripts": {
"build": "tsc && vite build && npm run build-tailwind",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"build-tailwind": "NODE_ENV=production npx tailwindcss -o ./dist/styles.css --minify"
"build-tailwind": "NODE_ENV=production npx tailwindcss -o ./dist/styles.css --minify",
"prepare": "npm run build"
},
"peerDependencies": {
"react": "^18.2.0",

View File

@@ -9,6 +9,7 @@ lerna-debug.log*
node_modules
dist
storybook-static
dist-ssr
*.local

View File

@@ -1,4 +1,5 @@
import 'tailwindcss/tailwind.css'
import '../src/index.css'
import type { Preview } from '@storybook/react';

View File

@@ -0,0 +1,25 @@
# @portaljs/components
## 0.1.11
### Patch Changes
- [`e82e2ae`](https://github.com/datopian/portaljs/commit/e82e2ae0211ea3e4701703d353b44cf1001434ef) Thanks [@olayway](https://github.com/olayway)! - Fix: replace deprecated `prepublish` script with `prepare`
## 0.1.10
### Patch Changes
- [`40d80d2`](https://github.com/datopian/portaljs/commit/40d80d2282bf8464c1aafb393975065078ad9ea3) Thanks [@olayway](https://github.com/olayway)! - Fix: missing files in the published package.
## 0.1.9
### Patch Changes
- [#933](https://github.com/datopian/portaljs/pull/933) [`683159d`](https://github.com/datopian/portaljs/commit/683159da02708ce520e9aad9707f2b96b598ec8b) Thanks [@demenech](https://github.com/demenech)! - More params added to <LineChart />, loading spinners added to <Table /> and <LineChart />, minor fixes
## 0.1.8
### Patch Changes
- [#924](https://github.com/datopian/portaljs/pull/924) [`959fe5a`](https://github.com/datopian/portaljs/commit/959fe5a588b12786e3f2e642c24009d77bceac46) Thanks [@luccasmmg](https://github.com/luccasmmg)! - Fixed redeclaration of formal parameter "x" error after next build

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@portaljs/components",
"version": "0.1.6",
"version": "0.1.11",
"type": "module",
"description": "https://portaljs.org",
"keywords": [
@@ -16,7 +16,8 @@
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"build-tailwind": "NODE_ENV=production npx tailwindcss -o ./dist/styles.css --minify"
"build-tailwind": "NODE_ENV=production npx tailwindcss -o ./dist/styles.css --minify",
"prepare": "npm run build"
},
"peerDependencies": {
"react": "^18.2.0",
@@ -34,8 +35,10 @@
"react-hook-form": "^7.43.9",
"react-query": "^3.39.3",
"react-vega": "^7.6.0",
"rollup-plugin-re": "^1.0.7",
"vega": "5.25.0",
"vega-lite": "5.1.0"
"vega-lite": "5.1.0",
"vitest": "^0.31.4"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.0.7",

View File

@@ -1,12 +1,13 @@
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import Papa from 'papaparse';
import { Grid } from '@githubocto/flat-ui';
import LoadingSpinner from './LoadingSpinner';
const queryClient = new QueryClient();
export async function getCsv(url: string, corsProxy?: string) {
if (corsProxy) {
url = corsProxy + url
url = corsProxy + url;
}
const response = await fetch(url, {
headers: {
@@ -36,30 +37,6 @@ export async function parseCsv(file: string): Promise<any> {
});
}
const Spinning = () => {
return (
<div role="status w-fit mx-auto">
<svg
aria-hidden="true"
className="w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-emerald-600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
</div>
);
};
export interface FlatUiTableProps {
url?: string;
data?: { [key: string]: number | string }[];
@@ -80,10 +57,15 @@ export const FlatUiTable: React.FC<FlatUiTableProps> = ({
);
};
const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv, corsProxy }) => {
const TableInner: React.FC<FlatUiTableProps> = ({
url,
data,
rawCsv,
corsProxy,
}) => {
if (data) {
return (
<div className="w-full" style={{height: '500px'}}>
<div className="w-full" style={{ height: '500px' }}>
<Grid data={data} />
</div>
);
@@ -95,19 +77,22 @@ const TableInner: React.FC<FlatUiTableProps> = ({ url, data, rawCsv, corsProxy }
);
const { data: parsedData, isLoading: isParsing } = useQuery(
['dataPreview', csvString],
() => parseCsv(rawCsv ? rawCsv as string : csvString as string),
() => parseCsv(rawCsv ? (rawCsv as string) : (csvString as string)),
{ enabled: rawCsv ? true : !!csvString }
);
if (isParsing || isDownloadingCSV)
<div className="w-full">
<Spinning />
<div className="w-full flex justify-center items-center h-[500px]">
<LoadingSpinner />
</div>;
if (parsedData)
return (
<div className="w-full" style={{height: '500px'}}>
<div className="w-full" style={{ height: '500px' }}>
<Grid data={parsedData.data} />
</div>
);
return <Spinning />
return (
<div className="w-full flex justify-center items-center h-[500px]">
<LoadingSpinner />
</div>
);
};

View File

@@ -1,10 +1,19 @@
import { useEffect, useState } from 'react';
import LoadingSpinner from './LoadingSpinner';
import { VegaLite } from './VegaLite';
import loadData from '../lib/loadData';
type AxisType = 'quantitative' | 'temporal';
type TimeUnit = 'year' | undefined; // or ...
export type LineChartProps = {
data: Array<Array<string | number>> | string | { x: string; y: number }[];
title?: string;
xAxis?: string;
xAxisType?: AxisType;
xAxisTimeUnit: TimeUnit;
yAxis?: string;
yAxisType?: AxisType;
fullWidth?: boolean;
};
@@ -13,15 +22,16 @@ export function LineChart({
fullWidth = false,
title = '',
xAxis = 'x',
xAxisType = 'temporal',
xAxisTimeUnit = 'year', // TODO: defaults to undefined would probably work better... keeping it as it's for compatibility purposes
yAxis = 'y',
yAxisType = 'quantitative',
}: LineChartProps) {
var tmp = data;
if (Array.isArray(data)) {
tmp = data.map((r) => {
return { x: r[0], y: r[1] };
});
}
const vegaData = { table: tmp };
const [isLoading, setIsLoading] = useState<boolean>(false);
// By default, assumes data is an Array...
const [specData, setSpecData] = useState<any>({ name: 'table' });
const spec = {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
title,
@@ -33,9 +43,7 @@ export function LineChart({
strokeWidth: 1,
tooltip: true,
},
data: {
name: 'table',
},
data: specData,
selection: {
grid: {
type: 'interval',
@@ -45,19 +53,44 @@ export function LineChart({
encoding: {
x: {
field: xAxis,
timeUnit: 'year',
type: 'temporal',
timeUnit: xAxisTimeUnit,
type: xAxisType,
},
y: {
field: yAxis,
type: 'quantitative',
type: yAxisType,
},
},
};
if (typeof data === 'string') {
spec.data = { url: data } as any;
return <VegaLite fullWidth={fullWidth} spec={spec} />;
} as any;
useEffect(() => {
// If data is string, assume it's a URL
if (typeof data === 'string') {
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) => {
setSpecData({ values: res, format: { type: 'csv' } });
setIsLoading(false);
});
}
}, []);
var vegaData = {};
if (Array.isArray(data)) {
var dataObj;
dataObj = data.map((r) => {
return { x: r[0], y: r[1] };
});
vegaData = { table: dataObj };
}
return <VegaLite fullWidth={fullWidth} data={vegaData} spec={spec} />;
return isLoading ? (
<div className="w-full flex items-center justify-center w-[600px] h-[300px]">
<LoadingSpinner />
</div>
) : (
<VegaLite fullWidth={fullWidth} data={vegaData} spec={spec} />
);
}

View File

@@ -0,0 +1,23 @@
export default function LoadingSpinner() {
return (
<div role="status w-fit mx-auto">
<svg
aria-hidden="true"
className="w-16 h-16 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-slate-600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span className="sr-only">Loading...</span>
</div>
);
}

View File

@@ -23,6 +23,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import parseCsv from '../lib/parseCsv';
import DebouncedInput from './DebouncedInput';
import loadData from '../lib/loadData';
import LoadingSpinner from './LoadingSpinner';
export type TableProps = {
data?: Array<{ [key: string]: number | string }>;
@@ -39,6 +40,8 @@ export const Table = ({
url = '',
fullWidth = false,
}: TableProps) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
if (csv) {
const out = parseCsv(csv);
ogData = out.rows;
@@ -77,15 +80,22 @@ export const Table = ({
useEffect(() => {
if (url) {
setIsLoading(true);
// TODO: exception handling. What if the file doesn't exist? What if fetching was not possible?
loadData(url).then((data) => {
const { rows, fields } = parseCsv(data);
setData(rows);
setCols(fields);
setIsLoading(false);
});
}
}, [url]);
return (
return isLoading ? (
<div className="w-full h-full min-h-[500px] flex items-center justify-center">
<LoadingSpinner />
</div>
) : (
<div className={`${fullWidth ? 'w-[90vw] ml-[calc(50%-45vw)]' : 'w-full'}`}>
<DebouncedInput
value={globalFilter ?? ''}

View File

@@ -1,3 +1,10 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind utilities;
/* Temporary fix for a size issue with FlatUiTable loading indicator on Firefox */
@layer base {
svg[tw^='animate-pulse w-12'] {
max-width: 100px;
}
}

View File

@@ -19,10 +19,19 @@ const meta: Meta = {
description:
'Name of the X axis on the data. Required when the "data" parameter is an URL.',
},
xAxisType: {
description: 'Type of the X axis',
},
xAxisTimeUnit: {
description: 'Time unit of the X axis (optional)',
},
yAxis: {
description:
'Name of the Y axis on the data. Required when the "data" parameter is an URL.',
},
yAxisType: {
description: 'Type of the Y axis',
},
fullWidth: {
description:
'Whether the component should be rendered as full bleed or not',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
import{j as p}from"./jsx-runtime-94f6e698.js";import{r as u}from"./index-8db94870.js";const a=({value:e,onChange:o,debounce:s=500,...c})=>{const[t,r]=u.useState(e);return u.useEffect(()=>{r(e)},[e]),u.useEffect(()=>{const n=setTimeout(()=>{o(t)},s);return()=>clearTimeout(n)},[t]),p.jsx("input",{...c,value:t,onChange:n=>r(n.target.value)})};try{a.displayName="DebouncedInput",a.__docgenInfo={description:"",displayName:"DebouncedInput",props:{value:{defaultValue:null,description:"",name:"value",required:!0,type:{name:"any"}},onChange:{defaultValue:null,description:"",name:"onChange",required:!0,type:{name:"any"}},debounce:{defaultValue:{value:"500"},description:"",name:"debounce",required:!1,type:{name:"number"}}}}}catch{}export{a as D};
//# sourceMappingURL=DebouncedInput-c720676c.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"DebouncedInput-c720676c.js","sources":["../../src/components/DebouncedInput.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nconst DebouncedInput = ({\n value: initialValue,\n onChange,\n debounce = 500,\n ...props\n}) => {\n const [value, setValue] = useState(initialValue);\n\n useEffect(() => {\n setValue(initialValue);\n }, [initialValue]);\n\n useEffect(() => {\n const timeout = setTimeout(() => {\n onChange(value);\n }, debounce);\n\n return () => clearTimeout(timeout);\n }, [value]);\n\n return (\n <input\n {...props}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n />\n );\n};\n\nexport default DebouncedInput;\n"],"names":["DebouncedInput","initialValue","onChange","debounce","props","value","setValue","useState","useEffect","timeout","jsx","e"],"mappings":"sFAEA,MAAAA,EAAA,CAAA,CAAwB,MAAAC,EACf,SAAAC,EACP,SAAAC,EAAA,IACW,GAAAC,CAEb,IAAA,CACE,KAAA,CAAAC,EAAAC,CAAA,EAAAC,EAAA,SAAAN,CAAA,EAEAO,OAAAA,EAAAA,UAAA,IAAA,CACEF,EAAAL,CAAA,CAAqB,EAAA,CAAAA,CAAA,CAAA,EAGvBO,EAAAA,UAAA,IAAA,CACE,MAAAC,EAAA,WAAA,IAAA,CACEP,EAAAG,CAAA,CAAc,EAAAF,CAAA,EAGhB,MAAA,IAAA,aAAAM,CAAA,CAAiC,EAAA,CAAAJ,CAAA,CAAA,EAGnCK,EAAA,IACE,QAAC,CAAA,GAAAN,EACK,MAAAC,EACJ,SAAAM,GAAAL,EAAAK,EAAA,OAAA,KAAA,CACwC,CAAA,CAG9C"}

View File

@@ -1,2 +0,0 @@
import{_ as p}from"./iframe-1eda5ccb.js";import{R as e,r as a}from"./index-8db94870.js";import{r as c,u}from"./react-18-ff2c0a32.js";import{C as h,A as l,H as E,D as d}from"./index-89936ab1.js";import"../sb-preview/runtime.js";import"./_commonjsHelpers-042e6b4d.js";import"./index-8ce4a492.js";import"./index-d475d2ea.js";import"./isNativeReflectConstruct-099dc9ad.js";import"./index-d37d4223.js";import"./index-6e6be2d5.js";import"./index-356e4a49.js";var x={code:h,a:l,...E},_=class extends a.Component{constructor(){super(...arguments),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(t){let{showException:r}=this.props;r(t)}render(){let{hasError:t}=this.state,{children:r}=this.props;return t?null:r}},S=class{constructor(){this.render=async(t,r,o)=>{let n={...x,...r==null?void 0:r.components};return new Promise((s,m)=>{p(()=>import("./index-186b3228.js"),["./index-186b3228.js","./index-1d576ef5.js","./index-8db94870.js","./_commonjsHelpers-042e6b4d.js"],import.meta.url).then(({MDXProvider:i})=>c(e.createElement(_,{showException:m,key:Math.random()},e.createElement(i,{components:n},e.createElement(d,{context:t,docsParameter:r}))),o)).then(s)})},this.unmount=t=>{u(t)}}};export{S as DocsRenderer,x as defaultComponents};
//# sourceMappingURL=DocsRenderer-EYKKDMVH-b61c696a.js.map

View File

@@ -1 +0,0 @@
{"version":3,"mappings":"qcAIG,IAACA,EAAkB,CAAC,KAAKC,EAAgB,EAAEC,EAAU,GAAGC,CAAU,EAAEC,EAAc,cAAcC,WAAS,CAAC,aAAa,CAAC,MAAM,GAAG,SAAS,EAAE,KAAK,MAAM,CAAC,SAAS,EAAE,CAAE,CAAC,OAAO,0BAA0B,CAAC,MAAO,CAAC,SAAS,EAAE,CAAC,CAAC,kBAAkBC,EAAI,CAAC,GAAG,CAAC,cAAAC,CAAa,EAAE,KAAK,MAAMA,EAAcD,CAAG,CAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAAE,CAAQ,EAAE,KAAK,MAAM,CAAC,SAAAC,CAAQ,EAAE,KAAK,MAAM,OAAOD,EAAS,KAAKC,CAAQ,CAAC,EAAEC,EAAa,KAAK,CAAC,aAAa,CAAC,KAAK,OAAO,MAAMC,EAAQC,EAAcC,IAAU,CAAC,IAAIC,EAAW,CAAC,GAAGd,EAAkB,GAAGY,GAAA,YAAAA,EAAe,UAAU,EAAE,OAAO,IAAI,QAAQ,CAACG,EAAQC,IAAS,CAAAC,EAAA,IAAC,OAAO,qBAAe,EAAC,sHAAC,KAAK,CAAC,CAAC,YAAAC,CAAW,IAAIC,EAAcC,EAAM,cAAchB,EAAc,CAAC,cAAcY,EAAO,IAAI,KAAK,OAAM,CAAE,EAAEI,EAAM,cAAcF,EAAY,CAAC,WAAAJ,CAAU,EAAEM,EAAM,cAAcC,EAAK,CAAC,QAAAV,EAAQ,cAAAC,CAAa,CAAC,CAAC,CAAC,EAAEC,CAAO,CAAC,EAAE,KAAKE,CAAO,CAAE,CAAC,CAAC,EAAE,KAAK,QAAQF,GAAS,CAACS,EAAeT,CAAO,CAAE,CAAE,CAAC","names":["defaultComponents","CodeOrSourceMdx","AnchorMdx","HeadersMdx","ErrorBoundary","Component","err","showException","hasError","children","DocsRenderer","context","docsParameter","element","components","resolve","reject","__vitePreload","MDXProvider","renderElement","React","Docs","unmountElement"],"sources":["../../node_modules/@storybook/addon-docs/dist/chunk-PCJTTTQV.mjs"],"sourcesContent":["import React, { Component } from 'react';\nimport { renderElement, unmountElement } from '@storybook/react-dom-shim';\nimport { CodeOrSourceMdx, AnchorMdx, HeadersMdx, Docs } from '@storybook/blocks';\n\nvar defaultComponents={code:CodeOrSourceMdx,a:AnchorMdx,...HeadersMdx},ErrorBoundary=class extends Component{constructor(){super(...arguments);this.state={hasError:!1};}static getDerivedStateFromError(){return {hasError:!0}}componentDidCatch(err){let{showException}=this.props;showException(err);}render(){let{hasError}=this.state,{children}=this.props;return hasError?null:children}},DocsRenderer=class{constructor(){this.render=async(context,docsParameter,element)=>{let components={...defaultComponents,...docsParameter?.components};return new Promise((resolve,reject)=>{import('@mdx-js/react').then(({MDXProvider})=>renderElement(React.createElement(ErrorBoundary,{showException:reject,key:Math.random()},React.createElement(MDXProvider,{components},React.createElement(Docs,{context,docsParameter}))),element)).then(resolve);})},this.unmount=element=>{unmountElement(element);};}};\n\nexport { DocsRenderer, defaultComponents };\n"],"file":"assets/DocsRenderer-EYKKDMVH-b61c696a.js"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
import{j as o}from"./jsx-runtime-94f6e698.js";import{M as i}from"./index-89936ab1.js";import{u as s}from"./index-1d576ef5.js";import"./index-8db94870.js";import"./_commonjsHelpers-042e6b4d.js";import"./iframe-1eda5ccb.js";import"../sb-preview/runtime.js";import"./index-d475d2ea.js";import"./isNativeReflectConstruct-099dc9ad.js";import"./index-8ce4a492.js";import"./index-d37d4223.js";import"./index-6e6be2d5.js";import"./index-356e4a49.js";function M(t={}){const{wrapper:n}=Object.assign({},s(),t.components);return n?o.jsx(n,Object.assign({},t,{children:o.jsx(e,{})})):e();function e(){const r=Object.assign({h1:"h1",p:"p",strong:"strong",a:"a",br:"br"},s(),t.components);return o.jsxs(o.Fragment,{children:[o.jsx(i,{title:"Components/Introduction"}),`
`,o.jsx(r.h1,{id:"welcome-to-the-portaljs-components-guide",children:"Welcome to the PortalJS components guide"}),`
`,o.jsxs(r.p,{children:[o.jsx(r.strong,{children:"Official Website:"})," ",o.jsx(r.a,{href:"https://portaljs.org",target:"_blank",rel:"nofollow noopener noreferrer",children:"portaljs.org"}),o.jsx(r.br,{}),`
`,o.jsx(r.strong,{children:"Docs:"})," ",o.jsx(r.a,{href:"https://portaljs.org/docs",target:"_blank",rel:"nofollow noopener noreferrer",children:"portaljs.org/docs"}),o.jsx(r.br,{}),`
`,o.jsx(r.strong,{children:"GitHub:"})," ",o.jsx(r.a,{href:"https://github.com/datopian/portaljs",target:"_blank",rel:"nofollow noopener noreferrer",children:"github.com/datopian/portaljs"})]})]})}}export{M as default};
//# sourceMappingURL=Introduction-7f71f7c8.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"Introduction-7f71f7c8.js","sources":["../../stories/Introduction.mdx"],"sourcesContent":["import { Meta } from '@storybook/blocks';\n\n<Meta title=\"Components/Introduction\" />\n\n# Welcome to the PortalJS components guide\n\n**Official Website:** [portaljs.org](https://portaljs.org) \n**Docs:** [portaljs.org/docs](https://portaljs.org/docs) \n**GitHub:** [github.com/datopian/portaljs](https://github.com/datopian/portaljs)"],"names":["MDXContent","props","MDXLayout","_provideComponents","_jsx","_createMdxContent","_components","_jsxs","_Fragment","Meta"],"mappings":"0bAIA,SAASA,EAAWC,EAAQ,GAAI,CAC9B,KAAM,CAAC,QAASC,CAAS,EAAI,OAAO,OAAO,CAAE,EAAEC,EAAoB,EAAEF,EAAM,UAAU,EACrF,OAAOC,EAAYE,EAAAA,IAAKF,EAAW,OAAO,OAAO,CAAE,EAAED,EAAO,CAC1D,SAAUG,EAAAA,IAAKC,EAAmB,EAAE,CACxC,CAAG,CAAC,EAAIA,IACN,SAASA,GAAoB,CAC3B,MAAMC,EAAc,OAAO,OAAO,CAChC,GAAI,KACJ,EAAG,IACH,OAAQ,SACR,EAAG,IACH,GAAI,IACL,EAAEH,EAAoB,EAAEF,EAAM,UAAU,EACzC,OAAOM,EAAAA,KAAMC,EAAAA,SAAW,CACtB,SAAU,CAACJ,EAAI,IAACK,EAAM,CACpB,MAAO,yBACR,CAAA,EAAG;AAAA,EAAML,MAAKE,EAAY,GAAI,CAC7B,GAAI,2CACJ,SAAU,0CACX,CAAA,EAAG;AAAA,EAAMC,OAAMD,EAAY,EAAG,CAC7B,SAAU,CAACF,EAAAA,IAAKE,EAAY,OAAQ,CAClC,SAAU,mBACX,CAAA,EAAG,IAAKF,MAAKE,EAAY,EAAG,CAC3B,KAAM,uBACN,OAAQ,SACR,IAAK,+BACL,SAAU,cACpB,CAAS,EAAGF,EAAAA,IAAKE,EAAY,GAAI,CAAA,CAAE,EAAG;AAAA,EAAMF,EAAAA,IAAKE,EAAY,OAAQ,CAC3D,SAAU,OACX,CAAA,EAAG,IAAKF,MAAKE,EAAY,EAAG,CAC3B,KAAM,4BACN,OAAQ,SACR,IAAK,+BACL,SAAU,mBACpB,CAAS,EAAGF,EAAAA,IAAKE,EAAY,GAAI,CAAA,CAAE,EAAG;AAAA,EAAMF,EAAAA,IAAKE,EAAY,OAAQ,CAC3D,SAAU,SACX,CAAA,EAAG,IAAKF,MAAKE,EAAY,EAAG,CAC3B,KAAM,uCACN,OAAQ,SACR,IAAK,+BACL,SAAU,8BACpB,CAAS,CAAC,CACV,CAAO,CAAC,CACR,CAAK,CACF,CACH"}

View File

@@ -1,21 +0,0 @@
import{j as d}from"./jsx-runtime-94f6e698.js";import{V as c}from"./VegaLite-30eeb950.js";import"./index-8db94870.js";import"./_commonjsHelpers-042e6b4d.js";import"./Vega-d925c94f.js";import"./index-1fc0ca9a.js";function i({data:e=[],fullWidth:n=!1,title:f="",xAxis:x="x",yAxis:g="y"}){var s=e;Array.isArray(e)&&(s=e.map(o=>({x:o[0],y:o[1]})));const v={table:s},r={$schema:"https://vega.github.io/schema/vega-lite/v5.json",title:f,width:"container",height:300,mark:{type:"line",color:"black",strokeWidth:1,tooltip:!0},data:{name:"table"},selection:{grid:{type:"interval",bind:"scales"}},encoding:{x:{field:x,timeUnit:"year",type:"temporal"},y:{field:g,type:"quantitative"}}};return typeof e=="string"?(r.data={url:e},d.jsx(c,{fullWidth:n,spec:r})):d.jsx(c,{fullWidth:n,data:v,spec:r})}try{i.displayName="LineChart",i.__docgenInfo={description:"",displayName:"LineChart",props:{data:{defaultValue:{value:"[]"},description:"",name:"data",required:!1,type:{name:"string | (string | number)[][] | { x: string; y: number; }[]"}},title:{defaultValue:{value:""},description:"",name:"title",required:!1,type:{name:"string"}},xAxis:{defaultValue:{value:"x"},description:"",name:"xAxis",required:!1,type:{name:"string"}},yAxis:{defaultValue:{value:"y"},description:"",name:"yAxis",required:!1,type:{name:"string"}},fullWidth:{defaultValue:{value:"false"},description:"",name:"fullWidth",required:!1,type:{name:"boolean"}}}}}catch{}const w={title:"Components/LineChart",component:i,tags:["autodocs"],argTypes:{data:{description:`Data to be displayed.
E.g.: [["1990", 1], ["1991", 2]]
OR
"https://url.to/data.csv"`},title:{description:"Title to display on the chart. Optional."},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.'},fullWidth:{description:"Whether the component should be rendered as full bleed or not"}}},t={name:"Line chart from array of data points",args:{data:[["1850",-.41765878],["1851",-.2333498],["1852",-.22939907],["1853",-.27035445],["1854",-.29163003]]}},a={name:"Line chart from URL",args:{title:"Oil Price x Year",data:"https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv",xAxis:"Date",yAxis:"Price"}};var l,p,m;t.parameters={...t.parameters,docs:{...(l=t.parameters)==null?void 0:l.docs,source:{originalSource:`{
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]]
}
}`,...(m=(p=t.parameters)==null?void 0:p.docs)==null?void 0:m.source}}};var u,h,y;a.parameters={...a.parameters,docs:{...(u=a.parameters)==null?void 0:u.docs,source:{originalSource:`{
name: 'Line chart from URL',
args: {
title: 'Oil Price x Year',
data: 'https://raw.githubusercontent.com/datasets/oil-prices/main/data/wti-year.csv',
xAxis: 'Date',
yAxis: 'Price'
}
}`,...(y=(h=a.parameters)==null?void 0:h.docs)==null?void 0:y.source}}};const U=["FromDataPoints","FromURL"];export{t as FromDataPoints,a as FromURL,U as __namedExportsOrder,w as default};
//# sourceMappingURL=LineChart.stories-de9527a1.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"LineChart.stories-de9527a1.js","sources":["../../src/components/LineChart.tsx"],"sourcesContent":["import { VegaLite } from './VegaLite';\n\nexport type LineChartProps = {\n data: Array<Array<string | number>> | string | { x: string; y: number }[];\n title?: string;\n xAxis?: string;\n yAxis?: string;\n fullWidth?: boolean;\n};\n\nexport function LineChart({\n data = [],\n fullWidth = false,\n title = '',\n xAxis = 'x',\n yAxis = 'y',\n}: LineChartProps) {\n var tmp = data;\n if (Array.isArray(data)) {\n tmp = data.map((r) => {\n return { x: r[0], y: r[1] };\n });\n }\n const vegaData = { table: tmp };\n const spec = {\n $schema: 'https://vega.github.io/schema/vega-lite/v5.json',\n title,\n width: 'container',\n height: 300,\n mark: {\n type: 'line',\n color: 'black',\n strokeWidth: 1,\n tooltip: true,\n },\n data: {\n name: 'table',\n },\n selection: {\n grid: {\n type: 'interval',\n bind: 'scales',\n },\n },\n encoding: {\n x: {\n field: xAxis,\n timeUnit: 'year',\n type: 'temporal',\n },\n y: {\n field: yAxis,\n type: 'quantitative',\n },\n },\n };\n if (typeof data === 'string') {\n spec.data = { url: data } as any;\n return <VegaLite fullWidth={fullWidth} spec={spec} />;\n }\n\n return <VegaLite fullWidth={fullWidth} data={vegaData} spec={spec} />;\n}\n"],"names":["LineChart","data","fullWidth","title","xAxis","yAxis","tmp","r","vegaData","spec","jsx","VegaLite"],"mappings":"mNAUO,SAAAA,EAAA,CAAmB,KAAAC,EAAA,CAAA,EAChB,UAAAC,EAAA,GACI,MAAAC,EAAA,GACJ,MAAAC,EAAA,IACA,MAAAC,EAAA,GAEV,EAAA,CACE,IAAAC,EAAAL,EACA,MAAA,QAAAA,CAAA,IACEK,EAAAL,EAAA,IAAAM,IACE,CAAA,EAAAA,EAAA,CAAA,EAAA,EAAAA,EAAA,CAAA,GAA0B,GAG9B,MAAAC,EAAA,CAAA,MAAAF,GACAG,EAAA,CAAa,QAAA,kDACF,MAAAN,EACT,MAAA,YACO,OAAA,IACC,KAAA,CACF,KAAA,OACE,MAAA,QACC,YAAA,EACM,QAAA,EACJ,EACX,KAAA,CACM,KAAA,OACE,EACR,UAAA,CACW,KAAA,CACH,KAAA,WACE,KAAA,QACA,CACR,EACF,SAAA,CACU,EAAA,CACL,MAAAC,EACM,SAAA,OACG,KAAA,UACJ,EACR,EAAA,CACG,MAAAC,EACM,KAAA,cACD,CACR,CACF,EAEF,OAAA,OAAAJ,GAAA,UACEQ,EAAA,KAAA,CAAA,IAAAR,CAAA,EACAS,EAAA,IAAAC,EAAA,CAAA,UAAAT,EAAA,KAAAO,CAAA,CAAA,GAGFC,EAAAA,IAAAC,EAAA,CAAA,UAAAT,EAAA,KAAAM,EAAA,KAAAC,CAAA,CAAA,CACF;;;;;;;;;;;;;;;;;;;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,32 +0,0 @@
import{j as i}from"./jsx-runtime-94f6e698.js";import{V as s}from"./Vega-d925c94f.js";import"./index-8db94870.js";import"./_commonjsHelpers-042e6b4d.js";import"./index-1fc0ca9a.js";function e(t){return i.jsx(s,{...t})}try{e.displayName="Vega",e.__docgenInfo={description:"",displayName:"Vega",props:{}}}catch{}const l={title:"Components/Vega",component:e,tags:["autodocs"]},a={name:"Chart built with Vega",args:{data:{table:[{y:-.418,x:1850},{y:.923,x:2020}]},spec:{$schema:"https://vega.github.io/schema/vega-lite/v4.json",mark:"bar",data:{name:"table"},encoding:{x:{field:"x",type:"ordinal"},y:{field:"y",type:"quantitative"}}}}};var n,r,o;a.parameters={...a.parameters,docs:{...(n=a.parameters)==null?void 0:n.docs,source:{originalSource:`{
name: 'Chart built with Vega',
args: {
data: {
table: [{
y: -0.418,
x: 1850
}, {
y: 0.923,
x: 2020
}]
},
spec: {
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
mark: 'bar',
data: {
name: 'table'
},
encoding: {
x: {
field: 'x',
type: 'ordinal'
},
y: {
field: 'y',
type: 'quantitative'
}
}
}
}
}`,...(o=(r=a.parameters)==null?void 0:r.docs)==null?void 0:o.source}}};const y=["Primary"];export{a as Primary,y as __namedExportsOrder,l as default};
//# sourceMappingURL=Vega.stories-751bd69b.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"Vega.stories-751bd69b.js","sources":["../../src/components/Vega.tsx"],"sourcesContent":["// Wrapper for the Vega component\nimport { Vega as VegaOg } from \"react-vega\";\n\nexport function Vega(props) {\n return <VegaOg {...props} />;\n}\n"],"names":["Vega","props","jsx","VegaOg"],"mappings":"oLAGO,SAAAA,EAAAC,EAAA,CACL,OAAAC,EAAA,IAAAC,EAAA,CAAA,GAAAF,CAAA,CAAA,CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}

View File

@@ -1,2 +0,0 @@
import{j as p}from"./jsx-runtime-94f6e698.js";import{R as s}from"./index-8db94870.js";import{V as u}from"./Vega-d925c94f.js";function l(){return l=Object.assign||function(e){for(var r=1;r<arguments.length;r++){var a=arguments[r];for(var t in a)Object.prototype.hasOwnProperty.call(a,t)&&(e[t]=a[t])}return e},l.apply(this,arguments)}function c(e){return s.createElement(u,l({},e,{mode:"vega-lite"}))}function o({Component:e,defaultWFull:r=!0}){return a=>{const t={...a};let n=t.className||"";return t.fullWidth===!0?n+=" w-[90vw] ml-[calc(50%-45vw)] max-w-none":r&&(n+=" w-full"),t.className=n,p.jsx(e,{...t})}}try{o.displayName="applyFullWidthDirective",o.__docgenInfo={description:"",displayName:"applyFullWidthDirective",props:{Component:{defaultValue:null,description:"",name:"Component",required:!0,type:{name:"any"}},defaultWFull:{defaultValue:{value:"true"},description:"",name:"defaultWFull",required:!1,type:{name:"boolean"}}}}}catch{}function i(e){const r=o({Component:c});return p.jsx(r,{...e})}try{i.displayName="VegaLite",i.__docgenInfo={description:"",displayName:"VegaLite",props:{}}}catch{}export{i as V};
//# sourceMappingURL=VegaLite-30eeb950.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"VegaLite-30eeb950.js","sources":["../../node_modules/react-vega/esm/VegaLite.js","../../src/lib/applyFullWidthDirective.tsx","../../src/components/VegaLite.tsx"],"sourcesContent":["function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nimport React from 'react';\nimport Vega from './Vega';\n\n/**\n * Syntactic sugar for using vega-lite with Vega\n * @param props\n */\nexport default function VegaLite(props) {\n return /*#__PURE__*/React.createElement(Vega, _extends({}, props, {\n mode: \"vega-lite\"\n }));\n}","export default function applyFullWidthDirective({\n Component,\n defaultWFull = true,\n}) {\n return (props) => {\n const newProps = { ...props };\n\n let newClassName = newProps.className || \"\";\n if (newProps.fullWidth === true) {\n newClassName += \" w-[90vw] ml-[calc(50%-45vw)] max-w-none\";\n } else if (defaultWFull) {\n // So that charts and tables will have the\n // same width as the text content, but images\n // can have its width set using the width prop\n newClassName += \" w-full\";\n }\n newProps.className = newClassName;\n\n return <Component {...newProps} />;\n };\n}\n","// Wrapper for the Vega Lite component\nimport { VegaLite as VegaLiteOg } from \"react-vega\";\nimport applyFullWidthDirective from \"../lib/applyFullWidthDirective\";\n\nexport function VegaLite(props) {\n const Component = applyFullWidthDirective({ Component: VegaLiteOg });\n\n return <Component {...props} />;\n}\n"],"names":["_extends","target","i","source","key","VegaLite","props","React","Vega","applyFullWidthDirective","Component","defaultWFull","newProps","newClassName","jsx","VegaLiteOg"],"mappings":"6HAAA,SAASA,GAAW,CAAE,OAAAA,EAAW,OAAO,QAAU,SAAUC,EAAQ,CAAE,QAASC,EAAI,EAAGA,EAAI,UAAU,OAAQA,IAAK,CAAE,IAAIC,EAAS,UAAUD,CAAC,EAAG,QAASE,KAAOD,EAAc,OAAO,UAAU,eAAe,KAAKA,EAAQC,CAAG,IAAKH,EAAOG,CAAG,EAAID,EAAOC,CAAG,GAAS,OAAOH,GAAkBD,EAAS,MAAM,KAAM,SAAS,CAAI,CAS9S,SAASK,EAASC,EAAO,CACtC,OAAoBC,EAAM,cAAcC,EAAMR,EAAS,CAAA,EAAIM,EAAO,CAChE,KAAM,WACP,CAAA,CAAC,CACJ,CCbA,SAAAG,EAAA,CAAgD,UAAAC,EAC9C,aAAAC,EAAA,EAEF,EAAA,CACE,OAAAL,GAAA,CACE,MAAAM,EAAA,CAAA,GAAAN,GAEA,IAAAO,EAAAD,EAAA,WAAA,GACA,OAAAA,EAAA,YAAA,GACEC,GAAA,2CAAgBF,IAKhBE,GAAA,WAEFD,EAAA,UAAAC,EAEAC,EAAA,IAAAJ,EAAA,CAAA,GAAAE,CAAA,CAAA,CAAgC,CAEpC,8UChBO,SAAAP,EAAAC,EAAA,CACL,MAAAI,EAAAD,EAAA,CAAA,UAAAM,CAAA,CAAA,EAEA,OAAAD,EAAA,IAAAJ,EAAA,CAAA,GAAAJ,CAAA,CAAA,CACF","x_google_ignoreList":[0]}

View File

@@ -1,32 +0,0 @@
import{V as i}from"./VegaLite-30eeb950.js";import"./jsx-runtime-94f6e698.js";import"./index-8db94870.js";import"./_commonjsHelpers-042e6b4d.js";import"./Vega-d925c94f.js";import"./index-1fc0ca9a.js";const c={title:"Components/VegaLite",component:i,tags:["autodocs"],argTypes:{data:{description:"Data to be used by Vega Lite. See the Vega Lite docs: https://vega.github.io/vega-lite/docs/data.html."},spec:{description:"Spec to be used by Vega Lite. See the Vega Lite docs: https://vega.github.io/vega-lite/docs/spec.html."}}},e={name:"Chart built with Vega Lite",args:{data:{table:[{y:-.418,x:1850},{y:.923,x:2020}]},spec:{$schema:"https://vega.github.io/schema/vega-lite/v4.json",mark:"bar",data:{name:"table"},encoding:{x:{field:"x",type:"ordinal"},y:{field:"y",type:"quantitative"}}}}};var t,a,n;e.parameters={...e.parameters,docs:{...(t=e.parameters)==null?void 0:t.docs,source:{originalSource:`{
name: 'Chart built with Vega Lite',
args: {
data: {
table: [{
y: -0.418,
x: 1850
}, {
y: 0.923,
x: 2020
}]
},
spec: {
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
mark: 'bar',
data: {
name: 'table'
},
encoding: {
x: {
field: 'x',
type: 'ordinal'
},
y: {
field: 'y',
type: 'quantitative'
}
}
}
}
}`,...(n=(a=e.parameters)==null?void 0:a.docs)==null?void 0:n.source}}};const g=["Primary"];export{e as Primary,g as __namedExportsOrder,c as default};
//# sourceMappingURL=VegaLite.stories-a09b0c5c.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"VegaLite.stories-a09b0c5c.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}

View File

@@ -1,2 +0,0 @@
import{b as W,b,d}from"./index-89936ab1.js";import"./iframe-1eda5ccb.js";import"../sb-preview/runtime.js";import"./index-8db94870.js";import"./_commonjsHelpers-042e6b4d.js";import"./index-d475d2ea.js";import"./isNativeReflectConstruct-099dc9ad.js";import"./index-8ce4a492.js";import"./index-d37d4223.js";import"./index-6e6be2d5.js";import"./index-356e4a49.js";export{W as WithToolTipState,b as WithTooltip,d as WithTooltipPure};
//# sourceMappingURL=WithTooltip-FBT32F6Q-518acee3.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"WithTooltip-FBT32F6Q-518acee3.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}

View File

@@ -1,2 +0,0 @@
var f=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function l(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function a(e){if(e.__esModule)return e;var r=e.default;if(typeof r=="function"){var o=function n(){if(this instanceof n){var t=[null];t.push.apply(t,arguments);var u=Function.bind.apply(r,t);return new u}return r.apply(this,arguments)};o.prototype=r.prototype}else o={};return Object.defineProperty(o,"__esModule",{value:!0}),Object.keys(e).forEach(function(n){var t=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(o,n,t.get?t:{enumerable:!0,get:function(){return e[n]}})}),o}export{a,f as c,l as g};
//# sourceMappingURL=_commonjsHelpers-042e6b4d.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"_commonjsHelpers-042e6b4d.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
import{M as r,a,u as m,w as n}from"./index-1d576ef5.js";import"./index-8db94870.js";import"./_commonjsHelpers-042e6b4d.js";export{r as MDXContext,a as MDXProvider,m as useMDXComponents,n as withMDXComponents};
//# sourceMappingURL=index-186b3228.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index-186b3228.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}

View File

@@ -1,2 +0,0 @@
import{R as o}from"./index-8db94870.js";const u=o.createContext({});function c(t){return e;function e(r){const n=a(r.components);return o.createElement(t,{...r,allComponents:n})}}function a(t){const e=o.useContext(u);return o.useMemo(()=>typeof t=="function"?t(e):{...e,...t},[e,t])}const i={};function f({components:t,children:e,disableParentContext:r}){let n;return r?n=typeof t=="function"?t({}):t||i:n=a(t),o.createElement(u.Provider,{value:n},e)}export{u as M,f as a,a as u,c as w};
//# sourceMappingURL=index-1d576ef5.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index-1d576ef5.js","sources":["../../node_modules/@mdx-js/react/lib/index.js"],"sourcesContent":["/**\n * @typedef {import('react').ReactNode} ReactNode\n * @typedef {import('mdx/types.js').MDXComponents} Components\n *\n * @typedef Props\n * Configuration.\n * @property {Components | MergeComponents | null | undefined} [components]\n * Mapping of names for JSX components to React components.\n * @property {boolean | null | undefined} [disableParentContext=false]\n * Turn off outer component context.\n * @property {ReactNode | null | undefined} [children]\n * Children.\n *\n * @callback MergeComponents\n * Custom merge function.\n * @param {Components} currentComponents\n * Current components from the context.\n * @returns {Components}\n * Merged components.\n */\n\nimport React from 'react'\n\n/**\n * @type {import('react').Context<Components>}\n * @deprecated\n * This export is marked as a legacy feature.\n * That means its no longer recommended for use as it might be removed\n * in a future major release.\n *\n * Please use `useMDXComponents` to get context based components and\n * `MDXProvider` to set context based components instead.\n */\nexport const MDXContext = React.createContext({})\n\n/**\n * @param {import('react').ComponentType<any>} Component\n * @deprecated\n * This export is marked as a legacy feature.\n * That means its no longer recommended for use as it might be removed\n * in a future major release.\n *\n * Please use `useMDXComponents` to get context based components instead.\n */\nexport function withMDXComponents(Component) {\n return boundMDXComponent\n\n /**\n * @param {Record<string, unknown> & {components?: Components | null | undefined}} props\n * @returns {JSX.Element}\n */\n function boundMDXComponent(props) {\n const allComponents = useMDXComponents(props.components)\n return React.createElement(Component, {...props, allComponents})\n }\n}\n\n/**\n * Get current components from the MDX Context.\n *\n * @param {Components | MergeComponents | null | undefined} [components]\n * Additional components to use or a function that takes the current\n * components and filters/merges/changes them.\n * @returns {Components}\n * Current components.\n */\nexport function useMDXComponents(components) {\n const contextComponents = React.useContext(MDXContext)\n\n // Memoize to avoid unnecessary top-level context changes\n return React.useMemo(() => {\n // Custom merge via a function prop\n if (typeof components === 'function') {\n return components(contextComponents)\n }\n\n return {...contextComponents, ...components}\n }, [contextComponents, components])\n}\n\n/** @type {Components} */\nconst emptyObject = {}\n\n/**\n * Provider for MDX context\n *\n * @param {Props} props\n * @returns {JSX.Element}\n */\nexport function MDXProvider({components, children, disableParentContext}) {\n /** @type {Components} */\n let allComponents\n\n if (disableParentContext) {\n allComponents =\n typeof components === 'function'\n ? components({})\n : components || emptyObject\n } else {\n allComponents = useMDXComponents(components)\n }\n\n return React.createElement(\n MDXContext.Provider,\n {value: allComponents},\n children\n )\n}\n"],"names":["MDXContext","React","withMDXComponents","Component","boundMDXComponent","props","allComponents","useMDXComponents","components","contextComponents","emptyObject","MDXProvider","children","disableParentContext"],"mappings":"wCAiCY,MAACA,EAAaC,EAAM,cAAc,EAAE,EAWzC,SAASC,EAAkBC,EAAW,CAC3C,OAAOC,EAMP,SAASA,EAAkBC,EAAO,CAChC,MAAMC,EAAgBC,EAAiBF,EAAM,UAAU,EACvD,OAAOJ,EAAM,cAAcE,EAAW,CAAC,GAAGE,EAAO,cAAAC,CAAa,CAAC,CAChE,CACH,CAWO,SAASC,EAAiBC,EAAY,CAC3C,MAAMC,EAAoBR,EAAM,WAAWD,CAAU,EAGrD,OAAOC,EAAM,QAAQ,IAEf,OAAOO,GAAe,WACjBA,EAAWC,CAAiB,EAG9B,CAAC,GAAGA,EAAmB,GAAGD,CAAU,EAC1C,CAACC,EAAmBD,CAAU,CAAC,CACpC,CAGA,MAAME,EAAc,CAAE,EAQf,SAASC,EAAY,CAAC,WAAAH,EAAY,SAAAI,EAAU,qBAAAC,CAAoB,EAAG,CAExE,IAAIP,EAEJ,OAAIO,EACFP,EACE,OAAOE,GAAe,WAClBA,EAAW,CAAA,CAAE,EACbA,GAAcE,EAEpBJ,EAAgBC,EAAiBC,CAAU,EAGtCP,EAAM,cACXD,EAAW,SACX,CAAC,MAAOM,CAAa,EACrBM,CACD,CACH","x_google_ignoreList":[0]}

Some files were not shown because too many files have changed in this diff Show More