From 58f9f9a9d8f960ff97b9d997f5120c420176b8f0 Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Mon, 8 Jun 2020 21:01:46 -0400 Subject: [PATCH 01/13] Fix linear gradient issue due to `react-native-svg` lib update --- src/abstract-chart.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/abstract-chart.js b/src/abstract-chart.js index c5ec6732..945de84d 100644 --- a/src/abstract-chart.js +++ b/src/abstract-chart.js @@ -267,10 +267,11 @@ class AbstractChart extends Component { Date: Mon, 15 Jun 2020 03:16:31 -0400 Subject: [PATCH 02/13] reimplement core chart, support bar-chart for full dynamic sizes The other graphs based on `abstract-chart.js` remain broken. --- index.d.ts | 6 +- src/abstract-chart.js | 166 +++++++++++++++++++----------------------- src/bar-chart.js | 144 ++++++++++++++++++++++-------------- 3 files changed, 168 insertions(+), 148 deletions(-) diff --git a/index.d.ts b/index.d.ts index 62419077..13cb3550 100644 --- a/index.d.ts +++ b/index.d.ts @@ -230,7 +230,8 @@ export interface BarChartProps { data: ChartData; width: number; height: number; - fromZero?: boolean; + defMax?: number; + defMin?: number; withInnerLines?: boolean; yAxisLabel: string; yAxisSuffix: string; @@ -244,6 +245,8 @@ export interface BarChartProps { segments?: number; showBarTops?: boolean; showValuesOnTopOfBars?: boolean; + withHorizontalLabels?: boolean; + withVerticalLabels?: boolean; } export class BarChart extends React.Component {} @@ -306,6 +309,7 @@ export class PieChart extends React.Component {} // ContributionGraph export interface ContributionGraphProps { + style?: ViewStyle; values: Array; endDate: Date; numDays: number; diff --git a/src/abstract-chart.js b/src/abstract-chart.js index c5ec6732..5b8ec08a 100644 --- a/src/abstract-chart.js +++ b/src/abstract-chart.js @@ -3,17 +3,18 @@ import React, { Component } from "react"; import { LinearGradient, Line, Text, Defs, Stop } from "react-native-svg"; class AbstractChart extends Component { + calcScaler = data => { - if (this.props.fromZero) { - return Math.max(...data, 0) - Math.min(...data, 0) || 1; - } else { - return Math.max(...data) - Math.min(...data) || 1; - } + const defMin = this.props.defMin ?? Math.min(...data); + const defMax = this.props.defMax ?? Math.max(...data); + return Math.max(...data, defMin, defMax) - Math.min(...data, defMin, defMax) || 1; }; calcBaseHeight = (data, height) => { - const min = Math.min(...data); - const max = Math.max(...data); + const defMin = this.props.defMin ?? Math.min(...data); + const defMax = this.props.defMax ?? Math.max(...data); + const min = Math.min(...data, defMin, defMax); + const max = Math.max(...data, defMin, defMax); if (min >= 0 && max >= 0) { return height; } else if (min < 0 && max <= 0) { @@ -24,18 +25,16 @@ class AbstractChart extends Component { }; calcHeight = (val, data, height) => { - const max = Math.max(...data); - const min = Math.min(...data); + const defMin = this.props.defMin ?? Math.min(...data); + const defMax = this.props.defMax ?? Math.max(...data); + const max = Math.max(...data, defMin, defMax); + const min = Math.min(...data, defMin, defMax); if (min < 0 && max > 0) { return height * (val / this.calcScaler(data)); } else if (min >= 0 && max >= 0) { - return this.props.fromZero - ? height * (val / this.calcScaler(data)) - : height * ((val - min) / this.calcScaler(data)); + return height * ((val - min) / this.calcScaler(data)); } else if (min < 0 && max <= 0) { - return this.props.fromZero - ? height * (val / this.calcScaler(data)) - : height * ((val - max) / this.calcScaler(data)); + return height * ((val - max) / this.calcScaler(data)); } }; @@ -58,22 +57,27 @@ class AbstractChart extends Component { return { fontSize: 12, fill: labelColor(0.8), - ...propsForLabels + ...propsForLabels, }; } renderHorizontalLines = config => { - const { count, width, height, paddingTop, paddingRight } = config; - const basePosition = height - height / 4; - - return [...new Array(count + 1)].map((_, i) => { - const y = (basePosition / count) * i + paddingTop; + const { count, width, height, gutterTop, horizontalLabelWidth, verticalLabelHeight, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + } = config; + const basePosition = height - verticalLabelHeight - paddingBottom; + const totalLineHeight = basePosition - paddingTop - gutterTop; + const x1 = horizontalLabelWidth + paddingLeft; + const x2 = width - paddingRight; + const lineGap = (count - 1) || 1; //handle divided by zero + return [...new Array(count)].map((_, i) => { + const y = basePosition - totalLineHeight / lineGap * i return ( @@ -81,65 +85,49 @@ class AbstractChart extends Component { }); }; - renderHorizontalLine = config => { - const { width, height, paddingTop, paddingRight } = config; - return ( - - ); - }; - renderHorizontalLabels = config => { const { count, data, height, - paddingTop, - paddingRight, + gutterTop, + horizontalLabelWidth, horizontalLabelRotation = 0, + verticalLabelHeight, decimalPlaces = 2, - formatYLabel = yLabel => yLabel + formatYLabel = yLabel => yLabel, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, } = config; + const { yAxisLabel = "", yAxisSuffix = "", - yLabelsOffset = 12 + yLabelsOffset = 12, + defMin = Math.min(...data), + defMax = Math.max(...data), } = this.props; - return [...Array(count === 1 ? 1 : count + 1).keys()].map((i, _) => { + const basePosition = height - verticalLabelHeight - paddingBottom; + const totalLineHeight = basePosition - paddingTop - gutterTop; + const lineGap = (count - 1) || 1; + + return [...Array(count === 1 ? 1 : count).keys()].map((i, _) => { let yLabel = i * count; - if (count === 1) { - yLabel = `${yAxisLabel}${formatYLabel( - data[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); - yLabel = `${yAxisLabel}${formatYLabel( - label.toFixed(decimalPlaces) - )}${yAxisSuffix}`; - } + const label = this.calcScaler(data) / lineGap * i + Math.min(...data, defMin, defMax); + yLabel = `${yAxisLabel}${formatYLabel( + label.toFixed(decimalPlaces) + )}${yAxisSuffix}`; + + const x = horizontalLabelWidth - yLabelsOffset; + const y = basePosition - totalLineHeight / lineGap * i; - const basePosition = height - height / 4; - const x = paddingRight - yLabelsOffset; - const y = - count === 1 && this.props.fromZero - ? paddingTop + 4 - : (height * 3) / 4 - (basePosition / count) * i + paddingTop; return ( xLabel + formatXLabel = xLabel => xLabel, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, } = config; + const { xAxisLabel = "", xLabelsOffset = 0, @@ -172,16 +163,19 @@ class AbstractChart extends Component { if (stackedBar) { fac = 0.71; } + + const labelWidth = (width - horizontalLabelWidth - paddingRight - paddingLeft) / labels.length; + const midPoint = labelWidth / 2; + + const y = height - paddingBottom - verticalLabelHeight + xLabelsOffset + fontSize*1.5; + return labels.map((label, i) => { if (hidePointsAtIndex.includes(i)) { return null; } - const x = - (((width - paddingRight) / labels.length) * i + - paddingRight + - horizontalOffset) * - fac; - const y = (height * 3) / 4 + paddingTop + fontSize * 2 + xLabelsOffset; + + const x = (paddingLeft + horizontalLabelWidth + labelWidth * i + midPoint + horizontalOffset) * fac; + return ( { - const { data, width, height, paddingTop, paddingRight } = config; + const { data, width, height, gutterTop, horizontalLabelWidth } = config; const { yAxisInterval = 1 } = this.props; return [...new Array(Math.ceil(data.length / yAxisInterval))].map( (_, i) => { @@ -207,15 +201,15 @@ class AbstractChart extends Component { ); @@ -223,20 +217,6 @@ class AbstractChart extends Component { ); }; - renderVerticalLine = config => { - const { height, paddingTop, paddingRight } = config; - return ( - - ); - }; - renderDefs = config => { const { width, @@ -246,9 +226,11 @@ class AbstractChart extends Component { useShadowColorFromDataset, data } = config; + const fromOpacity = config.hasOwnProperty("backgroundGradientFromOpacity") ? config.backgroundGradientFromOpacity : 1.0; + const toOpacity = config.hasOwnProperty("backgroundGradientToOpacity") ? config.backgroundGradientToOpacity : 1.0; @@ -267,10 +249,11 @@ class AbstractChart extends Component { { @@ -11,27 +15,37 @@ class BarChart extends AbstractChart { return barPercentage; }; + barPosSetup = (config) => { + const { data, width, height, gutterTop, horizontalLabelWidth, verticalLabelHeight, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + } = config; + const innerHeight = (height - paddingTop - paddingBottom - verticalLabelHeight - gutterTop); + const baseHeight = this.calcBaseHeight(data, innerHeight); + const labelWidth = (width - horizontalLabelWidth - paddingRight - paddingLeft) / data.length; + const midPoint = labelWidth / 2; + const barWidth = this.props.barWidth * this.getBarPercentage(); + return { innerHeight, baseHeight, labelWidth, midPoint, barWidth }; + } + renderBars = config => { - const { data, width, height, paddingTop, paddingRight, barRadius } = config; - const baseHeight = this.calcBaseHeight(data, height); - return data.map((x, i) => { - const barHeight = this.calcHeight(x, data, height); - const barWidth = 32 * this.getBarPercentage(); + const { data, width, height, gutterTop, horizontalLabelWidth, verticalLabelHeight, barRadius, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + } = config; + + const { innerHeight, baseHeight, labelWidth, midPoint, barWidth } = this.barPosSetup(config); + + return data.map((value, i) => { + const barHeight = this.calcHeight(value, data, innerHeight); + const x = horizontalLabelWidth + paddingLeft + labelWidth * i + midPoint - barWidth/2; + const y = (barHeight > 0 ? baseHeight - barHeight : baseHeight) + gutterTop + paddingTop; return ( 0 ? baseHeight - barHeight : baseHeight) / 4) * 3 + - paddingTop - } + x={x} + y={y} rx={barRadius} width={barWidth} - height={(Math.abs(barHeight) / 4) * 3} + height={Math.abs(barHeight)} fill="url(#fillShadowGradient)" /> ); @@ -39,20 +53,21 @@ class BarChart extends AbstractChart { }; renderBarTops = config => { - const { data, width, height, paddingTop, paddingRight } = config; - const baseHeight = this.calcBaseHeight(data, height); - return data.map((x, i) => { - const barHeight = this.calcHeight(x, data, height); - const barWidth = 32 * this.getBarPercentage(); + const { data, width, height, gutterTop, horizontalLabelWidth, verticalLabelHeight, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + } = config; + + const { innerHeight, baseHeight, labelWidth, midPoint, barWidth } = this.barPosSetup(config); + + return data.map((value, i) => { + const barHeight = this.calcHeight(value, data, innerHeight); + const x = horizontalLabelWidth + paddingLeft + labelWidth * i + midPoint - barWidth/2; + const y = baseHeight - barHeight + gutterTop + paddingTop; return ( { - const { data, width, height, paddingTop, paddingRight } = config; - const baseHeight = this.calcBaseHeight(data, height); - return data.map((x, i) => { - const barHeight = this.calcHeight(x, data, height); - const barWidth = 32 * this.getBarPercentage(); + const { data, width, height, gutterTop, horizontalLabelWidth, verticalLabelHeight, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + } = config; + + const { innerHeight, baseHeight, labelWidth, midPoint, barWidth } = this.barPosSetup(config); + + return data.map((value, i) => { + const barHeight = this.calcHeight(value, data, innerHeight); + const x = horizontalLabelWidth + paddingLeft + labelWidth * i + midPoint; + const y = baseHeight - barHeight + gutterTop + paddingTop - 2; return ( + {this.renderDefs({ ...config, - ...this.props.chartConfig + ...this.props.chartConfig, })} @@ -141,7 +181,6 @@ class BarChart extends AbstractChart { ? this.renderHorizontalLines({ ...config, count: segments, - paddingTop }) : null} @@ -151,8 +190,6 @@ class BarChart extends AbstractChart { ...config, count: segments, data: data.datasets[0].data, - paddingTop, - paddingRight }) : null} @@ -161,9 +198,6 @@ class BarChart extends AbstractChart { ? this.renderVerticalLabels({ ...config, labels: data.labels, - paddingRight, - paddingTop, - horizontalOffset: barWidth * this.getBarPercentage() }) : null} @@ -171,8 +205,6 @@ class BarChart extends AbstractChart { {this.renderBars({ ...config, data: data.datasets[0].data, - paddingTop, - paddingRight })} @@ -180,8 +212,6 @@ class BarChart extends AbstractChart { this.renderValuesOnTopOfBars({ ...config, data: data.datasets[0].data, - paddingTop, - paddingRight })} @@ -189,8 +219,6 @@ class BarChart extends AbstractChart { this.renderBarTops({ ...config, data: data.datasets[0].data, - paddingTop, - paddingRight })} @@ -199,4 +227,8 @@ class BarChart extends AbstractChart { } } +BarChart.defaultProps = { + barWidth: 32, +} + export default BarChart; From a82b7b87eeca3f3affe8bbc9c510afbb62a108c5 Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Mon, 15 Jun 2020 03:39:00 -0400 Subject: [PATCH 03/13] Update README.ME for chartStyle --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 441f4cfa..2cbcb43e 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,17 @@ const chartConfig = { color: (opacity = 1) => `rgba(26, 255, 146, ${opacity})`, strokeWidth: 2, // optional, default 3 barPercentage: 0.5, - useShadowColorFromDataset: false // optional + useShadowColorFromDataset: false, // optional + gutterTop: 10, // optional, default dynamic size: 10% * innerHeight after paddingTop and paddingBottom + horizontalLabelWidth: 30, // optional, default dynamic size:20% * innerHeight after paddingTop and paddingBottom + verticalLabelHeight: 30, // optional, default dynamic size: 15% * innerWidth after paddingLeft and paddingRight + chartStyle: { //optional + borderRadius: 10, //default 0 + paddingTop: 10, //default 0 + paddingBottom: 10, + paddingLeft: 10, + paddingRight: 10, + }, }; ``` @@ -106,6 +116,9 @@ const chartConfig = { | barRadius | Number | Defines the radius of each bar | | propsForBackgroundLines | props | Override styles of the background lines, refer to react-native-svg's Line documentation | | propsForLabels | props | Override styles of the labels, refer to react-native-svg's Text documentation | +| gutterTop | number | Define the gap between highest coordinate and padding | +| horizontalLabelWidth | number | Define the width of horizontal labels | +| verticalLabelHeight | number | Define the height of vertical labels | ## Responsive charts From 7fad3a7abe9c1d4448f95eb880ba47a29103346d Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Tue, 16 Jun 2020 19:37:27 -0400 Subject: [PATCH 04/13] re-invent heatmap to fully dynamy and layout-control --- src/contribution-graph/index.js | 139 +++++++++++++++++++++++++------- 1 file changed, 109 insertions(+), 30 deletions(-) diff --git a/src/contribution-graph/index.js b/src/contribution-graph/index.js index be958343..9d70a3d5 100755 --- a/src/contribution-graph/index.js +++ b/src/contribution-graph/index.js @@ -14,12 +14,9 @@ import { convertToDate } from "./dateHelpers"; -const SQUARE_SIZE = 20; -const MONTH_LABEL_GUTTER_SIZE = 8; -const paddingLeft = 32; - function mapValue(x, in_min, in_max, out_min, out_max) { - return ((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min; + const diff = (in_max - in_min) || 1; //prevent divided by 0 if in_max == in_min + return ((x - in_min) * (out_max - out_min)) / diff + out_min; } class ContributionGraph extends AbstractChart { @@ -47,19 +44,70 @@ class ContributionGraph extends AbstractChart { }); } + setContentLayout(contentWidth, contentHeight) { + const { width, height } = this.props; + const { paddingTop, paddingLeft, paddingRight, paddingBottom, justifyContent, alignItems } = this.props.chartConfig.chartStyle; + var justifyOffset, alignOffset; + + // vertical + switch(justifyContent) { + case "start": justifyOffset = 0 + paddingTop; break; + case "center": justifyOffset = (height - contentHeight) / 2; break; + case "end": justifyOffset = height - contentHeight - paddingBottom; break; + default: justifyOffset = 0; + } + // horizontal + switch(alignItems) { + case "start": alignOffset = 0 + paddingLeft; break; + case "center": alignOffset = (width - contentWidth) / 2; break; + case "end": alignOffset = width - contentWidth - paddingRight; break; + default: alignOffset = 0; + } + + return [justifyOffset, alignOffset]; + } + + mainLayoutSetup() { + const { paddingTop, paddingLeft, paddingRight, paddingBottom } = this.props.chartConfig.chartStyle; + const [x,y] = this.getViewBox(); + this.props.width = this.props.width > paddingLeft + paddingRight + x ? this.props.width : paddingLeft + paddingRight + x; + this.props.height = this.props.height > paddingTop + paddingBottom + y ? this.props.height : paddingTop + paddingBottom + y; + } + + setDynamicSquareSize() { + const { paddingTop, paddingLeft, paddingRight, paddingBottom } = this.props.chartConfig.chartStyle; + const innerHeight = this.props.height - paddingTop - paddingBottom; + const innerWidth = this.props.width - paddingLeft - paddingRight; + const labelOrientedSize = (DAYS_IN_WEEK - 1) * this.props.gutterSize + this.props.month_label_gutter_size; + const WeekOrientedSize = (this.getWeekCount() - 1) * this.props.gutterSize; + + if(this.props.horizontal) { + // + 1 becuase label itself is + const height = (innerHeight - labelOrientedSize) / (DAYS_IN_WEEK + 1); + const width = (innerWidth - WeekOrientedSize) / this.getWeekCount(); + return height < width ? height : width; + }else { + const height = (innerHeight - WeekOrientedSize) / this.getWeekCount(); + // minus extra gutter size and divided by extra 1 respected to getMonthLabelSize() for vertical layout + const width = (innerWidth - labelOrientedSize - this.props.month_label_gutter_size) / (DAYS_IN_WEEK + 2); + return height < width ? height : width; + } + return 0; + } + getSquareSizeWithGutter() { - return (this.props.squareSize || SQUARE_SIZE) + this.props.gutterSize; + return this.props.squareSize + this.props.gutterSize; } getMonthLabelSize() { - let { squareSize = SQUARE_SIZE } = this.props; + let { squareSize } = this.props; if (!this.props.showMonthLabels) { return 0; } if (this.props.horizontal) { - return squareSize + MONTH_LABEL_GUTTER_SIZE; + return squareSize + this.props.month_label_gutter_size; } - return 2 * (squareSize + MONTH_LABEL_GUTTER_SIZE); + return 2 * (squareSize + this.props.month_label_gutter_size); } getStartDate() { @@ -189,16 +237,20 @@ class ContributionGraph extends AbstractChart { getTransformForWeek(weekIndex) { if (this.props.horizontal) { - return [weekIndex * this.getSquareSizeWithGutter(), 50]; + const [justifyOffset, alignOffset] = this.setContentLayout(this.getWidth(), this.getHeight()); + return [weekIndex * this.getSquareSizeWithGutter() + alignOffset, + this.getMonthLabelSize() + justifyOffset]; } - return [10, weekIndex * this.getSquareSizeWithGutter()]; + const [justifyOffset, alignOffset] = this.setContentLayout(this.getHeight(), this.getWidth()); + return [this.getMonthLabelSize() + alignOffset, + weekIndex * this.getSquareSizeWithGutter() + justifyOffset]; } getTransformForMonthLabels() { if (this.props.horizontal) { return null; } - return `${this.getWeekWidth() + MONTH_LABEL_GUTTER_SIZE}, 0`; + return `${this.getWeekWidth() + this.props.month_label_gutter_size}, 0`; } getTransformForAllWeeks() { @@ -210,9 +262,9 @@ class ContributionGraph extends AbstractChart { getViewBox() { if (this.props.horizontal) { - return `${this.getWidth()} ${this.getHeight()}`; + return [this.getWidth(), this.getHeight()]; } - return `${this.getHeight()} ${this.getWidth()}`; + return [this.getHeight(), this.getWidth()]; } getSquareCoordinates(dayIndex) { @@ -223,16 +275,19 @@ class ContributionGraph extends AbstractChart { } getMonthLabelCoordinates(weekIndex) { + const { paddingTop, paddingLeft, paddingRight, paddingBottom, justifyContent, alignItems } = this.props.chartConfig.chartStyle; if (this.props.horizontal) { + const [justifyOffset, alignOffset] = this.setContentLayout(this.getWidth(), this.getHeight()); return [ - weekIndex * this.getSquareSizeWithGutter(), - this.getMonthLabelSize() - MONTH_LABEL_GUTTER_SIZE + weekIndex * this.getSquareSizeWithGutter() + alignOffset, + this.getMonthLabelSize() - this.props.month_label_gutter_size + justifyOffset ]; } const verticalOffset = -2; + const [justifyOffset, alignOffset] = this.setContentLayout(this.getHeight(), this.getWidth()); return [ - 0, - (weekIndex + 1) * this.getSquareSizeWithGutter() + verticalOffset + 0 + alignOffset, + (weekIndex + 1) * this.getSquareSizeWithGutter() + verticalOffset + justifyOffset ]; } @@ -246,20 +301,22 @@ class ContributionGraph extends AbstractChart { } const [x, y] = this.getSquareCoordinates(dayIndex); - const { squareSize = SQUARE_SIZE } = this.props; + const { squareSize } = this.props; return ( { this.handleDayPress(index); }} + onLongPress={() => { + }} {...this.getTooltipDataAttrsForIndex(index)} /> ); @@ -299,6 +356,7 @@ class ContributionGraph extends AbstractChart { } renderMonthLabels() { + const { paddingTop, paddingBottom, paddingLeft, paddingRight } = this.props.chartConfig.chartStyle; if (!this.props.showMonthLabels) { return null; } @@ -309,11 +367,12 @@ class ContributionGraph extends AbstractChart { (weekIndex + 1) * DAYS_IN_WEEK ); const [x, y] = this.getMonthLabelCoordinates(weekIndex); + return endOfWeek.getDate() >= 1 && endOfWeek.getDate() <= DAYS_IN_WEEK ? ( {this.props.getMonthLabel @@ -326,11 +385,30 @@ class ContributionGraph extends AbstractChart { render() { const { style = {} } = this.props; - let { borderRadius = 0 } = style; - if (!borderRadius && this.props.chartConfig.style) { - const stupidXo = this.props.chartConfig.style.borderRadius; - borderRadius = stupidXo; + const defaultChartStyle = { + borderRadius: 0, + paddingTop: 0, + paddingBottom: 0, + paddingRight: 0, + paddingLeft: 0, + justifyContent: 'start', + justifyOffset: 0, + alignItems: 'start', + alignOffset: 0, } + + this.props.chartConfig = { + ...this.props.chartConfig, + chartStyle: { + ...defaultChartStyle, + ...this.props.chartConfig.chartStyle, + }, + } + + // setup dynamic size if user does not provide their own squareSize + this.props.squareSize = this.props.squareSize || this.setDynamicSquareSize() || 20; + this.mainLayoutSetup(); + return ( @@ -342,8 +420,8 @@ class ContributionGraph extends AbstractChart { {this.renderMonthLabels()} @@ -358,12 +436,13 @@ ContributionGraph.defaultProps = { numDays: 200, endDate: new Date(), gutterSize: 1, - squareSize: SQUARE_SIZE, + month_label_gutter_size: 8, + squareSize: 0, horizontal: true, showMonthLabels: true, showOutOfRangeDays: false, accessor: "count", - classForValue: value => (value ? "black" : "#8cc665") + classForValue: value => (value ? "black" : "#8cc665"), }; export default ContributionGraph; From ecd718d6937b174c2f77395f66b8ca2aa19bb7ed Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Tue, 16 Jun 2020 19:49:38 -0400 Subject: [PATCH 05/13] Update README.md --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2cbcb43e..d184de38 100644 --- a/README.md +++ b/README.md @@ -423,6 +423,15 @@ const commitsData = [ chartConfig={chartConfig} /> ``` +Extra chartStyle for heatmap +```js +const chartConfig = { + chartStyle: { + justifyContent: 'start' || 'center' || 'end', //optional, defualt is 'start'; + alignItems: 'start' || 'center' || 'end', //optional, default is 'start'; + }, +}; +``` | Property | Type | Description | | ------------------ | -------- | ------------------------------------------------------------------------------------------- | @@ -430,7 +439,7 @@ const commitsData = [ | width | Number | Width of the chart, use 'Dimensions' library to get the width of your screen for responsive | | height | Number | Height of the chart | | gutterSize | Number | Size of the gutters between the squares in the chart | -| squareSize | Number | Size of the squares in the chart | +| squareSize | Number | Optional, Size of the squares in the chart, dynamic size will be auto applied if prop is not provided | | horizontal | boolean | Should graph be laid out horizontally? Defaults to `true` | | showMonthLabels | boolean | Should graph include labels for the months? Defaults to `true` | | showOutOfRangeDays | boolean | Should graph be filled with squares, including days outside the range? Defaults to `false` | From 1e2c5b5981ccaf20ec5d23be896c09991d0e3991 Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:10:16 -0400 Subject: [PATCH 06/13] fully responsive support for line-chart, All padding includes. Fix issue include `useNativeDriver` warning for RN>6.2. Some crashes on `scrollDot` when user missing some trivial styles and props. --- src/line-chart/line-chart.js | 268 +++++++++++++++++++---------------- 1 file changed, 148 insertions(+), 120 deletions(-) diff --git a/src/line-chart/line-chart.js b/src/line-chart/line-chart.js index 04307e11..fa9917b9 100644 --- a/src/line-chart/line-chart.js +++ b/src/line-chart/line-chart.js @@ -15,7 +15,7 @@ import { Rect, G } from "react-native-svg"; -import AbstractChart from "../abstract-chart"; +import AbstractChart, { GRAPH_RATIO } from "../abstract-chart"; import { LegendItem } from "./legend-item"; let AnimatedCircle = Animated.createAnimatedComponent(Circle); @@ -27,7 +27,7 @@ class LineChart extends AbstractChart { scrollableDotHorizontalOffset: new Animated.Value(0) }; - getColor = (dataset, opacity) => { + getColor = (dataset={}, opacity) => { return (dataset.color || this.props.chartConfig.color)(opacity); }; @@ -38,6 +38,26 @@ class LineChart extends AbstractChart { getDatas = data => data.reduce((acc, item) => (item.data ? [...acc, ...item.data] : acc), []); + linePositionHelper = config => { + const { + width, + height, + data, + linejoinType, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + verticalLabelHeight, + horizontalLabelWidth, + gutterTop, + } = config; + + const datas = this.getDatas(data); + const innerHeight = height - paddingTop - paddingBottom - verticalLabelHeight - gutterTop; + const innerWidth = width - horizontalLabelWidth - paddingRight - paddingLeft; + const baseHeight = this.calcBaseHeight(datas, innerHeight) + + return { datas, innerHeight, innerWidth, baseHeight }; + } + getPropsForDots = (x, i) => { const { getDotProps, chartConfig = {} } = this.props; if (typeof getDotProps === "function") { @@ -46,18 +66,20 @@ class LineChart extends AbstractChart { const { propsForDots = {} } = chartConfig; return { r: "4", ...propsForDots }; }; + renderDots = config => { const { data, width, height, - paddingTop, - paddingRight, - onDataPointClick + onDataPointClick, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + verticalLabelHeight, + horizontalLabelWidth, + gutterTop, } = config; const output = []; - const datas = this.getDatas(data); - const baseHeight = this.calcBaseHeight(datas, height); + const { datas, innerHeight, innerWidth, baseHeight } = this.linePositionHelper(config); const { getDotColor, hidePointsAtIndex = [], @@ -73,11 +95,10 @@ class LineChart extends AbstractChart { if (hidePointsAtIndex.includes(i)) { return; } - const cx = - paddingRight + (i * (width - paddingRight)) / dataset.data.length; - const cy = - ((baseHeight - this.calcHeight(x, datas, height)) / 4) * 3 + - paddingTop; + const lineHeight = this.calcHeight(x, datas, innerHeight); + const gapWidth = innerWidth / dataset.data.length; + const cx = i * gapWidth + horizontalLabelWidth + paddingLeft; + const cy = baseHeight - lineHeight + gutterTop + paddingTop; const onPress = () => { if (!onDataPointClick || hidePointsAtIndex.includes(i)) { return; @@ -126,22 +147,22 @@ class LineChart extends AbstractChart { data, width, height, - paddingTop, - paddingRight, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, scrollableDotHorizontalOffset, scrollableDotFill, scrollableDotStrokeColor, scrollableDotStrokeWidth, scrollableDotRadius, - scrollableInfoViewStyle, - scrollableInfoTextStyle, - scrollableInfoSize, - scrollableInfoOffset + scrollableInfoViewStyle = {}, + scrollableInfoTextStyle = {}, + scrollableInfoSize = {height:20, width:20}, + scrollableInfoOffset = 10, + verticalLabelHeight, + horizontalLabelWidth, + gutterTop, } = config; const output = []; - const datas = this.getDatas(data); - const baseHeight = this.calcBaseHeight(datas, height); - + const { datas, innerHeight, innerWidth, baseHeight } = this.linePositionHelper(config); let vl = []; const perData = width / data[0].data.length; @@ -206,7 +227,7 @@ class LineChart extends AbstractChart { data.forEach(dataset => { if (dataset.withScrollableDot == false) return; - const perData = width / dataset.data.length; + const perData = innerWidth / dataset.data.length; let values = []; let yValues = []; let xValues = []; @@ -216,26 +237,15 @@ class LineChart extends AbstractChart { for (let index = 0; index < dataset.data.length; index++) { values.push(index * perData); - const yval = - ((baseHeight - - this.calcHeight( - dataset.data[dataset.data.length - index - 1], - datas, - height - )) / - 4) * - 3 + - paddingTop; + + const lineHeight = this.calcHeight(dataset.data[dataset.data.length - index - 1], datas, innerHeight); + const gapWidth = innerWidth / dataset.data.length; + const yval = baseHeight - lineHeight + gutterTop + paddingTop; + const xval = paddingLeft + horizontalLabelWidth + (dataset.data.length - index - 1) * gapWidth; + yValues.push(yval); - const xval = - paddingRight + - ((dataset.data.length - index - 1) * (width - paddingRight)) / - dataset.data.length; xValues.push(xval); - - yValuesLabel.push( - yval - (scrollableInfoSize.height + scrollableInfoOffset) - ); + yValuesLabel.push(yval + scrollableInfoOffset); xValuesLabel.push(xval - scrollableInfoSize.width / 2); } @@ -308,9 +318,15 @@ class LineChart extends AbstractChart { return this.renderBezierShadow(config); } - const { data, width, height, paddingRight, paddingTop, useColorFromDataset } = config; - const datas = this.getDatas(data); - const baseHeight = this.calcBaseHeight(datas, height); + const { data, width, height, useColorFromDataset, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + verticalLabelHeight, + horizontalLabelWidth, + gutterTop, + } = config; + + const { datas, innerHeight, innerWidth, baseHeight } = this.linePositionHelper(config); + return config.data.map((dataset, index) => { return ( { - const x = - paddingRight + - (i * (width - paddingRight)) / dataset.data.length; - const y = - ((baseHeight - this.calcHeight(d, datas, height)) / 4) * 3 + - paddingTop; + const lineHeight = this.calcHeight(d, datas, innerHeight); + const gapWidth = innerWidth / dataset.data.length; + const x = paddingLeft + horizontalLabelWidth + i * gapWidth; + const y = baseHeight - lineHeight + gutterTop + paddingTop; return `${x},${y}`; }) .join(" ") + - ` ${paddingRight + - ((width - paddingRight) / dataset.data.length) * - (dataset.data.length - 1)},${(height / 4) * 3 + - paddingTop} ${paddingRight},${(height / 4) * 3 + paddingTop}` + ` ${paddingLeft + horizontalLabelWidth + (innerWidth / dataset.data.length) * (dataset.data.length - 1)}, + ${paddingTop + gutterTop + innerHeight} ${paddingLeft + horizontalLabelWidth}, + ${paddingTop + gutterTop + innerHeight}` } fill={`url(#fillShadowGradient${useColorFromDataset ? `_${index}` : ''})`} strokeWidth={0} @@ -347,21 +360,24 @@ class LineChart extends AbstractChart { const { width, height, - paddingRight, - paddingTop, data, - linejoinType + linejoinType, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + verticalLabelHeight, + horizontalLabelWidth, + gutterTop, } = config; + const output = []; - const datas = this.getDatas(data); - const baseHeight = this.calcBaseHeight(datas, height); + + const { datas, innerHeight, innerWidth, baseHeight } = this.linePositionHelper(config); + data.forEach((dataset, index) => { const points = dataset.data.map((d, i) => { - const x = - (i * (width - paddingRight)) / dataset.data.length + paddingRight; - const y = - ((baseHeight - this.calcHeight(d, datas, height)) / 4) * 3 + - paddingTop; + const lineHeight = this.calcHeight(d, datas, innerHeight); + const gapWidth = innerWidth / dataset.data.length; + const x = i * gapWidth + horizontalLabelWidth + paddingLeft; + const y = baseHeight - lineHeight + gutterTop + paddingTop; return `${x},${y}`; }); output.push( @@ -380,20 +396,25 @@ class LineChart extends AbstractChart { }; getBezierLinePoints = (dataset, config) => { - const { width, height, paddingRight, paddingTop, data } = config; + const { width, height, data, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + verticalLabelHeight, + horizontalLabelWidth, + gutterTop, + } = config; + if (dataset.data.length === 0) { return "M0,0"; } - const datas = this.getDatas(data); + const { datas, innerHeight, innerWidth, baseHeight } = this.linePositionHelper(config); + const gapWidth = innerWidth / dataset.data.length; + const x = i => - Math.floor( - paddingRight + (i * (width - paddingRight)) / dataset.data.length - ); - const baseHeight = this.calcBaseHeight(datas, height); + Math.floor(paddingLeft + horizontalLabelWidth + i * gapWidth); const y = i => { - const yHeight = this.calcHeight(dataset.data[i], datas, height); - return Math.floor(((baseHeight - yHeight) / 4) * 3 + paddingTop); + const yHeight = this.calcHeight(dataset.data[i], datas, innerHeight); + return Math.floor(baseHeight - yHeight + paddingTop + gutterTop); }; return [`M${x(0)},${y(0)}`] @@ -428,14 +449,23 @@ class LineChart extends AbstractChart { }; renderBezierShadow = config => { - const { width, height, paddingRight, paddingTop, data, useColorFromDataset } = config; + const { width, height, data, useColorFromDataset, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + verticalLabelHeight, + horizontalLabelWidth, + gutterTop, + } = config; + + const { datas, innerHeight, innerWidth, baseHeight } = this.linePositionHelper(config); + return data.map((dataset, index) => { + const gapWidth = innerWidth / dataset.data.length; const d = this.getBezierLinePoints(dataset, config) + - ` L${paddingRight + - ((width - paddingRight) / dataset.data.length) * - (dataset.data.length - 1)},${(height / 4) * 3 + - paddingTop} L${paddingRight},${(height / 4) * 3 + paddingTop} Z`; + ` L${paddingLeft + horizontalLabelWidth + + gapWidth * (dataset.data.length - 1)} + ,${innerHeight + gutterTop + paddingTop} L${paddingLeft + horizontalLabelWidth}, + ${innerHeight + gutterTop + paddingTop} Z`; return ( - {this.props.data.legend && - this.renderLegend(config.width, legendOffset)} - + { + this.props.data.legend && + this.renderLegend(config.width, legendOffset) + } + {this.renderDefs({ ...config, ...chartConfig, @@ -542,14 +597,6 @@ class LineChart extends AbstractChart { ? this.renderHorizontalLines({ ...config, count: count, - paddingTop, - paddingRight - }) - : withOuterLines - ? this.renderHorizontalLine({ - ...config, - paddingTop, - paddingRight }) : null} @@ -559,8 +606,6 @@ class LineChart extends AbstractChart { ...config, count: count, data: datas, - paddingTop, - paddingRight, formatYLabel, decimalPlaces: chartConfig.decimalPlaces }) @@ -571,14 +616,6 @@ class LineChart extends AbstractChart { ? this.renderVerticalLines({ ...config, data: data.datasets[0].data, - paddingTop, - paddingRight - }) - : withOuterLines - ? this.renderVerticalLine({ - ...config, - paddingTop, - paddingRight }) : null} @@ -587,19 +624,15 @@ class LineChart extends AbstractChart { ? this.renderVerticalLabels({ ...config, labels, - paddingRight, - paddingTop, formatXLabel }) : null} {this.renderLine({ - ...config, ...chartConfig, - paddingRight, - paddingTop, - data: data.datasets + ...config, + data: data.datasets, })} @@ -607,8 +640,6 @@ class LineChart extends AbstractChart { this.renderShadow({ ...config, data: data.datasets, - paddingRight, - paddingTop, useColorFromDataset: chartConfig.useShadowColorFromDataset, })} @@ -617,19 +648,15 @@ class LineChart extends AbstractChart { this.renderDots({ ...config, data: data.datasets, - paddingTop, - paddingRight, onDataPointClick })} {withScrollableDot && this.renderScrollableDot({ - ...config, ...chartConfig, + ...config, data: data.datasets, - paddingTop, - paddingRight, onDataPointClick, scrollableDotHorizontalOffset })} @@ -639,13 +666,12 @@ class LineChart extends AbstractChart { decorator({ ...config, data: data.datasets, - paddingTop, - paddingRight })} - {withScrollableDot && ( + { + withScrollableDot && ( - )} + ) + } ); } From 9be8679c6adaf6552784270e1c2aa91ad7ce9713 Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:11:53 -0400 Subject: [PATCH 07/13] Add tooltip to contribution chart onPressIn and onPressOut Fix: migrate Deprecated UNSAFE_componentWillReceiveProps to standard RN method. --- index.d.ts | 3 + src/contribution-graph/index.js | 122 +++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/index.d.ts b/index.d.ts index 13cb3550..46a4dfa7 100644 --- a/index.d.ts +++ b/index.d.ts @@ -324,6 +324,9 @@ export interface ContributionGraphProps { accessor?: string; getMonthLabel?: (monthIndex: number) => string; onDayPress?: ({ count: number, date: Date }) => void; + toggleTooltip?: boolean; + tooltipContent?: (dateInfo: { date: string, [accessor: string]: string}, + args: {x:number, y:number, index:number}) => JSX.Element; } export class ContributionGraph extends React.Component< diff --git a/src/contribution-graph/index.js b/src/contribution-graph/index.js index 9d70a3d5..7eb63b1a 100755 --- a/src/contribution-graph/index.js +++ b/src/contribution-graph/index.js @@ -23,20 +23,17 @@ class ContributionGraph extends AbstractChart { constructor(props) { super(props); - let { maxValue, minValue, valueCache } = this.getValueCache(props.values); - this.state = { - maxValue, - minValue, - valueCache + maxValue: +Infinity, + minValue: -Infinity, + valueCache: {}, }; } - UNSAFE_componentWillReceiveProps(nextProps) { + setupValueCacheFromProps() { let { maxValue, minValue, valueCache } = this.getValueCache( - nextProps.values + this.props.values ); - this.setState({ maxValue, minValue, @@ -44,6 +41,20 @@ class ContributionGraph extends AbstractChart { }); } + componentDidMount() { + this.setupValueCacheFromProps(); + } + + componentDidUpdate(prevProps, prevState) { + const large = (prevProps.values.length < this.props.values.length) ? this.props.values : prevProps.values; + const small = (prevProps.values.length < this.props.values.length) ? prevProps.values : this.props.values; + var res = large.filter(item1 => + !small.some(item2 => (item2['date'] === item1['date'] && item2['value'] === item2['value']))); + if(res.length > 0) { + this.setupValueCacheFromProps(); + } + } + setContentLayout(contentWidth, contentHeight) { const { width, height } = this.props; const { paddingTop, paddingLeft, paddingRight, paddingBottom, justifyContent, alignItems } = this.props.chartConfig.chartStyle; @@ -162,10 +173,9 @@ class ContributionGraph extends AbstractChart { return { valueCache: values.reduce((memo, value) => { const date = convertToDate(value.date); - const index = Math.floor( + const index = Math.ceil( (date - this.getStartDateWithEmptyDays()) / MILLISECONDS_IN_ONE_DAY ); - minValue = Math.min(value[this.props.accessor], minValue); maxValue = Math.max(value[this.props.accessor], maxValue); @@ -304,24 +314,47 @@ class ContributionGraph extends AbstractChart { const { squareSize } = this.props; return ( - { - this.handleDayPress(index); - }} - onLongPress={() => { - }} - {...this.getTooltipDataAttrsForIndex(index)} - /> + toggleColor={this.props.chartConfig.toggleColor} + from={ (toggleVisible, fill=this.getClassNameForIndex(index)) => + {this.handleDayPress(index);}} + onPressIn={toggleVisible} + onPressOut={toggleVisible} + title={this.getTitleForIndex(index)} + fill={fill} + /> + } + toggleTooltip={this.props.toggleTooltip} + > + { + this.squareTooltip({index, x, y}) + } + ); } + squareTooltip(args) { + const { index, x, y } = args; + if(!this.props.toggleTooltip && this.props.tooltipContent){ + return; + } + + const dateInfo = this.state.valueCache[index] && this.state.valueCache[index].value + ? this.state.valueCache[index].value + : { + [this.props.accessor]: 0, + date: new Date(this.getStartDate().valueOf() + + index * MILLISECONDS_IN_ONE_DAY).toISOString().split('T')[0] + } + return this.props.tooltipContent(dateInfo, args); + } + handleDayPress(index) { if (!this.props.onDayPress) { return; @@ -333,7 +366,7 @@ class ContributionGraph extends AbstractChart { [this.props.accessor]: 0, date: new Date( this.getStartDate().valueOf() + index * MILLISECONDS_IN_ONE_DAY - ) + ).toISOString().split('T')[0] } ); } @@ -409,6 +442,21 @@ class ContributionGraph extends AbstractChart { this.props.squareSize = this.props.squareSize || this.setDynamicSquareSize() || 20; this.mainLayoutSetup(); + // reserved code for future if want to handle special gesture events + // this._panResponder = PanResponder.create({ + // onStartShouldSetPanResponder: (evt, gestureState) => true, + // onStartShouldSetPanResponderCapture: (evt, gestureState) => true, + // onMoveShouldSetPanResponder: (evt, gestureState) => true, + // onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, + // onPanResponderGrant: (evt, gestureState) => { + // console.log(evt.nativeEvent.locationX,evt.nativeEvent.locationY); + // }, + // onPanResponderMove: (evt, gestureState) => { + // // X position relative to the page + // console.log(evt.nativeEvent.locationX,evt.nativeEvent.locationY); + // } + // }); + return ( @@ -432,6 +480,29 @@ class ContributionGraph extends AbstractChart { } } +class Tooltip extends React.Component { + constructor(props) { + super(props); + this.state = { + isVisible: false + }; + } + + toggleVisible = () => { + this.setState(prevState => ({isVisible: !prevState.isVisible})); + } + render() { + const fill = this.props.toggleTooltip && this.state.isVisible ? this.props.toggleColor : undefined; + + return ( + <> + { this.props.from(this.toggleVisible, fill) } + { this.props.toggleTooltip && this.state.isVisible ? this.props.children : null } + + ) + } +} + ContributionGraph.defaultProps = { numDays: 200, endDate: new Date(), @@ -442,6 +513,7 @@ ContributionGraph.defaultProps = { showMonthLabels: true, showOutOfRangeDays: false, accessor: "count", + toggleTooltip: false, classForValue: value => (value ? "black" : "#8cc665"), }; From 1be57b2163ac68ca0aff661de7fabfff947228dd Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:42:30 -0400 Subject: [PATCH 08/13] Update abstract-chart compatibility for line-chart, shift default GRAPH_RATIO to line-chart.js --- src/abstract-chart.js | 24 +++++++++++++++--------- src/line-chart/line-chart.js | 17 ++++++++--------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/abstract-chart.js b/src/abstract-chart.js index 7c465d72..13d36cd1 100644 --- a/src/abstract-chart.js +++ b/src/abstract-chart.js @@ -151,6 +151,7 @@ class AbstractChart extends Component { verticalLabelRotation = 0, formatXLabel = xLabel => xLabel, chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + midPoint = 0, } = config; const { @@ -165,7 +166,6 @@ class AbstractChart extends Component { } const labelWidth = (width - horizontalLabelWidth - paddingRight - paddingLeft) / labels.length; - const midPoint = labelWidth / 2; const y = height - paddingBottom - verticalLabelHeight + xLabelsOffset + fontSize*1.5; @@ -193,23 +193,29 @@ class AbstractChart extends Component { }; renderVerticalLines = config => { - const { data, width, height, gutterTop, horizontalLabelWidth } = config; - const { yAxisInterval = 1 } = this.props; + const { data, width, height, gutterTop, horizontalLabelWidth, verticalLabelHeight, + chartStyle: { paddingTop, paddingLeft, paddingRight, paddingBottom }, + } = config; + const { + yAxisInterval = 1, + adjustment = 1, + } = this.props; + const innerWidth = width - horizontalLabelWidth - paddingLeft - paddingRight; + const gap = innerWidth / (data.length / yAxisInterval); + return [...new Array(Math.ceil(data.length / yAxisInterval))].map( (_, i) => { return ( ); diff --git a/src/line-chart/line-chart.js b/src/line-chart/line-chart.js index 066cb36c..3868ed7e 100644 --- a/src/line-chart/line-chart.js +++ b/src/line-chart/line-chart.js @@ -15,9 +15,15 @@ import { Rect, G } from "react-native-svg"; -import AbstractChart, { GRAPH_RATIO } from "../abstract-chart"; +import AbstractChart from "../abstract-chart"; import { LegendItem } from "./legend-item"; +const GRAPH_RATIO = { + gutterTop: 0.1 , + horizontalLabelWidth: 0.2, + verticalLabelHeight: 0.15, +} + let AnimatedCircle = Animated.createAnimatedComponent(Circle); class LineChart extends AbstractChart { @@ -517,6 +523,7 @@ class LineChart extends AbstractChart { onDataPointClick, verticalLabelRotation = 0, horizontalLabelRotation = 0, + gutterTop = 0, formatYLabel = yLabel => yLabel, formatXLabel = xLabel => xLabel, segments, @@ -534,14 +541,6 @@ class LineChart extends AbstractChart { const { scrollableDotHorizontalOffset } = this.state; const { labels = [] } = data; - const { - borderRadius = 0, - paddingTop = 16, - paddingRight = 64, - margin = 0, - marginRight = 0, - paddingBottom = 0 - } = style; const config = { width, From 49ed337c816bd3aec05bcec63552c71e1dea653c Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Mon, 22 Jun 2020 22:12:50 -0400 Subject: [PATCH 09/13] Fix: correct midpoint for BarChart labels --- src/bar-chart.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bar-chart.js b/src/bar-chart.js index f7e14c21..68e6cae2 100644 --- a/src/bar-chart.js +++ b/src/bar-chart.js @@ -162,6 +162,9 @@ class BarChart extends AbstractChart { config.verticalLabelHeight = this.props.chartConfig.verticalLabelHeight ?? (height - config.chartStyle.paddingTop - config.chartStyle.paddingBottom) * BAR_RATIO.verticalLabelHeight; + const labelWidth = (config.width - config.horizontalLabelWidth - + config.chartStyle.paddingRight - config.chartStyle.paddingLeft) / data.labels.length; + return ( @@ -198,6 +201,7 @@ class BarChart extends AbstractChart { ? this.renderVerticalLabels({ ...config, labels: data.labels, + midPoint: labelWidth / 2, }) : null} From 5910ef620595e751a44c5636308ec131611f6cf1 Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Sun, 5 Jul 2020 21:39:41 -0400 Subject: [PATCH 10/13] Fix: did not update when data changes --- src/contribution-graph/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contribution-graph/index.js b/src/contribution-graph/index.js index 7eb63b1a..ea45e1b9 100755 --- a/src/contribution-graph/index.js +++ b/src/contribution-graph/index.js @@ -49,7 +49,7 @@ class ContributionGraph extends AbstractChart { const large = (prevProps.values.length < this.props.values.length) ? this.props.values : prevProps.values; const small = (prevProps.values.length < this.props.values.length) ? prevProps.values : this.props.values; var res = large.filter(item1 => - !small.some(item2 => (item2['date'] === item1['date'] && item2['value'] === item2['value']))); + !small.some(item2 => (item2['date'] === item1['date'] && item2['value'] === item1['value']))); if(res.length > 0) { this.setupValueCacheFromProps(); } From bfb53c809870541c5dcc2ce555c262259fca4500 Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Tue, 7 Jul 2020 03:13:34 -0400 Subject: [PATCH 11/13] correct data display on scrollableInfo --- src/line-chart/line-chart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/line-chart/line-chart.js b/src/line-chart/line-chart.js index 3868ed7e..b328db57 100644 --- a/src/line-chart/line-chart.js +++ b/src/line-chart/line-chart.js @@ -171,7 +171,7 @@ class LineChart extends AbstractChart { const { datas, innerHeight, innerWidth, baseHeight } = this.linePositionHelper(config); let vl = []; - const perData = width / data[0].data.length; + const perData = innerWidth / data[0].data.length; for (let index = 0; index < data[0].data.length; index++) { vl.push(index * perData); } From 6caba943825691c15bf1afeca87fe6cf9cc78ac1 Mon Sep 17 00:00:00 2001 From: aboveyunhai <35160613+aboveyunhai@users.noreply.github.com> Date: Wed, 22 Jul 2020 21:34:19 -0400 Subject: [PATCH 12/13] seperate hideLabel from hidePointsArIndex. correct vertical line hieght render --- src/abstract-chart.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/abstract-chart.js b/src/abstract-chart.js index 13d36cd1..c24e3e93 100644 --- a/src/abstract-chart.js +++ b/src/abstract-chart.js @@ -157,7 +157,7 @@ class AbstractChart extends Component { const { xAxisLabel = "", xLabelsOffset = 0, - hidePointsAtIndex = [] + hideLabelsAtIndex = [] } = this.props; const fontSize = 12; let fac = 1; @@ -170,7 +170,7 @@ class AbstractChart extends Component { const y = height - paddingBottom - verticalLabelHeight + xLabelsOffset + fontSize*1.5; return labels.map((label, i) => { - if (hidePointsAtIndex.includes(i)) { + if (hideLabelsAtIndex.includes(i)) { return null; } @@ -199,11 +199,15 @@ class AbstractChart extends Component { const { yAxisInterval = 1, adjustment = 1, + innerLines, } = this.props; const innerWidth = width - horizontalLabelWidth - paddingLeft - paddingRight; - const gap = innerWidth / (data.length / yAxisInterval); - return [...new Array(Math.ceil(data.length / yAxisInterval))].map( + const lineNum = innerLines || data.length; + + const gap = innerWidth / (lineNum / yAxisInterval); + + return [...new Array(Math.ceil(lineNum / yAxisInterval))].map( (_, i) => { return ( Date: Wed, 29 Jul 2020 22:32:10 -0400 Subject: [PATCH 13/13] Update index.d.ts --- index.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/index.d.ts b/index.d.ts index 46a4dfa7..f0701a74 100644 --- a/index.d.ts +++ b/index.d.ts @@ -68,6 +68,8 @@ export interface LineChartProps { * Show inner dashed lines - default: True. */ + defMax?: number; + defMin?: number; withScrollableDot?: boolean; withInnerLines?: boolean; /** @@ -239,6 +241,9 @@ export interface BarChartProps { style?: ViewStyle; horizontalLabelRotation?: number; verticalLabelRotation?: number; + hideLabelsAtIndex?: (number | null)[]; + barWidth?: number; + decorator?: ({}:any) => JSX.Element; /** * The number of horizontal lines */