react-hightchart: all values in event.point are null - reactjs

I am using react-highchart-official in my project. I need to retrieve some values from the click event. In the component, the event listener is set using plotOptions like this:
plotOptions: {
series: {
point: {
events: {
click: event => {
this.props.onClick && this.props.onClick(event);
}
}
}
}
}
It gets invoked with event object which contains a point entry. point contains useful information regarding chart.
The chart component is used as:
import React from 'react';
import { render } from 'react-dom';
// Import Highcharts
import Highcharts from 'highcharts';
// Import our demo components
import Chart from './components/Chart.jsx';
// Load Highcharts modules
require('highcharts/modules/exporting')(Highcharts);
const chartOptions = {
title: {
text: ''
},
series: [
{
data: [[1, 'Highcharts'], [1, 'React'], [3, 'Highsoft']],
keys: ['y', 'name'],
type: 'pie'
}
]
};
class App extends React.Component {
state = { flag: true };
onChartClick(event) {
// All values of event.point are null. Not sure why.
// Even though it's not a synthetic event.
console.log(event.point);
this.setState({ flag: false });
}
render() {
const { flag } = this.state;
return (
<div>
<h1>Demos</h1>
<h2>Highcharts</h2>
{flag && (
<Chart
highcharts={Highcharts}
onClick={this.onChartClick.bind(this)}
/>
)}
</div>
);
}
}
For some unknown reasons, all the value inside point property of click event is set to null if I hide the chart. Is there any way I can avoid it? In the actual project on chart click I need to redirect user to somewhere. To imitate the situation, I have hidden the chart.
Codesandbox

This situation exactly shows what happened with your point data, see:
Demo: https://jsfiddle.net/BlackLabel/o70mLrnt/
var chart = Highcharts.chart('container', {
series: [{
data: [43934, 52503, 57177, 69658, 97031, 119931, 137133, 154175]
}]
});
console.log(chart.series[0].points[1])
chart.destroy();
Setting the flag to false unmounting the component which destroys the chart and later console.log doesn't have 'access' to chart and particular point properties.
If you need only a few data from the point a good option will be setting them in the state before the flag will be changed.
Like this - this will keep your point y value.
this.setState({
pointValue: event.point.y
});
Another solution which came to my mind is to set the whole point object to some variable, but here the setTimeout needs to be used.
Demo with setState solution and saving object to variable: https://codesandbox.io/s/highcharts-react-demo-dnbdv

Related

useState one behind the rendered element

import React, { useState } from "react";
import { Form } from "react-bootstrap";
import geneList from '../data/SampleSheet.json';
import C3 from 'c3'
import C3Chart from 'c3';
import 'c3/c3.css';
const GENEDATA = [["Gnai3",501, 747, 705, 543, 689], ["genen",3,3,3,3],["Cdc45",22, 30 ,10 ,28, 29]] as [string, ...number[]][];
const GENENAMES = ["Gnai3", "genen", "Cdc45"];
export function Genes(): JSX.Element {
// This is the State (Model)
const [gene, setGene] = useState<([string, ...number[]])>(GENEDATA[0]);
const [geneName, setGeneName] = useState<string>(GENENAMES[0]);
// This is the Control
//no idea why the gene name is one behind but maybe you can figure it out
function updateGene(event: React.ChangeEvent<HTMLSelectElement>) {
setGeneName(event.target.value);
const tempGene = gene;
const changeData = GENEDATA.find((name: [string, ...number[]]): boolean => name[0] === geneName);
if(changeData === undefined){
setGene(tempGene);
alert("Gene not found!");
}
else{
alert(geneName);
setGene(changeData);
}
}
// eslint-disable-next-line #typescript-eslint/no-unused-vars
var chart = C3.generate({
bindto: '#chart',
data: {
columns: [
gene
]
},
axis: {
y: {
label:"Activation Level"
}
},
color: {
pattern: ['#ff7f0e']
},
});
// This is the View
return (
<div>
<Form.Group controlId="Genes">
<Form.Label>Pick gene to display: </Form.Label>
<Form.Select value={geneName} onChange={updateGene}>
{ GENENAMES.map((geneName: string) =>
<option key={geneName} value={geneName}>{geneName}</option>
)}
</Form.Select>
<div id="chart"></div>
</Form.Group>
Selected Gene: {geneName}
</div>
);
}
Im trying to graph some data using the C3 library and the data used in the graph should be synced up with the current geneName, however, it seems that when my page re-renders, the geneName seems to be one ahead of the gene state and I am not sure why.
I thought that maybe it was because state update was being done in on function all together so that the useState wasnt set to the correct value yet which is true. I need my graph data to reflect the current geneName and its data.
I actually figured it out right after creating this post.
It was because i was setting my data name and its respective data equal to the current geneName state. Because the state doesnt change until after the render, it was getting the actual current state which is not what I wanted.
The fix was simply setting the gene to event.target.value insted of geneName
The fix was in my .find

