Zoom to a specific point in react-simple-maps - javascript

Problem
I don't understand how to properly zoom to a position of a click, .center(center) on projection doesn't do anything.
Code
import React, { Fragment, useState } from 'react'
import { ComposableMap, ZoomableGroup, Geographies, Geography } from 'react-simple-maps'
import { geoConicEqualArea } from 'd3-geo'
import map from '../public/Russia.json'
const VectorMap = ({ onClick, width = 990, height = 505, onBlur }) => {
const [zoom, setZoom] = useState(1)
const [center, setCenter] = useState([100, 100])
// Zoom event handlers
const onWheel = e => (e.deltaY > 0 ? setZoom(prev => prev - 0.3) : setZoom(prev => prev + 0.3))
return (
<Fragment>
<ComposableMap
projection={() =>
geoConicEqualArea()
.scale(690)
// projection doesn't change anyway
.center(center)
.parallels([40, 80])
.rotate([265])
.translate([130, 5])
}
{...{ width, height }}
style={{
width: '85vw',
height: '100vh'
}}
>
<ZoomableGroup {...{ zoom }}>
<Geographies geography={map}>
{(geographies, projection) =>
geographies.map((geography, i) => (
<Geography
key={i}
{...{ geography, onWheel, projection, onBlur }}
onClick={(obj, e) => {
onClick(obj)
setCenter(e.clientX, e.clientY)
// setZoom(1.5)
}}
style={{
default: {
fill: '#ECEFF1',
stroke: '#607D8B',
strokeWidth: 0.75,
outline: 'none'
},
hover: {
fill: '#607D8B',
stroke: '#607D8B',
strokeWidth: 0.75,
outline: 'none'
},
pressed: {
fill: '#FF5722',
stroke: '#607D8B',
strokeWidth: 0.75,
outline: 'none'
}
}}
/>
))
}
</Geographies>
</ZoomableGroup>
</ComposableMap>
<button className="ZoomBtn" onClick={() => setZoom(prev => prev + 0.1)}>
+
</button>
<button className="ZoomBtn" onClick={() => setZoom(prev => prev - 0.1)}>
-
</button>
</Fragment>
)
}
export default VectorMap
I also noticed there's a center property for ZoomableGroup but it also doesn't seem to be working properly. It zooms to one place and map becomes no draggable anymore.
Here are the warnings in console:
Warning: componentWillReceiveProps has been renamed, and is not recommended for use.
* Move data fetching code or side effects to componentDidUpdate.
* If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps.
* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.
Please update the following components: Geographies, ZoomableGroup dll_d6a88dbe3071bd165157.js:12608:15
Source map error: Error: Invalid URL: webpack://[name]_[chunkhash]/webpack/bootstrap
Resource URL: http://localhost/_next/static/development/dll/dll_d6a88dbe3071bd165157.js?ts=1577625154482
Source Map URL: dll_d6a88dbe3071bd165157.js.map
Unexpected value
translate(
NaN
NaN
)
scale(1)
translate(-495 -250)
parsing transform attribute.
Additional info
React version: 16.12
react-simple-maps version: 0.12.1
Help would be appreciated.

You can modify the value of center in ComposableMap.
Suppose if you want center to be at (20, 20), you must write:
center:[20, 20]

Related

not getting new values from useState react

