I'm working to use Victory's charting library with React to render a Animating Circular Progress bar like so:
https://formidable.com/open-source/victory/gallery/animating-circular-progress-bar
Here is my code:
import React from 'react';
import {connect} from 'react-redux';
import {VictoryPie, VictoryAnimation, VictoryLabel} from 'victory';
class YourRatings2 extends React.Component {
constructor() {
super();
this.state = {
percent: 25, data: this.getData(0)
};
}
componentDidMount() {
let percent = 25;
}
getData(percent) {
return [{x: 1, y: percent}, {x: 2, y: 100 - percent}];
}
render() {
return (
<section className="YourRatings">
<h2>Core Skills</h2>
<div>
<svg viewBox="0 0 400 400" width="100%" height="100%">
<VictoryPie
animate={{duration: 1000}}
width={400} height={400}
data={this.state.data}
innerRadius={120}
cornerRadius={3}
labels={() => null}
style={{
data: { fill: (d) => {
const color = d.y > 30 ? "green" : "red";
return d.x === 1 ? color : "transparent";
}
}
}}
/>
<VictoryAnimation duration={1000} data={this.state}>
{(newProps) => {
return (
<VictoryLabel
textAnchor="middle" verticalAnchor="middle"
x={200} y={200}
text={`${Math.round(newProps.percent)}%`}
style={{ fontSize: 45 }}
/>
);
}}
</VictoryAnimation>
</svg>
</div>
</section>
);
}
}
const mapStateToProps = state => {
return {
};
};
export default connect(mapStateToProps)(YourRatings2);
Unfortunately, this is only rendering the text, not the full graph. see:
Any pointers as to what I am doing wrong?
I'm not an expert on this library but the example that you gave has an interval function to update this.state.data with the new percentage. You are setting initial percentage to 0 and not updating.
Setting the interval like the example or setting the initial state with the example below should fix the problem.
Example
constructor() {
super();
this.state = {
percent: 25,
data: this.getData(25)
};
}
I think you may need to add the prop standalone={false} to VictoryPie
Related
I'm trying to replicate this https://codepen.io/swizec/pen/bgvEvp
I've installed the d3-timer package with npm https://www.npmjs.com/package/d3-timer
It is definitely there because I have read through the files.
What I am confused about is how to import the timer into my code. In the code on codepen it just uses d3.timer but doesn't show the import above. So I tried importing d3 but it can't find it in the d3-timer package. I tried timer, Timer, D3, d3.
So my question is - how do I go about investigating the package to work out what the names of the exports are?
Or if that is too complicated - in this particular case what should I be importing to get the functionality of d3.timer?
Many thanks!
Code from code pen:
const Component = React.Component;
const Ball = ({ x, y }) => (
<circle cx={x} cy={y} r={5} />
);
const MAX_H = 750;
class App extends Component {
constructor() {
super();
this.state = {
y: 5,
vy: 0
}
}
componentDidMount() {
this.timer = d3.timer(() => this.gameLoop());
this.gameLoop();
}
componentWillUnmount() {
this.timer.stop();
}
gameLoop() {
let { y, vy } = this.state;
if (y > MAX_H) {
vy = -vy*.87;
}
this.setState({
y: y+vy,
vy: vy+0.3
})
}
render() {
return (
<svg width="100%" height={MAX_H}>
<Ball x={50} y={this.state.y} />
</svg>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'));
my code
import React from 'react';
import d3 from 'd3-timer'
const Component = React.Component;
const Ball = ({ x, y }) => (
<circle cx={x} cy={y} r={5} />
);
const MAX_H = 750;
export default class App extends Component {
constructor() {
super();
this.state = {
y: 5,
vy: 0
}
}
componentDidMount() {
this.timer = d3.timer(() => this.gameLoop());
this.gameLoop();
}
componentWillUnmount() {
this.timer.stop();
}
gameLoop() {
let { y, vy } = this.state;
if (y > MAX_H) {
vy = -vy*.87;
}
this.setState({
y: y+vy,
vy: vy+0.3
})
}
render() {
return (
<svg width="100%" height={MAX_H}>
<Ball x={50} y={this.state.y} />
</svg>
)
}
}
Error message:
Attempted import error: 'd3-timer' does not contain a default export (imported as 'd3').
Try
import { timer } from 'd3-timer' and then use timer()
I have a network that I want to draw with Konva (and the react-konva bindings). When positions update I want to animate the nodes in the network to their new positions while also animating the start and end position of the link that connects them.
I started with the following simple example, but can't seem to get a Line to animate in the same way that the nodes do.
Is there a way to fix this, or am I approaching it in the wrong way?
import React from "react";
import { Stage, Layer, Rect, Line } from "react-konva";
class Node extends React.Component {
componentDidUpdate() {
this.rect.to({
x: this.props.x,
y: this.props.y,
});
}
render() {
const { id } = this.props;
const color = id === "a" ? "blue" : "red";
return (
<Rect
ref={node => {
this.rect = node;
}}
width={5}
height={5}
fill={color}
/>
);
}
}
class Link extends React.Component {
componentDidUpdate() {
const x0 = 0;
const y0 = 0;
const x1 = 100;
const y1 = 100;
this.line.to({
x: x0,
y: y0,
points: [x1, y1, x0, y0],
});
}
render() {
const color = "#ccc";
return (
<Line
ref={node => {
this.line = node;
}}
stroke={color}
/>
);
}
}
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: [{ id: "a", x: 0, y: 0 }, { id: "b", x: 200, y: 200 }],
links: [
{
source: "a",
target: "b",
},
],
};
}
handleClick = () => {
const nodes = this.state.nodes.map(node => {
const position = node.x === 0 ? { x: 200, y: 200 } : { x: 0, y: 0 };
return Object.assign({}, node, position);
});
this.setState({
nodes,
});
};
render() {
const { links, nodes } = this.state;
return (
<React.Fragment>
<Stage width={800} height={800}>
<Layer>
{nodes.map((node, index) => {
return (
<Node
key={`node-${index}`}
x={node.x}
y={node.y}
id={node.id}
/>
);
})}
</Layer>
<Layer>
{links.map(link => {
return (
<Link
source={nodes.find(node => node.id === link.source)}
target={nodes.find(node => node.id === link.target)}
/>
);
})}
</Layer>
</Stage>
<button onClick={this.handleClick}>Click me</button>
</React.Fragment>
);
}
}
export default Graph;
You may need to set initial values for points attribute for a better tween.
Also, you are not using the source and target in the Link component. You should use that props for calculating animations.
import React from "react";
import { render } from "react-dom";
import { Stage, Layer, Rect, Line } from "react-konva";
class Node extends React.Component {
componentDidMount() {
this.rect.setAttrs({
x: this.props.x,
y: this.props.y
});
}
componentDidUpdate() {
this.rect.to({
x: this.props.x,
y: this.props.y
});
}
render() {
const { id } = this.props;
const color = id === "a" ? "blue" : "red";
return (
<Rect
ref={node => {
this.rect = node;
}}
width={5}
height={5}
fill={color}
/>
);
}
}
class Link extends React.Component {
componentDidMount() {
// set initial value:
const { source, target } = this.props;
console.log(source, target);
this.line.setAttrs({
points: [source.x, source.y, target.x, target.y]
});
}
componentDidUpdate() {
this.animate();
}
animate() {
const { source, target } = this.props;
this.line.to({
points: [source.x, source.y, target.x, target.y]
});
}
render() {
const color = "#ccc";
return (
<Line
ref={node => {
this.line = node;
}}
stroke={color}
/>
);
}
}
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: [{ id: "a", x: 0, y: 0 }, { id: "b", x: 200, y: 200 }],
links: [
{
source: "a",
target: "b"
}
]
};
}
handleClick = () => {
const nodes = this.state.nodes.map(node => {
const position = node.x === 0 ? { x: 200, y: 200 } : { x: 0, y: 0 };
return Object.assign({}, node, position);
});
this.setState({
nodes
});
};
render() {
const { links, nodes } = this.state;
return (
<React.Fragment>
<Stage width={800} height={300}>
<Layer>
{nodes.map((node, index) => {
return (
<Node
key={`node-${index}`}
x={node.x}
y={node.y}
id={node.id}
/>
);
})}
</Layer>
<Layer>
{links.map(link => {
return (
<Link
source={nodes.find(node => node.id === link.source)}
target={nodes.find(node => node.id === link.target)}
/>
);
})}
</Layer>
</Stage>
<button onClick={this.handleClick}>Click me</button>
</React.Fragment>
);
}
}
render(<Graph />, document.getElementById("root"));
Demo: https://codesandbox.io/s/react-konva-animating-line-demo-erufn
Using react-native, I'm creating sub-Components within the parent App and providing their position to the array this.state.objLocation within the parent App.
I can get the initial location data into the array straight after the render, but because my subcomponents are draggable, each time they re-render on drag, it adds a new position object to the array.
I'd like to avoid this, and I thought that creating this.state = { firstRender: true } in the constructor and then using componentDidMount = () => { this.setState({ firstRender: false }) } after the first render would allow me to create a 'gate' to stop the addition of the extra position objects.
I can see that if I comment out //componentDidMount = () => { this.setState({ firstRender: false }) } then I will get multiple entries to my array but if it's included in the class I get absolutely none.
So possibly my interpretation of the render lifecycle and componentDidMount is incorrect?
Here is my code.
// App
import React, { Component } from 'react';
import { View, Text, } from 'react-native';
import styles from './cust/styles';
import Draggable from './cust/draggable';
const dataArray = [{num: 1,id: 'A',},{num: 2,id: 'B',},{num: 3,id: 'Z',}]
export default class Viewport extends Component {
constructor(props){
super(props);
this.state = {
dID : null,
objLocation: [],
firstRender: true,
};
}
render(){
return (
<View style={styles.mainContainer}>
<View style={styles.draggableContainer}>
<Text>Draggable Container</Text> {dataArray.map( d => { return(
<Draggable
id={d.id}
onLayout={ e=> this.onLayout(e)}
onPanResponderGrant={(dID) =>this.setState({ dID })}
onPanResponderRelease={() => this.setState({dID: null})} /> ) })}
<View style={[styles.findPoint ]} />
</View>
<View style={styles.infoBar}>
<Text>{this.state.dID ? this.state.dID : ''}</Text>{this.compFrame()}
</View>
</View>
);
}
onLayout = (e) => {
if ( e && this.state.firstRender) {
const n = e.nativeEvent.layout;
const position = {
width: n.width,
height: n.height,
x: n.x,
y: n.y
}
console.log(position);
this.setState({
objLocation: this.state.objLocation.concat([position])
});
}
}
componentWillMount = () => {
console.log("START");
}
compFrame = () => {
return(
this.state.objLocation.map( d => {<View style={[styles.findPoint2,{left: d.x, top: d.y, width: d.width, height: d.height} ]} ></View>})
)
}
componentDidMount = () => {
this.setState({firstRender: true })
console.log(this.state.objLocation.length);
}
}
// Draggable
import React, { Component } from 'react';
import { Text, PanResponder, Animated } from 'react-native';
import styles from './styles';
class Draggable extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
};
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
this.props.onPanResponderGrant(this.props.id);
},
onPanResponderMove: Animated.event([ null, {
dx: this.state.pan.x,
dy: this.state.pan.y,
},
]),
onPanResponderRelease: () => {
Animated.spring(this.state.pan, { toValue: { x: 0, y: 0 } }).start();
this.props.onPanResponderRelease();
},
});
}
render() {
return (
<Animated.View
onLayout={ (e) => this.props.onLayout(e) }
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout(), styles.circleAlt, styles.position]}>
<Text style={styles.textAlt}>Drag me!</Text>
<Text style={styles.textNum}>{this.props.id}</Text>
</Animated.View>
);
}
componentDidMount = () => {
this.props.onLayout(this.props.dragEvent)
}
}
export default Draggable;
// Output of console.log
START xxx
0
{width:108,height:108,x:133.5,y:376.5}
{width:108,height:108,x:133.5,y:78.5}
{width:108,height:108,x:133.5,y:227.5}
You could set the firstRender state in onLayout function
onLayout = (e) => {
if ( e && this.state.firstRender) {
const n = e.nativeEvent.layout;
const position = {
width: n.width,
height: n.height,
x: n.x,
y: n.y
}
console.log(position);
this.setState({
firstRender: false,
objLocation: this.state.objLocation.concat([position])
});
}
}
According to the information provided by you, your onLayout function is called by the component so its not included in the component lifecycle process, so when the component completes its lifecycle it goes into componentDidMount after mounting (which is not calling onLayout func) & thus changed the firstRender state to false and hence when you drag the component each time it goes from true to false.
I hope this explains
I feel like I've hacked this, to get it to work, so please correct me as to correct procedure.
This is the onLayout method from the App. I've included an if statement that checks if the new positions array length is equal too the dataArray length that the draggable items are based on.
It looks like this.
onLayout = (e) => {
if ( this.state.objLocation.length != dataArray.length ) {
if ( e ) {
const n = e.nativeEvent.layout;
const position = {
width: n.width,
height: n.height,
x: n.x,
y: n.y
}
console.log(position);
this.setState({
objLocation: this.state.objLocation.concat([position])
});
}
}
}
import React, {Component} from 'react';
import './App.css';
class App extends Component {
render() {
return (
<div className="container">
<LightningCounterDisplay/>
</div>
);
}
}
class LightningCounter extends Component {
constructor(props) {
super(props);
this.state = {
strikes : 0
};
}
timerTick() {
this.setState({
strikes: this.state.strikes + 100
});
}
componentDidMount() {
setInterval(this.timerTick, 1000);
}
render() {
return (
<h1>{this.state.strikes}</h1>
);
}
}
class LightningCounterDisplay extends Component {
render() {
const divStyle = {
width: 250,
textAligh: "center",
backgroundColor: "black",
padding: 40,
fontFamily: "sans-serif",
color: "#999",
borderRadius: 10
}
return (
<div style={divStyle}>
<LightningCounter/>
</div>
);
}
}
export default App;
I started studying react.js and es6 from yesterday.
I tried to make a part that increased by 100 in one second.
but, it occurs TypeError: Cannot read property 'strikes' of undefined.
Can you tell where the problem is?
How should I fix it?
Thank you.
One solution is to use bind, alternatively you can also use arrow function for timerTick
timerTick = () => {
this.setState(prevState => ({
strikes: prevState.strikes + 100
}));
}
with arrow function, using setInterval(this.timerTick, 1000); would work.
I solved it through 'bind'.
componentDidMount() {
setInterval(this.timerTick.bind(this), 1000);
}
It works, but is this the right solution?
I want to animate the depth of the whole Card when the mouse is over it.
I try this (so-so I'm new in React) but I have no idea how to do it:
<Card
linkButton={true}
href="/servicios/"
onClick={Link.handleClick} zDepth={3}
onMouseEnter={this.setState({zDepth={1}})}>
</Card>
Thanks in advance.
5 years later and there is still no correct answer, you do not have to set the component state when it hovers, just use the pseudo-class :hover:
<Card
sx={{
':hover': {
boxShadow: 20, // theme.shadows[20]
},
}}
>
If you want to use styled():
const options = {
shouldForwardProp: (prop) => prop !== 'hoverShadow',
};
const StyledCard = styled(
Card,
options,
)(({ theme, hoverShadow = 1 }) => ({
':hover': {
boxShadow: theme.shadows[hoverShadow],
},
}));
<StyledCard hoverShadow={10}>
<Content />
</StyledCard>
Live Demo
constructor(props) {
super(props);
this.state = { shadow: 1 }
}
onMouseOver = () => this.setState({ shadow: 3 });
onMouseOut = () => this.setState({ shadow: 1 });
<Card
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
zDepth={this.state.shadow}
>
Updated #1
Full example
// StyledCard.js
import React, { Component } from 'react';
import { Card } from 'material-ui/Card';
class StyledCard extends Component {
state: {
shadow: 1
}
onMouseOver = () => this.setState({ shadow: 3 });
onMouseOut = () => this.setState({ shadow: 1 });
render() {
return (
<Card
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
zDepth={this.state.shadow}
>
{this.props.children}
</Card>
);
}
export default StyledCard;
.
// Container.js
import React from 'react';
import StyledCard from './StyledCard';
const Container = () => [
<StyledCard>Card 1</StyledCard>,
<StyledCard>Card 2</StyledCard>,
<StyledCard>Card 3</StyledCard>,
];
export default Container;
UPDATED #2
With HOC
// withShadow.js
import React from 'react';
const withShadow = (Component, { init = 1, hovered = 3 }) => {
return class extends React.Component {
state: {
shadow: init
};
onMouseOver = () => this.setState({ shadow: hovered });
onMouseOut = () => this.setState({ shadow: init });
render() {
return (
<Component
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
zDepth={this.state.shadow}
{...this.props}
/>
);
}
};
};
export default withShadow;
.
// Container.js
import React from 'react';
import { Card } from 'material-ui/Card';
import withShadow from './withShadow';
const CardWithShadow = withShadow(Card, { init: 2, hovered: 4 });
const Container = () => [
<CardWithShadow>Card 1</CardWithShadow>,
<CardWithShadow>Card 2</CardWithShadow>,
<CardWithShadow>Card 3</CardWithShadow>,
];
export default Container;
#Alex Sandiiarov answer didnt work for me. The docs show to use the raised property.
https://material-ui.com/api/card/
class Component extends React.Component{
state = {
raised:false
}
toggleRaised = () => this.setState({raised:!this.state.raised});
render(){
return <Card onMouseOver={this.toggleRaised}
onMouseOut={this.toggleRaised}
raised={this.state.raised}>
...
</Card>
}
}