Skip to content

Commit 5b5335e

Browse files
authored
Merge pull request #763 from indiespirit/codex/easy-win-issue-fixes
[codex] Fix chart edge case rendering
2 parents e2a7231 + 0a7baf5 commit 5b5335e

6 files changed

Lines changed: 75 additions & 34 deletions

File tree

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
"react-native-svg": "> 6.4.1"
5454
},
5555
"dependencies": {
56-
"lodash": "^4.17.13",
5756
"paths-js": "^0.4.10",
5857
"point-in-polygon": "^1.0.1"
5958
},

src/AbstractChart.tsx

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ class AbstractChart<
4646
> extends Component<AbstractChartProps & IProps, AbstractChartState & IState> {
4747
private chartId = nextChartId++;
4848

49+
protected getValidData = (data: number[] = []) => {
50+
return data.filter(value => typeof value === "number" && isFinite(value));
51+
};
52+
4953
protected getGradientId = (id: string) => {
5054
return `chart-kit-${this.chartId}-${id}`;
5155
};
@@ -55,48 +59,70 @@ class AbstractChart<
5559
};
5660

5761
calcScaler = (data: number[]) => {
62+
const values = this.getValidData(data);
63+
64+
if (values.length === 0) {
65+
return 1;
66+
}
67+
5868
if (this.props.fromZero && this.props.fromNumber) {
5969
return (
60-
Math.max(...data, this.props.fromNumber) - Math.min(...data, 0) || 1
70+
Math.max(...values, this.props.fromNumber) - Math.min(...values, 0) || 1
6171
);
6272
} else if (this.props.fromZero) {
63-
return Math.max(...data, 0) - Math.min(...data, 0) || 1;
73+
return Math.max(...values, 0) - Math.min(...values, 0) || 1;
6474
} else if (this.props.fromNumber) {
6575
return (
66-
Math.max(...data, this.props.fromNumber) -
67-
Math.min(...data, this.props.fromNumber) || 1
76+
Math.max(...values, this.props.fromNumber) -
77+
Math.min(...values, this.props.fromNumber) || 1
6878
);
6979
} else {
70-
return Math.max(...data) - Math.min(...data) || 1;
80+
return Math.max(...values) - Math.min(...values) || 1;
7181
}
7282
};
7383

7484
calcBaseHeight = (data: number[], height: number) => {
75-
const min = Math.min(...data);
76-
const max = Math.max(...data);
85+
const values = this.getValidData(data);
86+
87+
if (values.length === 0) {
88+
return height;
89+
}
90+
91+
const min = Math.min(...values);
92+
const max = Math.max(...values);
7793
if (min >= 0 && max >= 0) {
7894
return height;
7995
} else if (min < 0 && max <= 0) {
8096
return 0;
8197
} else if (min < 0 && max > 0) {
82-
return (height * max) / this.calcScaler(data);
98+
return (height * max) / this.calcScaler(values);
8399
}
84100
};
85101

86102
calcHeight = (val: number, data: number[], height: number) => {
87-
const max = Math.max(...data);
88-
const min = Math.min(...data);
103+
if (typeof val !== "number" || !isFinite(val)) {
104+
return 0;
105+
}
106+
107+
const values = this.getValidData(data);
108+
109+
if (values.length === 0) {
110+
return 0;
111+
}
112+
113+
const max = Math.max(...values);
114+
const min = Math.min(...values);
89115

90116
if (min < 0 && max > 0) {
91-
return height * (val / this.calcScaler(data));
117+
return height * (val / this.calcScaler(values));
92118
} else if (min >= 0 && max >= 0) {
93119
return this.props.fromZero
94-
? height * (val / this.calcScaler(data))
95-
: height * ((val - min) / this.calcScaler(data));
120+
? height * (val / this.calcScaler(values))
121+
: height * ((val - min) / this.calcScaler(values));
96122
} else if (min < 0 && max <= 0) {
97123
return this.props.fromZero
98-
? height * (val / this.calcScaler(data))
99-
: height * ((val - max) / this.calcScaler(data));
124+
? height * (val / this.calcScaler(values))
125+
: height * ((val - max) / this.calcScaler(values));
100126
}
101127
};
102128

