diff --git a/CHANGELOG.md b/CHANGELOG.md index 095c2ef..8e34cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## v1.1.0 +- Use arc calculations to render range values https://github.com/shipt/segmented-arc-for-react-native/pull/86 +- Arc drawing fix https://github.com/shipt/segmented-arc-for-react-native/pull/87 +- Readme updates + +## v1.1.0 + - Add a support for scaling the display scale of arc segments https://github.com/shipt/segmented-arc-for-react-native/pull/70 - Dependency updates diff --git a/README.md b/README.md index 5a06eb5..a27fa5b 100644 --- a/README.md +++ b/README.md @@ -108,28 +108,55 @@ export default App; Try this example yourself [here](./example). +### Custom segment sizing + +If you would like to adjust the sizing of the segments individually, just provide the `arcDegreeScale` property on segments whose size you'd like to control. Segments without an `arcDegreeScale` will be equally sized for the remainder of the arc. + +```js +const segments = [ + { + arcDegreeScale: 0.5, // will take up half the entire arc + filledColor: '#FF746E', + emptyColor: '#F2F3F5' + }, + { + filledColor: '#F5E478', // each of these segments will use a third of the remainder of the arc, or 1/6 of the total since the segment above is using 50% + emptyColor: '#F2F3F5' + }, + { + filledColor: '#78F5CA', + emptyColor: '#F2F3F5' + }, + { + filledColor: '#6E73FF', + emptyColor: '#F2F3F5' + } +]; +``` + # 📖 Props -| Name | Type | Default | Description | -| --------------------------- | --------------------------------------------------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| fillValue | number (0-100) | 0 | Current progress value | -| segments | Array of { scale: number, filledColor: string, emptyColor: string, data: object } | [] | Segments of the arc. Here, scale is a percentage value out of 100%, filledColor for filled part of a segment, and emptyColor is background color for an empty segment, data could be any object that you'd want to receive back for a segment. See example above. | -| filledArcWidth | number | 8 | Thickness of progress line | -| emptyArcWidth | number | 8 | Thickness of background line | -| spaceBetweenSegments | number | 2 | Space between segments | -| arcDegree | number | 180 | Degree of arc | -| radius | number | 100 | Arc radius | -| isAnimated | bool | true | Enable/disable progress animation | -| animationDuration | number | 1000 | Progress animation duration | -| animationDelay | number | 0 | Progress animation delay | -| ranges | Array of strings | [] | Arc ranges (segments) display values | -| rangesTextColor | string | '#000000' | Color of ranges text | -| rangesTextStyle | object | { fontSize: 12 } | Ranges text styling | -| showArcRanges | bool | false | Show/hide arc ranges | -| middleContentContainerStyle | object | {} | Extra styling for the middle content container | -| capInnerColor | string | '#28E037' | Cap's inner color | -| capOuterColor | string | '#FFFFFF' | Cap's outer color | -| children | function | | Pass a function as a child. It receives metaData with the last filled segment's data as an argument. From there you can extract data object. See example above. | +| Name | Type | Default | Description | +| --------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| fillValue | number (0-100) | 0 | Current progress value | +| segments | Array of { scale: number, filledColor: string, emptyColor: string, data: object, arcDegreeScale: number } | [] | Segments of the arc. Here, scale is a percentage value out of 100%, filledColor for filled part of a segment, emptyColor is background color for an empty segment, data could be any object that you'd want to receive back for a segment, and arcDegreeScale is used to resize each segment. See examples above. | +| filledArcWidth | number | 8 | Thickness of progress line | +| emptyArcWidth | number | 8 | Thickness of background line | +| spaceBetweenSegments | number | 2 | Space between segments | +| arcDegree | number | 180 | Degree of arc | +| radius | number | 100 | Arc radius | +| isAnimated | bool | true | Enable/disable progress animation | +| animationDuration | number | 1000 | Progress animation duration | +| animationDelay | number | 0 | Progress animation delay | +| ranges | Array of strings | [] | Arc ranges (segments) display values | +| rangesTextColor | string | '#000000' | Color of ranges text | +| rangesTextStyle | object | { fontSize: 12 } | Ranges text styling | +| showArcRanges | bool | false | Show/hide arc ranges | +| middleContentContainerStyle | object | {} | Extra styling for the middle content container | +| capInnerColor | string | '#28E037' | Cap's inner color | +| capOuterColor | string | '#FFFFFF' | Cap's outer color | +| alignRangesWithSegments | bool | true | This might be useful when using segment[].arcDegreeScale values to customize the size of individual segments. If you'd like the range display to align with the edge of each segment, pass this prop as `true`. Otherwise, range displays will be distributed evenly across the arc. | +| children | function | | Pass a function as a child. It receives metaData with the last filled segment's data as an argument. From there you can extract data object. See example above. | | | ## 📋 Attributions diff --git a/example/yarn.lock b/example/yarn.lock index aaf8c2a..f29c600 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -1673,7 +1673,7 @@ nullthrows "^1.1.1" "@shipt/segmented-arc-for-react-native@file:..": - version "1.1.1" + version "1.2.1" dependencies: prop-types "^15.8.1" diff --git a/package.json b/package.json index b00a83d..e2f0864 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shipt/segmented-arc-for-react-native", - "version": "1.1.1", + "version": "1.2.1", "type": "module", "description": "Segmented arc component for React Native ", "main": "src/index.js", diff --git a/src/SegmentedArc.js b/src/SegmentedArc.js index c06f972..6943794 100644 --- a/src/SegmentedArc.js +++ b/src/SegmentedArc.js @@ -27,6 +27,7 @@ export const SegmentedArc = ({ rangesTextStyle = styles.rangeTextStyle, capInnerColor = '#28E037', capOuterColor = '#FFFFFF', + alignRangesWithSegments = true, children }) => { const [arcAnimatedValue] = useState(new Animated.Value(0)); @@ -147,6 +148,7 @@ export const SegmentedArc = ({ {arcs.map((arc, index) => ( @@ -198,7 +201,8 @@ SegmentedArc.propTypes = { scale: PropTypes.number, filledColor: PropTypes.string.isRequired, emptyColor: PropTypes.string.isRequired, - data: PropTypes.object + data: PropTypes.object, + arcDegreeScale: PropTypes.number }) ).isRequired, filledArcWidth: PropTypes.number, @@ -216,7 +220,8 @@ SegmentedArc.propTypes = { rangesTextColor: PropTypes.string, rangesTextStyle: PropTypes.object, capInnerColor: PropTypes.string, - capOuterColor: PropTypes.string + capOuterColor: PropTypes.string, + alignRangesWithSegments: PropTypes.bool }; export { SegmentedArcContext }; export default SegmentedArc; diff --git a/src/__tests__/__snapshots__/SegmentedArc.spec.js.snap b/src/__tests__/__snapshots__/SegmentedArc.spec.js.snap index 21e4c7b..346cb26 100644 --- a/src/__tests__/__snapshots__/SegmentedArc.spec.js.snap +++ b/src/__tests__/__snapshots__/SegmentedArc.spec.js.snap @@ -11,9 +11,64 @@ exports[`SegmentedArc automatically increases the component's height when arcDeg { radius, filledArcWidth, margin, - arcsStart, - spaceBetweenSegments, - arcSegmentDegree, ranges, rangesTextColor, - rangesTextStyle + rangesTextStyle, + spaceBetweenSegments, + arcs, + arcsStart, + arcSegmentDegree, + alignRangesWithSegments } = segmentedArcContext; const { mappedPathKeys, rangesStartOffset } = useMemo(() => { @@ -34,6 +36,23 @@ export const RangesDisplay = () => { return ; }; + const _getAlignedRangesPath = (id, index) => { + const rangeRadius = radius + filledArcWidth; + const centerX = rangeRadius + margin; + const centerY = rangeRadius + margin; + + const arc = arcs[index]; + const previousArc = arcs[index - 1]; + + let end = arcsStart; + if (previousArc) end = previousArc.end + spaceBetweenSegments; + + let start = end; + if (arc) start = arc.end; + + return ; + }; + const _getRangesDisplayValue = (key, index) => { let isLastRange = index === ranges.length - 1; let pathId = key; @@ -49,7 +68,7 @@ export const RangesDisplay = () => { }; return ( - {mappedPathKeys.map(_getRangesPath)} + {mappedPathKeys.map(alignRangesWithSegments ? _getAlignedRangesPath : _getRangesPath)} {mappedPathKeys.map(_getRangesDisplayValue)} ); diff --git a/src/components/__tests__/RangesDisplay.spec.js b/src/components/__tests__/RangesDisplay.spec.js index d09cd9c..a078fc5 100644 --- a/src/components/__tests__/RangesDisplay.spec.js +++ b/src/components/__tests__/RangesDisplay.spec.js @@ -26,7 +26,12 @@ describe('RangesDisplay', () => { spaceBetweenSegments: 10, margin: 40, ranges: ['1.25', '2.5', '3.75', '5'], - rangesTextColor: '#000' + rangesTextColor: '#000', + arcs: [ + { start: 50, end: 0 }, + { start: 100, end: 50 }, + { start: 150, end: 100 } + ] }; props = {}; @@ -36,4 +41,11 @@ describe('RangesDisplay', () => { it('renders default', () => { expect(wrapper).toMatchSnapshot(); }); + + it('renders with aligned segments', () => { + contextValue.alignRangesWithSegments = true; + + getWrapper(props); + expect(wrapper).toMatchSnapshot(); + }); }); diff --git a/src/components/__tests__/__snapshots__/RangesDisplay.spec.js.snap b/src/components/__tests__/__snapshots__/RangesDisplay.spec.js.snap index 0ad23df..a9599d7 100644 --- a/src/components/__tests__/__snapshots__/RangesDisplay.spec.js.snap +++ b/src/components/__tests__/__snapshots__/RangesDisplay.spec.js.snap @@ -11,7 +11,7 @@ exports[`RangesDisplay renders default 1`] = ` > + + + + + + + + + + + + + + + + + + + + + + +`; + +exports[`RangesDisplay renders with aligned segments 1`] = ` + + + + + + { }); it('returns d path for an arc', () => { - expect(drawArc(152, 152, 104, 136.5, 158)).toEqual( - 'M 248.4271208749459 113.04091428474516 A 104 104 0 0 0 227.43893458527793 80.41112412784959' - ); + expect(drawArc(152, 152, 104, 136.5, 158)).toMatchSnapshot(); }); it('returns d path for an arc - opposite angle needed for ratings range display', () => { - expect(drawArc(152, 152, 104, 136.5, 158, true)).toEqual( - 'M 248.4271208749459 113.04091428474516 A 104 104 0 0 1 227.43893458527793 80.41112412784959' - ); + expect(drawArc(152, 152, 104, 136.5, 158, true)).toMatchSnapshot(); }); }); diff --git a/src/utils/arcHelpers.js b/src/utils/arcHelpers.js index 4246906..934aded 100644 --- a/src/utils/arcHelpers.js +++ b/src/utils/arcHelpers.js @@ -9,13 +9,31 @@ export const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => { export const drawArc = (x, y, radius, startAngle, endAngle, range = false) => { const start = polarToCartesian(x, y, radius, endAngle); + const midPoint = polarToCartesian(x, y, radius, (startAngle + endAngle) / 2); const end = polarToCartesian(x, y, radius, startAngle); - const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1'; - const sweepFlag = range ? 1 : 0; - const d = ['M', start.x, start.y, 'A', radius, radius, 0, largeArcFlag, sweepFlag, end.x, end.y].join(' '); - + const d = [ + 'M', + start.x, + start.y, + 'A', + radius, + radius, + 0, + 0, + sweepFlag, + midPoint.x, + midPoint.y, + 'A', + radius, + radius, + 0, + 0, + sweepFlag, + end.x, + end.y + ].join(' '); return d; };