How to call a click event on react geo chart? - javascript

I have basic geo chart from react geocharts
<Chart
width={calculateMapHeight()}
height={'575px'}
chartType="GeoChart"
data={user.details}
getSelection={(e) => console.log('test')}
select={console.log('test')}
enableRegionInteractivity={true}
regionClick={(e) => console.log('test')}
onClick={(e) => console.log('test')}
mapsApiKey="apikey"
rootProps={{ 'data-testid': '1' }}
options={{
backgroundColor: 'transparent',
defaultColor: red,
animation: {
startup: true,
duration: 2500,
},
}}
/>
but I can't work out what I need to do to call an event when a user click on a country? I've tried to log stuff from all the methods above but nothing is working
also as a side note, it only seems to show the country on hover when that country has been passed into my map. is it possible to turn it on for all countries?

Define an array of chartEvents. In your case use select as eventName. Use chartEvents prop and supply the chartEvents array to it.
The callback receives the selected array using which you can figure out the index of your chart data array. Upon country selection, simply use your orignial whole data and find the selected country.
Use ready event and make an api call and fetch all countries and put them in a state and use it as data to chart. This way, you can dynamically have all countries are populated in the chart
Working demo with sample data - codesandbox
const options = {
title: "Population of Largest U.S. Cities",
chartArea: { width: "30%" },
hAxis: {
title: "Total Population",
minValue: 0
},
vAxis: {
title: "City"
}
};
export default function App() {
const [allCountries, setAllCountries] = useState([["Country"]]);
const chartEvents = [
{
eventName: "select",
callback({ chartWrapper }) {
const selectedId = chartWrapper.getChart().getSelection();
if (selectedId.length) {
// console.log("Selected Country", data[selectedId[0].row + 1]);
console.log("Selected Country", allCountries[selectedId[0].row + 1]);
} else {
console.log("No Country to show ");
}
}
},
{
eventName: "ready",
callback: ({ chartWrapper, google }) => {
fetch("https://restcountries.eu/rest/v2/all").then(res =>
res.json().then(res => {
const allCountries = res.map(c => [c.name]);
console.log("allCountries", allCountries);
setAllCountries(prev => [...prev, ...allCountries]);
})
);
}
}
];
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Chart
// width={calculateMapHeight()}
height={"575px"}
width={"575px"}
chartType="GeoChart"
// data={user.details}
chartEvents={chartEvents}
// data={data}
data={allCountries}
getSelection={e => console.log("test")}
// select={() => console.log("test")}
enableRegionInteractivity={true}
regionClick={e => console.log("test")}
onClick={e => console.log("test")}
mapsApiKey="apikey"
rootProps={{ "data-testid": "1" }}
options={{
backgroundColor: "transparent",
defaultColor: "red",
animation: {
startup: true,
duration: 2500
}
}}
/>
</div>
);
}

There is an example of handling select event.
With your code:
const chartEvents = [
{
eventName: "select",
callback({ chartWrapper }) {
console.log("Selected ", chartWrapper.getChart().getSelection());
}
}
];
<Chart
width={calculateMapHeight()}
height={'575px'}
chartType="GeoChart"
data={user.details}
chartEvents={chartEvents}
enableRegionInteractivity={true}
mapsApiKey="apikey"
rootProps={{ 'data-testid': '1' }}
options={{
backgroundColor: 'transparent',
defaultColor: red,
animation: {
startup: true,
duration: 2500,
},
}}
/>
NOTE: if you use some old version < 3.0 then chartEvents prop isn't available, instead you can use events prop.

Related

Fetching data with useEffect()

