I am using Victory for data visualisation in my project. However, while implementing event handlers in my charts, I noticed that while they change the target properties, the charts are never re-rendered so nothing changes.
Below is an example from Victory's documentation, which does not work on my machine:
<div>
<h3>Click Me</h3>
<VictoryScatter
style={{ data: { fill: "#c43a31" } }}
size={9}
labels={() => null}
events={[{
target: "data",
eventHandlers: {
onClick: () => {
return [
{
target: "data",
mutation: (props) => {
const fill = props.style && props.style.fill;
return fill === "black" ? null : { style: { fill: "black" } };
}
}, {
target: "labels",
mutation: (props) => {
return props.text === "clicked" ?
null : { text: "clicked" };
}
}
];
}
}
}]}
data={[{ x: 1, y: 2 },
{ x: 2, y: 3 },
{ x: 3, y: 5 },
{ x: 4, y: 4 },
{ x: 5, y: 7 }]}
/>
</div>
After some debugging, I can confirm that the data and labels properties of the component are changed as a result of the onClick event, but these changes are not actually reflected in the chart. Any solutions?
It seems the issue is caused by having <React.StrictMode> on in the code inside index.js. Removing it solves the problem. I am not exactly sure why though!!
Related
I try to configure the license of Fusionchart in ReactNative as in this URL https://www.npmjs.com/package/react-native-fusioncharts#license-configuration.
But still, it shows the watermark which should not be visible. Is there anything I missed?
import React, { Component } from 'react';
import { AppRegistry, StyleSheet, Text, View, Platform } from 'react-native';
import ReactNativeFusionCharts from 'react-native-fusioncharts';
global.licenseConfig = {
key: "license-key",
creditLabel: false // true/false to show/hide watermark respectively
};
export default class App extends Component {
constructor(props) {
super(props);
//STEP 2 - Chart Data
const chartData = [
{ label: 'Venezuela', value: '250' },
{ label: 'Saudi', value: '260' },
{ label: 'Canada', value: '180' },
{ label: 'Iran', value: '140' },
{ label: 'Russia', value: '115' },
{ label: 'UAE', value: '100' },
{ label: 'US', value: '30' },
{ label: 'China', value: '30' },
];
//STEP 3 - Chart Configurations
const chartConfig = {
type: 'column2d',
width: 400,
height: 400,
dataFormat: 'json',
dataSource: {
chart: {
caption: 'Countries With Most Oil Reserves [2017-18]',
subCaption: 'In MMbbl = One Million barrels',
xAxisName: 'Country',
yAxisName: 'Reserves (MMbbl)',
numberSuffix: 'K',
theme: 'fusion',
exportEnabled: 1, // to enable the export chart functionality
},
data: chartData,
},
};
const events = {
// Add your events method here:
// Event name should be in small letters.
dataPlotClick: (ev, props) => {
console.log('dataPlotClick');
},
dataLabelClick: (ev, props) => {
console.log('dataLabelClick');
},
};
this.state = {
chartConfig,
events
};
}
render() {
return (
<View style={styles.container}>
<Text style={styles.heading}>FusionCharts Integration with React Native</Text>
<View style={styles.chartContainer}>
<ReactNativeFusionCharts chartConfig={this.state.chartConfig} events={this.state.events} />
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 50,
height: 500,
backgroundColor: 'white'
},
heading: {
fontSize: 20,
textAlign: 'center',
marginBottom: 10,
},
chartContainer: {
borderColor: 'red',
borderWidth: 1,
height: 500,
},
});
// skip this line if using Create React Native App
AppRegistry.registerComponent('ReactNativeFusionCharts', () => App);
I also add the below code in the root component but not worked.
global.licenseConfig = {
key: "license-key",
creditLabel: false // true/false to show/hide watermark respectively
};
Answering my own question. Hope this will be helpful to someone.
Issue is latest react-native-fusionchart 5.0.0 is not updated with fusionchart 3.17.0. You may need to manually copy the fusionchart content to react-native-fusionchart.
Copy the node_module/fusionchart content into node_modules/react-native-fusioncharts/src/modules/fusionchart folder and run below script.
find fusioncharts -name "*.js" -exec sh -c 'mv "$0" "${0%.js}.fcscript"' {} \;
Then the watermark vanishes as expected. These steps are configured in the gulp script but somehow it seems to be not working.
Hope this issue will be fixed soon.
Currently been using react-spring to do our animations in our app; unfortunately, animation is not something I excel in and the design our designer gave us for our new logo is leaving me stumped on implementation. It is pretty easy with plain old JS but implementing it in react-spring has proved a challenge that I can not get past.
The end goal for the animation is to look like this:
https://codepen.io/darylginn/pen/GRqZxBZ
Currently, I am up to this stage:
import React from "react";
import { useTrail, animated } from "react-spring";
const Loader: React.FC = () => {
// Animations
const paths = [
{
id: 1,
color: "#466FB5",
d: "M90.6672 33H16L53.3336 96.4409L90.6672 33Z",
},
{
id: 2,
color: "#0093D3",
d: "M53.3347 96.4443H128.002L90.6683 33.0034L53.3347 96.4443Z",
},
{
id: 3,
color: "#53BFA2",
d: "M128.001 96.3701H53.3336L90.6672 159.811L128.001 96.3701Z",
},
{
id: 4,
color: "#93C83F",
d: "M90.6675 159.892H165.335L128.001 96.4417L90.6675 159.892Z",
},
{
id: 5,
color: "#58B647",
d: "M165.334 159.892H90.6664L128 223.333L165.334 159.892Z",
},
{
id: 6,
color: "#F2E90B",
d: "M202.667 96.4436H128L165.334 159.894L202.667 96.4436Z",
},
{
id: 7,
color: "#FBB12C",
d: "M128.001 96.4443H202.668L165.335 33.0034L128.001 96.4443Z",
},
{
id: 8,
color: "#FF5E8D",
d: "M240 33H165.333L202.666 96.4409L240 33Z",
},
];
const trail = useTrail(paths.length, {
from: {
scale: 1,
},
to: async (next: any) => {
while (1) {
await next({ scale: 1.5 });
await next({ scale: 1 });
}
},
});
return (
<div style={{ width: "200px", height: "200px" }}>
<svg
width="72"
height="72"
viewBox="0 0 256 256"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{ overflow: "visible" }}>
{trail.map(({ scale }, index) => {
const path = paths[index];
return (
<animated.path
key={path.id}
fill={path.color}
d={path.d}
style={{
transformOrigin: "center",
transform: scale.interpolate((s: any) => `scale(${s})`),
}}
/>
);
})}
</svg>
</div>
);
};
The main issue I am at now is the scale of each triangle in the SVG needs to happen one after another, but nothing I do with the useTrail make this happen.
I did try adding a delay like this to the useTrail
delay: (key: any) => key * 200
But the delay doesn't even seem to make a difference. If someone could help make sure each triangle finish it sequences before the next one starts that would be great.
Bonus if you can also help me add the colour change as seen in the original design (see link).
I have tried posting in the react-spring community but got no replies
I would change the useTrail to one useSpring for all the triangles. If you change the x value from 0 to 8, then you can use interpolate and a range for each triangle to change. For example for the second triangle you can use range:[0,1,1.5,2,8],output:[1,1,1.5,1,1]. It means that when x is between 1 and 2 it will change the scale from 1 to 1.5 to 1 and all other places it will remain 1.
const props = useSpring({
from: {
x: 0
},
config: {
duration: 4000
},
to: async (next: any) => {
while (1) {
await next({ x: 8 });
await next({ reset: true });
}
}
});
I also added the color interpolation.
{paths.map((path, index) => {
const colors = [];
for (let i = 0; i < 8; i++) {
colors.push(paths[(i + index) % 8].color);
}
return (
<animated.path
key={path.id}
fill={path.color}
d={path.d}
style={{
transformOrigin: "center",
transform: props.x
.interpolate({
range: [0, index, index + 0.5, index + 1, 8],
output: [1, 1, 1.5, 1, 1]
})
.interpolate((x) => `scale(${x})`),
fill: props.x.interpolate({
range: [0, 1, 2, 3, 4, 5, 6, 7, 8],
output: colors
})
}}
/>
);
})}
I have an example. There is some glitch with the center triangle. But I think you get the idea.
Example: https://codesandbox.io/s/animate-triangles-sequentially-with-interpolating-and-range-oer84
I'm using Nivo bar to represent a user's progress on a budget. I've normalized the data by dividing the category balance by the category goal. Example data.
[{
"category": "Gas",
"budget": 0.24,
"over_budget": 0.0
},
{
"category": "Groceries",
"budget": 1.0,
"over_budget": 0.26
}]
I don't want these values to be used as the label on the chart. I plan to use the actual balance value as the label. I have an endpoint that will return the balance for a category and have attempted the following to use that value:
<ResponsiveBar
...
label={d => this.getDollarAmount(d.value)}
...
>
With the function POC as:
getDollarAmount(value) {
console.log("hitting getDollarAmount")
return 1
};
The log message gets logged 500+ times. My expectation would be that the function would only be hit once for each bar in the chart.
I'm still learning react so this could be something obvious. Thanks in advance!
EDIT - Here's the entire BarChart component:
import axios from 'axios';
import React, { Component } from "react";
import { ResponsiveBar } from '#nivo/bar'
// Nivo theming
const theme = {
axis: {
ticks: {
line: {
stroke: "#e9ecee",
strokeWidth: 40
},
text: {
// fill: "#919eab",
fill: "black",
fontFamily: "BlinkMacSystemFont",
fontSize: 16
}
}
},
grid: {
line: {
stroke: "#e9ecee",
strokeWidth: 5
}
},
legends: {
text: {
fontFamily: "BlinkMacSystemFont"
}
}
};
let budgetStatusAPI = 'http://127.0.0.1:8000/api/budget_status/?auth_user=1&month=2020-02-01';
class BarChart extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
this.getDollarAmount = this.getDollarAmount.bind(this);
}
componentDidMount() {
console.log("component did mount")
axios.get(budgetStatusAPI).then(response => {
this.setState({
data: response.data
}, function () {
console.log(this.state.data);
})
});
}
componentDidUpdate() {
console.log("component did update")
}
getDollarAmount(value) {
console.log("hitting getDollarAmount")
console.log(value)
return 1
};
render() {
const hard_data = [
{
"category": "Groceries",
"budget_status": 1.0,
"over_budget": .26,
},
{
"category": "Gas",
"budget_status": .24,
"over_budget": 0.0,
}]
return(
<ResponsiveBar
maxValue={1.5}
markers={[
{
axis: 'x',
value: 1,
lineStyle: { stroke: 'rgba(0, 0, 0, .35)', strokeWidth: 2 },
legend: 'Goal',
legendOrientation: 'horizontal',
legendPosition: 'top'
},
]}
enableGridX={false}
gridXValues={[1]}
enableGridY={false}
data={this.state.data}
// data={hard_data}
keys={['budget_status', 'over_budget']}
indexBy="category"
margin={{ top: 25, right: 130, bottom: 50, left: 125 }}
padding={0.3}
layout="horizontal"
colors={{ scheme: 'set2' }}
theme={theme}
defs={[
{
id: 'dots',
type: 'patternDots',
background: 'inherit',
color: '#38bcb2',
size: 4,
padding: 1,
stagger: true
},
{
id: 'lines',
type: 'patternLines',
background: 'inherit',
color: '#eed312',
rotation: -45,
lineWidth: 6,
spacing: 10
}
]}
borderColor={{ from: 'color', modifiers: [ [ 'darker', 1.6 ] ] }}
axisBottom={null}
label={d => this.getDollarAmount(d.value)}
isInteractive={false}
animate={true}
motionStiffness={90}
motionDamping={15}
/>
)
}
}
export default BarChart;
Reproduced here: https://codesandbox.io/s/nivo-bar-label-issue-k4qek
The multiple calling is happening because the bar chart is calling label function for each animation tick/frame render. If we setup a counter, we'll see with animate prop set to true it will render from 450+ to 550+ times, but if we set the prop animate to false, we'll it renders 6 times which is exactly how many price values are > 0.0.
If you want to avoid all these renders, you'll have to disable animation using animate={false} prop like this:
getDollarAmount(value) {
// Remove every console.log inside this function
return `$${value}`
}
render() {
return (
<ResponsiveBar
animate={false}
label={d => this.getDollarAmount(d.value)}
...
);
}
You can check it running to your cloned CodeSandbox. I have set animate to false and the counter log inside getDollarAmount is calling 6 times. Try to change animate to true and you'll see the 500+- renders.
Also, you don't have to create a function for each label call, you can just pass the getDollarAmount function and let it handle the whole d parameter like this:
getDollarAmount(d) {
// Remove every console.log inside this function
return `$${d.value}`
}
render() {
return (
<ResponsiveBar
animate={false}
label={this.getDollarAmount}
...
);
}
I am using victory-native chart to render a pie chart. I am confused on how to pass the data fetched from Rest API to the {data} being passed to the pie chart for it's 'y' values. Here is my code:
import React, {Component} from "react";
import { StyleSheet, View } from "react-native";
import axios from 'axios'
import { VictoryPie, VictoryGroup, VictoryTheme } from "victory-native";
import Svg from 'react-native-svg';
export default class Chart extends Component {
state= {
id: '********************',
data: []
}
componentDidMount(){
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Access-Control-Allow-Headers': 'x-access-token',
'x-access-token': this.state.id
}
axios.post('http://bi.servassure.net/api/SalesOverviewOEMLevel2', {
oem:'all'
},
{headers: headers}).then((res) => {
let TotalSales = res.data.data[0].TotalSales;
this.setState({
data: TotalSales
})
console.log(this.state.data);
})
.catch(err => {
console.log(err)
});
}
render() {
const myColorScale = ["#1b4f72", "#21618c", "#2e86c1","#3498db", "#76d7c4", "#d1f2eb"];
const data = [
{ x: 1, y: 6, i: 0 },
{ x: 2, y: 2, i: 1 },
{ x: 3, y: 3, i: 2 },
{ x: 4, y: 5, i: 3 },
]
return (
<View style={styles.container}>
<Svg
height={200}
width={200}
viewBox={'0 0 300 300'}
preserveAspectRatio='none'
>
<VictoryGroup width={300} theme={VictoryTheme.material}>
<VictoryPie
style={{
data: {
fill: (d) => myColorScale[d.i]
},
labels: { fill: "white", fontSize: 10, fontWeight: "bold" }
}}
radius={100}
innerRadius={60}
labelRadius={70}
data={data}
labels={(d) => `y: ${d.y}`}
/>
</VictoryGroup>
</Svg>
</View>
);
}
}
This is the consoled form of my data that I want to pass into as 'Y' values to the chart:
The static data is rendering chart perfectly, but stuck on dynamic on how to loop the values. Please help to figure out.
You need to transform your data to match your example data:
Try this:
const your_data = this.state.data.map((val, index) => {
// you can also pass an i- value, but that's up to you
return { x: index, y: val };
});
and then:
<VictoryPie
style={{
data: {
fill: (d) => myColorScale[d.x] // d.x, because i didn't specify an "i-value"
},
labels: { fill: "white", fontSize: 10, fontWeight: "bold" }
}}
radius={100}
innerRadius={60}
labelRadius={70}
data={your_data} // pass here your new data
labels={(d) => `y: ${d.y}`}
/>
This is a video of what I have so far https://drive.google.com/file/d/1tJMZfpe8hcqLcNMYiE-6pzwHX0eLofv1/view?usp=sharing
I'm trying to make the "Available for 3 months" fixed once the contentOffset.y has reached the header. Similar to what a position: sticky in css would do.
So far, I thought of doing this via onScroll prop in the Scroll View Component but the issue is, I already have animations (Animated.Event) from the parent and child component in place, meaning the only way for me to do this would be via the listener function in the Animated.Event but doing that, leads to a super choppy animation if the useNativeDriver option is set to false.
If I set that to true, the whole thing won't work (it crashes). The error is along the lines "onScroll is not a function, it's an instance of Animated.event"
So, Say we have two components, the parent is Parent.js and the child (a scroll view) is ChildScrollView.js
The ChildScrollView.js already has animations on the scroll view, but we need to add some more in the Parent.js component, to do with Navigation which the ChildScrollView.js can't handle
So it's coded like this:
Parent.js
componentWillMount() {
const { navigation } = this.props
const { scrollY } = this.state
const bgColor = scrollY.interpolate({
inputRange: [HEADER_HEIGHT / 4, HEADER_HEIGHT / 2],
outputRange: ['transparent', 'rgb(255,255,255)'],
extrapolate: 'clamp',
})
const titleColor = scrollY.interpolate({
inputRange: [0, HEADER_HEIGHT / 2],
outputRange: ['transparent', colors.paragraphs],
extrapolate: 'clamp',
})
const titleMarginTop = scrollY.interpolate({
inputRange: [0, HEADER_HEIGHT / 2],
outputRange: [HEADER_HEIGHT, 0],
extrapolate: 'clamp',
})
navigation.setParams({
bgColor,
titleColor,
titleMarginTop,
})
}
onScroll() {
}
render() {
return (
<ChildScrollView
{...childProps}
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: scrollY } } },
], {
useNativeDriver: false, // when I do this, it become super laggy, but setting it to true makes the app crash
listener: this.onScroll,
})}
>
<MakeMeFixedOnScroll>I could do this in css with "position: sticky"</MakeMeFixedOnScroll>
</ChildScrollView>
)
}
And the child is similar,
<Animated.ScrollView
{...props}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
{
useNativeDriver: false,
listener: event => {
if (onScroll) onScroll(event)
},
}
)}
scrollEventThrottle={16}
>
I would use SectionList
<SectionList
renderItem={({item, index, section}) => (
<Text style={styles[item.type]}>{item.text}</Text>
)}
renderSectionHeader={({ section: { title } }) => (
title && <Text style={styles.sticky}>{title}</Text>
)}
sections={[
{ title: null, data: [{
type: 'heading', text: '133 Random Road'
}, {
type: 'heading', text: 'Donnybrook'
}, {
type: 'subtitle', text: 'Dublin 4'
}, {
type: 'title', text: 'From E1000/month'
}]
},
{ title: 'Available For 3 Month', data: [{
type: 'text', text: 'Beautiful for bedroom...'
}]
}
]}
stickySectionHeadersEnabled
/>