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}`}
/>
Related
I'm working on apexcharts. I managed to pass data in chart, but there seem to be issue with timestamps. I have two data i.e sender and receiver, each has its own timestamps. the graph's x-axis is based on the timestamps. but I don't know how to map the timestamp with its respective data. currently, the data in the graph are not showing right due to the timestamp issue. kindly help me to fix it. thanks in advance
here's my full code
const receiverData = [];
const senderData = [];
const timeData = [];
export default function GraphCard() {
const [GraphData, setGraphData] = useState([])
const showData = () => {
axios.get('http://localhost:8006/api/v2/user/transactions').then(function (res) {
var result = res.data.data;
console.log(result)
if (result.data) {
setGraphData(result.data)
for (const obj of result.data) {
if (obj.role === 'Sender') {
receiverData.push(obj.tokens)
timeData.push(obj.date)
}
if (obj.role === 'Receiver') {
senderData.push(obj.tokens)
timeData.push(obj.date)
}
}
console.log("Reciever", receiverData)
console.log("Sender", senderData)
}
else {
console.log('error')
}
})
}
const state = {
series: [{
color: '#000073',
name: 'Received',
data: receiverData.map((data) => (data))
},
{
color: '#CA9026',
name: 'Sent',
data: senderData.map((data) => (data))
}],
options: {
chart: {
height: 280,
type: 'area'
},
dataLabels: {
enabled: false
},
stroke: {
curve: 'smooth'
},
xaxis: {
type: 'datetime',
categories: timeData.map((data) =>(data))
},
yaxis: {
type: ''
},
tooltip: {
x: {
format: 'dd/MM/yy HH:mm'
},
},
},
}
useEffect(() => {
showData()
}, [])
return (
<Card >
<CardContent display='flex' sx={{ flexDirection: 'column', }} >
<Stack flexDirection='row' alignItems="center" gap={1}>
< SsidChartOutlinedIcon sx={{ color: "text.secondary" }} />
<Typography sx={{ fontSize: 15, fontWeight: 'bold' }} color="text.secondary" gutterBottom>
Recent Transactions
</Typography>
</Stack>
<div id="chart">
<ReactApexChart options={state.options} series={state.series} type="area" height={280} />
</div>
</CardContent>
</Card>
);
}
On apex chart website, they have a case that is similar to yours:
series: [{
data: [{ x: '05/06/2014', y: 54 }, { x: '05/08/2014', y: 17 } , ... , { x: '05/28/2014', y: 26 }]
}]
apexchart docs
To achieve this kind of objects, you can just do this:
const showData = () => {
axios.get('http://localhost:8006/api/v2/user/transactions').then(function (res) {
var result = res.data.data;
console.log(result)
if (result.data) {
setGraphData(result.data)
for (const obj of result.data) {
if (obj.role === 'Sender') {
receiverData.push({x: obj.date.toString(), y: obj.tokens}) //date.toString() is not necessary if your date is already a string
timeData.push(obj.date)
}
if (obj.role === 'Receiver') {
senderData.push({x: obj.date.toString(), y: obj.tokens})
timeData.push(obj.date)
}
}
console.log("Reciever", receiverData)
console.log("Sender", senderData)
}
else {
console.log('error')
}
})
}
And the rest should not need to be changed.
I deployed this repository to netlify and this url displayed my chart as expected on chrome with my macbook pro.
however if I open the url, it display nothing.
Actually, when I see victory chart official doc site, I can see the graphs both of devices.
so I'm wondering why my site dosen't work on iphone.
you can check my code on [my repo](this repository)
the essential part of the code is below.
import React from 'react';
import {
StringOrNumberOrCallback,
VictoryAxis,
VictoryChart,
VictoryLine,
} from 'victory';
const Chart: React.FC = () => {
const profitHistoryList = [
{
datetime: '2021-12-31 00:00:00',
cash: 10001.53,
id: 57,
},
{
datetime: '2022-01-03 00:00:00',
cash: 10001.53,
id: 58,
},
{
datetime: '2022-01-18 00:00:00',
cash: 9999.72,
id: 59,
},
{
datetime: '2022-01-19 00:00:00',
cash: 9999.72,
id: 60,
},
];
const data = profitHistoryList.map((p) => ({
x: new Date(p.datetime),
y: p.cash,
}));
const areaColor: StringOrNumberOrCallback =
data[data.length - 1].y > 10000 ? 'green' : 'red';
const { innerHeight: height } = window;
return (
<td className="w-40 md:w-full">
<VictoryChart
style={{
background: { fill: 'black' },
}}
height={(height * 1) / 12}
scale={{ x: 'time' }}
padding={{ top: 10, left: 10, right: 10, bottom: 10 }}
>
<VictoryLine
data={data}
style={{
data: {
stroke: areaColor,
},
}}
/>
<VictoryAxis dependentAxis style={{ axis: { stroke: 'none' } }} />
<VictoryAxis style={{ axis: { stroke: 'none' } }} />
</VictoryChart>
</td>
);
};
export default Chart;
import Position from 'Position';
import { VFC } from 'react';
import './App.css';
import './index.css';
const App: VFC = () => (
<div className="App">
<Position />
</div>
);
export default App;
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 have a state which is an object containing an array and that array contains an object which looks something like this
[{"tone":"negative","value":0},{"tone":"neutral","value":91},{"tone":"positive","value":9}].
So I want to plot a bar chart using only the values from this array of objects. I want to send these values to another component which can be used to plot bar charts dynamically. But I'm not sure how to do it. can someone please show how to send the values to the barchart component and use them in the barchart as well?
This is the code
state={
analysis: {
tonal: [],
anxiety: []
}
}
Analysis = async () => {
//some api call
const {
...tonalAnalysis
} = result.scores;
const tonalArray = Object.entries(tonalAnalysis).reduce(
(carry, [tone, value]) => [
...carry,
{ tone: tone.toLowerCase(), value: parseInt(value) }
],
[]
);
this.setState({
analysis: { ...this.state.analysis, tonal: tonalArray }
});
console.log("Tonal array" + JSON.stringify(this.state.analysis.tonal)); //console logs `[{"tone":"negative","value":0},{"tone":"neutral","value":91},{"tone":"positive","value":9}]`
};
render(){
return {
<BarCharts/> // confused how to send the values as props here
}
the bar chart component where I will use
import React from "react";
import { Bar } from "react-chartjs-2";
import "./App.css";
class BarCharts extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {
labels: [
negative,
neutral,
positive
],
datasets: [
{
label: "Value plotting",
backgroundColor: "rgba(255,99,132,0.2)",
borderColor: "rgba(255,99,132,1)",
borderWidth: 1,
hoverBackgroundColor: "rgba(255,99,132,0.4)",
hoverBorderColor: "rgba(255,99,132,1)",
data: [65, 59, 80, 81, 56, 55, 40] //want to use the values here dynamically. Don't want these static values
}
]
}
};
}
render() {
const options = {
responsive: true,
legend: {
display: false
},
type: "bar"
};
return (
<Bar
data={this.state.data}
width={null}
height={null}
options={options}
/>
);
}
}
export default BarCharts;
You can create a HighChart wrapper component that can be used for any Highchart graphs.
Note:- every time the data set changes you need to destroy and re-render the graph again in order to make the graph reflect changes.
// #flow
import * as React from "react";
import merge from "lodash/merge";
import Highcharts from "highcharts";
import isEqual from "lodash/isEqual";
export type Props = {
config?: Object,
data: Array<any>,
onRendered?: () => void
};
class HighchartWrapper extends React.PureComponent<Props> {
container: ?HTMLElement;
chart: any;
static defaultProps = {
config: {},
onRendered: () => {}
};
componentDidMount() {
this.drawChart(this.props);
}
componentWillReceiveProps(nextProps: Props) {
const data= [...this.props.data];
if (!isEqual(nextProps.config, this.props.config) || !isEqual(nextProps.data, data)) {
this.destroyChart();
this.drawChart(nextProps);
}
}
destroyChart() {
if (this.chart) {
this.chart.destroy();
}
}
componentWillUnmount() {
this.destroyChart();
}
drawChart = (props: Props) => {
const { config: configProp, data, onRendered } = props;
if (this.container) {
let config = merge({}, configProp);
this.chart = new Highcharts.chart(this.container, { ...{ ...config, ...{ series: [...data] } } }, onRendered);
}
};
render() {
return <div ref={ref => (this.container = ref)} />;
}
}
export default HighchartWrapper;
In order use it for BarChart just pass the appropriate bar chart config.
<HighchartWrapper config={{
chart: {
type: "bar"
}
}}
data={[]}
>
Edit
import React from "react";
import BarChart from "./BarChart";
export default function App() {
return (
<div style={{ width: 400, height: 840 }}>
<BarChart
config={{
chart: {
height: 840,
type: "bar"
},
xAxis: {
categories: ["Positive", "Neutral", "Negative" ],
title: {
text: null
}
},
yAxis: {
min: 0,
title: {
text: "Population (millions)",
align: "high"
},
labels: {
overflow: "justify"
}
}
}}
data={[
{
name: "Series Name",
data: [90, 9, 10]
}
]}
/>
</div>
);
}
Just add your desired props in at component declaration :
<BarCharts data={this.state.analysis}/>
And on your BarChart Component you will need to just extract the values from your arrays, this just in case you need the same structure:
...
this.state = {
data: {
labels: [
negative,
neutral,
positive
],
datasets: [
{
label: "Value plotting",
backgroundColor: "rgba(255,99,132,0.2)",
borderColor: "rgba(255,99,132,1)",
borderWidth: 1,
hoverBackgroundColor: "rgba(255,99,132,0.4)",
hoverBorderColor: "rgba(255,99,132,1)",
data: extractValues(this.props.data)
}
]
}
...
//This method can be reused in a hook or in a lifecycle method to keep data updated.
const extractValues = (data) => {
return data.map( d => d.value);
}
You can map the array so your code would be:
state={
analysis: {
tonal: [],
anxiety: []
}
}
Analysis = async () => {
//some api call
const {
...tonalAnalysis
} = result.scores;
const tonalArray = Object.entries(tonalAnalysis).reduce(
(carry, [tone, value]) => [
...carry,
{ tone: tone.toLowerCase(), value: parseInt(value) }
],
[]
);
this.setState({
analysis: { ...this.state.analysis, tonal: tonalArray }
});
console.log("Tonal array" + JSON.stringify(this.state.analysis.tonal)); //console logs `[{"tone":"negative","value":0},{"tone":"neutral","value":91},{"tone":"positive","value":9}]`
};
render(){
return {
<BarCharts values={this.state.analysis.tonal.map((entry) => entry.value)}/> // confused how to send the values as props here
}
And your barchart would be:
import React from "react";
import { Bar } from "react-chartjs-2";
import "./App.css";
class BarCharts extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {
labels: [
negative,
neutral,
positive
],
datasets: [
{
label: "Value plotting",
backgroundColor: "rgba(255,99,132,0.2)",
borderColor: "rgba(255,99,132,1)",
borderWidth: 1,
hoverBackgroundColor: "rgba(255,99,132,0.4)",
hoverBorderColor: "rgba(255,99,132,1)",
data: props.values //want to use the values here dynamically. Don't want these static values
}
]
}
};
}
render() {
const options = {
responsive: true,
legend: {
display: false
},
type: "bar"
};
return (
<Bar
data={this.state.data}
width={null}
height={null}
options={options}
/>
);
}
}
export default BarCharts;
I am using react-plotly to generate a large timeline of data (10,000-100,000 points) and I animate across the data in another window. I need to get a scrubber (vertical line) that moves with a react-property representing time, but I need to update the scrubber without updating the rest of the timeline, since it would take so long to do so. How can I get just the vertical line to update?
Edit: Was asked for code
In the following code, the backtracks and thresholds objects are Uint32Arrays and represent the y-axis of traces, where the x-axes are the Uint32Arrays backtracksTime and thresholdsTime. What I am trying to get is a vertical line at the x-coordinate currentTime.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Plotly from 'plotly.js';
import Plot from 'react-plotly.js';
import styles from './style.scss';
export default class ThresholdWindow extends Component {
static propTypes = {
name: PropTypes.string,
backtracks: PropTypes.object,
backtracksTime: PropTypes.object,
thresholds: PropTypes.object,
thresholdsTime: PropTypes.object,
currentTime: PropTypes.number,
}
constructor(props) {
super(props);
this.state = {
plotRevision: 0,
width: 0,
height: 0,
};
}
componentDidMount() {
const resizeObserver = new ResizeObserver(entries => {
const oldPlotRevision = this.state.plotRevision;
const rect = entries[0].contentRect;
this.setState({
plotRevision: oldPlotRevision + 1,
height: rect.height,
width: rect.width,
});
});
resizeObserver.observe(this.container);
}
shouldComponentUpdate(nextProps, nextState) {
if (this.state.plotRevision !== nextState.plotRevision) {
return true;
} else if (this.props.currentTime !== nextProps.currentTime) {
return true;
}
return false;
}
render() {
const data = [
{
name: 'Threshold',
type: 'scattergl',
mode: 'lines',
x: this.props.thresholdsTime,
y: this.props.thresholds,
side: 'above',
},
{
name: 'Backtracks',
type: 'scattergl',
mode: 'lines',
x: this.props.backtracksTime,
y: this.props.backtracks,
},
{
name: 'Current Time',
type: 'scattergl',
mode: 'lines',
x: [this.props.currentTime, this.props.currentTime],
y: [0, 1],
yaxis: 'y2',
},
];
return (
<div className={styles['threshold-window']} ref={(el) => { this.container = el; }}>
<Plot
divId={`backtracks-${this.props.name}`}
className={styles['threshold-graph']}
ref={(el) => { this.plot = el; }}
layout={{
width: this.state.width,
height: this.state.height,
yaxis: {
fixedrange: true,
},
yaxis2: {
side: 'right',
range: [0, 1],
},
margin: {
l: 35,
r: 15,
b: 20,
t: 15,
},
legend: {
orientation: 'h',
y: 1,
},
}}
revision={this.state.plotRevision}
data={data}
/>
</div>
);
}
}
Edit2: I don't actually see the currentTime line anywhere, so I'm pretty sure there's a bug somewhere.
With react-plotly.js the performance should be decent, as it will only redraw what it needs to.