Skip to content

Commit daa3b55

Browse files
authored
fix: x-scale for linear band charts (#384)
1 parent d8f4531 commit daa3b55

File tree

3 files changed

+84
-53
lines changed

3 files changed

+84
-53
lines changed

.playground/playgroud.tsx

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,42 @@
11
import React, { Fragment } from 'react';
2-
import { Axis, Chart, getAxisId, getSpecId, Position, ScaleType, Settings, BarSeries } from '../src';
2+
import { Axis, Settings, Chart, getAxisId, getSpecId, Position, ScaleType, AreaSeries, CurveType } from '../src';
33

44
export class Playground extends React.Component {
55
render() {
6+
const data = [
7+
{ x: 7.053400039672852, y: 1.019049570549345 },
8+
{ x: 16.8664653595564, y: 1.5285743558240172 },
9+
{ x: 26.67953067943995, y: 0.5095247852746725 },
10+
{ x: 36.4925959993235, y: 0.12998767296647204 },
11+
{ x: 74.95778185805996, y: 0.3718786139686189 },
12+
{ x: 88.40302934524654, y: 0.14487824285108267 },
13+
{ x: 122.9147676270215, y: 0.07890686802154025 },
14+
{ x: 186.28060795710638, y: 0.4344198127360625 },
15+
{ x: 197.79021192408248, y: 0.47910304703632484 },
16+
{ x: 208.22638015747071, y: 0.15180409193531094 },
17+
{ x: 241.16356871580467, y: 0.0778711327650822 },
18+
{ x: 305.3722147500643, y: 0.05038552439310782 },
19+
{ x: 404.60706563679923, y: 0.04950569918337908 },
20+
{ x: 505.60553818596964, y: 0.010256529428346779 },
21+
{ x: 993.0998738606771, y: 0.06490505477669992 },
22+
{ x: 1070.1354763603908, y: 0 },
23+
];
624
return (
725
<Fragment>
826
<div className="chart">
927
<Chart>
10-
<Settings
11-
showLegend={true}
12-
theme={{
13-
axes: {
14-
gridLineStyle: {
15-
horizontal: {
16-
stroke: 'red',
17-
strokeWidth: 0.5,
18-
opacity: 1,
19-
dash: [0, 0],
20-
},
21-
vertical: {
22-
stroke: 'blue',
23-
strokeWidth: 0.5,
24-
opacity: 1,
25-
dash: [4, 4],
26-
},
27-
},
28-
},
29-
}}
30-
/>
31-
<Axis
32-
id={getAxisId('y')}
33-
position={Position.Left}
34-
domain={{
35-
min: 50,
36-
max: 250,
37-
}}
38-
showGridLines
39-
/>
40-
<Axis showGridLines id={getAxisId('x')} position={Position.Bottom} />
41-
<BarSeries
42-
id={getSpecId('bar')}
43-
yScaleType={ScaleType.Linear}
28+
<Settings />
29+
<Axis id={getAxisId('bottom')} position={Position.Bottom} tickFormat={(d) => d.toFixed(0)} ticks={5} />
30+
<Axis id={getAxisId('left')} position={Position.Left} tickFormat={(d) => d.toFixed(1)} hide={true} />
31+
<AreaSeries
32+
id={getSpecId('aaa')}
33+
name={'aaa'}
4434
xScaleType={ScaleType.Linear}
45-
xAccessor={0}
46-
yAccessors={[1]}
47-
data={[[0, 100], [1, 50], [3, 400], [4, 250], [5, 235]]}
35+
yScaleType={ScaleType.Linear}
36+
xAccessor="x"
37+
yAccessors={['y']}
38+
data={data}
39+
curve={CurveType.CURVE_STEP_AFTER}
4840
/>
4941
</Chart>
5042
</div>

src/utils/scales/scale_continuous.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,22 @@ describe('Scale Continuous', () => {
162162
});
163163
});
164164

165+
describe('xScale values with minInterval and bandwidth', () => {
166+
const domain = [7.053400039672852, 1070.1354763603908];
167+
168+
it('should return nice ticks when minInterval & bandwidth are 0', () => {
169+
const scale = new ScaleContinuous(
170+
{
171+
type: ScaleType.Linear,
172+
domain,
173+
range: [0, 100],
174+
},
175+
{ minInterval: 0, bandwidth: 0 },
176+
);
177+
expect(scale.ticks()).toEqual([100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]);
178+
});
179+
});
180+
165181
describe('time ticks', () => {
166182
const timezonesToTest = ['Asia/Tokyo', 'Europe/Berlin', 'UTC', 'America/New_York', 'America/Los_Angeles'];
167183

src/utils/scales/scale_continuous.ts

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
11
import { bisectLeft } from 'd3-array';
2-
import { scaleLinear, scaleLog, scaleSqrt, scaleUtc } from 'd3-scale';
2+
import {
3+
scaleLinear,
4+
scaleLog,
5+
scaleSqrt,
6+
scaleUtc,
7+
ScaleLinear,
8+
ScaleLogarithmic,
9+
ScalePower,
10+
ScaleTime,
11+
} from 'd3-scale';
312
import { DateTime } from 'luxon';
13+
414
import { clamp, mergePartial } from '../commons';
515
import { ScaleContinuousType, ScaleType, Scale } from './scales';
616

17+
/**
18+
* d3 scales excluding time scale
19+
*/
20+
type D3ScaleNonTime = ScaleLinear<any, any> | ScaleLogarithmic<any, any> | ScalePower<any, any>;
21+
22+
/**
23+
* All possible d3 scales
24+
*/
25+
type D3Scale = D3ScaleNonTime | ScaleTime<any, any>;
26+
727
const SCALES = {
828
[ScaleType.Linear]: scaleLinear,
929
[ScaleType.Log]: scaleLog,
@@ -129,7 +149,7 @@ export class ScaleContinuous implements Scale {
129149
readonly timeZone: string;
130150
readonly barsPadding: number;
131151
readonly isSingleValueHistogram: boolean;
132-
private readonly d3Scale: any;
152+
private readonly d3Scale: D3Scale;
133153

134154
constructor(scaleData: ScaleData, options?: Partial<ScaleOptions>) {
135155
const { type, domain, range } = scaleData;
@@ -144,13 +164,10 @@ export class ScaleContinuous implements Scale {
144164
} = mergePartial(defaultScaleOptions, options);
145165

146166
this.d3Scale = SCALES[type]();
147-
if (type === ScaleType.Log) {
148-
this.domain = limitLogScaleDomain(domain);
149-
this.d3Scale.domain(this.domain);
150-
} else {
151-
this.domain = domain;
152-
this.d3Scale.domain(domain);
153-
}
167+
const cleanDomain = type === ScaleType.Log ? limitLogScaleDomain(domain) : domain;
168+
this.domain = cleanDomain;
169+
this.d3Scale.domain(cleanDomain);
170+
154171
const safeBarPadding = clamp(barsPadding, 0, 1);
155172
this.barsPadding = safeBarPadding;
156173
this.bandwidth = bandwidth * (1 - safeBarPadding);
@@ -182,13 +199,18 @@ export class ScaleContinuous implements Scale {
182199
return currentDateTime.minus({ minutes: currentOffset }).toMillis();
183200
});
184201
} else {
185-
if (this.minInterval > 0) {
202+
/**
203+
* This case is for the xScale (minInterval is > 0) when we want to show bars (bandwidth > 0)
204+
*
205+
* We want to avoid displaying inner ticks between bars in a bar chart when using linear x scale
206+
*/
207+
if (minInterval > 0 && bandwidth > 0) {
186208
const intervalCount = Math.floor((this.domain[1] - this.domain[0]) / this.minInterval);
187-
this.tickValues = new Array(intervalCount + 1).fill(0).map((d, i) => {
209+
this.tickValues = new Array(intervalCount + 1).fill(0).map((_, i) => {
188210
return this.domain[0] + i * this.minInterval;
189211
});
190212
} else {
191-
this.tickValues = this.d3Scale.ticks(ticks);
213+
this.tickValues = (this.d3Scale as D3ScaleNonTime).ticks(ticks);
192214
}
193215
}
194216
}
@@ -205,12 +227,13 @@ export class ScaleContinuous implements Scale {
205227
ticks() {
206228
return this.tickValues;
207229
}
208-
invert(value: number) {
230+
invert(value: number): number {
209231
let invertedValue = this.d3Scale.invert(value);
210232
if (this.type === ScaleType.Time) {
211-
invertedValue = DateTime.fromJSDate(invertedValue).toMillis();
233+
invertedValue = DateTime.fromJSDate(invertedValue as Date).toMillis();
212234
}
213-
return invertedValue;
235+
236+
return invertedValue as number;
214237
}
215238
invertWithStep(
216239
value: number,

0 commit comments

Comments
 (0)