Learning react by coding, here i'm using 'react-zoom-pan-pinch' for mouse scroll wheel, so when i zoomIn using mouse scroll and its scale gets bigger than '1.311' then it should make circle smaller ("10"), here i have created demo of what i want to achieve.
https://codesandbox.io/s/react-zoom-pan-pinch-forked-svx5v6?file=/src/index.js
you can check from console when value gets bigger that circle becomes smaller,
but in my real code i'm using 'viewGenerator' (react-d3-graph) and customNode where i have that svg and circle
import { Graph } from "react-d3-graph";
const [circleR, setCircleR] = useState("50");
const zoomChange = (event: any) => {
if (event.state.scale < 1.311) {
setCircleR("50");
}
if (event.state.scale > 1.311) {
setCircleR("10");
}
console.log("event state scale ", event.state.scale);
console.log("event circleR ", circleR);
};
function CustomNode(props) {
return (
<div>
<svg viewBox="-1 -1 2 2">
<circle
cx="0"
cy="0"
r={circleR}
strokeWidth="0.1"
stroke= "black"
fill= "orange"
/>
</svg>
</div>
);
}
const default_config = {
d3_config: {
nodeHighlightBehavior: false,
staticGraphWithDragAndDrop: true,
automaticRearrangeAfterDropNode: false,
d3: { disableLinkForce: true },
height: 512,
// width: 576,
minZoom: 1,
maxZoom: 1,
node: {
color: "lightblue",
size: 500,
highlightStrokeColor: "blue",
fontSize: 20,
highlightFontSize: 22,
labelProperty: (node: default_config) =>
node.name ? node.name : node.id,
viewGenerator: (node) => (
<CustomNode node={node} focusId={null} />
),
}
};
const [config, setConfig] = useState(default_config);
<Graph
config={config.d3_config}
/>
Problem is 'r={circleR}' is just taking default value from state, and its not updating it when i'm zooming in or zooming out.
Inside my 'zoomChange ' function i have two console.logs and i can check that it gives me right values and changes between '10' and '50' (zoomChange function works fine), then what is the reason i'm not getting updated value to 'r={circleR}' ?
writing values to it like 'r="50" or r="10"' will work but i want updated values according to setState.
english is not my mother language so could be mistakes !

Why is my react-tooltip sometimes displaying on the parent element?

I've made a covid related travel map here: Where Can We Go Now
It's a static react JS website, billy basic with create-react-app. It uses amongst other things the excellent React Simple Maps (kudos to the creator) and react-tooltip.
Expected behaviour:
Locally the tooltip works fine when tested with npm run start
I move my mouse over a country and the tooltip displays some relevant information approximately where my mouse is on the map.
The Problem:
. . . once published with npm run build sort of works but strange things happen.
Please follow link to website to observe, but in short, if the sea (i.e. an area that isn't a country - so not defined by an svg in the topojson) is clicked then the tooltip is place at the top centre of the whole map itself.
Some bits of relevant code:
Index.js (snippet):
const [content, setContent] = useState("");
return (
<div><h1> Where can we go on holiday ?* </h1>
<MapChart setTooltipContent={setContent}/>
<ReactTooltip html={true}>{content}</ReactTooltip>
</div>
);
}
MapChart.js (snippet):
return (
<div>
<p><small>*Right now in the UK: {data.headlineAdviceNow} Last updated at {updateString}</small></p>
<ComposableMap
projectionConfig={{
rotate: [-40, -30, 10],
scale: 1600
}}
width={mapWidth}
height={mapHeight}
style={{ maxWidth: "100%", height: "80vh", background: "#b3b3b3", borderStyle: "solid", borderColor: "white", margin: 0.5,
borderRadius: 20 }}
>
<ZoomableGroup zoom={zoom} center={center} translateExtent={[
[0, 0],
[mapWidth, mapHeight]
]} maxZoom={15} data-tip="">
<Sphere stroke="#E4E5E6" strokeWidth={0.5} />
<Graticule stroke="#E4E5E6" strokeWidth={0.2} />
{!loading && (<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map((geo) => {
const d = data.countries.find((s) => s.country.iso_a3 === geo.properties.ISO_A3);
return (
<Geography
key={geo.rsmKey}
geography={geo}
style={{
default: {
outline: "none",
stroke: "grey",
strokeWidth: "1",
strokeLinecap: "butt",
fill: d ? colorScale(d[attr]) : "#F5F4F6"
},
hover: {
outline: "none",
stroke: "black",
strokeWidth: "3",
strokeLinecap: "butt",
fill: d ? colorScale(d[attr]) : "#F5F4F6"
},
pressed: {
outline: "none"
}
}}
onMouseEnter={() => {
const NAME = geo.properties.NAME;
const travelIndex = d ? d["indexDescription"] : "Unknown";
//const border = d ? d["borderStatusString"] : "Unknown";
const returnStatus = d ? (fromCountry==="GBR" ? d["ukCategory"] : "TBC") : "Unknown";
const vaccinePc = d ? d.vaccineData["total_vaccinations_per_hundred"] + "%" : "NK";
const arrival = (() => {try{ return d["restrictionsFrom"][data["iomKeys"].indexOf(fromCountry)].split("-") } catch {return [4,""]}});
const arrivalLevel = rstrctLkup[arrival()[0]]["short"]
//const arvlRtns = arrival()[1].length===0 ? arrival()[1] : arrival()[1].split(",")
//var text =""
//var i;
//for (i = 0; i < arvlRtns.length; i++) {
// if (data["restrictionDefs"][arvlRtns[i]]) {
// if (i===0) {text="<br />Arrival Details:<br />"}
// text += data["restrictionDefs"][arvlRtns[i]] + "<br>";
//}}
setTooltipContent(`<b>${NAME.toUpperCase()}:</b> ${travelIndex}<br /><p style="text-align: left"><b>RISK FACTORS</b><br />Vaccinated: ${vaccinePc}<br /><br /><b>CURRENT RESTRICTIONS</b><br />Entry: ${arrivalLevel}<br />Return: ${returnStatus}</p>`);
}}
onMouseLeave={() => {
setTooltipContent("");
}}
/>
);
})
}
</Geographies>
)}
</ZoomableGroup>
</ComposableMap>
</div>
);
};
export default memo(MapChart);
What I've tried:
Variations of setting tooltip position, and moving where and what data-tip="" is. Also, banging head on wall.
I was inaccurate in my question, so didn't provide relevant details that was the source of the problem.
I publish with github actions and this was the fix:
yarn install --frozen-lockfile
The frozen lockfile was the key to make sure I was using the exact versions of relevant packages as I had been testing locally. I'd erroneously suggested I was using npm.

