Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare release v0.2.0 #181

Merged
merged 16 commits into from
Sep 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
- **I'm submitting a ...**
[ ] bug report
[ ] feature request
[ ] question about the decisions made in the repository
[ ] question about how to use this project
**I'm submitting a ...**

- **Summary**
- [ ] bug report
- [ ] feature request
- [ ] question about the decisions made in the repository
- [ ] question about how to use this project

- **Link to minimal reproduction**
<!--
please provide a link to a minimal reproduction of the issue e.g. on https://livecodes.io
You may use this as a starting point: https://livecodes.io/?x=id/6sdjrf25i67
-->
**Summary**

- **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.)
**Link to minimal reproduction**

<!--
please provide a link to a minimal reproduction of the issue e.g. on https://livecodes.io
You may use this as a starting point: https://livecodes.io/?x=id/6sdjrf25i67
-->

**Other information**

<!-- (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.) -->
25 changes: 14 additions & 11 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
- **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
**What kind of change does this PR introduce?**

- **What is the current behavior?** (You can also link to an open issue here)
<!-- (Bug fix, feature, docs update, ...) -->

- **What is the new behavior (if this is a feature change)?**
**What is the current behavior?**

- **Issue related to this PR**
<!--
please make sure that these changes have been discussed and approved in an issue.
Otherwise, please do: https://github.com/hatemhosny/racing-bars/issues/new
-->
**What is the new behavior (if this is a feature change)?**

[ ] The changes have been discussed in an issue and the approach has been approved.
**Issue related to this PR**

issue:
<!--
please make sure that these changes have been discussed and approved in an issue.
Otherwise, please do: https://github.com/hatemhosny/racing-bars/issues/new
-->

- **Other information**:
- [ ] The changes have been discussed in an issue and the approach has been approved.

- issue link:

