Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow passing CustomIcon to Symbol in VictoryLegend and VictoryScatter #2900

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/neat-pandas-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"victory-core": minor
"victory-scatter": minor
---

Allow passing CustomIcon to Symbol in VictoryLegend and VictoryScatter
19 changes: 17 additions & 2 deletions demo/ts/components/victory-legend-demo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { VictoryLabel, Border } from "victory-core";
import { VictoryLegend } from "victory-legend";
import { FaMoon, FaSun } from 'react-icons/fa'

const containerStyle: React.CSSProperties = {
display: "flex",
Expand All @@ -18,6 +19,12 @@ const legendStyle = {

const symbolSize = 5;
const symbolSpacer = 10;

const CustomSun = (props) => {
return (
<FaSun {...props} x={props.x - 7} y={props.y - 7} />)
}

const data = [
{
name: "Series 1",
Expand Down Expand Up @@ -72,18 +79,26 @@ const data = [
name: "Series 6: also quite long",
symbol: {
size: symbolSize,
type: "circle",
type: <CustomSun size={15} />,
fill: "orange",
},
labels: {
fill: "blue",
},
},
{
name: "Series 7",
symbol: {
size: symbolSize,
type: (props) => <FaMoon {...props} x={props.x - 8} y={props.y - 8} size={15} />,
fill: "orange",
},
},
];

const LegendDemo = () => (
<div className="demo" style={containerStyle}>
<svg height={800} width={1000} style={{ margin: "2%" }}>
<svg height={800} width={1200} style={{ margin: "2%" }}>
<VictoryLegend
standalone={false}
x={25}
Expand Down
29 changes: 29 additions & 0 deletions demo/ts/components/victory-scatter-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "victory-core";
import bubbleData from "./bubble-data";
import symbolData from "./symbol-data";
import { FaFootballBall, FaMoon, FaSmile, FaStar, FaSun, FaVolleyballBall } from "react-icons/fa";

type DataType = {
x?: string | number;
Expand Down Expand Up @@ -136,6 +137,8 @@ class CatPoint extends React.Component<any, CatPointInterface> {
}
}

const CustomIcon = (props) => <FaStar x={props.x - 25} y={props.y - 25} size={15} />

export default class VictoryScatterDemo extends React.Component<
any,
VictoryScatterDemoState
Expand Down Expand Up @@ -208,7 +211,33 @@ export default class VictoryScatterDemo extends React.Component<
y={(d) => Math.sin(2 * Math.PI * d.x)}
samples={25}
/>
{/* custom icons */}
<VictoryScatter
style={{
parent: style.parent
}}
data={[
{ x: 1, y: 45, symbol: (props) => <FaSun {...props} /> },
{ x: 2, y: 85, symbol: <FaVolleyballBall size={30} /> },
{ x: 3, y: 55, symbol: <CustomIcon /> },
{
x: 4, y: 25, symbol: (props) => {
const x = props.x - 10;
const y = props.y - 10;
return <FaFootballBall x={x} y={y} size={40} />;
}
},
{ x: 5, y: 65, symbol: <FaSmile /> }
]}

size={50}
/>
<VictoryScatter
style={{
parent: style.parent
}}
symbol={<FaMoon size={10} />}
/>
<VictoryScatter
style={symbolStyle}
width={500}
Expand Down
1 change: 1 addition & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"prism-react-renderer": "^2.3.1",
"react-cool-inview": "^3.0.1",
"react-copy-to-clipboard": "^5.1.0",
"react-icons": "^5.3.0",
"react-inlinesvg": "^4.1.1",
"react-live": "^4.1.6",
"react-markdown": "^9.0.1",
Expand Down
57 changes: 54 additions & 3 deletions docs/src/content/docs/victory-legend.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ id: 16
title: VictoryLegend
category: more
type: docs
scope: null
scope:
- reactIconsFa
---

# VictoryLegend
Expand Down Expand Up @@ -112,10 +113,51 @@ containerComponent={<VictoryContainer responsive={false}/>}

`type: array[{ name, symbol, labels }]`

Specify data via the `data` prop. `VictoryLegend` expects data as an array of objects with `name` (required), `symbol`, and `labels` properties. The `data` prop must be given as an array. The symbol rendered may be changed by altering the `type` property of the `symbol` object. Valid types include: circle", "diamond", "plus", "minus", "square", "star", "triangleDown", and "triangleUp"
Specify data via the `data` prop. `VictoryLegend` expects data as an array of objects with `name` (required), `symbol`, and `labels` properties. The `data` prop must be given as an array. The symbol rendered may be changed by altering the `type` property of the `symbol` object. Valid types include: circle", "diamond", "plus", "minus", "square", "star", "triangleDown", and "triangleUp". It also supports custom icons.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds a bit more description

Suggested change
Specify data via the `data` prop. `VictoryLegend` expects data as an array of objects with `name` (required), `symbol`, and `labels` properties. The `data` prop must be given as an array. The symbol rendered may be changed by altering the `type` property of the `symbol` object. Valid types include: circle", "diamond", "plus", "minus", "square", "star", "triangleDown", and "triangleUp". It also supports custom icons.
Specify data via the `data` prop. `VictoryLegend` expects data as an array of objects with `name` (required), `symbol`, and `labels` properties. The `data` prop must be given as an array. The symbol rendered may be changed by altering the `type` property of the `symbol` object. Valid types include: circle", "diamond", "plus", "minus", "square", "star", "triangleDown", and "triangleUp". It also supports SVG icons from a custom component or an SVG based icon library like [react-icons](https://react-icons.github.io/react-icons/)


_default:_ `data={[{ name: "Series 1" }, { name: "Series 2" }]}`

custom icons example:
```jsx
import { FaSun, FaFootballBall } from "react-icons/fa";

const CustomIconSun = (props) => {
const x = props.x - 11;
const y = props.y - 11;
return (
<FaSun {...props} size={20} x={x} y={y} />
)
}
const App (){
return (
<VictoryLegend
orientation="vertical"
rowGutter={0}
style={{
labels: { lineHeight: 0.275 },
}}
symbolSpacer={18}
data={[
{ name: "One", symbol: { type: <CustomIconSun />, fill: "red" } },
{ name: "Three", symbol: { type: "star", fill: "brown", size: 6 } },
{
name: "Five",
symbol: {
type: (props) => {
const x = props.x - 8;
const y = props.y - 10;
return <FaFootballBall {...props} x={x} y={y} size={15} />;
},
fill: "red",
},
},
]}
/>;
)
}
```

Icons from `react-icons/fa` can be used inside playground for testing.
```playground
<VictoryLegend x={125} y={50}
orientation="horizontal"
Expand All @@ -124,7 +166,16 @@ _default:_ `data={[{ name: "Series 1" }, { name: "Series 2" }]}`
data={[
{ name: "One", symbol: { fill: "tomato", type: "star" } },
{ name: "Two", symbol: { fill: "orange" }, labels: { fill: "orange" } },
{ name: "Three", symbol: { fill: "gold" } }
{ name: "Three", symbol: { fill: "gold" } },
{ name: "Four", symbol: {
type: (props)=>{
const FaSun = reactIconsFa['FaSun'];
const x = props.x - 8;
const y = props.y - 8;
return <FaSun size={15} x={x} y={y}/>
}
}
},
]}
/>
```
Expand Down
3 changes: 2 additions & 1 deletion docs/src/content/docs/victory-primitives.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,8 @@ _note_ `Box` also exported as `Border`
- `shapeRendering` _string_ the shape rendering attribute to apply to the rendered path
- `size` _number or function_ the size of the point. When this prop is given as a function, it will be called with the rest of the props supplied to `Point`.
- `style` _object_ the styles to apply to the rendered element
- `symbol` _"circle", "cross", "diamond", "plus", "minus", "square", "star", "triangleDown", "triangleUp"_ which symbol the point should render. This prop may also be given as a function that returns one of the above options. When this prop is given as a function, it will be called with the rest of the props supplied to `Point`.
- `symbol` _"circle", "cross", "diamond", "plus", "minus", "square", "star", "triangleDown", "triangleUp"_ , which symbol the point should render.It also supports "custom react icons".
This prop may also be given as a function that returns one of the above options. When this prop is given as a function, it will be called with the rest of the props supplied to `Point`.
- `tabIndex` _number or function_ number will be applied to the rendered path. When this prop is given as a function it will be called with the rest of the props supplied to `Point`
- `transform` _string_ a transform that will be supplied to elements this component renders
- `x` _number_ the x coordinate of the center of the point
Expand Down
47 changes: 42 additions & 5 deletions docs/src/content/docs/victory-scatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ category: charts
type: docs
scope:
- sampleData
- reactIconsFa
---

# VictoryScatter
Expand Down Expand Up @@ -530,16 +531,52 @@ _default (provided by default theme):_ See [grayscale theme][] for more detail

`type: function || options`

The `symbol` prop determines which symbol should be drawn to represent data points. Options are: "circle", "cross", "diamond", "plus", "minus", "square", "star", "triangleDown", "triangleUp". When this prop is given as a function, it will be evaluated for each point with the props corresponding to that point. If no `symbol` prop is specified, a circle will be rendered. `symbol` may also be set directly on each data object.
The `symbol` prop determines which symbol should be drawn to represent data points. Options are: "circle", "cross", "diamond", "plus", "minus", "square", "star", "triangleDown", "triangleUp".It also supports "custom React icons". When this prop is given as a function, it will be evaluated for each point with the props corresponding to that point. If no `symbol` prop is specified, a circle will be rendered. `symbol` may also be set directly on each data object.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor fix for spacing and slight reword

Suggested change
The `symbol` prop determines which symbol should be drawn to represent data points. Options are: "circle", "cross", "diamond", "plus", "minus", "square", "star", "triangleDown", "triangleUp".It also supports "custom React icons". When this prop is given as a function, it will be evaluated for each point with the props corresponding to that point. If no `symbol` prop is specified, a circle will be rendered. `symbol` may also be set directly on each data object.
The `symbol` prop determines which symbol should be drawn to represent data points. Options are: "circle", "cross", "diamond", "plus", "minus", "square", "star", "triangleDown", "triangleUp". It also supports SVG icons from a custom component or an SVG based icon library like [react-icons](https://react-icons.github.io/react-icons/). When this prop is given as a function, it will be evaluated for each point with the props corresponding to that point. If no `symbol` prop is specified, a circle will be rendered. `symbol` may also be set directly on each data object.


_default:_ `symbol="circle"`

```jsx
import { FaSun,FaStar, FaVolleyballBall } from "react-icons/fa";
const CustomIcon = (props) => <FaStar x={props.x - 25} y={props.y - 25} />
const App(){
return <VictoryScatter
data={[
{ x: 1, y: 85, symbol: <FaVolleyballBall size={20} /> },
{ x: 2, y: 55, symbol: <CustomIcon /> },
{ x: 3, y: 25, symbol: (props) => {
const x = props.x - 20;
const y = props.y - 20;
return <FaFootballBall x={x} y={y} size={40} />;
}
}
]}
size={50}
/>
}
```

```playground
<VictoryScatter
symbol={({ datum }) => datum.y > 3 ? "triangleUp" : "triangleDown"}
size={7}
data={sampleData}
<>
<VictoryScatter
symbol={({ datum }) => datum.y > 3 ? "triangleUp" : "triangleDown"}
size={7}
data={sampleData}
/>
<VictoryScatter
size={10}
data={[
{ x: 1, y: 85, symbol: "star" },
{ x: 2, y: 55,
symbol: (props)=>{
const FaSun = reactIconsFa['FaSun'];
const x = props.x - 8;
const y = props.y - 8;
return <FaSun size={30} x={x} y={y}/>
}
},
]}
/>
</>
```

## theme
Expand Down
2 changes: 2 additions & 0 deletions docs/src/partials/markdown/scope-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import _ from "lodash";
import * as d3Array from "d3-array";
import * as d3Scale from "d3-scale";
import * as d3Time from "d3-time";
import * as reactIconsFa from 'react-icons/fa'

import styled from "styled-components";
import basketballData from "../../data/basketball-data";
Expand All @@ -25,6 +26,7 @@ const scopeMap = {
Slider,
basketballData,
listeningData,
reactIconsFa,
sampleData: [
{
x: 1,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-hot-loader": "4.13.0",
"react-icons": "^5.3.0",
"react-test-renderer": "^18.1.0",
"remark-parse": "^7.0.1",
"remark-stringify": "^7.0.3",
Expand Down
49 changes: 41 additions & 8 deletions packages/victory-core/src/victory-primitives/point.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { isValidElement } from "react";
import { defaults } from "lodash";

import * as Helpers from "../victory-util/helpers";
Expand All @@ -15,7 +15,7 @@ export interface PointProps extends VictoryCommonPrimitiveProps {
// eslint-disable-next-line @typescript-eslint/ban-types
size?: number | Function;
// eslint-disable-next-line @typescript-eslint/ban-types
symbol?: ScatterSymbolType | Function;
symbol?: ScatterSymbolType | Function | React.ReactElement;
x?: number;
y?: number;
}
Expand Down Expand Up @@ -49,9 +49,19 @@ const evaluateProps = (props) => {
const id = Helpers.evaluateProp(props.id, props);
const size = Helpers.evaluateProp(props.size, props);
const style = Helpers.evaluateStyle(props.style, props);
const symbol = Helpers.evaluateProp(props.symbol, props);
const tabIndex = Helpers.evaluateProp(props.tabIndex, props);

let isSymbolCallbackFn = false;
let symbol;

if (typeof props.symbol === "function") {
const symbolFn = props.symbol(props);
isSymbolCallbackFn = isValidElement(symbolFn);
symbol = isSymbolCallbackFn ? props.symbol : symbolFn;
} else {
symbol = props.symbol;
}

return Object.assign({}, props, {
ariaLabel,
desc,
Expand All @@ -60,11 +70,11 @@ const evaluateProps = (props) => {
style,
symbol,
tabIndex,
isSymbolCallbackFn,
});
};

const defaultProps = {
pathComponent: <Path />,
role: "presentation",
shapeRendering: "auto",
};
Expand All @@ -73,18 +83,41 @@ export const Point = (initialProps: PointProps) => {
const props = evaluateProps(defaults({}, initialProps, defaultProps));
const userProps = UserProps.getSafeUserProps(props);

return React.cloneElement(props.pathComponent!, {
const commonProps = {
...props.events,
"aria-label": props.ariaLabel,
d: getPath(props),
style: props.style,
desc: props.desc,
tabIndex: props.tabIndex,
role: props.role,
shapeRendering: props.shapeRendering,
className: props.className,
transform: props.transform,
clipPath: props.clipPath,
...userProps,
};

// check if symbol is React element
if (isValidElement(props.symbol) || props.isSymbolCallbackFn) {
const symbolProps = {
...commonProps,
height: props.size,
width: props.size,
x: props.x,
y: props.y,
};
if (props.isSymbolCallbackFn) {
const symbol = props.symbol(symbolProps);
return React.cloneElement(symbol);
}
return React.cloneElement(props.symbol, {
...symbolProps,
});
}

const pathComponent = <Path />;
return React.cloneElement(pathComponent!, {
d: getPath(props),
desc: props.desc,
clipPath: props.clipPath,
...commonProps,
});
};
Loading