Stacked Bar graph with ReactJS

I have to do a single stacked bar graph getting the data from an API, but I am finding some issues.
1- On one hand, the data I get is not rounded, even if I use %.
2- On the other hand, the total I get is not always 100%, then the bar is sometimes a few pixels shorter and sometimes a few pixels longer than expected.
import styled from 'styled-components'
import { Data } from '../../Types'
const Colors: Record<string, string> = {
warning: 'yellow',
good: 'green',
danger: 'red',
}
const StackedBar = (props: {
title?: string
Data: Data | null
}) => {
return (
<div>
<div
style={{
display: 'flex',
flex: '1 1 auto',
alignSelf: 'auto',
}}
>
{props && props.Data ? (
props.Data.items.map((item) => {
const percentage =
(item.count / items_total) * 100
return (
<Rectangle
percentage={percentage}
color={
itemColors[item.items_total]
}
/>
)
})
) : (
<Rectangle percentage={100} color="grey" />
)}
</div>
</div>
)
}
export default StackedBar
const Rectangle = styled.div<{ percentage: number; color: string }>`
height: 20px;
width: ${(props) => props.percentage}%;
background-color: ${(props) => props.color};
`
const NormalBold = styled.p`
font-weight: 700;
font-size: var(--font-size-normal);
`
percentage =(item.count / items_total) * 100, here items_ total is not defined, check the code again. I think it should be item.items_total.
for rounding use Math.round(percentage) to round off values.
I think this should resolve your issues.

What is the correct way to set the new coordinates of a view after translation animation in react-native?

