Skip to content

Commit

Permalink
Merge pull request #197 from hatemhosny/value-decimals
Browse files Browse the repository at this point in the history
Value decimals
  • Loading branch information
hatemhosny authored Sep 7, 2024
2 parents b01eeac + ae94970 commit 83c625d
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 56 deletions.
42 changes: 42 additions & 0 deletions src/lib/options/__tests__/validate-options.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,45 @@ test('Tests to validate fillDateGapsValue option', () => {
const invalidOpts = validateOptions(options);
expect(invalidOpts.fillDateGapsValue).toBeUndefined();
});

test('Tests to validate valueDecimals option', () => {
// Valid options
const options: any = { valueDecimals: 'preserve' };
const validatedOpts = validateOptions(options);
expect(validatedOpts.valueDecimals).toBe('preserve');

// Valid options
const options2: any = { valueDecimals: 2 };
const validatedOpts2 = validateOptions(options2);
expect(validatedOpts2.valueDecimals).toBe(2);

// Invalid options
options.valueDecimals = 'invalid';
const invalidOpts = validateOptions(options);
expect(invalidOpts.valueDecimals).toBeUndefined();
});

test('Tests to validate colorMap option', () => {
// Valid options
const options: any = { colorMap: ['blue', 'red'] };
const validatedOpts = validateOptions(options);
expect(validatedOpts.colorMap).toStrictEqual(['blue', 'red']);

// Valid options
const options2: any = {
colorMap: {
India: 'orange',
'United States': 'blue',
},
};
const validatedOpts2 = validateOptions(options2);
expect(validatedOpts2.colorMap).toStrictEqual({
India: 'orange',
'United States': 'blue',
});

// Invalid options
options.colorMap = 'invalid';
const invalidOpts = validateOptions(options);
expect(invalidOpts.colorMap).toBeUndefined();
});
1 change: 1 addition & 0 deletions src/lib/options/options.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface Options {
dataShape: 'long' | 'wide' | 'auto';
dataType: 'json' | 'csv' | 'tsv' | 'xml' | 'auto';
dataTransform: null | ((data: Data[] | WideData[]) => Data[] | WideData[]);
valueDecimals: 'preserve' | number;
fillDateGapsInterval: null | 'year' | 'month' | 'day';
fillDateGapsValue: 'last' | 'interpolate';
labelsPosition: 'inside' | 'outside' | 'none';
Expand Down
1 change: 1 addition & 0 deletions src/lib/options/options.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const defaultOptions: Options = {
dataShape: 'auto',
dataType: 'auto',
dataTransform: null,
valueDecimals: 'preserve',
fillDateGapsInterval: null,
fillDateGapsValue: 'interpolate',
makeCumulative: false,
Expand Down
13 changes: 11 additions & 2 deletions src/lib/options/validate-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function validateOptions(options: Partial<Options>): Partial<Options> {
// Validate number options
numberOpts.forEach((opt) => {
if (!is(options[opt], 'number')) return;
newOptions[opt] = options[opt];
newOptions[opt] = Number(options[opt]);
});

// Validate string options
Expand Down Expand Up @@ -117,6 +117,13 @@ export function validateOptions(options: Partial<Options>): Partial<Options> {
newOptions.fillDateGapsValue = options.fillDateGapsValue;
}

if (options.valueDecimals === 'preserve') {
newOptions.valueDecimals = 'preserve';
}
if (is(options.valueDecimals, 'number')) {
newOptions.valueDecimals = Number(options.valueDecimals);
}

// Validate array of options
if (is(options.fixedOrder, 'array', 'string')) {
newOptions.fixedOrder = options.fixedOrder;
Expand All @@ -127,7 +134,9 @@ export function validateOptions(options: Partial<Options>): Partial<Options> {
newOptions.dataTransform = options.dataTransform;
}

validateColorMap(options.colorMap);
if (validateColorMap(options.colorMap)) {
newOptions.colorMap = options.colorMap;
}

return newOptions;
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/race.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export async function race(
'startDate',
'endDate',
'fixedOrder',
'makeCumulative',
];
let dataOptionsChanged = false;
dataOptions.forEach((key) => {
Expand Down
19 changes: 14 additions & 5 deletions src/lib/renderer/render-frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as d3 from '../d3';

import type { Data } from '../data';
import type { Store } from '../store';
import { getDateSlice, safeName, getColor, getIconID, getText } from '../utils';
import { getDateSlice, safeName, getColor, getIconID, getText, countDecimals } from '../utils';
import type { RenderOptions } from './render-options';
import { selectFn, highlightFn, halo } from './helpers';
import { updateControls } from './controls';
Expand Down Expand Up @@ -50,6 +50,7 @@ export function renderFrame(data: Data[], store: Store, renderOptions: RenderOpt
const currentDate = store.getState().ticker.currentDate;
const CompleteDateSlice = getDateSlice(currentDate, data, store);
const dateSlice = CompleteDateSlice.slice(0, topN);
const valueDecimals = store.getState().options.valueDecimals;

if (showGroups) {
svg
Expand Down Expand Up @@ -154,7 +155,11 @@ export function renderFrame(data: Data[], store: Store, renderOptions: RenderOpt
.attr('class', 'valueLabel')
.attr('x', (d: Data) => x(d.value) + 5)
.attr('y', () => y(topN + 1) + marginBottom + 5)
.text((d: Data) => d3.format(',.0f')(d.lastValue as number))
.text((d: Data) =>
valueDecimals === 'preserve'
? d.lastValue
: d3.format(`,.${countDecimals(d.lastValue ?? d.value)}f`)(d.lastValue as number),
)
.transition()
.duration(tickDuration)
.ease(d3.easeLinear)
Expand All @@ -168,10 +173,14 @@ export function renderFrame(data: Data[], store: Store, renderOptions: RenderOpt
.attr('x', (d: Data) => x(d.value) + 5)
.attr('y', (d: Data) => barY(d) + barHalfHeight)
.tween('text', function (d: Data) {
const lastValue = sameDate ? d.value : (d.lastValue as number);
const i = d3.interpolateRound(lastValue, d.value);
const lastValue = Number(sameDate ? d.value : (d.lastValue as number)) || 0;
const i = d3.interpolate(lastValue, d.value);
return function (t: number) {
this.textContent = d3.format(',')(i(t));
const decimals =
valueDecimals === 'preserve'
? Math.max(countDecimals(d.value), countDecimals(lastValue))
: valueDecimals;
this.textContent = d3.format(`,.${decimals || 0}f`)(i(t));
};
});

Expand Down
9 changes: 7 additions & 2 deletions src/lib/renderer/render-initial-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as d3 from '../d3';

import type { Store } from '../store';
import type { Data } from '../data';
import { getDateSlice, getText, getColor, safeName, getIconID } from '../utils';
import { getDateSlice, getText, getColor, safeName, getIconID, countDecimals } from '../utils';
import type { RenderOptions } from './render-options';
import { calculateDimensions } from './calculate-dimensions';
import { renderHeader } from './render-header';
Expand All @@ -20,6 +20,7 @@ export function renderInitialView(data: Data[], store: Store, renderOptions: Ren
const CompleteDateSlice = getDateSlice(currentDate, data, store);
const dateSlice = CompleteDateSlice.slice(0, topN);
const lastDateIndex = dates.indexOf(currentDate) > 0 ? dates.indexOf(currentDate) - 1 : 0;
const valueDecimals = store.getState().options.valueDecimals;
renderOptions.lastDate = dates[lastDateIndex];

if (!root || dateSlice.length === 0) return;
Expand Down Expand Up @@ -115,7 +116,11 @@ export function renderInitialView(data: Data[], store: Store, renderOptions: Ren
.attr('class', 'valueLabel')
.attr('x', (d: Data) => x(d.value) + 5)
.attr('y', (d: Data) => barY(d) + barHalfHeight)
.text((d: Data) => d3.format(',.0f')(d.lastValue as number));
.text((d: Data) =>
valueDecimals === 'preserve'
? d.lastValue
: d3.format(`,.${countDecimals(d.lastValue ?? d.value)}f`)(d.lastValue as number),
);

if (showIcons) {
const defs = (renderOptions.defs = svg.append('svg:defs'));
Expand Down
2 changes: 2 additions & 0 deletions src/lib/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ function toNumber(s: string | number) {
return +nums;
}

export const countDecimals = (n: number) => String(n).split('.')[1]?.length || 0;

export function random(InputSeed: string | number) {
const seed = toNumber(InputSeed);
const x = Math.sin(seed) * 10000;
Expand Down
1 change: 1 addition & 0 deletions src/vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const props = {
dataTransform: [Function, Object], // Object for null
fillDateGapsInterval: [String, Object], // Object for null
fillDateGapsValue: String,
valueDecimals: Number,
makeCumulative: String, // Boolean
title: String,
subTitle: String,
Expand Down
14 changes: 14 additions & 0 deletions website/docs/documentation/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,20 @@ const options = {
};
```

### valueDecimals

Number of decimal places to display for values. By default (`"preserve"`), the values in the data are used as is.

- Type: `number | "preserve"`
- Default: `"preserve"`
- Example: [view in gallery](../gallery/value-decimals.md)

```js
const options = {
valueDecimals: 0,
};
```

### width

Specifies the width of the chart.
Expand Down
53 changes: 10 additions & 43 deletions website/docs/gallery/_gallery-demos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,62 +254,22 @@ export const fillDateGapsNull: ChartProps = {
label: 'Filling Date Gaps: null (default)',
dataUrl: '/data/population.csv',
fillDateGapsInterval: null,
dataTransform: function multiplyBy1000(data) {
return data.map((d) => ({
...d,
value: Number(d.value) * 1000,
}));
},
dynamicProps: {
dataTransform: `function multiplyBy1000(data) {
return data.map((d) => ({
...d,
value: Number(d.value) * 1000,
}));
}`,
},
};

export const fillDateGapsMonthInterpolate: ChartProps = {
label: 'Filling Date Gaps: month - interpolate',
dataUrl: '/data/population.csv',
fillDateGapsInterval: 'month',
fillDateGapsValue: 'interpolate',
dataTransform: function multiplyBy1000(data) {
return data.map((d) => ({
...d,
value: Number(d.value) * 1000,
}));
},
dynamicProps: {
dataTransform: `function multiplyBy1000(data) {
return data.map((d) => ({
...d,
value: Number(d.value) * 1000,
}));
}`,
},
valueDecimals: 2,
};

export const fillDateGapsMonthLast: ChartProps = {
label: 'Filling Date Gaps: month - last',
dataUrl: '/data/population.csv',
fillDateGapsInterval: 'month',
fillDateGapsValue: 'last',
dataTransform: function multiplyBy1000(data) {
return data.map((d) => ({
...d,
value: Number(d.value) * 1000,
}));
},
dynamicProps: {
dataTransform: `function multiplyBy1000(data) {
return data.map((d) => ({
...d,
value: Number(d.value) * 1000,
}));
}`,
},
valueDecimals: 2,
};

export const fixedOrder: ChartProps = {
Expand Down Expand Up @@ -369,7 +329,7 @@ export const labelsPosition: ChartProps = {
};

export const labelsPositionNone: ChartProps = {
label: 'Labels Position',
label: 'Hidden Labels',
dataUrl: '/data/population.csv',
dataTransform: (data) =>
data.map((d) => ({
Expand Down Expand Up @@ -470,3 +430,10 @@ export const topN: ChartProps = {
title: 'World Population',
topN: 5,
};

export const valueDecimals: ChartProps = {
label: 'Value Decimals',
dataUrl: '/data/population.csv',
title: 'World Population',
valueDecimals: 3,
};
19 changes: 19 additions & 0 deletions website/docs/gallery/value-decimals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Value Decimals
hide_table_of_contents: true
---

import RacingBars from '../../src/components/RacingBars';
import { valueDecimals } from './\_gallery-demos.ts';

A demo for using [`valueDecimals`](../documentation/options.md#valuedecimals) to control the number of decimal places to display for values.

<!--truncate-->

### Chart

<div className="gallery">
<RacingBars
{...valueDecimals}
/>
</div>
5 changes: 5 additions & 0 deletions website/docs/guides/colors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Colors
---

TODO...
Loading

0 comments on commit 83c625d

Please sign in to comment.