I am facing an issue with having my res value (array) from the getLogs function to be populated to the DataGrid.
UPDATED CODES:
LogDetails.jsx
const columns = [
{ field: "id", headerName: "ID", width: 30 },
{
field: "name",
headerName: "Name",
width: 250,
},
{
field: "activity",
headerName: "Activity",
width: 350,
},
{
field: "date",
headerName: "Date",
width: 250,
},
];
export default function LogDetails() {
const [logs, setLogs] = useState([]);
useEffect(() => {
function logs() {
getLogs().then((res) => {
setLogs(res);
});
}
logs();
}, []);
return (
<Box sx={{ width: "100%" }}>
{logs.length ? (
<DataGrid
rows={logs}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
disableSelectionOnClick
autoHeight
/>
): null}
</Box>
);
}
function.js
export async function getLogs() {
var rows = [];
const q = query(collection(db, "logs"), orderBy("date", "desc"));
const docQueury = await getDocs(q);
var count = 1;
docQueury.forEach(async (log) => {
const useref = await getDoc(log.data().user);
const date = new Timestamp(
log.data().date.seconds,
log.data().date.nanoseconds
)
.toDate()
.toLocaleString("en-sg");
rows.push({
id: count++,
name: useref.data().name,
activity: log.data().activity,
date: date,
});
});
return rows;
}
Output of "rows" from getLogs function:
Output of states from LogDetails.jsx:
UPDATE:
If I were to run the above codes and then delete what is under useEffect(), the data will be populated.
useEffect(() => {
//delete what is under here
}, []);
In addition, I happened to experiment using const rows in the page itself. The data was able to populate successfully to the grid. Thus, right now I suppose it has to do with how my codes under useEffect() has some issue?
const rows = [
{
id: 1,
name: "test",
activity: "test",
date: "test"
}
]
The correct way to do the conditional render of the DataGrid would be:
return (
<Box sx={{ width: "100%" }}>
{fetchLogs.length ? (
<DataGrid
rows={fetchLogs}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
disableSelectionOnClick
autoHeight
/>
): null}
</Box>
);
fetchLogs && (<DataGrid/>) will always render the DataGrid because an empty array is "truthy". And fetchLogs.length && (<DataGrid/>) would render 0 when the array is empty.
I'm not sure if this solves the OP's problem but at least it would yield the expected results with regard to conditionally rendering the grid. And as per my previous comment, fetchLogs is not a good name for a state variable. It sounds like a function.
Since you are fetching the data from an api, in the beginning the fetchLogs will be undefined.
So first you should check if fetchLogs exists and only then populate to the DataGrid.
Update
As you see, you already get the data in getLogs function, you could do this instead. Now, if you console the logs, data should be there. And if still the table is not populated, it means field names don't match.
Also pay attention to naming the functions and variables. Make them more readable.
useEffect(() => {
const data = getLogs()
setLogs(data)
}, []);
return (
<Box sx={{ width: "100%" }}>
{fetchLogs.length>0 && (
<DataGrid
rows={fetchLogs}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
disableSelectionOnClick
autoHeight
/>
)}
</Box>
);

Using a Prop to populate an array - React useState

I have 3 dropdowns, each which controls state. When this dropdown is selected it will set the target and send it to redux. For example.
const [interviewStep1, setinterviewStep1] = useState('Phone Screening')
const [interviewStep2, setinterviewStep2] = useState('Formal Interview')
const [interviewStep3, setinterviewStep3] = useState('Reference Check')
This is sent to redux in this manner.
<Dropdown_Content>
{interviewStageSelection.map((option) => (
<Dropdown_Item
key={option}
onClick={(e) => {
setinterviewStep1(option)
setisActive(!isActive)
console.log(interviewStep1)
setisActive(false)
updateInterview1(dispatch, option)
}}
>
<Typography variant="subtitle5" color="black" sx={{ "&:hover": { color: "white" } }}>{option}</Typography>
</Dropdown_Item>
))}
</Dropdown_Content>
I then pass this state as props into my next component.
export default function JobPostInterviewVerticalStepper(interviewStep1, interviewStep2, interviewStep3)
this does come through, but then I want to display in my array. How do I use these props?
const steps = [
{
label: 'Phone Screening',
//I WANT A interviewStep1 here!
},
{
label: 'Formal Interview',
},
{
label: 'Reference Check',
},
{
label: 'Offer',
},
];

useEffect hook isn't triggered inside ReactDOMServer.renderToString()