@@ -207,6 +233,8 @@ class AbstractChart<
207233
formatYLabel = (yLabel: string) => yLabel,
208234
verticalLabelsHeightPercentage = DEFAULT_X_LABELS_HEIGHT_PERCENTAGE
209235
} = config;
236+
const values = this.getValidData(data);
237+
const labelData = values.length === 0 ? [0] : values;
210238

211239
const {
212240
yAxisLabel = "",
@@ -218,12 +246,12 @@ class AbstractChart<
218246

219247
if (count === 1) {
220248
yLabel = `${yAxisLabel}${formatYLabel(
221-
data[0].toFixed(decimalPlaces)
249+
labelData[0].toFixed(decimalPlaces)
222250
)}${yAxisSuffix}`;
223251
} else {
224252
const label = this.props.fromZero
225-
? (this.calcScaler(data) / count) * i + Math.min(...data, 0)
226-
: (this.calcScaler(data) / count) * i + Math.min(...data);
253+
? (this.calcScaler(labelData) / count) * i + Math.min(...labelData, 0)
254+
: (this.calcScaler(labelData) / count) * i + Math.min(...labelData);
227255
yLabel = `${yAxisLabel}${formatYLabel(
228256
label.toFixed(decimalPlaces)
229257
)}${yAxisSuffix}`;

src/HelperTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export interface Dataset {
2424
key?: string | number;
2525

2626
/** Stroke Dash Array */
27-
strokeDashArray?: number[];
27+
strokeDashArray?: number[] | string;
2828

2929
/** Stroke Dash Offset */
3030
strokeDashOffset?: number;

src/StackedBarChart.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,10 @@ class StackedBarChart extends AbstractChart<
110110
fac = 0.7;
111111
}
112112
const sum = this.props.percentile ? x.reduce((a, b) => a + b, 0) : border;
113+
const safeSum = sum || 1;
113114
const barsAreaHeight = height * verticalLabelsHeightPercentage;
114115
for (let z = 0; z < x.length; z++) {
115-
h = barsAreaHeight * (x[z] / sum);
116+
h = barsAreaHeight * (x[z] / safeSum);
116117
const y = barsAreaHeight - h + st;
117118
const xC =
118119
(paddingRight +

src/contribution-graph/ContributionGraph.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import _ from "lodash";
21
import React from "react";
32
import { View } from "react-native";
43
import { G, Rect, RectProps, Svg, Text } from "react-native-svg";
@@ -20,6 +19,7 @@ import { ContributionGraphProps, ContributionGraphState } from ".";
2019
const SQUARE_SIZE = 20;
2120
const MONTH_LABEL_GUTTER_SIZE = 8;
2221
const paddingLeft = 32;
22+
const range = (count: number) => Array.from({ length: count }, (_, i) => i);
2323

2424
export type ContributionChartValue = {
2525
value: number;
@@ -322,15 +322,15 @@ class ContributionGraph extends AbstractChart<
322322
const [x, y] = this.getTransformForWeek(weekIndex);
323323
return (
324324
<G key={weekIndex} x={x} y={y}>
325-
{_.range(DAYS_IN_WEEK).map(dayIndex =>
325+
{range(DAYS_IN_WEEK).map(dayIndex =>
326326
this.renderSquare(dayIndex, weekIndex * DAYS_IN_WEEK + dayIndex)
327327
)}
328328
</G>
329329
);
330330
}
331331

332332
renderAllWeeks() {
333-
return _.range(this.getWeekCount()).map(weekIndex =>
333+
return range(this.getWeekCount()).map(weekIndex =>
334334
this.renderWeek(weekIndex)
335335
);
336336
}
@@ -340,7 +340,7 @@ class ContributionGraph extends AbstractChart<
340340
return null;
341341
}
342342

343-
const weekRange = _.range(this.getWeekCount() - 1); // don't render for last week, because label will be cut off
343+
const weekRange = range(this.getWeekCount() - 1); // don't render for last week, because label will be cut off
344344

345345
return weekRange.map(weekIndex => {
346346
const endOfWeek = shiftDate(

src/line-chart/LineChart.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,20 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
237237

238238
getDatas = (data: Dataset[]): number[] => {
239239
return data.reduce(
240-
(acc, item) => (item.data ? [...acc, ...item.data] : acc),
240+
(acc, item) =>
241+
item.data ? [...acc, ...this.getValidData(item.data)] : acc,
241242
[]
242243
);
243244
};
244245

246+
getStrokeDashArray = (dataset: Dataset) => {
247+
const { strokeDashArray } = dataset;
248+
249+
return Array.isArray(strokeDashArray)
250+
? strokeDashArray.join(",")
251+
: strokeDashArray;
252+
};
253+
245254
getPropsForDots = (x: any, i: number) => {
246255
const { getDotProps, chartConfig } = this.props;
247256

@@ -307,6 +316,7 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
307316
getColor: opacity => this.getColor(dataset, opacity)
308317
});
309318
};
319+
const pressProps = { onPressIn, onClick: onPressIn } as any;
310320

311321
output.push(
312322
<Circle
@@ -318,7 +328,7 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
318328
? getDotColor(x, i)
319329
: this.getColor(dataset, 0.9)
320330
}
321-
onPressIn={onPressIn}
331+
{...pressProps}
322332
{...this.getPropsForDots(x, i)}
323333
/>,
324334
<Circle
@@ -328,7 +338,7 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
328338
r="14"
329339
fill="#fff"
330340
fillOpacity={0}
331-
onPressIn={onPressIn}
341+
{...pressProps}
332342
/>,
333343
<React.Fragment key={`dot-content-${datasetIndex}-${i}`}>
334344
{renderDotContent({ x: cx, y: cy, index: i, indexData: x })}
@@ -620,9 +630,8 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
620630
const baseHeight = this.calcBaseHeight(datas, height);
621631
const xMax = this.getXMaxValues(data);
622632

623-
let lastPoint: string;
624-
625633
data.forEach((dataset, index) => {
634+
let lastPoint: string;
626635
const points = dataset.data.map((d, i) => {
627636
if (d === null) return lastPoint;
628637
const x = (i * (width - paddingRight)) / xMax + paddingRight;
@@ -641,7 +650,7 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
641650
fill="none"
642651
stroke={this.getColor(dataset, 0.2)}
643652
strokeWidth={this.getStrokeWidth(dataset)}
644-
strokeDasharray={dataset.strokeDashArray}
653+
strokeDasharray={this.getStrokeDashArray(dataset)}
645654
strokeDashoffset={dataset.strokeDashOffset}
646655
/>
647656
);
@@ -729,7 +738,7 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
729738
fill="none"
730739
stroke={this.getColor(dataset, 0.2)}
731740
strokeWidth={this.getStrokeWidth(dataset)}
732-
strokeDasharray={dataset.strokeDashArray}
741+
strokeDasharray={this.getStrokeDashArray(dataset)}
733742
strokeDashoffset={dataset.strokeDashOffset}
734743
/>
735744
);
@@ -839,6 +848,10 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
839848
};
840849

841850
const datas = this.getDatas(data.datasets);
851+
const firstDataset = data.datasets[0] || { data: [] };
852+
const hasScrollableData =
853+
withScrollableDot &&
854+
data.datasets.some(dataset => dataset.data && dataset.data.length > 0);
842855

843856
let count = Math.min(...datas) === Math.max(...datas) ? 1 : 4;
844857
if (segments) {
@@ -903,7 +916,7 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
903916
(withInnerLines
904917
? this.renderVerticalLines({
905918
...config,
906-
data: data.datasets[0].data,
919+
data: firstDataset.data,
907920
paddingTop: paddingTop as number,
908921
paddingRight: paddingRight as number
909922
})
@@ -955,7 +968,7 @@ class LineChart extends AbstractChart<LineChartProps, LineChartState> {
955968
})}
956969
</G>
957970
<G>
958-
{withScrollableDot &&
971+
{hasScrollableData &&
959972
this.renderScrollableDot({
960973
...config,
961974
...chartConfig,

0 commit comments

Comments
 (0)