I have created the following component in ReactJS.
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import React, { useEffect, useState } from "react";
import axios from "axios";
const Chart = () => {
const [data, setData] = useState(null);
useEffect(() => {
axios.get("http://127.0.0.1:8000/fail-count").then((resp) => {
console.log(resp.data)
setData(resp.data.message);
});
}, []);
return (
<div>
<ResponsiveContainer width={'100%'} aspect = {3} debounce={3}>
<BarChart data={data} margin={{ top: 10, right: 30, left: 20, bottom: 5, }}>
<CartesianGrid strokeDasharray="3 3"/>
<XAxis dataKey="DEALER"/>
<YAxis/>
<Tooltip/>
<Legend/>
<Bar dataKey="Count" fill="#8884d8"/>
</BarChart>
</ResponsiveContainer>
</div>
)
}
export default Chart
As you can see I am getting data from my local host backend.
The data retrieved from there is structured as below..
[
{
"DEALER": "Dealer A",
"Count": 47
},
{
"DEALER": "Dealer B",
"Count": 46
},
{
"DEALER": "Dealer C",
"Count": 46
},
{
"DEALER": "Dealer D",
"Count": 32
},
{
"DEALER": "Dealer E",
"Count": 31
}
]
Reading through re-charts docs it looks like my data is structured correctly as per their examples. Recharts-Bar-Chart
[
{
"name": "Page A",
"uv": 4000,
"pv": 2400
},
{
"name": "Page B",
"uv": 3000,
"pv": 1398
},
{
"name": "Page C",
"uv": 2000,
"pv": 9800
},
{
"name": "Page D",
"uv": 2780,
"pv": 3908
},
{
"name": "Page E",
"uv": 1890,
"pv": 4800
},
{
"name": "Page F",
"uv": 2390,
"pv": 3800
},
{
"name": "Page G",
"uv": 3490,
"pv": 4300
}
]
Im quite new to react so Im unsure if Im passing data variable correctly from useState hook?
Furthermore, if I comment out this section of code..
const [data, setData] = useState(null);
useEffect(() => {
axios.get("http://127.0.0.1:8000/fail-count").then((resp) => {
console.log(resp.data)
setData(resp.data.message);
});
}, []);
And inplace of that use
const data = [
{
"DEALER": "Dealer A",
"Count": 47
},
{
"DEALER": "Dealer B",
"Count": 46
},
{
"DEALER": "Dealer C",
"Count": 46
},
{
"DEALER": "Dealer D",
"Count": 32
},
{
"DEALER": "Dealer E",
"Count": 31
}
]
const Chart = () => {return (
<div>
<ResponsiveContainer width={'100%'} aspect = {3} debounce={3}>
<BarChart data={data} margin={{ top: 10, right: 30, left: 20, bottom: 5, }}>
<CartesianGrid strokeDasharray="3 3"/>
<XAxis dataKey="DEALER"/>
<YAxis/>
<Tooltip/>
<Legend/>
<Bar dataKey="Count" fill="#8884d8"/>
</BarChart>
</ResponsiveContainer>
</div>
)
}
export default Chart
Then the chart data renders correctly..
You should use async - await for your http requests. Data is not getting set properly.
useEffect(() => {
fetchData = async () => {
await axios.get("http://127.0.0.1:8000/fail-count")
.then((resp) => {
setData(resp.data.message);
}
}
fetchData();
}, [])
you can also do using create new function using async - await.if you getting the same above data response then it should be work.
import axios from "axios";
import { useEffect, useState } from "react";
import {
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
CartesianGrid,
Legend
} from "recharts";
import "./styles.css";
const Chart = () => {
const [data, setData] = useState(null);
const getData = async () => {
const { data } = await axios.get(`http://127.0.0.1:8000/fail-count`);
setData(data.message);
};
useEffect(() => {
getData();
}, []);
return (
<div>
<ResponsiveContainer width={"100%"} aspect={3} debounce={3}>
<BarChart
data={data}
margin={{ top: 10, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="DEALER" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="Count" fill="#8884d8" />
</BarChart>
</ResponsiveContainer>
</div>
);
};
export default Chart;
Related
I have a CartSummary component where I show the products that are added to the cart. When the user triggers addProduct method, the state reloads and re-fetchs the cart from the server. In short, I save the cart information in database, when somenone add/remove a product from the cart I reload the cart. However, I get the the above error. It might be caused by asynchronous programming. I tried to use loader before the cart information arrives to the client side, but it didn't work. Also, the state is not reloaded when I add a new product to the cart. I tried to log the cart info to the console each time(marked in CartSummary class), it seems the cartItems is empty at first, but then it refilled.That's why I made a codition such that cart.cartItems.length>0 ?.... : .... Still not working.
CartSummary component where I show the cart details to the user.
import React, {useEffect} from 'react';
import {Badge, DropdownItem, DropdownMenu, DropdownToggle, Spinner, UncontrolledDropdown} from "reactstrap";
import {useDispatch, useSelector} from "react-redux";
import {cartActions} from "../redux/slice/cartSlice";
import {useNavigate} from "react-router-dom"
import alertify from "alertifyjs";
import {fetchCart} from "../redux/thunk/cartActionThunk";
import {PageSpinner} from "./spinner/Spinner";
const CartSummary = () => {
const cart = useSelector(state => (state.cartStore.cart))
const isLoading = useSelector(state => (state.uiStore.isLoading))
const dispatch = useDispatch()
const handleRemoveFromCart = (product) => {
dispatch(cartActions.removeFromCart(product.id))
alertify.error(product.productName + " removed from the cart", 1)
}
const navigate = useNavigate()
useEffect(() => {
}, [dispatch])
console.log(cart) // HERE I LOG THE CART
return (
<div>
<UncontrolledDropdown nav inNavbar>
<DropdownToggle nav caret>
Cart
</DropdownToggle>
<DropdownMenu>
{
isLoading ?
<PageSpinner></PageSpinner>
:
cart.cartItems.map((item) => (
<DropdownItem
style={{
justifyContent: "space-between",
display: "flex",
alignContent: "center",
margin: 10
}}
key={item.id}>
{item.product.productName}
<Badge color="success">{item.totalQuantity}</Badge>
</DropdownItem>
))
}
<DropdownItem divider/>
<DropdownItem style={{textAlign: "end"}}> <Badge
color="success">${cart.totalPrice}</Badge></DropdownItem>
<DropdownItem style={{textAlign: "center"}} onClick={() => navigate("/cart-details")}>Show
cart details</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
</div>
);
};
export default CartSummary;
Cart Actions Thunk
import {API_URL} from "../../const/url";
import {cartActions} from "../slice/cartSlice";
import alertify from "alertifyjs";
import {uiActions} from "../slice/uiSlice";
export const addToCart = (product) => {
const productDto = {
"id": product.id,
"productName": product.productName,
"quantityPerUnit": product.quantityPerUnit,
"unitPrice": product.unitPrice,
"unitsInStock": product.unitsInStock,
"categoryId": product.category.id
}
const userId = 1;
return (dispatch) => {
dispatch(uiActions.setIsLoading(true))
fetch(API_URL + '/cartItem?userId=' + userId, {
method: 'POST',
headers: {"Content-Type": "application/json"},
body: JSON.stringify(productDto)
}
).then((response) => {
if (!response.ok) {
throw Error("Couldn't add " + productDto.productName + " to the cart.")
}
return response.json()
}).then((result) => {
dispatch(cartActions.addProduct(result))
dispatch(uiActions.setIsLoading(false))
alertify.success(productDto.productName + " added successfully.")
}).catch((err) => {
dispatch(uiActions.setError(err))
dispatch(uiActions.setIsLoading(false))
alertify.error(err.message)
})
}
}
export const fetchCart = () => {
const userId = 1;
return (dispatch)=>{
dispatch(uiActions.setIsLoading(true))
fetch(API_URL + '/cartItem?userId=' + userId, {
method: 'GET'
}).then((response)=>{
if(!response.ok){
throw Error("Couldn't fetch cart data")
}
return response.json()
}).then((result)=>{
dispatch(cartActions.fetchCart(result))
dispatch(uiActions.setIsLoading(false))
}).catch((err)=>{
dispatch(uiActions.setError(err))
dispatch(uiActions.setIsLoading(false))
alertify.error(err.message)
})
}
}
export const removeFromCart = (product) => {
return (dispatch) => {
fetch(API_URL + '/')
}
}
CartSlice
const cartSlice = createSlice({
name: 'cart',
initialState: {cart: {cartItems: [], totalPrice: 0, totalQuantity: 0}},
reducers: {
//payload is product
addProduct(state, action) {
state.cart.cartItems.push(action)
},
//payload is product id
removeFromCart(state, action) {
const existingCartItem = state.cart.cartItemList.find((cartItem) => cartItem.product.id === action.payload)
state.cart.totalPrice -= existingCartItem.totalPrice;
state.cart.totalQuantity--;
state.cart.cartItemList = state.cart.cartItemList.filter((cartItem) => cartItem.product.id !== action.payload)
},
fetchCart(state, action) {
state.cart = action.payload
}
}
})
export const cartActions = cartSlice.actions;
export default cartSlice;
Json structure for cart
{
"id": 1,
"cartItems": [
{
"id": 19,
"totalQuantity": 1,
"totalPrice": 12.5,
"product": {
"id": 3,
"productName": "Scottish Longbreads",
"quantityPerUnit": "10 boxes x 8 pieces",
"unitPrice": 12.5,
"unitsInStock": 6,
"categoryId": 3
}
},
{
"id": 20,
"totalQuantity": 1,
"totalPrice": 31.0,
"product": {
"id": 10,
"productName": "Ikura",
"quantityPerUnit": "12 - 200 ml jars",
"unitPrice": 31.0,
"unitsInStock": 31,
"categoryId": 8
}
},
{
"id": 21,
"totalQuantity": 2,
"totalPrice": 69.6,
"product": {
"id": 4,
"productName": "Mozzarella di Giovanni",
"quantityPerUnit": "24 - 200 g pkgs.",
"unitPrice": 34.8,
"unitsInStock": 14,
"categoryId": 4
}
},
{
"id": 22,
"totalQuantity": 1,
"totalPrice": 37.0,
"product": {
"id": 6,
"productName": "Queso Manchego La Pastora",
"quantityPerUnit": "10 - 500 g pkgs.",
"unitPrice": 37.0,
"unitsInStock": 86,
"categoryId": 6
}
},
{
"id": 23,
"totalQuantity": 1,
"totalPrice": 38.0,
"product": {
"id": 8,
"productName": "Queso Manchego La Pastora",
"quantityPerUnit": "10 - 500 g pkgs.",
"unitPrice": 38.0,
"unitsInStock": 86,
"categoryId": 8
}
},
{
"id": 24,
"totalQuantity": 1,
"totalPrice": 33.25,
"product": {
"id": 5,
"productName": "Wimmers gute Semmelknödel",
"quantityPerUnit": "20 bags x 4 pieces",
"unitPrice": 33.25,
"unitsInStock": 22,
"categoryId": 5
}
},
{
"id": 25,
"totalQuantity": 1,
"totalPrice": 13.0,
"product": {
"id": 2,
"productName": "Original Frankfurer grüne So",
"quantityPerUnit": "12 boxes",
"unitPrice": 13.0,
"unitsInStock": 11,
"categoryId": 8
}
}
],
"totalQuantity": 7,
"totalPrice": 234.35
}
So I'm trying to populate a in-memory MirageJS' database with a post request of the data in useEffect, making a get request after. Then I save the result to columnsNames state and use it to map this array and read the information as a label of Tab MaterialUI component. However, I have some questions:
When I put a console.log after useEffect or inside get request, both response.data and columnsNames state have 2 identical arrays. Why 2 and not 1?
At runtime the console prints
Uncaught TypeError: columnsNames.map is not a function
The above error occurred in the <ScrollableTabsButtonAuto> component
and nothing is showed in the screen. Why?
Anyone that could help me, thanks in advance!
Data.ts
export const data = [
{
"id": 1,
"title": "Test 1",
"steps": [
"Read a starting book",
"Keep calm and drink coffee",
"Don't Panic so much",
]
},
{
"id": 2,
"title": "Test 2",
"steps": [
"Get some helpful help",
"Don't loose yourself",
]
},
{
"id": 3,
"title": "Test 3",
"steps": [
]
},
{
"id": 4,
"title": "Test 4",
"steps": [
]
},
{
"id": 5,
"title": "Test 5",
"steps": [
]
},
{
"id": 6,
"title": "Test 6",
"steps": [
]
}
]
App.tsx
import { createServer, Model } from 'miragejs';
createServer({
models: {
columns: Model,
},
routes() {
this.namespace = 'api'
this.get('columns', () => {
return this.schema.all('columns')
})
this.post('columns', (schema, request) => {
const data = JSON.parse(request.requestBody)
return schema.create('columns', data)
})
}
})
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Tab.tsx
interface ColumnTypes {
id: number,
title: string,
steps: string[]
}
export default function ScrollableTabsButtonAuto() {
const [value, setValue] = React.useState(0);
const [columnsNames, setColumnsNames] = React.useState<ColumnTypes[]>([])
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
React.useEffect(() => {
api.post('columns', data)
.then( () => {
api.get<ColumnTypes[]>('columns')
.then(response => setColumnsNames(response.data))
})
}, [])
return (
<Box sx={{ maxWidth: { xs: 320, sm: 1280 }, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
variant="scrollable"
scrollButtons="auto"
aria-label="scrollable auto tabs example"
>
{columnsNames.map(({ id, title }) => (
<Tab
label={title}
key={id}
/>)
)}
</Tabs>
</Box>
);
}
How to modify series and options, i want to make chart type=treemap on react hooks like this
i have name, user and percent on api.
{
"data": [
{
"id": "1",
"name": "Pisces",
"user": "95",
"percent": "3.15%",
},
{
"id": "2",
"name": "Leo",
"user": "50",
"percent": "2.35%",
},
{
"id": "3",
"name": "Capricorn",
"user": "91",
"percent": "3.12%",
}
]
}
and source for apex https://apexcharts.com/docs/chart-types/treemap-chart/
import React, { useState,useEffect } from 'react';
import axios from 'axios';
import './App.css';
import Chart from 'react-apexcharts'
import icUser from './image/profile-user.png'
import icChart from './image/pie-chart.png'
const App =()=> {
const [dataUser,setDataUser]=useState([])
useEffect(() => {
axios.get("http://localhost:4000/data")
.then(response =>{
setDataUser(response.data)
}).catch(e => {
alert(e);
})
}, [])
const series = {.....}
const options = {.....}
return (
<div>
<Chart options={options} series={series} height={350} type="treemap"/>
</div>
)
}
export default App
In series you need to pass an array like this, Where x is the name, and y percentage. and In option you can modify the treemap chart like change height, type, plotOptions and more...
const App = () => {
const [dataUser, setDataUser] = useState([])
useEffect(() => {
axios.get("http://localhost:4000/data")
.then(response => {
setDataUser(response.data)
}).catch(e => {
alert(e);
})
}, [])
const seriesData = [];
const options = {}
dataUser.map((val) => {
seriesData.push(
{
x: val.name, //
y: val.percent //
}
);
});
const series = [{ data: seriesData }];
return (
<div>
<Chart options={options} series={series} height={350} type="treemap" />
</div>
)
}
export default App
In my use case I need to create interactive farm maps where I can select fields.
I'm trying to do this using the react-simple-maps component.
what was done
I created the farm Shapfile map in QGIS.
Converted to TopoJSON using Mapshaper as per this tutorial.
However the map does not render correctly, see CodeSandbox.
What can I be missing?
Is this the best component to use in this case?
Map error
TopoJSON Map
{
"type": "Topology",
"arcs": [
[
[0, 62],
[51, 60],
[60, -46],
[-85, -76],
[-26, 62]
],
[
[112, 77],
[-60, 44],
[92, 110],
[57, -40],
[0, -2],
[-66, -60],
[14, -19],
[-37, -33]
]
],
"transform": {
"scale": [15.721852200470671, 19.17233904106825],
"translate": [-65942.30731638917, 8482615.288037943]
},
"objects": {
"Contorno_UTM": {
"type": "GeometryCollection",
"geometries": [
{
"arcs": [[0]],
"type": "Polygon",
"properties": { "id": 1, "area_ha": 197.4585 }
},
{
"arcs": [[1]],
"type": "Polygon",
"properties": { "id": 2, "area_ha": 299.0857 }
}
]
}
}
}
React Simple Map component
import React, { memo } from "react";
import {
ZoomableGroup,
ComposableMap,
Geographies,
Geography
} from "react-simple-maps";
import map from "./map.json";
const geoUrl = map;
const rounded = (num) => {
if (num > 1000000000) {
return Math.round(num / 100000000) / 10 + "Bn";
} else if (num > 1000000) {
return Math.round(num / 100000) / 10 + "M";
} else {
return Math.round(num / 100) / 10 + "K";
}
};
const MapChart = ({ setTooltipContent }) => {
return (
<>
<ComposableMap data-tip="" projectionConfig={{ scale: 200 }}>
<ZoomableGroup>
<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map((geo) => (
<Geography
key={geo.rsmKey}
geography={geo}
onMouseEnter={() => {
const { id, area_ha } = geo.properties;
setTooltipContent(`${id} — ${rounded(area_ha)}`);
}}
onMouseLeave={() => {
setTooltipContent("");
}}
style={{
default: {
fill: "#D6D6DA",
outline: "none"
},
hover: {
fill: "#F53",
outline: "none"
},
pressed: {
fill: "#E42",
outline: "none"
}
}}
/>
))
}
</Geographies>
</ZoomableGroup>
</ComposableMap>
</>
);
};
export default memo(MapChart);
I managed to solve it, the problem was in the project's SRC configuration, it was UTM and the correct is WGS 84, I changed, exported in GeoJSON format and converted to TopoJSON using Mapshaper, changed the projections and rotation and now everything is ok as you can see in CodeSandbox.
projection="geoAzimuthalEqualArea"
projectionConfig={{
rotate: [56.22, 13.66, 0],
scale: 360000
}}
I am using react-mapbox-gl#3.4.1 and supercluster#3.0.2 to get the list of features in a clicked cluster.
I saw that in V3.0.0 of supercluster the zoom factor is encoded into the cluster_id.
I am using a GeoJSONLayer to do the clustering (which may not be the right way to do it for supercluster) and the correct features are not returned OnClick.
Clicking some clusters throws a run-time error:
'No cluster with the specified id'.
Any idea why no features or the wrong features are being returned?
import React, { Component } from 'react';
import ReactMap, { Layer, GeoJSONLayer } from 'react-mapbox-gl';
import SuperCluster from 'supercluster';
const accessToken = "pk.eyJ1IjoiYWxleDMxNjUiLCJhIjoiY2o0MHp2cGtiMGFrajMycG5nbzBuY2pjaiJ9.QDApU0XH2v35viSwQuln5w";
const style = "mapbox://styles/mapbox/streets-v9";
const Map = ReactMap({
accessToken
});
const featureCollection = {
type: 'FeatureCollection',
features: [
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-104.9847, 39.73915] },
"properties": { "id": 1 }
},
{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-104.967478, 39.732269] },
"properties": { "id": 2 }
}
]
}
const mapStyle = {
height: '100vh',
width: '100vw'
};
const cluster = SuperCluster({
maxZoom: 14,
radius: 50,
});
class App extends Component {
componentWillMount () {
cluster.load(featureCollection.features);
}
onClick (evt) {
const map = evt.target;
const features = map.queryRenderedFeatures(evt.point, { layers: ['clustered_layer'] });
if(!features.length) {
return;
}
const feature = features[0];
const cluster_id = feature.properties.cluster_id;
const all_features = cluster.getLeaves(cluster_id, 500);
}
onStyleLoad (map) {
map.on('click', 'clustered_layer', this.onClick.bind(this));
}
render() {
return (
<Map
center={ [-104.9847, 39.73915] }
containerStyle={mapStyle}
onStyleLoad={ this.onStyleLoad.bind(this) }
style={style}
zoom={[5]}
>
<GeoJSONLayer
id="source_id"
data={ featureCollection }
sourceOptions={{
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
}}
/>
<Layer
id="clustered_layer"
type="circle"
sourceId="source_id"
filter={ ['has', 'point_count']}
paint={{
'circle-color': '#0991dd',
'circle-radius': {
property: 'point_count',
type: 'interval',
stops: [
[0, 20],
[100, 30],
[750, 40]
]
}
}}
/>
<Layer
id="unclustered_layer"
type="circle"
sourceId="source_id"
paint={{
'circle-color': '#3fff80',
'circle-radius': 18
}}
filter={ ['!has', 'point_count'] }
/>
<Layer
id="text_layer"
type="symbol"
sourceId="source_id"
layout={{
'text-field': '{point_count}',
'text-size': 12
}}
filter={ ['has', 'point_count'] }
/>
</Map>
);
}
}
export default App;
WebpackBin showing this issue
Any help would be appreciated.
Update
I was able to encode the cluster_id the way supercluster does it and get the clustered items.
const cluster_id = (id << 5) + (zoom + 1);
Thanks