React-leaflet-draw doesn't keep updated onCreated to a hook state of drawn features

Context
I am trying to save my collection of my drawn features dynamically into a state hook, and keeping them updated from onCreated, onEdited and onDeleted.
This is how my UI looks like:
Problem
The issue happens when, I click on the trash icon and remove one of the created features with (let's say) id=2, and then create another new feature of id=3; it looks like the onCreated method doesn't have it's state of features updated, and when creating the feature of id 3, it returns to show the deleted figure of id 2 in the data (but not on the map).
This is how it looks like when the old figure is deleted, but when creating a new one, the onCreate makes it persistent in the data displayed at the right container.
This is the code that I run: (The featureCollection is the state hook passed from the App.js):
import { FeatureGroup } from "react-leaflet";
import { EditControl } from "react-leaflet-draw";
export default function EditFeature({ lkey, setlKey, featuresCollection, setFeaturesCollection }) {
const _onCreated = (e) => {
let layer = { id: e.layer._leaflet_id, ...e.layer.toGeoJSON() };
setlKey(e.layer._leaflet_id);
setFeaturesCollection({
...featuresCollection,
features: [layer, ...featuresCollection.features]
});
};
const _onDeleted = (e) => {
let unwanted = Object.keys(e.layers._layers);
unwanted = unwanted.map(item => Number(item));
// Filter out those layers whose id is in the unwanted array
let filtered = featuresCollection.features.filter(e => !unwanted.includes(e.id));
setFeaturesCollection({
...featuresCollection,
features: [...filtered]
});
};
return (
<FeatureGroup>
<EditControl
key={lkey}
id="EditControl"
onCreated={_onCreated}
onDeleted={_onDeleted}
position="topright"
draw={{
circle: false,
circlemarker: false,
polyline: false,
marker: false,
polygon: {
allowIntersection: false,
shapeOptions: {
color: "purple",
weight: 3
},
},
rectangle: {
shapeOptions: {
color: "purple",
weight: 3
}
}
}}
/>
</FeatureGroup>
);
};
My main goal is to achieve that my state keeps updated the figures shown in the map, in order to then export those to a geojson file, like the web GeoJSON.io does! (within React tho).
Reference:
https://github.com/alex3165/react-leaflet-draw/issues/154

Without setTimeout component is not rendering the proper data

This question is merely for curiosity. So, I have a parent component that fetches some data (Firebase) and saves that data in the state, and also passes the data to the child. The child's code is the following:
import { Bar, Doughnut } from 'react-chartjs-2'
import { Chart } from 'chart.js/auto'
import { useState, useEffect } from 'react'
import _ from 'lodash'
const initialData = {
labels: [],
datasets: [
{
id: 0,
data: [],
backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#FF6384'],
color: ['#FF6384', '#36A2EB', '#FFCE56', '#FF6384'],
borderWidth: 0,
},
],
}
var config2 = {
maintainAspectRatio: true,
responsive: true,
}
export default function DoughnutChart(props) {
const [data, setData] = useState(initialData)
useEffect(() => {
const newData = _.cloneDeep(initialData)
var wins = 0
var losses = 0
var labels = ['wins', ' losses']
//Whithout seTimeout the chart is not updated
setTimeout(() => {
props.trades.forEach((trade) => {
if (trade.cprice) {
if (trade.cprice >= trade.price) {
wins++
} else {
losses++
}
}
})
newData.datasets[0].data = [wins, losses]
newData.labels = labels
setData(newData)
}, 1000)
}, [props.trades])
return (
<Doughnut
data={data}
options={config2}
redraw
/>
)
}
As you can see the child listens with useEffect to props changes, and then I process the props as I want to plot the necessary information on the Chart.
The thing is that in the beginning, the code didn't work (the Chart didn't display anything despite the props changed), I console logged the props, and it seems that something was happening too fast (if I console the length of the props.trades it showed me 0, but if I consoled the object it shows data in it) So that the forEach statement wasn't starting to iterate in the first place. When I added the setTimeout it started working if a put a 1000 milliseconds (with 500 milliseconds it doesn't work).
I'm a beginner at React and would be very interested in why this happens and what is the best approach to handle these small delays in memory that I quite don't understand.

Highcharts checkboxes multiply when window resize event fires

