Related
I am building an app for gym-goers to record and track their progress. For each exercise they input, it should render a chart showing their previous track record. This is working fine.
Users can then add another entry to this track record, but it does not update the chart unless you refresh the page. I can't work out why or how to fix it.
There are a number of different components involved - a parent Exercise.js one, then an ExerciseFooter.js one, which contains the buttons to adjust the target or add a new entry to the exercise, and then AddHistory.js and SetTarget.js components which contain modals and the logic to update the exercise via Redux and MongoDB.
A minimal version of the Exercise.js page is here (I've collapsed the stuff that's mainly styling into single lines as much as possible):
import React, { useState, useEffect } from "react";
import { ExerciseFooter } from "./ExerciseFooter";
import { Line } from "react-chartjs-2";
import { useLocation } from "react-router-dom";
import { useSelector } from "react-redux";
export const Exercise = (props) => {
const location = useLocation();
const users = useSelector((state) => state.auth);
const localUser = JSON.parse(localStorage.getItem("profile"));
const [user, setUser] = useState("");
const [exerciseProp, setExerciseProp] = useState({
history: [""],
target: 0,
});
useEffect(() => {
localUser &&
localUser?.result &&
users.length > 0 &&
setUser(
users.filter(
(filteredUser) => filteredUser._id == props.match.params.userId
)[0]
);
if (!localUser) setUser("");
setExerciseProp(
user?.exercises?.filter(
(exercise) => exercise._id == props.match.params.exerciseId
)[0]
);
}, [users, location]);
//styling for chart
const [barData, setBarData] = useState({
labels: [""],
datasets: [
{ label: "Target", fill: false, radius: 0, data: [""], borderColor: ["rgba(35, 53, 89)"], borderWidth: [3], },
{ label: "You did", data: [""], tension: 0.3, borderColor: ["white"], backgroundColor: ["white"], borderWidth: 3, },
],
});
//updating chart data
var weightArr = [];
var dateArr = [];
var targetArr = [];
if (exerciseProp) {
exerciseProp.history.map((hist) =>
weightArr.push(parseInt(hist.weight) || 0)
);
exerciseProp.history.map((hist) => dateArr.push(hist.date));
for (let i = 0; i < exerciseProp.history.length; i++) {
targetArr.push(exerciseProp.target);
}
}
useEffect(() => {
if (exerciseProp) {
setBarData({
labels: dateArr,
datasets: [
{
label: "Target",
fill: false,
radius: 0,
data: targetArr,
borderColor: ["rgba(35, 53, 89)"], borderWidth: [3],
},
{
label: "Weight",
data: weightArr,
tension: 0.3, borderColor: ["white"], backgroundColor: ["white"], borderWidth: 3,
},
],
});
}
}, [users]);
//render chart ones exerciseProp is populated
if (exerciseProp) {
return (
<div style={{ marginTop: "200px" }}>
<Line
data={barData}
options={{ plugins: { title: { display: false, }, legend: { display: false, }, },
scales: { x: { grid: { color: "white", font: { family: "Dongle", size: 20, }, }, ticks: { color: "white", font: { family: "Dongle", size: 20, }, }, }, y: { grid: { color: "white", }, ticks: { color: "white", font: { family: "Dongle", size: 20, }, }, }, }, }}
/>
{exerciseProp && <ExerciseFooter user={user} exercise={exerciseProp} />}
</div>
);
} else {
return <>Loading...</>;
}
};
I've tried doing a few different things but nothing has worked. I tried adding an 'update' state variable which was updated by a function passed down to the the various dispatches, and then added it to the dependencies of the useEffects, but that didn't seem to make any difference.
Any help much appreciated! As I say, if I just force a refresh then it works fine but know that's bad practice so trying to work out why it isn't re-rendering correctly.
Thanks!
You just have to enable redraw prop
like this
<Line
redraw={true}
data={barData}
options={{ plugins: { title: { display: false, }, legend: { display: false, }, },
scales: { x: { grid: { color: "white", font: { family: "Dongle", size: 20, }, }, ticks: { color: "white", font: { family: "Dongle", size: 20, }, }, }, y: { grid: { color: "white", }, ticks: { color: "white", font: { family: "Dongle", size: 20, }, }, }, }, }}/>
this all you have to do
redraw={true}
Right now my current graph behavior is like this
It's a react component
<PieChart
data={[{
textinfo: "none",
values: [60, 20, 10, 5, 5], labels: ['Healthy', 'Mild', 'Moderate', 'Severe', 'Critical'], hole: .6, type: 'pie', marker: {
colors: ['#68B34D', '#FFDF83', '#FE9551', '#FE0801', '#B90000']
}
}]}
layout={{
title: { text: 'Health Status', x: '0.1', size: '14', family: 'Montserrat, sans-serif' }, annotations: [
{
font: {
size: 13,
color: '#323D4B',
family: 'Montserrat, sans-serif'
},
text: `<br>Machines</b>`,
showarrow: false,
}
], showlegend: true,
legend: {orientation:'h', x: 0.5, y: -.7, xanchor: 'center', yanchor: 'bottom', font: { family: 'Montserrat, sans- serif' } },
height: 320,
width: 248,
margin: { l: 0, r: 0, t: 50, b: 20 },
}}
/>
My expected behavior is, legends (Piechart info) should show label respective value too
Healthy: 60
Mild : 10
PieChart code
import createPlotlyComponent from "react-plotly.js/factory";
export const Plot = createPlotlyComponent(Plotly);
export function PieChart(props: PieChartProps) {
const { data, layout } = props;
return <Plot data={data} layout={layout} />;
}
So currently I have this as my code for the victory line chat
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { VictoryLine, VictoryChart } from 'victory-native'
let data = [
{
x: 1,
y: 0
},
{
x: 2,
y: 0
},
{
x: 3,
y: 0
},
{
x: 4,
y: 70
},
{
x: 5,
y: 73
},
{
x: 5,
y: 73
},
{
x: 5,
y: 73
},
{
x: 5,
y: 73
}
]
export default function Graph() {
return (
<VictoryLine style={styles.graph} height={150} width={350} data={data} style={{data: {stroke: 'orange', strokeWidth: 2.5}}} />
)
}
const styles = StyleSheet.create({
graph: {
marginRight: 0
}
})
which gives me a line chart that looks like this. Is there a way to:
A) Plot dotted point on the line for each data point
B) Just dot the last data point in the data list. Example image here of what I want to achieve
You can wrap your VictoryLine with an VictroyChart and hide the axis, like this sample
<View>
<VictoryChart polar={this.state.polar} height={390}>
<VictoryAxis style={{
axis: {stroke: "transparent"},
ticks: {stroke: "transparent"},
tickLabels: { fill:"transparent"}
}} />
<VictoryLine
interpolation={this.state.interpolation} data={data}
style={{ data: { stroke: "#c43a31" } }}
/>
<VictoryScatter data={[data[data.length-1]]}
size={5}
style={{ data: { fill: "#c43a31" } }}
/>
</VictoryChart>
</View>
I am defining four lines chart with Apex Chart.
My data series:
series: [
{
name: "first",
data: [28, 29, 33, 36, 32, 32, 33]
},
{
name: "second",
data: [12, 11, 14, 18, 17, 13, 13]
},
{
name: "third",
data: [48, 29, 33, 16, 32, 62, 3]
},
{
name: "forth",
data: [22, 11, 54, 18, 27, 53, 13]
}
],
I want my tooltip output to be like:
first-label : {series.dataOfFirstLabel}
second-label : {series.dataOfSecondLabel}
third-label : {series.dataOfThirdLabel}
forth-label : {series.dataOfForthLabel}
How can I define my custom tooltip?
Thanks.
Try this:
tooltip: {
custom: function (series: any) {
return `<div>
${series.map((el: any) => {
return `<div class="d-flex flex- column">
<p>${el.name}-label:${el.data.map((element: any)=>element).join("")}</p> </div>`; })
</div>`},}
https://codepen.io/apexcharts/pen/NBdyvV
In chart options
enter code here
tooltip: {
enabled: true,
enabledOnSeries: true,
shared: true,
followCursor: true,
intersect: false,
inverseOrder: false,
custom: undefined,
fillSeriesColor: true,
theme: false,
style: {
fontSize: "12px",
fontFamily: undefined,
},
onDatasetHover: {
highlightDataSeries: false,
},
x: {
show: true,
format: "dd MMM",
formatter: undefined,
},
y: {
show: false,
formatter: undefined,
title: {
formatter: (seriesName) => seriesName,
},
},
z: {
formatter: undefined,
title: "Size: ",
},
marker: {
show: false,
},
items: {
display: "flex",
},
fixed: {
enabled: false,
position: "topRight",
offsetX: 0,
offsetY: 0,
},
},
//sh
you can add these properties
refer - https://apexcharts.com/docs/options/tooltip/
Instead string literal you can use renderToString from react-dom/server.
import { renderToString } from 'react-dom/server'
So the custom function would look like this:
// Some series that you use in the chart
const seriesData = [
{
name: 'South',
data: [
[1486771200000, 40],
[1486857600000, 18],
[1486944000000, 43],
],
},
{
name: 'North',
data: [
[1486771200000, 16],
[1486857600000, 15],
[1486944000000, 10],
],
}]
...
// Tooltip option for apexChart
tooltip: {
custom: ({ series, seriesIndex, dataPointIndex, w }) => {
const xValue = seriesData[seriesIndex].data[dataPointIndex][0]
const xDate = new Date(xValue).toLocaleDateString('cs-CZ')
return renderToString(
<div>
<p>
{xDate}
</p>
{/* Map through series to get labels and values */}
{seriesData.map((s, i) => (
<div key={s.name} className="tooltipItem">
<div style={{
minWidth: "12px",
backgroundColor: w.globals.colors[i]
}}
/>
<p >
MyLabel:{' '}
</p>
<p>
{series[i][dataPointIndex]}
</p>
</div>
))}
</div>
)
},
},
I'm new to React and trying to learn. My personal project is homebrewing display site.
I want to show some of the details in radial bar. I followed tutorial and made array data file, and index file where details show. Now I'm stuck getting single values to display in radial bar.
Also values need to be calculated from percent to numeric. I got that working. I tried some push functions but did not get it to work way I wanted. My coding skills are beginner level.
display example
data.js
import product1 from '../../images/product-1.png';
import product2 from '../../images/product-1.png';
export const productData = [
{
id: 1,
img: product1,
alt: 'Beer',
name: 'Lager',
desc: 'Saaz',
ibu: '35',
ebc: '40',
abv: '8.5',
},
{
id: 2,
img: product2,
alt: 'Beer',
name: 'IPA',
desc: 'Mango, Citrus',
ibu: '85',
ebc: '25',
abv: '5.5',
},
];
index.js
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import {
ProductsContainer,
ProductWrapper,
ProductsHeading,
ProductTitle,
ProductCard,
ProductImg,
ProductInfo,
ProductDesc,
ProductIbu,
ProductEbc,
ProductAbv,
} from './ProductsElements';
const max = 119;
function valueToPercent(value) {
return (value * 100) / max;
}
class IBU extends Component {
constructor(props) {
super(props);
this.state = {
series: [valueToPercent(15)],
options: {
plotOptions: {
radialBar: {
startAngle: -90,
endAngle: 90,
hollow: {
margin: 10,
size: '70%',
background: '#222222',
image: undefined,
imageOffsetX: 0,
imageOffsetY: 0,
position: 'front',
dropShadow: {
enabled: true,
top: 5,
left: 0,
blur: 4,
opacity: 0.9,
},
},
track: {
background: '#d42d2d',
strokeWidth: '67%',
margin: 0, // margin is in pixels
dropShadow: {
enabled: true,
top: 0,
left: 0,
blur: 4,
color: '#000',
opacity: 0.6,
},
},
dataLabels: {
show: true,
name: {
offsetY: -10,
show: true,
color: '#fff',
fontSize: '17px',
},
value: {
formatter: function (value) {
return (parseFloat(value) * max) / 100;
},
color: '#dadada',
fontSize: '36px',
show: true,
},
},
},
},
fill: {
colors: [
function ({ value, seriesIndex, w }) {
if (value < 55) {
return '#7E36AF';
} else if (value >= 55 && value < 80) {
return '#164666';
} else {
return '#D9534F';
}
},
],
},
stroke: {
lineCap: 'round',
},
labels: ['IBU'],
},
};
}
render() {
return <Chart options={this.state.options} series={this.state.series} type="radialBar" height={250} />;
}
}
const Products = ({ data }) => {
return (
<ProductsContainer>
<ProductsHeading>heading</ProductsHeading>
<ProductWrapper>
{data.map((product, index) => {
return (
<ProductCard key={index}>
<ProductImg src={product.img} alt={product.alt} />
<ProductInfo>
<ProductTitle>{product.name}</ProductTitle>
<ProductDesc>{product.desc}</ProductDesc>
<ProductIbu>{product.ibu}</ProductIbu>
<IBU></IBU>
<ProductEbc>{product.ebc}</ProductEbc>
<ProductAbv>{product.abv}</ProductAbv>
</ProductInfo>
</ProductCard>
);
})}
</ProductWrapper>
</ProductsContainer>
);
};
export default Products;