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
}}
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
}
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;
So my understanding according to this post:
React Redux: Uncaught Invariant Violation (Objects are not valid as a React child)
is that my {job.title} and so on are objects and React won't let me just plop an object into the render output, but I guess I don't have enough experience to understand fully as to why I am getting this error in the first place nor how to fix it.
So in this screen:
import React, { Component } from "react";
import { View, Text, Platform } from "react-native";
import { MapView } from "expo";
import { connect } from "react-redux";
import { Card, Button } from "react-native-elements";
import Swipe from "../components/Swipe";
import * as actions from "../actions";
class DeckScreen extends Component {
renderCard(job) {
return (
<Card title={job.title}>
<View style={{ height: 300 }}>
<MapView
scrollEnabled={false}
style={{ flex: 1 }}
cacheEnabled={Platform.OS === "android" ? true : false}
/>
</View>
<View style={styles.detailWrapper}>
<Text>{job.company}</Text>
<Text>{job.post_date.toString()}</Text>
</View>
<Text>
{job.description.replace(/<span>/g, "").replace(/<\/span>/g, "")}
</Text>
</Card>
);
}
renderNoMoreCards() {
return <Card title="No more jobs" />;
}
render() {
return (
<View>
<Swipe
data={this.props.jobs}
renderCard={this.renderCard}
renderNoMoreCards={this.renderNoMoreCards}
keyProp="id"
/>
</View>
);
}
}
const styles = {
detailWrapper: {
flexDirection: "row",
justifyContent: "space-around",
marginBottom: 10
}
};
function mapStateToProps({ jobs }) {
return { jobs: jobs.listing };
}
export default connect(
mapStateToProps,
actions
)(DeckScreen);
and in my component:
import React, { Component } from "react";
import {
View,
Animated,
PanResponder,
Dimensions,
LayoutAnimation,
UIManager
} from "react-native";
const SCREEN_WIDTH = Dimensions.get("window").width;
const SWIPE_THRESHOLD = 0.25 * SCREEN_WIDTH;
const SWIPE_OUT_DURATION = 250;
class Swipe extends Component {
static defaultProps = {
onSwipeRight: () => {},
onSwipeLeft: () => {},
keyProp: "id"
};
constructor(props) {
super(props);
const position = new Animated.ValueXY();
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: (event, gestureState) => true,
onPanResponderMove: (event, gestureState) => {
position.setValue({ x: gestureState.dx, y: gestureState.dy });
},
onPanResponderRelease: (event, gestureState) => {
if (gestureState.dx > SWIPE_THRESHOLD) {
this.forceSwipe("right");
} else if (gestureState.dx < -SWIPE_THRESHOLD) {
this.forceSwipe("left");
} else {
this.resetPosition();
}
}
});
this.state = { panResponder, position, index: 0 };
}
componentWillReceiveProps(nextProps) {
if (nextProps.data !== this.props.data) {
this.setState({ index: 0 });
}
}
componentWillUpdate() {
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true);
LayoutAnimation.spring();
}
forceSwipe(direction) {
const x = direction === "right" ? SCREEN_WIDTH : -SCREEN_WIDTH;
Animated.timing(this.state.position, {
toValue: { x, y: 0 },
duration: SWIPE_OUT_DURATION
}).start(() => this.onSwipeComplete(direction));
}
onSwipeComplete(direction) {
const { onSwipeLeft, onSwipeRight, data } = this.props;
const item = data[this.state.index];
direction === "right" ? onSwipeRight(item) : onSwipeLeft(item);
this.state.position.setValue({ x: 0, y: 0 });
this.setState({ index: this.state.index + 1 });
}
resetPosition() {
Animated.spring(this.state.position, {
toValue: { x: 0, y: 0 }
}).start();
}
getCardStyle() {
const { position } = this.state;
const rotate = position.x.interpolate({
inputRange: [-SCREEN_WIDTH * 1.5, 0, SCREEN_WIDTH * 1.5],
outputRange: ["-120deg", "0deg", "120deg"]
});
return {
...position.getLayout(),
transform: [{ rotate }]
};
}
renderCards() {
if (this.state.index >= this.props.data.length) {
return this.props.renderNoMoreCards();
}
return this.props.data
.map((item, i) => {
if (i < this.state.index) {
return null;
}
if (i === this.state.index) {
return (
<Animated.View
key={item[this.props.keyProp]}
style={[this.getCardStyle(), styles.cardStyle]}
{...this.state.panResponder.panHandlers}
>
{this.props.renderCard(item)}
</Animated.View>
);
}
return (
<Animated.View
key={item[this.props.keyProp]}
style={[styles.cardStyle, { top: 10 * (i - this.state.index) }]}
>
{this.props.renderCard(item)}
</Animated.View>
);
})
.reverse();
}
render() {
return <View>{this.renderCards()}</View>;
}
}
const styles = {
cardStyle: {
position: "absolute",
width: SCREEN_WIDTH
}
};
export default Swipe;
when I do a console.log(job); it renders everything you would see in the API endpoint:
And when I do a console.log(job.title); I get this:
[03:46:03] Front End Designer Summer Internship
[03:46:03] Senior UX / Product Designer - Investments AI team
[03:46:03] Technical AV Analyst
[03:46:03] Senior Designer
[03:46:03] Junior Developer
[03:46:03] Webinar Producer + Host
[03:46:03] Front End Web Developer
[03:46:03] Android Developer needed for React Native project
[03:46:03] UX Designer
[03:46:03] Software Product Engineer - Reinventing VC
Along the lines of doing a console.log(job);, I decided to then do a console.log(job.title); and I got back:
[03:46:03] Front End Designer Summer Internship
[03:46:03] Senior UX / Product Designer - Investments AI team
[03:46:03] Technical AV Analyst
[03:46:03] Senior Designer
[03:46:03] Junior Developer
[03:46:03] Webinar Producer + Host
[03:46:03] Front End Web Developer
[03:46:03] Android Developer needed for React Native project
[03:46:03] UX Designer
[03:46:03] Software Product Engineer - Reinventing VC
so that was not the problem, then I decided to move further and do a console.log(job.company); and I got back:
[03:48:25] Object {
[03:48:25] "id": "planningcenter",
[03:48:25] "location": Object {
[03:48:25] "id": "carlsbadca",
[03:48:25] "name": "Carlsbad, CA",
[03:48:25] },
[03:48:25] "logo": "https://d2fcz5no062gar.cloudfront.nethttps://authenticjobs.s3.amazonaws.com/uploads/logos/edc867ddb3b30a5c8f8819d1a1347346/thumb/PC -Icon -- color .png",
[03:48:25] "name": "Planning Center",
[03:48:25] "tagline": "Software designed to help church volunteers",
[03:48:25] "type": null,
[03:48:25] "url": "http://planning.center",
[03:48:25] }
[03:48:26] Object {
[03:48:26] "id": "americaninternationalgroupaig",
[03:48:26] "location": Object {
[03:48:26] "id": "175waterstreetnewyorkny10038",
[03:48:26] "name": "175 Water Street New York, NY 10038",
[03:48:26] },
[03:48:26] "logo": "https://d2fcz5no062gar.cloudfront.net/assets/images/defaults/company-blank.png",
[03:48:26] "name": "American International Group (AIG)",
[03:48:26] "tagline": "We're a 100 year old insurance company with operations globally!",
[03:48:26] "type": null,
[03:48:26] "url": "https://www.aig.com/about-us",
[03:48:26] }
[03:48:26] Object {
[03:48:26] "id": "universityofcalifornialosangeles",
[03:48:26] "location": Object {
[03:48:26] "id": "losangelesca",
[03:48:26] "name": "Los Angeles, CA",
bingo! there is the problem, returning objects on {job.company}, so then I decided to try console.log(job.company.id); and got:
[03:49:14] planningcenter
[03:49:15] americaninternationalgroupaig
[03:49:15] universityofcalifornialosangeles
[03:49:15] whitneymuseumofamericanart
[03:49:15] studioality
[03:49:15] convertkit
[03:49:15] metropolitanareaplanningcouncil
[03:49:15] jenzy
[03:49:15] cfainstitute
[03:49:15] nfx
boom! problem solved and my UI is rendering now.
I'm pretty new to D3.js library. I've been trying to implement it in React JS project.
I'm just using d3 functions for calculations and rendering part will be done by React JS.
I successfully created a tree but I'm getting links with weird style (Not as expected)
This is what I expected (As per D3 Documentation)
But I got the below tree :(
Also I can't able to collapse or expand...
You can find the code below.
componentDidMount() {
const width = 800, height = 800;
const tree = d3.tree().size([height, width - 160]);
const stratify = d3.stratify().id((d) => {
return d.name;
}).parentId((d) => {
return d.parent;
});
const root = stratify(this.state.data)
.sort((a, b) => {
return (a.height - b.height) || a.id.localeCompare(b.id);
});
this.setState({ paths: tree(root).links() });
this.setState({ nodes: root.descendants() })
}
render() {
let paths = this.state.paths && this.state.paths.map(item => {
let d = d3
.linkHorizontal()
.x((d) => {
return d.y;
})
.y((d) => {
return d.x;
});
return <path className='link' d={d(item)} />
})
let nodes = this.state.nodes && this.state.nodes.map((node, i) => {
return <g key={node.id} className={"node" + node.children ? " node--internal" : " node--leaf"}
transform={`translate(${node.y}, ${node.x})`}>
<circle r="10" style={{ 'fill': node.children ? 'lightsteelblue' : 'black' }} />
<text y="0" dy="0" textAnchor="middle"
style={{ 'fillOpacity': 1 }}>{node.name}</text>
</g>
})
return (
<svg className="tree-chart-basic" ref={(r) => this.chartRf = r} style={{ width: '800px', height: '800px' }}>
<g transform='translate(20,20)'>
{nodes}
{paths}
</g>
</svg>
);
}
this.state.data will be having the array as follows
[
{ "name": "ProjectA", "parent": "" },
{ "name": "ApplicationA", "parent": "ProjectA" },
{ "name": "EnvironmentB", "parent": "ProjectA" },
{ "name": "TierC", "parent": "ApplicationA" },
{ "name": "TierD", "parent": "ApplicationA" },
{ "name": "TierE", "parent": "ApplicationA" },
{ "name": "ServiceF", "parent": "EnvironmentB" },
{ "name": "ContainerG", "parent": "EnvironmentB" },
{ "name": "ContainerH", "parent": "TierE" },
{ "name": "ContainerH", "parent": "TierE" },
{ "name": "ContainerH", "parent": "TierE" },
{ "name": "ContainerH", "parent": "TierE" },
{ "name": "ContainerH", "parent": "TierE" },
{ "name": "ContainerH", "parent": "TierE" }
]
How can I get the expected Tree in my code?
How can I get collapse/expand functionality?
Please tell me what I'm doing wrong. Thanks for the time. (:
I finally got the solution to my answer.
I need to specify the style to <path> tag as follows.
<path
fill="none"
stroke="#97a6ff"
strokeWidth="2px"
/>
I had the same issue when using react-tree-graph which uses d3 under the hood. Luckily you can add custom classes to customize everything. I use css-modules so I had to escape the global classnames.
import { Tree } from 'react-tree-graph';
import style from './tree.module.scss';
// ...
<Tree svgProps={{ className: style.acmeProductTree }} />
// tree.module.scss
svg.acmeProductTree path:global(.link) {
fill: none;
stroke: #2593b8;
stroke-width: 1.5px;
}
As I found out just now you can alternatively apply a custom class directly to the path element:
<Tree pathProps={{ className: style.acmeProductTreeLink }} />
.acmeProductTreeLink {
fill: none;
stroke: #2593b8;
stroke-width: 1.5px;
}
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