diff --git a/demo/ts/components/victory-pie-demo.tsx b/demo/ts/components/victory-pie-demo.tsx index 8cfa399d0..1549c69d1 100644 --- a/demo/ts/components/victory-pie-demo.tsx +++ b/demo/ts/components/victory-pie-demo.tsx @@ -2,7 +2,7 @@ import React from "react"; import { random, range } from "lodash"; import { VictoryPie } from "victory-pie"; import { VictoryTooltip } from "victory-tooltip"; -import { VictoryTheme, LineSegment } from "victory-core"; +import { VictoryTheme, LineSegment,PolyLineSegment } from "victory-core"; interface VictoryPieDemoState { data: { @@ -364,6 +364,50 @@ export default class VictoryPieDemo extends React.Component< labelIndicatorInnerOffset={45} labelIndicatorOuterOffset={15} /> + `${datum.name}`} + data={[ + { name: "Mark", y: 40 }, + { name: "Robert", y: 12 }, + { name: "Emily", y: 34 }, + { name: "Marion", y: 23 }, + { name: "Nicolas", y: 28 }, + { name: "Karen", y: 18 }, + ]} + /> + + } + /> + ); diff --git a/docs/src/content/docs/victory-pie.md b/docs/src/content/docs/victory-pie.md index c766d577d..ef1a8d6a6 100644 --- a/docs/src/content/docs/victory-pie.md +++ b/docs/src/content/docs/victory-pie.md @@ -250,6 +250,32 @@ _default:_ `` /> ``` +## labelIndicatorType + +`type: singleLine | polyLine` + +`singleLine` is used to draw single line label indicator and `polyLine` is set to draw +polyline label indicator. + +_default:_ `singleLine` + +```playground +
+ datum.y} + labelIndicator +/> + datum.y} + labelIndicator + labelIndicatorType ="polyLine" + labelIndicatorOuterOffset= {15} +/> +
+``` + ## labelPlacement `type "parallel" || "perpendicular" || "vertical" || function` @@ -523,7 +549,7 @@ y={(d) => d.value + d.error} `type: boolean || element` -The `labelIndicator` prop defines the label indicator line between labels and the pie chart. If this prop is used as a boolean,then the default indicator will be displayed. To customize or pass your own styling `` can be passed to labelIndicator. LabelIndicator is functional only when labelPosition = "centroid". To adjust the labelIndicator length, `labelIndicatorInnerOffset` and `labelIndicatorOuterOffset` props can be used alongside labelIndicator. +The `labelIndicator` prop defines the label indicator line between labels and the pie chart. If this prop is used as a boolean,then the default indicator will be displayed. To customize or pass your own styling `` can be passed to labelIndicator. LabelIndicator is functional only when labelPosition = "centroid". To adjust the labelIndicator length, `labelIndicatorInnerOffset`,`labelIndicatorMiddleOffset` and `labelIndicatorOuterOffset` props can be used alongside labelIndicator. ```playground
@@ -541,6 +567,22 @@ The `labelIndicator` prop defines the label indicator line between labels and th labelIndicatorInnerOffset={10} labelIndicatorOuterOffset={5} /> + + } + labelIndicatorType="polyLine" + labelIndicatorInnerOffset={10} + labelIndicatorMiddleOffset={10} + labelIndicatorOuterOffset={25} + />
``` ## labelIndicatorInnerOffset @@ -557,18 +599,49 @@ The `labelIndicatorInnerOffset` prop defines the offset by which the indicator l /> ``` +## labelIndicatorMiddleOffset + +`type: number` + +The `labelIndicatorMiddleOffset` prop defines the offset by which the polyLine indicator length outside pie chart is being drawn. Higher the number longer the length. + +```playground + +``` + ## labelIndicatorOuterOffset `type: number` -The `labelIndicatorOuterOffset` prop defines the offset by which the indicator length outside the pie chart is being drawn. Higher the number shorter the length. +For labelIndicatorType `singleLine`, `labelIndicatorOuterOffset` prop defines the offset by which the indicator length outside the pie chart is being drawn. Higher the number shorter the length. + +For labelIndicatorType `polyLine`, `labelIndicatorOuterOffset` prop defines the offset by which the indicator length outside the pie chart is being drawn from the mid point to the label. Higher the number higher the length. ```playground - +
+ + +
``` [animations guide]: /guides/animations diff --git a/docs/src/content/docs/victory-primitives.md b/docs/src/content/docs/victory-primitives.md index 549fe0367..645219298 100644 --- a/docs/src/content/docs/victory-primitives.md +++ b/docs/src/content/docs/victory-primitives.md @@ -50,6 +50,12 @@ Used by `Arc`, `Area`, `Bar`, `Curve`, `Flyout`, `Point`, `Slice`, and `Voronoi` const Path = (props) => ; ``` +### PolyLine + +```jsx +const PolyLine = (props) => ; +``` + ### Rect Used by `VictoryClipPath`, `Background`, `Border`, and `Candle` @@ -161,6 +167,28 @@ The `LineSegment` component renders straight lines. This component is used to re - `y1` _number_ the y coordinate of the beginning of the line - `y2` _number_ the y coordinate of the end of the line +### PolyLineSegment + +The `PolyLineSegment` component renders straight lines connecting several points. This component is used to render polyline label indicator[VictoryPie][]. [View the source] + +**Props** + +- `active` _boolean_ a flag signifying whether the component is active +- `ariaLabel` _string or function_ a prop controlling the aria-label that will be applied to the rendered polyLineComponent. When this prop is given as a function it will be called with the rest of the props supplied to `PolyLineSegment` +- `className` _string_ the class name that will be applied to the rendered element +- `data` _array_ the entire dataset +- `datum` _object_ the data point corresponding to this line +- `events` _object_ events to attach to the rendered element +- `id` _string or number_ an id to apply to the rendered component +- `index` _number_ the index of this component within the dataset +- `polyLineComponent` _element_ the rendered polyLine element _default_ `` +- `role` _string_ the aria role to assign to the element +- `shapeRendering` _string_ the shape rendering attribute to apply to the rendered elements +- `style` _object_ the styles to apply to the rendered element +- `tabIndex` _number or function_ will be applied to the rendered polyLineComponent. When this prop is given as a function it will be called with the rest of the props supplied to `PolyLineSegment` +- `transform` _string_ a transform that will be supplied to elements this component renders +- `points` _number_ this attribute defines the list of points (pairs of x,y absolute coordinates) required to draw the polyline. + ### Background The `Background` component is used to render an SVG background on VictoryChart. `Background` will render a `` for charts with `polar={true}` and a `` element for all other charts. [View the source][background] diff --git a/packages/victory-core/src/exports.test.ts b/packages/victory-core/src/exports.test.ts index 78bb94f72..c1288e7c6 100644 --- a/packages/victory-core/src/exports.test.ts +++ b/packages/victory-core/src/exports.test.ts @@ -63,6 +63,9 @@ import { Point, PointPathHelpers, PointProps, + PolyLine, + PolyLineSegment, + PolyLineSegmentProps, Portal, PortalContext, PortalContextValue, @@ -169,6 +172,8 @@ describe("victory-core", () => { "Path", "Point", "PointPathHelpers", + "PolyLine", + "PolyLineSegment", "Portal", "PortalContext", "Rect", diff --git a/packages/victory-core/src/victory-primitives/index.ts b/packages/victory-core/src/victory-primitives/index.ts index 2f6027e5c..54113bc94 100644 --- a/packages/victory-core/src/victory-primitives/index.ts +++ b/packages/victory-core/src/victory-primitives/index.ts @@ -7,6 +7,8 @@ export * from "./line"; export * from "./line-segment"; export * from "./path"; export * from "./point"; +export * from "./polyline"; +export * from "./polyline-segment"; export * from "./rect"; export * from "./text"; export * from "./tspan"; diff --git a/packages/victory-core/src/victory-primitives/polyline-segment.tsx b/packages/victory-core/src/victory-primitives/polyline-segment.tsx new file mode 100644 index 000000000..2619609ca --- /dev/null +++ b/packages/victory-core/src/victory-primitives/polyline-segment.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import * as Helpers from "../victory-util/helpers"; +import { assign } from "lodash"; +import { VictoryCommonPrimitiveProps } from "../victory-util/common-props"; +import { PolyLine } from "./polyline"; + +export interface PolyLineSegmentProps extends VictoryCommonPrimitiveProps { + polylineComponent?: React.ReactElement; + points?: string; +} + +const evaluateProps = (props) => { + /** + * Potential evaluated props are: + * `ariaLabel` + * `id` + * `style` + * `tabIndex` + */ + const ariaLabel = Helpers.evaluateProp(props.ariaLabel, props); + const id = Helpers.evaluateProp(props.id, props); + const style = Helpers.evaluateStyle( + assign({ stroke: "black", fill: "none" }, props.style), + props, + ); + const tabIndex = Helpers.evaluateProp(props.tabIndex, props); + + return assign({}, props, { ariaLabel, id, style, tabIndex }); +}; + +const defaultProps = { + polylineComponent: , + role: "presentation", + shapeRendering: "auto", +}; + +export const PolyLineSegment = (initialProps: PolyLineSegmentProps) => { + const props = evaluateProps({ ...defaultProps, ...initialProps }); + + return React.cloneElement(props.polylineComponent!, { + ...props.events, + "aria-label": props.ariaLabel, + style: props.style, + tabIndex: props.tabIndex, + className: props.className, + role: props.role, + shapeRendering: props.shapeRendering, + points: props.points, + transform: props.transform, + clipPath: props.clipPath, + }); +}; diff --git a/packages/victory-core/src/victory-primitives/polyline.tsx b/packages/victory-core/src/victory-primitives/polyline.tsx new file mode 100644 index 000000000..b90c78dde --- /dev/null +++ b/packages/victory-core/src/victory-primitives/polyline.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { VictoryPrimitiveShapeProps } from "./types"; + +export const PolyLine = (props: VictoryPrimitiveShapeProps) => { + return ( + // @ts-expect-error FIXME: "id cannot be a number" + + ); +}; diff --git a/packages/victory-pie/src/helper-methods.ts b/packages/victory-pie/src/helper-methods.ts index e44667e01..a9c0172f0 100644 --- a/packages/victory-pie/src/helper-methods.ts +++ b/packages/victory-pie/src/helper-methods.ts @@ -290,6 +290,70 @@ export const getLabelIndicatorPropsForLineSegment = ( return defaults({}, labelIndicatorProps); }; +export const getLabelIndicatorPropsForPolyLineSegment = ( + props, + calculatedValues, +) => { + const { + innerRadius, + radius, + slice, + labelIndicatorInnerOffset, + labelIndicatorOuterOffset, + labelIndicatorMiddleOffset, + index, + } = props; + const { origin } = calculatedValues; + + const centerX = origin.x; + const centerY = origin.y; + + const middleRadius = getAverage([innerRadius, radius]); + const midAngle = getAverage([slice.endAngle, slice.startAngle]); + + const innerOffset = middleRadius + labelIndicatorInnerOffset; + + // First point + const x1 = centerX + getXOffset(innerOffset, midAngle); + const y1 = centerY + getYOffset(innerOffset, midAngle); + + // second point + const midPointInfo = { + innerRadius: radius + labelIndicatorMiddleOffset, + outerRadius: radius + labelIndicatorMiddleOffset, + startAngle: slice.startAngle, + endAngle: slice.endAngle, + }; + + const arcGenerator = d3Shape.arc(); + const midPoint = arcGenerator.centroid(midPointInfo); + const isRightLabel = midPoint[0] > 0; + + const textAnchor = isRightLabel ? "start" : "end"; + // third point + const labelPosX = + midPoint[0] + labelIndicatorOuterOffset * (isRightLabel ? 1 : -1); + const labelPosY = midPoint[1]; + + const points = `${Math.round(x1)},${Math.round(y1)} + ${Math.round(midPoint[0]) + centerX},${ + Math.round(midPoint[1]) + centerY + } + ${Math.round(midPoint[0]) + centerX},${ + Math.round(midPoint[1]) + centerY + } + ${labelPosX + centerX},${Math.round(midPoint[1] + centerY)} + `; + const labelIndicatorProps = { + points, + index, + textAnchor, + x: labelPosX + centerX, + y: labelPosY + centerY, + }; + return defaults({}, labelIndicatorProps); +}; + export const getBaseProps = (initialProps, fallbackProps) => { const props = Helpers.modifyProps(initialProps, fallbackProps, "pie"); const calculatedValues = getCalculatedValues(props); @@ -311,6 +375,7 @@ export const getBaseProps = (initialProps, fallbackProps) => { padAngle, disableInlineStyles, labelIndicator, + labelIndicatorType, } = calculatedValues; const radius = props.radius || defaultRadius; const initialChildProps = { @@ -353,7 +418,10 @@ export const getBaseProps = (initialProps, fallbackProps) => { ); if (labelIndicator) { const labelProps = childProps[eventKey].labels; - if (labelProps.calculatedLabelRadius > radius) { + if ( + labelIndicatorType === "singleLine" && + labelProps.calculatedLabelRadius > radius + ) { childProps[eventKey].labelIndicators = getLabelIndicatorPropsForLineSegment( Object.assign({}, props, dataProps), @@ -361,6 +429,18 @@ export const getBaseProps = (initialProps, fallbackProps) => { labelProps, ); } + + if (labelIndicatorType === "polyLine") { + const labelIndicatorProps = getLabelIndicatorPropsForPolyLineSegment( + Object.assign({}, props, dataProps), + calculatedValues, + ); + + childProps[eventKey].labelIndicators = labelIndicatorProps; + labelProps.x = labelIndicatorProps.x; + labelProps.y = labelIndicatorProps.y; + labelProps.textAnchor = labelIndicatorProps.textAnchor; + } } } return childProps; diff --git a/packages/victory-pie/src/slice.tsx b/packages/victory-pie/src/slice.tsx index c25d51982..b7154ea61 100644 --- a/packages/victory-pie/src/slice.tsx +++ b/packages/victory-pie/src/slice.tsx @@ -20,6 +20,7 @@ export type VictorySliceLabelPlacementType = | "parallel" | "perpendicular"; export type VictorySliceTTargetType = "data" | "labels" | "parent"; +export type VictorySliceLabelIndicatorType = "singleLine" | "polyLine"; export interface SliceProps extends VictoryCommonProps { ariaLabel?: StringOrCallback; diff --git a/packages/victory-pie/src/victory-pie.tsx b/packages/victory-pie/src/victory-pie.tsx index a8abd6c45..7e22f701c 100644 --- a/packages/victory-pie/src/victory-pie.tsx +++ b/packages/victory-pie/src/victory-pie.tsx @@ -20,6 +20,7 @@ import { VictoryStyleInterface, EventsMixinClass, VictoryDatableProps, + PolyLineSegment, } from "victory-core"; import { getBaseProps } from "./helper-methods"; import { @@ -28,6 +29,7 @@ import { VictorySliceTTargetType, VictorySliceLabelPlacementType, VictorySliceLabelPositionType, + VictorySliceLabelIndicatorType, } from "./slice"; export interface VictoryPieProps @@ -46,7 +48,9 @@ export interface VictoryPieProps innerRadius?: NumberOrCallback; labelIndicator?: boolean | React.ReactElement; labelIndicatorInnerOffset?: number; + labelIndicatorMiddleOffset?: number; labelIndicatorOuterOffset?: number; + labelIndicatorType?: VictorySliceLabelIndicatorType; labelPlacement?: | VictorySliceLabelPlacementType | ((props: SliceProps) => VictorySliceLabelPlacementType); @@ -86,6 +90,8 @@ const fallbackProps = { labelPosition: "centroid", labelIndicatorInnerOffset: 15, labelIndicatorOuterOffset: 5, + labelIndicatorMiddleOffset: 10, + labelIndicatorType: "singleLine", }; const datumHasXandY = (datum) => { @@ -170,6 +176,7 @@ class VictoryPieBase extends React.Component { groupComponent, labelIndicator, labelPosition, + labelIndicatorType, } = props; if (!groupComponent) { @@ -225,9 +232,12 @@ class VictoryPieBase extends React.Component { children.push(...labelComponents); } - if (showIndicator && labelIndicator) { + if (showIndicator) { let labelIndicatorComponent: React.ReactElement = ; + if (labelIndicatorType === "polyLine" && labelIndicator === true) { + labelIndicatorComponent = ; + } if (typeof labelIndicator === "object") { // pass user provided react component labelIndicatorComponent = labelIndicator; diff --git a/packages/victory/src/victory.test.ts b/packages/victory/src/victory.test.ts index bb0e16e03..8e8639950 100644 --- a/packages/victory/src/victory.test.ts +++ b/packages/victory/src/victory.test.ts @@ -50,6 +50,9 @@ import { Path, Point, PointProps, + PolyLine, + PolyLineSegment, + PolyLineSegmentProps, Portal, RawZoomHelpers, Rect, @@ -328,6 +331,8 @@ describe("victory", () => { "Path", "Point", "PointPathHelpers", + "PolyLine", + "PolyLineSegment", "Portal", "PortalContext", "RawZoomHelpers", diff --git a/stories/victory-pie.stories.tsx b/stories/victory-pie.stories.tsx index fc0ac49ea..1056bfa20 100644 --- a/stories/victory-pie.stories.tsx +++ b/stories/victory-pie.stories.tsx @@ -1,7 +1,12 @@ import React from "react"; import { VictoryPie, Slice } from "../packages/victory-pie"; import { VictoryTooltip } from "../packages/victory-tooltip"; -import { LineSegment, VictoryTheme, Helpers } from "../packages/victory-core"; +import { + LineSegment, + VictoryTheme, + Helpers, + PolyLineSegment, +} from "../packages/victory-core"; import { fromJS } from "immutable"; import styled from "styled-components"; import { Meta } from "@storybook/react"; @@ -624,6 +629,44 @@ export const LabelIndicator = () => { /> } /> + `${datum.name}`} + data={[ + { name: "Mark", y: 40 }, + { name: "Robert", y: 12 }, + { name: "Emily", y: 34 }, + { name: "Marion", y: 23 }, + { name: "Nicolas", y: 28 }, + { name: "Karen", y: 18 }, + ]} + /> + + } + /> ); };