I'm using leaflet and react-leaflet libraries to create a map inside a React Js app as well as Material UI library for the core UI components.
I'm creating a custom cluster and marker components (using React component, not using image/icon file) for the cluster and marker inside the map. I'm using react-leaflet-markercluster for the custom cluster feature and the pie chart from Apache Echarts library for the custom cluster component.
Problem
The problem I'm facing is the useEffect hook inside my CustomCluster component is never triggered.
Steps to produce
Run the playground here: https://codesandbox.io/s/stackoverflow-custom-cluster-react-leaflet-s2wwsh
This is the initial state
Press the zoom out button (top left corner)
We can see that the 3 markers become a single cluster. The console prints the cluster value from the CustomCluster component but there is no "update chart" message. It means that the useEffect hook is not triggered.
Press again the zoom out button
We can see that all 4 markers become a single cluster. The console prints the updated cluster value from the CustomCluster component but again there is no "update chart" message. It means that the useEffect hook is not triggered again.
Code
App.jsx
const customClusterIcon = (cluster, dummyLocationList) => {
return L.divIcon({
// className: 'marker-cluster-custom',
// html: `<span>${cluster.getChildCount()}</span>`,
// iconSize: L.point(40, 40, true),
className: "custom-icon",
html: ReactDOMServer.renderToString(
<ThemeProvider theme={customTheme}>
<StyledEngineProvider injectFirst>
<CustomCluster cluster={cluster} locationList={dummyLocationList} />
</StyledEngineProvider>
</ThemeProvider>
)
});
};
<MarkerClusterGroup
showCoverageOnHover={false}
spiderfyDistanceMultiplier={2}
iconCreateFunction={(cluster) =>
customClusterIcon(cluster, dummyLocationList)
}
>
{dummyLocationList.map((item, index) => (
<Marker
key={index}
position={[item.latitude, item.longitude]}
icon={L.divIcon({
className: "custom-icon",
html: ReactDOMServer.renderToString(
<ThemeProvider theme={customTheme}>
<StyledEngineProvider injectFirst>
<CustomMarker
movingStatus={item.status}
label={item.deviceName}
/>
</StyledEngineProvider>
</ThemeProvider>
)
})}
/>
))}
</MarkerClusterGroup>
CustomCluster.jsx
const CustomCluster = (props) => {
const { cluster, locationList } = props;
const classes = useStyles();
const chartRef = useRef();
let clusterLocationList = [];
cluster.getAllChildMarkers().forEach((itemCluster) => {
locationList.forEach((itemLocation) => {
if (
itemCluster._latlng.lat === itemLocation.latitude &&
itemCluster._latlng.lng === itemLocation.longitude
)
clusterLocationList.push(itemLocation);
});
});
const chartOption = {
series: [
{
name: "Access From",
type: "pie",
radius: ["40%", "70%"],
avoidLabelOverlap: false,
label: {
show: true,
position: "inner"
},
labelLine: {
show: false
},
data: [
{ value: 1048, name: "Search Engine" },
{ value: 735, name: "Direct" },
{ value: 580, name: "Email" },
{ value: 484, name: "Union Ads" },
{ value: 300, name: "Video Ads" }
]
}
]
};
useEffect(() => {
console.log("update chart");
let chart;
if (chartRef.current !== null) chart = init(chartRef.current);
const resizeChart = () => {
chart?.resize();
};
window.addEventListener("resize", resizeChart);
if (chartRef.current !== null) {
const chart = getInstanceByDom(chartRef.current);
chart.setOption(chartOption);
}
return () => {
chart?.dispose();
window.removeEventListener("resize", resizeChart);
};
}, [cluster]);
console.log(cluster);
return (
<>
{/* AVATAR */}
<Avatar>{cluster.getChildCount()}</Avatar>
{/* PIE CHART */}
<Box className={classes.chartContainer}>
<Box ref={chartRef} className={classes.chart} />
</Box>
</>
);
};
export default CustomCluster;
Question
Based on some articles on the internet, the useEffect hook is not triggered on React server-side render (SSR) for example here https://codewithhugo.com/react-useeffect-ssr/.
So what's the solution for this case?
The goal is to create a custom cluster feature using a pie chart.
Here is the sample http://bl.ocks.org/gisminister/10001728 but it uses Vanilla Js, not React Js.

How to clear Multiselect Dropdown in Semantic UI React using a button?

I have a Semantic UI React Multiselect Dropdown inside a React functional component and want to have buttons inside the menu, as such (still need to center them...)
How can I clear the selected values using the 'Clear' button?
I am able to clear the selection using the 'x' icon, but that's built into the component.
<Dropdown
search
multiple
selection
clearable
closeOnSelectionChange={false}
options={filterInitialSuggestions()}
className='selectDropdown'
header={dropdownButtons()}
/>
const dropdownButtons = () => {
return (
<div>
<Button positive size='mini'>
Save
</Button>
<Button grey size='mini' onClick={() => console.log('I want to reset the multi select dropdown')}>
Clear
</Button>
<Divider />
</div>
);
};
'Save' and 'Clear' buttons with React useState(). In Reactjs you don't need to use DOM queryselector.
https://codesandbox.io/s/white-leftpad-q6re3?file=/src/Fun.jsx
import React, { Component } from 'react';
import { Dropdown } from 'semantic-ui-react';
const options = [
{ key: 1, text: 'Choice 1', value: 1 },
{ key: 2, text: 'Choice 2', value: 2 },
{ key: 3, text: 'Choice 3', value: 3 },
{ key: 4, text: 'Choice 4', value: 4 },
{ key: 5, text: 'Choice 5', value: 5 },
{ key: 6, text: 'Choice 6', value: 6 },
]
class Example extends Component {
state = {
dropval: []
}
onDropChange = (e, { value }) => {
this.setState(
(prevState) => { return { ...prevState, dropval: value } },
// () => console.log(this.state)
)
}
render() {
return (
<div>
<Dropdown
search
multiple
selection
clearable
closeOnSelectionChange={false}
options={options}
className='selectDropdown'
onChange={this.onDropChange}
value={this.state.dropval}
style={{ width: 300 }}
/>
</div>
);
}
}
export default Example;
I figured out how to solve this issue. I'm not sure if this is the best way, but it seems to work decently.
const dropdownButtons = () => {
return (
<>
<div className='dropdown-saveButton'>
<Button
positive
size='mini'
onClick={() => {
saveValues();
}}
>
Save
</Button>
<Button size='mini' onClick={clearDropdown}>
Clear
</Button>
</div>
<Divider inverted />
</>
);
};
const clearDropdown = e => {
var element = dropdownRef.current.querySelector('[aria-selected="true"]');
if (element) {
dropdownRef.current.querySelector('.clear')?.click();
}
};
<Dropdown
multiple
selection
fluid
clearable
header={dropdownButtons} />