**Other information**:
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Happy linting! 💖
"ticker",
"utils",
"store",
"styles"
"styles",
"types"
]
}
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. See [standa

---

## [v0.2.0](https://github.com/hatemhosny/racing-bars/compare/v0.1.2...v0.2.0) (2024-09-01)

### Bug Fixes

- **data:** fix worker mixing data for multiple charts ([5160018](https://github.com/hatemhosny/racing-bars/commit/5160018b08c8a07683f8865cc9a194fac8cd2adb))
- **types:** fix library type definitions ([929f0af](https://github.com/hatemhosny/racing-bars/commit/929f0af452dacb28f341b47edabad3a0235507af))

### Features

- **data:** auto detect `dataShape` ([5ede0f7](https://github.com/hatemhosny/racing-bars/commit/5ede0f7a02a60d2d66e3ddd7dcca4a4be57423a5))
- **data:** handle empty or invalid data ([544c39d](https://github.com/hatemhosny/racing-bars/commit/544c39d56f5bf6e914f080e5d3c235e5b8455bfd))
- **options:** auto detect `dataType` ([9030de4](https://github.com/hatemhosny/racing-bars/commit/9030de44ce89fb4552aa8508983fb1e4f0570cad))

---

## [v0.1.2](https://github.com/hatemhosny/racing-bars/compare/v0.1.1...v0.1.2) (2024-08-30)

### Bug Fixes
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"serve": "live-server . --open=demo/ --watch=build,demo --cors --quiet",
"watch": "nodemon --watch src -e \"*\" ./scripts/build.js --dev",
"build": "run-s clean build:*",
"build:lib": "node ./scripts/build.js",
"build:ts": "tsc --emitDeclarationOnly --outFile build/racing-bars.d.ts && node ./scripts/patch-types.js",
"build:lib": "node ./scripts/build.js",
"build:ts-website": "cd website && npm run typecheck",
"build:website": "npm run copy:data && cd website && npm run build",
"copy:data": "recursive-copy data website/static/data",
Expand All @@ -42,7 +42,7 @@
"lint:stylelint-website": "stylelint \"website/**/*.{css,scss}\"",
"postinstall": "cd website && npm install",
"reset": "git clean -dfx && git reset --hard && npm i",
"clean": "recursive-delete build && recursive-delete tmp && recursive-delete website/build && recursive-delete website/static/data && recursive-delete website/docs/api",
"clean": "recursive-delete build && recursive-delete tmp && recursive-delete website/build && recursive-delete website/static/data && recursive-delete website/static/lib && recursive-delete website/docs/api",
"start-release": "node ./scripts/start-release.js"
},
"dependencies": {
Expand Down
6 changes: 5 additions & 1 deletion scripts/patch-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ const patchTypes = async () => {
.replace('declare module "index"', 'declare module "racing-bars"')
.replace('from "index"', 'from "racing-bars"')
.replace('declare module "react"', 'declare module "racing-bars/react"')
.replace('declare module "vue"', 'declare module "racing-bars/vue"');
.replace('declare module "vue"', 'declare module "racing-bars/vue"')
.replace(/declare module "lib\//g, 'declare module "racing-bars/lib/')
.replace(/from "lib\//g, 'from "racing-bars/lib/')
.replace(/declare module "shared\//g, 'declare module "racing-bars/shared/')
.replace(/from "shared\//g, 'from "racing-bars/shared/');
fs.writeFile(path.resolve(dtsPath), patched);
};

Expand Down
22 changes: 19 additions & 3 deletions src/lib/load-data.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import { json, csv, tsv, xml } from './d3';
import type { Data, WideData } from './data';
import type { Options, Data, WideData } from './index';

export function loadData(
url: string,
type: 'json' | 'csv' | 'tsv' | 'xml' = 'json',
type: Options['dataType'] = 'auto',
): Promise<Data[]> | Promise<WideData[]> {
const supportedTypes: Array<Exclude<Options['dataType'], 'auto'>> = ['json', 'csv', 'tsv', 'xml'];
const isSupported = (t: any) => supportedTypes.includes(t);

const detectDataType = () => {
const t = type.toLowerCase();
if (isSupported(t)) {
return t;
}
const extension = url.split('.').pop()?.toLowerCase() || '';
if (isSupported(extension)) {
return extension;
}
return 'json';
};

const handleError = () => {
throw new Error(`Failed to load data as ${type.toUpperCase()} from ${url}`);
};
switch (type) {

switch (detectDataType()) {
case 'json':
return json(url).catch(handleError) as Promise<Data[]> | Promise<WideData[]>;
case 'csv':
Expand Down
4 changes: 2 additions & 2 deletions src/lib/options/options.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export interface OptionsAction extends Action {
}

export interface Options {
dataShape: 'long' | 'wide';
dataType: 'json' | 'csv' | 'tsv' | 'xml';
dataShape: 'long' | 'wide' | 'auto';
dataType: 'json' | 'csv' | 'tsv' | 'xml' | 'auto';
dataTransform: null | ((data: Data[] | WideData[]) => Data[] | WideData[]);
fillDateGapsInterval: null | 'year' | 'month' | 'day';
fillDateGapsValue: 'last' | 'interpolate';
Expand Down
4 changes: 2 additions & 2 deletions src/lib/options/options.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import type { Options, OptionsAction } from './options.models';
import { actionTypes } from './options.actions';

export const defaultOptions: Options = {
dataShape: 'long',
dataType: 'json',
dataShape: 'auto',
dataType: 'auto',
dataTransform: null,
fillDateGapsInterval: null,
fillDateGapsValue: 'interpolate',
Expand Down
20 changes: 10 additions & 10 deletions src/lib/utils/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { loadData } from '../load-data';
// eslint-disable-next-line import/no-internal-modules
import workerSrc from '../../../tmp/racing-bars.worker.js';
import { getDates, getNextDate } from './dates';
import { createWorkerFromContent } from './utils';
import { createWorkerFromContent, generateId } from './utils';

const worker = createWorkerFromContent(workerSrc);

Expand All @@ -22,22 +22,22 @@ export async function prepareData(
}
data = dataTransform(await data);
}
const messageId = generateId();
worker.postMessage({
type: 'prepare-data',
data,
options: removeFnOptions(store.getState().options),
baseUrl: location.href,
messageId,
});
const preparedData = await new Promise<Data[]>((resolve) => {
worker.addEventListener(
'message',
(event) => {
if (event.data.type === 'data-prepared') {
resolve(event.data.data);
}
},
{ once: true },
);
const onMessage = (event: MessageEvent) => {
if (event.data.type === 'data-prepared' && event.data.messageId === messageId) {
resolve(event.data.data);
worker.removeEventListener('message', onMessage);
}
};
worker.addEventListener('message', onMessage);
});
storeDataCollections(preparedData, store, changingOptions);
return preparedData;
Expand Down
4 changes: 2 additions & 2 deletions src/lib/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { prepareData } from './prepare-data';
const worker: Worker = self as any as Worker;

worker.addEventListener('message', async (event) => {
const { type, data, options, baseUrl } = event.data;
const { type, data, options, baseUrl, messageId } = event.data;
if (type === 'prepare-data') {
const result = await prepareData(data, options, baseUrl);
worker.postMessage({ type: 'data-prepared', data: result });
worker.postMessage({ type: 'data-prepared', data: result, messageId });
}
});
22 changes: 20 additions & 2 deletions src/lib/worker/prepare-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function prepareData(
baseUrl: string,
): Promise<Data[]> {
return fetchData(data, options.dataType, baseUrl)
.then(handleEmptyData)
.then(filterByDate(options.startDate, options.endDate))
.then(wideDataToLong(options.dataShape))
.then(processFixedOrder(options.fixedOrder))
Expand All @@ -22,7 +23,7 @@ export function prepareData(

function fetchData(
data: Data[] | WideData[] | Promise<Data[]> | Promise<WideData[]> | string,
dataType: 'json' | 'csv' | 'tsv' | 'xml',
dataType: Options['dataType'],
baseUrl: string,
) {
if (typeof data === 'string') {
Expand All @@ -43,6 +44,12 @@ function isRelativeUrl(url: string) {
return !url.startsWith('https://') && !url.startsWith('http://') && !url.startsWith('data:');
}

function handleEmptyData(data: Data[] | WideData[]): Data[] | WideData[] {
return !Array.isArray(data) || data.length === 0
? [{ date: getDateString(new Date()), value: 0, name: '' }]
: data;
}

function filterByDate(startDate: string, endDate: string) {
return function (data: Data[] | WideData[]): Data[] | WideData[] {
return data
Expand Down Expand Up @@ -107,9 +114,20 @@ function calculateLastValues(makeCumulative = false) {
};
}

function detectDataShape(data: Data[] | WideData[], dataShape: Options['dataShape']) {
if (dataShape === 'long' || dataShape === 'wide') return dataShape;
const firstRow = data[0];
if ('date' in firstRow && 'name' in firstRow && 'value' in firstRow) {
return 'long';
}
return 'wide';
}

function wideDataToLong(dataShape: Options['dataShape'], nested = false) {
return function (data: WideData[]) {
if (dataShape === 'long') return data as Data[];
if (dataShape === 'long' || detectDataShape(data, dataShape) === 'long') {
return data as Data[];
}

const long = [] as Data[];
data.forEach((row) => {
Expand Down
2 changes: 1 addition & 1 deletion src/package.lib.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "racing-bars",
"version": "0.1.2",
"version": "0.2.0",
"description": "Bar chart race made easy 📶",
"author": "Hatem Hosny",
"license": "MIT",
Expand Down
26 changes: 14 additions & 12 deletions website/docs/documentation/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ race(data, '#race');
#### Load data from URL

Data can be loaded from a URL. The following formats are supported:
`json` (default), `csv`, `tsv` and `xml`.
`json`, `csv`, `tsv` and `xml`.

Data type is automatically detected by file extension, otherwise `json` is assumed.

- Load JSON from URL

Expand Down Expand Up @@ -216,7 +218,7 @@ date,Canada,Egypt,Greece,Panama,Singapore

### Usage

For wide data to be processed, the [`options`](./options.md) object should have the field [`dataShape`](./options.md#datashape) set to `"wide"`
For wide data to be processed, the [`options`](./options.md) object should have the field [`dataShape`](./options.md#datashape) set (or auto-detected) to `"wide"`

- JS array of objects

Expand Down Expand Up @@ -281,70 +283,70 @@ Example for long data with [optional fields](#long-data):
"date": "2017-01-01",
"name": "Egypt",
"value": 96.44,
"icon": "https://www.countryflags.io/eg/flat/64.png",
"icon": "https://flagsapi.com/EG/flat/64.png",
"group": "Africa"
},
{
"date": "2017-01-01",
"name": "Singapore",
"value": 5.61,
"icon": "https://www.countryflags.io/sg/flat/64.png",
"icon": "https://flagsapi.com/SG/flat/64.png",
"group": "Asia"
},
{
"date": "2017-01-01",
"name": "Greece",
"value": 10.75,
"icon": "https://www.countryflags.io/gr/flat/64.png",
"icon": "https://flagsapi.com/GR/flat/64.png",
"group": "Europe"
},
{
"date": "2017-01-01",
"name": "Panama",
"value": 4.11,
"icon": "https://www.countryflags.io/pa/flat/64.png",
"icon": "https://flagsapi.com/PA/flat/64.png",
"group": "North America"
},
{
"date": "2018-01-01",
"name": "Greece",
"value": 10.73,
"icon": "https://www.countryflags.io/gr/flat/64.png",
"icon": "https://flagsapi.com/GR/flat/64.png",
"group": "Europe"
},
{
"date": "2018-01-01",
"name": "Singapore",
"value": 5.64,
"icon": "https://www.countryflags.io/sg/flat/64.png",
"icon": "https://flagsapi.com/SG/flat/64.png",
"group": "Asia"
},
{
"date": "2018-01-01",
"name": "Canada",
"value": 37.06,
"icon": "https://www.countryflags.io/ca/flat/64.png",
"icon": "https://flagsapi.com/CA/flat/64.png",
"group": "North America"
},
{
"date": "2018-01-01",
"name": "Egypt",
"value": 98.42,
"icon": "https://www.countryflags.io/eg/flat/64.png",
"icon": "https://flagsapi.com/EG/flat/64.png",
"group": "Africa"
},
{
"date": "2017-01-01",
"name": "Canada",
"value": 36.54,
"icon": "https://www.countryflags.io/ca/flat/64.png",
"icon": "https://flagsapi.com/CA/flat/64.png",
"group": "North America"
},
{
"date": "2018-01-01",
"name": "Panama",
"value": 4.18,
"icon": "https://www.countryflags.io/pa/flat/64.png",
"icon": "https://flagsapi.com/PA/flat/64.png",
"group": "North America"
}
]
Expand Down
Loading
Loading