I am trying to run a few simple animations using react-native-animatable library. (But I believe the question should be generic to any react animations so adding other tags as well.)
The problem is, in the first time, the image animates just as expected. But when aimed to start second animation animation with the gesture, the image translation starts from its original coordinates.
A search yielt, in Android development (which is obviously not my case) there seems a method, setFillAfter which sets the coordinate after the animation.
My question is, how to set the location (left / top values for example) to the final translated point so that consecutive animation starts from the point the previous translation left.
The expo snack for below code block is here.
import * as React from 'react';
import { Image, StyleSheet, ImageBackground } from 'react-native';
import * as Animatable from 'react-native-animatable';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import testImg from './test.png';
import backImg from './back.png';
export default class App extends React.Component {
onTestMove(event) {
this.testAnimRef.transitionTo({
translateX: event.nativeEvent.translationX,
translateY: event.nativeEvent.translationY,
}, 0);
}
render() {
return (
<ImageBackground source={backImg} style={{ flex: 1 }} >
<PanGestureHandler
key={`test`}
onGestureEvent={(e) => { this.onTestMove(e) }}
onHandlerStateChange={e => { }}
>
<Animatable.View style={styles._animatable_view}
ref={((ref) => { this.testAnimRef = ref }).bind(this)}
useNativeDriver={true}
>
<Image source={testImg} style={styles._image} />
</Animatable.View>
</PanGestureHandler>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
_image: {
width: 50,
height: 25,
resizeMode: 'contain',
backgroundColor: 'black',
borderColor: 'gainsboro',
borderWidth: 2,
},
_animatable_view: {
position: "absolute",
top: 200,
left: 100,
},
});
I had the same problem trying to move around some cards in a view, and upon further dragging, they would reset to their origin.
My theory is/was that while the translated view would have its x / y coordinates translated, this would not apply to the parent of that view, and so the animated event passed from that component would initially have the original coordinates (nuke me if I'm wrong here)
So my solution was to keep an initial offset value in state, and maintain this every time the user releases the dragged motion
_onHandleGesture: any
constructor(props: OwnProps) {
super(props)
this.state = {
animationValue: new Animated.ValueXY({ x: 0, y: 0 }),
initialOffset: { x: 0, y: 0 },
}
this._onHandleGesture = (e: PanGestureHandlerGestureEvent) => {
this.state.animationValue.setValue({
x: e.nativeEvent.translationX + this.state.initialOffset.x, <- add initial offset to coordinates passed
y: e.nativeEvent.translationY + this.state.initialOffset.y,
})
}
}
_acceptCard = (cardValue: number) => {
const { targetLocation, onAccept } = this.props
const { x, y } = targetLocation
onAccept(cardValue)
Animated.spring(this.state.animationValue, {
// Some animation here
}).start(() => {
this.setState({ initialOffset: targetLocation }) // <- callback to set state value for next animation start
})
}
and the render method
<PanGestureHandler
onHandlerStateChange={this.onPanHandlerStateChange}
onGestureEvent={this._onHandleGesture}
failOffsetX={[-xThreshold, xThreshold]}
>
<Animated.View
style={{
position: "absolute",
left: 0,
top: 0,
transform: [{ translateX: this.state.animationValue.x }, { translateY: this.state.animationValue.y }],
}}
>
<CardTile size={size} content={content} layout={layout} backgroundImage={backgroundImage} shadow={shadow} />
</Animated.View>
</PanGestureHandler>
This example is based on the react-native-gesture-handler library, but the concept should apply to other solutions.
Dont know if this way is advisable, though it is functional.
Hope this helps!

Scale stage and drag element relative positions not working

Im having small issue with calculating realtime position while scaled stage in KonvaJS React. In my example I have two Rect's small (red) one position is relative to Big Rect (yellow), While dragging big rectangle small one moves relative to big one but when I scale UP or DOWN small one jumps some pixels in x, y positions.
Working example
import React from "react";
import ReactDOM from "react-dom";
import { Rect, Stage, Layer } from "react-konva";
import "./styles.css";
function Box({ attrs, draggable, updateAttr }) {
const onDragStart = e => {
const element = e.target;
if (typeof updateAttr === "function") {
updateAttr(element._lastPos);
}
};
const onDragMove = e => {
const element = e.target;
if (typeof updateAttr === "function") {
updateAttr(element._lastPos);
}
};
const onDragEnd = e => {
const element = e.target;
if (typeof updateAttr === "function") {
updateAttr(element._lastPos);
}
};
return (
<Rect
draggable={draggable}
onDragEnd={onDragEnd}
onDragMove={onDragMove}
onDragStart={onDragStart}
fill={attrs.fill}
x={attrs.x}
y={attrs.y}
width={attrs.width}
height={attrs.height}
/>
);
}
const calculatePos = ({ r1, r2 }) => ({
...r2,
x: parseInt(r1.width + r1.x + 10, 10),
y: parseInt(r1.y + r1.height / 2 - r2.height / 2, 10)
});
const boxAttr = {
x: 50,
y: 50,
width: 200,
height: 150,
fill: "yellow"
};
const secondAttr = calculatePos({
r2: {
fill: "red",
width: 20,
height: 20
},
r1: boxAttr
});
class App extends React.Component {
state = {
scale: 1,
boxAttr,
secondAttr
};
updateScale = scale => {
this.setState({ scale });
};
updateAttr = pos => {
const { secondAttr, boxAttr } = this.state;
this.setState({
secondAttr: calculatePos({
r2: secondAttr,
r1: { ...boxAttr, ...pos }
})
});
};
render() {
const { scale, boxAttr, secondAttr } = this.state;
return (
<div className="App">
<button
onClick={() => {
this.updateScale((parseFloat(scale) + 0.1).toFixed(1));
}}
>
+ Scale ({scale})
</button>
<button
onClick={() => {
this.updateScale((parseFloat(scale) - 0.1).toFixed(1));
}}
>
- Scale ({scale})
</button>
<Stage
scaleX={scale}
scaleY={scale}
width={window.innerWidth}
height={window.innerHeight}
>
<Layer>
<Box
updateAttr={this.updateAttr}
draggable={true}
attrs={boxAttr}
/>
<Box draggable={false} attrs={secondAttr} />
</Layer>
</Stage>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Please help me to update correct x,y for red rectangle.
I know Grouped Draggable solution will fix this issue, but I cannot
implement in my real application due to complex relations that cannot
be group for draggable reason
When the first box is dragged, you have to translate the mouse coordinates on the canvas to the coordinate system inside the scaled canvas to move the second box.
I modified the updateAttr method to do so.
There is a catch however, when the updateAttr is called by konva itself (after the dragEnd event) it does so with translated coordinates. That is why I added a second argument to the updateAttr called doScale. When calling it yourself, set it true and it will translate the coordinates, if konva calls it, there is no second argument and it evaluates to false and does no translation.
The code is available at: https://codesandbox.io/s/6126wz0kkk

Categories