Persistent Victory Charts VictoryVoronoiContainer tooltips on click

I'm implementing a combination VictoryLine and VictoryScatter chart with Victory-Charts using VictoryVoronoiContainer for displaying values at the mouse hover location. I need to make these hover details persist on click at multiple locations.
Example of just hover data: https://formidable.com/open-source/victory/docs/victory-voronoi-container/
Specifically, if a user clicks while a given popup is active, using VoronoiDimension='x' in this case, the hover popup details remain visible at their X coordinate. In a perfect world, any number of these would be visible.
Using events (https://formidable.com/open-source/victory/guides/events/). I can sort of fake it on a scatter point click (https://jsfiddle.net/kn6v9357/3/) but with the voronoidimension hover it's difficult to see the points and you have to be awfully precise with the click. Plus, when there's overlap you only trigger a click on the top layer, so overlapping data isn't shown on what persists.
Any suggestions or ideas?
Code for VictoryScatter events in from the jsFiddle (note this doesn't do what I want) with just a scatter component to keep it simple:
<VictoryChart
containerComponent = { <VictoryVoronoiContainer
voronoiDimension="x"
//voronoiBlacklist={['Scatter0','Scatter1','Scatter2',]}
labels={
(d) => {
return (
`${d.market}: ${d.metric1}`
)
}
}
/> }
>
<VictoryAxis
style = {{tickLabels: {fontSize: 8,angle: -15}}}
label = 'Date'
/>
<VictoryAxis dependentAxis
label = {this.state.metric === 'metric1' ? 'Metric 1' : 'Metric 2'}
style = {{tickLabels: {fontSize: 8}, axisLabel: {padding: 40}}}
axisLabelComponent = { <VictoryLabel style = {{fontSize: 12}} dx = {20} /> }
/>
{ this.viewablePlaces.map((place, i) =>
<VictoryGroup
animate = {{easing: "cubic",duration: 500,onLoad: {duration: 0}}}
key = {place + String(i) + 'Group'}
data = {this.get_data(place)}
x = {(d) => moment(d.date).format('MMM YY')}
y = {this.state.metric === 'metric1' ? 'metric1' : 'metric2'}
>
<VictoryScatter
key = {place + String(i) + 'Scatter'}
name = {"Scatter"+i}
style = {{
data: {fill: "#455A64",cursor: "pointer"},
labels: {fontSize: 12,padding: 2}
}}
size = {2.5}
labels={(d) => d.market + ': ' + String(d.metric2)}
labelComponent = {
<VictoryTooltip
orientation = {"top"}
pointerLength = {5}
pointerWidth = {3}
cornerRadius = {3}
/>
}
events={[
{
target: "data",
eventHandlers: {
onMouseOver: (props) => {
return [
{
target: "labels",
mutation: {active: true}
}
];
},
onMouseOut: (props) => {
return [
{
target: "labels",
mutation: (props) => props.name === 'clicked' ? {name: 'clicked', active: true} : null
}
];
},
onClick: (props) => {
return [
{
target: "data",
mutation: (props) => props.style.fill === 'orange' ?
null : {style: Object.assign({}, props.style, {fill: 'orange'}), size: 3.5}
},
{
target: "labels",
mutation: (props) => props.name === 'clicked' ?
null : {name: 'clicked', active: true}
}
];
}
}
}
]}
/>
</VictoryGroup>
)}
</VictoryChart>
Edit:
Here's another example with persistent points, but I need to find a way to also make the labels persist.
https://codesandbox.io/s/oq1w9xj8q6

Categories

Resources