I have an app which contains checkbox at the bottom of the chart (by the legend item).
When I resize the app window, event listener (which logs 'resize' events) changes the chart width accordingly and a new checkbox appears on the chart.
A new checkbox also appears as well when the chart theme is changed.
This makes me believe that whenever Highcharts are re-rendered (I use React), a new checkbox is created.
Initially, there is only one checkbox:
After single resize of the page:
After multiple resizes:
Also, there is always one checkbox at the top right corner of the chart (appears after the first resize):
The checkBox is added to the chart only once, at the componendDidMount() cycle. It is added to a single series using this option:
showCheckbox: true
The first and the only time the function that generates charts and sets this option is called in the main components cycle componentDidMount:
Main Container
import Charts from './Charts';
import * as highcharts from 'highcharts';
class MainComponent exteds React.Component<Props, State> {
public async componentDidMount() {
try {
// Here the charts are generated
const initialOpts = Charts.generateCharts();
const min = 1000;
const max = 5000;
highcharts.charts.map(chart => {
chart.xAxis[0].setExtremes(min, max, undefined, false)
});
} catch { ... }
this.chartDimensionsHandler();
window.addEventListener('resize', this.chartDimensionsHandler);
}
private chartDimensionsHandler() {
const chartHeight: number = ZoomService.getChartHeight();
this.setState({ chartHeight });
// When the state is set, component re-renders and causes the issue
}
/*
Theme is passed as props to the main component thus whenever theme changes,
new props arrive and component along with higcharts is also
re-rendered what causes a new checkbox to appear on top right of the chart
*/
}
Charts class
export class ChartService {
public generateCharts() {
return this.charts.map((char) => this.generateDataChart(chart));
}
private generateDataChart(chart: Chart): Options {
const { slices } = chart;
const xValues = slices.map(slice =>
Number(slice[chart.xAxis.name].value)
);
const xAxis = this.generateXAxisOptions(chart);
const yAxis = [ ... ];
const options = { xAxis, yAxis };
// Create line series
try {
const lineSeries = this.constructDataSeries(chart, xValues);
// Create events series
const eventsCharts = this.charts.filter(
x => x.type === 'events'
);
const eventsSeries = eventsCharts.map((evChart: Chart) => {
// Here I call a function which sets 'showCheckbox' to true
const allEvents = this.createEventSeries(evChart!)[0];
allEvents.yAxis = 1;
return allEvents;
});
const otherSeries = ...; // Irrelevant code
// Add all series
const series = [...otherSeries, ...eventsSeries];
return Object.assign(options, { series });
} catch (e) {
return Object.assign(
options,
{ series: [] },
{
lang: {
noData: `Fail.`
}
}
);
}
}
private constructEventSeries(chart) {
const { slices, yAxis, xAxis } = chart;
const xValues = slices.map(slice =>
Number(slice[xAxis.name].value)
);
return yAxis.map((item) => ({
name: item.label,
type: 'line',
data: this.convertEventData(slices, xValues),
lineWidth: 0,
showCheckbox: true, // THE PLACE WHERE I SET showCheckbox to true
marker: {
enabled: true,
radius: 10,
symbol : 'circle'
},
meta: {
type: 'events'
}
}));
}
}
Does anyone have an idea why a new checkbox is added to the chart every single render of it (no matter if the chart is resized or not)?
I found out that whenever resize event is logged, Highcharts automatically calls chart.reflow() function which caused the checkmarks to be multiplied. Component setState function made the situation even worse.
By changing the function which is called by the event from setting component's state into setting chart's dimensions directly with higcharts function chart.setSize(width, height, animation: boolean).
Then in the CSS I set the display of the second checkbox to none by using nth child property (because resize event always creates the second checkbox, there is no way to shut it (probably a bug from highcharts)).
This was my solution.

How can I display a Persona component in a CommandBar in React Fabric-UI?

I am trying to display a Persona component on the far right of my CommandBar component, which I use as a header for my application.
Here's a code snippet
const getFarItems = () => {
return [
{
key: 'profile',
text: <Persona text="Kat Larrson" />,
onClick: () => console.log('Sort')
}
]
}
const FabricHeader: React.SFC<props> = () => {
return (
<div>
<CommandBar
items={getItems()}
farItems={getFarItems()}
ariaLabel={'Use left and right arrow keys to navigate between commands'}
/>
</div>
);
}
This throws a type error because the text prop expects a string and not a component. Any help would be appreciated!
Under the ICommandBarItemProps there is a property called commandBarButtonAs that the docs state:
Method to override the render of the individual command bar button.
Note, is not used when rendered in overflow
And its default component is CommandBarButton which is basically a Button
Basically there are two ways to do this.
Keep using Button, and apply your own renderer. Basically the IButtonProps you can add onRenderChildren which would allow you to add any Component such as Persona to render. This example would show you how it is done https://codepen.io/micahgodbolt/pen/qMoYQo
const farItems = [{
// Set to null if you have submenus that want to hide the down arrow.
onRenderMenuIcon: () => null,
// Any renderer
onRenderChildren: () => ,
key: 'persona',
name: 'Persona',
iconOnly: true,
}]
Or add your own crazy component not dependent on CommandBarButton but that means you need to handle everything like focus, accessibility yourself. This example would show you how it is done https://codepen.io/mohamedhmansour/pen/GPNKwM
const farItems = [
{
key: 'persona',
name: 'Persona',
commandBarButtonAs: PersonaCommandBarComponent
}
];
function PersonaCommandBarComponent() {
return (
);
}

Resources