diff --git a/package.json b/package.json index 87ff92b8..3dcfbebe 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "react-native-svg": "> 6.4.1" }, "dependencies": { - "lodash": "^4.17.13", "paths-js": "^0.4.10", "point-in-polygon": "^1.0.1" }, diff --git a/src/AbstractChart.tsx b/src/AbstractChart.tsx index 5facb526..f307ce97 100644 --- a/src/AbstractChart.tsx +++ b/src/AbstractChart.tsx @@ -46,6 +46,10 @@ class AbstractChart< > extends Component { private chartId = nextChartId++; + protected getValidData = (data: number[] = []) => { + return data.filter(value => typeof value === "number" && isFinite(value)); + }; + protected getGradientId = (id: string) => { return `chart-kit-${this.chartId}-${id}`; }; @@ -55,48 +59,70 @@ class AbstractChart< }; calcScaler = (data: number[]) => { + const values = this.getValidData(data); + + if (values.length === 0) { + return 1; + } + if (this.props.fromZero && this.props.fromNumber) { return ( - Math.max(...data, this.props.fromNumber) - Math.min(...data, 0) || 1 + Math.max(...values, this.props.fromNumber) - Math.min(...values, 0) || 1 ); } else if (this.props.fromZero) { - return Math.max(...data, 0) - Math.min(...data, 0) || 1; + return Math.max(...values, 0) - Math.min(...values, 0) || 1; } else if (this.props.fromNumber) { return ( - Math.max(...data, this.props.fromNumber) - - Math.min(...data, this.props.fromNumber) || 1 + Math.max(...values, this.props.fromNumber) - + Math.min(...values, this.props.fromNumber) || 1 ); } else { - return Math.max(...data) - Math.min(...data) || 1; + return Math.max(...values) - Math.min(...values) || 1; } }; calcBaseHeight = (data: number[], height: number) => { - const min = Math.min(...data); - const max = Math.max(...data); + const values = this.getValidData(data); + + if (values.length === 0) { + return height; + } + + const min = Math.min(...values); + const max = Math.max(...values); if (min >= 0 && max >= 0) { return height; } else if (min < 0 && max <= 0) { return 0; } else if (min < 0 && max > 0) { - return (height * max) / this.calcScaler(data); + return (height * max) / this.calcScaler(values); } }; calcHeight = (val: number, data: number[], height: number) => { - const max = Math.max(...data); - const min = Math.min(...data); + if (typeof val !== "number" || !isFinite(val)) { + return 0; + } + + const values = this.getValidData(data); + + if (values.length === 0) { + return 0; + } + + const max = Math.max(...values); + const min = Math.min(...values); if (min < 0 && max > 0) { - return height * (val / this.calcScaler(data)); + return height * (val / this.calcScaler(values)); } else if (min >= 0 && max >= 0) { return this.props.fromZero - ? height * (val / this.calcScaler(data)) - : height * ((val - min) / this.calcScaler(data)); + ? height * (val / this.calcScaler(values)) + : height * ((val - min) / this.calcScaler(values)); } else if (min < 0 && max <= 0) { return this.props.fromZero - ? height * (val / this.calcScaler(data)) - : height * ((val - max) / this.calcScaler(data)); + ? height * (val / this.calcScaler(values)) + : height * ((val - max) / this.calcScaler(values)); } }; @@ -207,6 +233,8 @@ class AbstractChart< formatYLabel = (yLabel: string) => yLabel, verticalLabelsHeightPercentage = DEFAULT_X_LABELS_HEIGHT_PERCENTAGE } = config; + const values = this.getValidData(data); + const labelData = values.length === 0 ? [0] : values; const { yAxisLabel = "", @@ -218,12 +246,12 @@ class AbstractChart< if (count === 1) { yLabel = `${yAxisLabel}${formatYLabel( - data[0].toFixed(decimalPlaces) + labelData[0].toFixed(decimalPlaces) )}${yAxisSuffix}`; } else { const label = this.props.fromZero - ? (this.calcScaler(data) / count) * i + Math.min(...data, 0) - : (this.calcScaler(data) / count) * i + Math.min(...data); + ? (this.calcScaler(labelData) / count) * i + Math.min(...labelData, 0) + : (this.calcScaler(labelData) / count) * i + Math.min(...labelData); yLabel = `${yAxisLabel}${formatYLabel( label.toFixed(decimalPlaces) )}${yAxisSuffix}`; diff --git a/src/HelperTypes.ts b/src/HelperTypes.ts index 0af197fe..85aab4c0 100644 --- a/src/HelperTypes.ts +++ b/src/HelperTypes.ts @@ -24,7 +24,7 @@ export interface Dataset { key?: string | number; /** Stroke Dash Array */ - strokeDashArray?: number[]; + strokeDashArray?: number[] | string; /** Stroke Dash Offset */ strokeDashOffset?: number; diff --git a/src/StackedBarChart.tsx b/src/StackedBarChart.tsx index 301b5693..8d92ac2c 100644 --- a/src/StackedBarChart.tsx +++ b/src/StackedBarChart.tsx @@ -110,9 +110,10 @@ class StackedBarChart extends AbstractChart< fac = 0.7; } const sum = this.props.percentile ? x.reduce((a, b) => a + b, 0) : border; + const safeSum = sum || 1; const barsAreaHeight = height * verticalLabelsHeightPercentage; for (let z = 0; z < x.length; z++) { - h = barsAreaHeight * (x[z] / sum); + h = barsAreaHeight * (x[z] / safeSum); const y = barsAreaHeight - h + st; const xC = (paddingRight + diff --git a/src/contribution-graph/ContributionGraph.tsx b/src/contribution-graph/ContributionGraph.tsx index 54a44b98..84d86885 100644 --- a/src/contribution-graph/ContributionGraph.tsx +++ b/src/contribution-graph/ContributionGraph.tsx @@ -1,4 +1,3 @@ -import _ from "lodash"; import React from "react"; import { View } from "react-native"; import { G, Rect, RectProps, Svg, Text } from "react-native-svg"; @@ -20,6 +19,7 @@ import { ContributionGraphProps, ContributionGraphState } from "."; const SQUARE_SIZE = 20; const MONTH_LABEL_GUTTER_SIZE = 8; const paddingLeft = 32; +const range = (count: number) => Array.from({ length: count }, (_, i) => i); export type ContributionChartValue = { value: number; @@ -322,7 +322,7 @@ class ContributionGraph extends AbstractChart< const [x, y] = this.getTransformForWeek(weekIndex); return ( - {_.range(DAYS_IN_WEEK).map(dayIndex => + {range(DAYS_IN_WEEK).map(dayIndex => this.renderSquare(dayIndex, weekIndex * DAYS_IN_WEEK + dayIndex) )} @@ -330,7 +330,7 @@ class ContributionGraph extends AbstractChart< } renderAllWeeks() { - return _.range(this.getWeekCount()).map(weekIndex => + return range(this.getWeekCount()).map(weekIndex => this.renderWeek(weekIndex) ); } @@ -340,7 +340,7 @@ class ContributionGraph extends AbstractChart< return null; } - const weekRange = _.range(this.getWeekCount() - 1); // don't render for last week, because label will be cut off + const weekRange = range(this.getWeekCount() - 1); // don't render for last week, because label will be cut off return weekRange.map(weekIndex => { const endOfWeek = shiftDate( diff --git a/src/line-chart/LineChart.tsx b/src/line-chart/LineChart.tsx index 55740ceb..547379ce 100644 --- a/src/line-chart/LineChart.tsx +++ b/src/line-chart/LineChart.tsx @@ -237,11 +237,20 @@ class LineChart extends AbstractChart { getDatas = (data: Dataset[]): number[] => { return data.reduce( - (acc, item) => (item.data ? [...acc, ...item.data] : acc), + (acc, item) => + item.data ? [...acc, ...this.getValidData(item.data)] : acc, [] ); }; + getStrokeDashArray = (dataset: Dataset) => { + const { strokeDashArray } = dataset; + + return Array.isArray(strokeDashArray) + ? strokeDashArray.join(",") + : strokeDashArray; + }; + getPropsForDots = (x: any, i: number) => { const { getDotProps, chartConfig } = this.props; @@ -307,6 +316,7 @@ class LineChart extends AbstractChart { getColor: opacity => this.getColor(dataset, opacity) }); }; + const pressProps = { onPressIn, onClick: onPressIn } as any; output.push( { ? getDotColor(x, i) : this.getColor(dataset, 0.9) } - onPressIn={onPressIn} + {...pressProps} {...this.getPropsForDots(x, i)} />, { r="14" fill="#fff" fillOpacity={0} - onPressIn={onPressIn} + {...pressProps} />, {renderDotContent({ x: cx, y: cy, index: i, indexData: x })} @@ -620,9 +630,8 @@ class LineChart extends AbstractChart { const baseHeight = this.calcBaseHeight(datas, height); const xMax = this.getXMaxValues(data); - let lastPoint: string; - data.forEach((dataset, index) => { + let lastPoint: string; const points = dataset.data.map((d, i) => { if (d === null) return lastPoint; const x = (i * (width - paddingRight)) / xMax + paddingRight; @@ -641,7 +650,7 @@ class LineChart extends AbstractChart { fill="none" stroke={this.getColor(dataset, 0.2)} strokeWidth={this.getStrokeWidth(dataset)} - strokeDasharray={dataset.strokeDashArray} + strokeDasharray={this.getStrokeDashArray(dataset)} strokeDashoffset={dataset.strokeDashOffset} /> ); @@ -729,7 +738,7 @@ class LineChart extends AbstractChart { fill="none" stroke={this.getColor(dataset, 0.2)} strokeWidth={this.getStrokeWidth(dataset)} - strokeDasharray={dataset.strokeDashArray} + strokeDasharray={this.getStrokeDashArray(dataset)} strokeDashoffset={dataset.strokeDashOffset} /> ); @@ -839,6 +848,10 @@ class LineChart extends AbstractChart { }; const datas = this.getDatas(data.datasets); + const firstDataset = data.datasets[0] || { data: [] }; + const hasScrollableData = + withScrollableDot && + data.datasets.some(dataset => dataset.data && dataset.data.length > 0); let count = Math.min(...datas) === Math.max(...datas) ? 1 : 4; if (segments) { @@ -903,7 +916,7 @@ class LineChart extends AbstractChart { (withInnerLines ? this.renderVerticalLines({ ...config, - data: data.datasets[0].data, + data: firstDataset.data, paddingTop: paddingTop as number, paddingRight: paddingRight as number }) @@ -955,7 +968,7 @@ class LineChart extends AbstractChart { })} - {withScrollableDot && + {hasScrollableData && this.renderScrollableDot({ ...config, ...chartConfig,