Hey guys I have a slight problem here I have a Konva.js app which is working pretty well. I have this function called SET_STAGE_DATA that is supposed to save the images current position on the canvas so it can be redrawn when the canvas disapears. It works but when you scale the image from the left the image shifts. I am not sure how to fix this. I thought I may be able to fix it with offset but to no avail.
Heres my SET_STAGE_DATA function
[SET_STAGE_DATA](state){
const isStage = state.stage;
if(isStage){
const groups = state.stage.find("Group");
const stageData = [];
for (let x = 0; x < groups.length; x++) {
let g = groups[x];;
let i = g.findOne("Image").getAttrs();
let position = g.findOne("Image").position();
console.log("this is position", position);
let group = { x: g.getX(), y: g.getY() };
let image = { x: i.width, y: i.height, position };
let obj = { image, group };
stageData.push(obj);
}
I use the stageData just before I build the image like so
let groupPos;
let imagePos;
if(state.stageData){
console.log(true);
for(let i =0; i<state.stageData.length; i++){
imageObj.width = state.stageData[i].image.x;
imageObj.height = state.stageData[i].image.y;
imagePos = state.stageData[i].position;
groupPos = state.stageData[i].group;
}
} else {
groupPos = {
x: state.stage.width() / 2 - imageObj.width / 2,
y: state.stage.height() / 2 - imageObj.height / 2
};
}
console.log("data", {groupPos, image: {width: imageObj.width, height: imageObj.height}});
const image = new Konva.Image({
image: imageObj,
width: imageObj.width,
height: imageObj.height,
strokeWidth: 10,
stroke: "blue",
strokeEnabled: false
});
if(imagePos){
image.position(imagePos)
}
const group = new Konva.Group({
draggable: true,
x: groupPos.x,
y: groupPos.y
});
I also have the typical update function found in the documentation:
function update(activeAnchor) {
const group = activeAnchor.getParent();
let topLeft = group.get(".topLeft")[0];
let topRight = group.get(".topRight")[0];
let bottomRight = group.get(".bottomRight")[0];
let bottomLeft = group.get(".bottomLeft")[0];
let image = group.get("Image")[0];
let anchorX = activeAnchor.getX();
let anchorY = activeAnchor.getY();
// update anchor positions
switch (activeAnchor.getName()) {
case "topLeft":
topRight.y(anchorY);
bottomLeft.x(anchorX);
break;
case "topRight":
topLeft.y(anchorY);
bottomRight.x(anchorX);
break;
case "bottomRight":
bottomLeft.y(anchorY);
topRight.x(anchorX);
break;
case "bottomLeft":
bottomRight.y(anchorY);
topLeft.x(anchorX);
break;
}
// image.position(topLeft.position());
let width = topRight.getX() - topLeft.getX();
let height = bottomLeft.getY() - topLeft.getY();
if (width && height) {
image.width(width);
image.height(height);
}
}
function addAnchor(group, x, y, name) {
let anchor = new Konva.Circle({
x: x,
y: y,
stroke: "#666",
fill: "#ddd",
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
anchor.on("dragmove", function() {
update(this);
state.layer.draw();
});
anchor.on("mousedown touchstart", function() {
group.draggable(false);
this.moveToTop();
});
anchor.on("dragend", function(evt) {
group.draggable(true);
commit(SET_STAGE_DATA);
dispatch(UPDATE_ANIMATION, state.selectedNode);
state.layer.draw();
});
// add hover styling
anchor.on("mouseover", function() {
document.body.style.cursor = "pointer";
this.strokeWidth(4);
state.layer.draw();
});
anchor.on("mouseout", function() {
document.body.style.cursor = "default";
this.strokeWidth(2);
state.layer.draw();
});
group.add(anchor);
}
state.layer.draw();
},
when I scale to the left the image goes outside of the anchors on redraw. I know I can fix this visually as long as I don't redraw the image by doing image.position(topLeft.position()); as you can see is commented out but it always acts as if positioning isn't set if you drag from the left. If I scale from the bottom right everything is fine. it acts as though it goes back to the previous position of the images left top corrner but from what I understand using stage.find() will give you the current stage. I'm at a totally loss.
See in the image how it does not stay in the box. Any help here would be great thanks.
[1]: https://i.stack.imgur.com/HEfHP.jpg
Update the SET_STAGE_DATA function to look like this
[SET_STAGE_DATA](state){
const isStage = state.stage;
const isEditorMode = state.editorMode;
if(isStage && !isEditorMode){
const groups = state.stage.find("Group");
const stageData = [];
for (let x = 0; x < groups.length; x++) {
let g = groups[x];
let i = g.findOne("Image").getAttrs();
let group = {x: g.getX() + i.x, y: g.getY() + i.y };
let image = i;
let obj = { image, group };
stageData.push(obj);
}
state.stageData = stageData;
} else {
state.stageData = null;
}
}
group.x and y don't update on scale but image.x and y do. On move image x and y are 0 and if you add them to the group.x and y it will put the anchors where you need them to be.
Related
I am building a component that will display any image inside a parent div and allows dragging of the image (when larger than the div) as well as scaling on both a double click or pinch on mobile. I am using inline style changes on the image to test the behavior and so far everything works as I wanted...except that when I change the image transform:scale() all the calculations that effectively set the correct limits to prevent offsetting an image outside the parent div no longer behave as expected. It does work perfectly if I keep the scale=1.
So for example, with a parent Div width and height of 500/500 respectively, and an image that say is 1500x1000, I prevent any "over"offsetting when dragging the image by setting limits of leftLimit = -(1500-500) = -1000 and topLimit = -(1000-500) = -500. This works perfectly at the initial scale of 1. However, I have a dblClick event that scales the image upwards at .5 intervals, and once the scale changes from 1 to any other number, the methods I use above to calculate offset Limits are no longer value. So for example if I increase the scale to 2, in theory that same 1500x1000 image in a 500x500 div should NOW have leftLimit = -(3000-500) = -2500 and topLimit = -(2000-500) = 1500. But these new calculated limits allow the image to be dragged right out of the parent div region. For reference here is the code. Any help or methods for testing what's actually going on would be very much appreciated.
Note the image is being loaded as a file for test, it's a fairly large base64 string. The code is as follows (btw, I am figuring my use of so many 'state' variables probably exposes my ignorance of how such values could/should really persist across renderings. I am still quite new to React) :
import * as React from 'react'
import * as types from '../../types/rpg-types'
import imgSource from './testwebmap.jpg'
import * as vars from '../../data/mapImage'
const testImg = require('./testwebmap.jpg')
export default function MyMapImage() {
let divRef = React.useRef<HTMLDivElement>(null)
let imgRef = React.useRef<HTMLImageElement>(null)
const [loaded, setLoaded] = React.useState<boolean>(false)
const [imgTop, setImgTop] = React.useState<number>(0)
const [imgLeft, setImgLeft] = React.useState<number>(0)
const [scHeight, setSCHeight] = React.useState<number>(100)
const [scWidth, setSCWidth] = React.useState<number>(100)
const [imgScale, setImgScale] = React.useState<number>(1)
const [natHeight, setNatHeight] = React.useState<number>(100)
const [natWidth, setNatWidth] = React.useState<number>(100)
const [oldXCoord, setOldXCoord] = React.useState<number>(0)
const [oldYCoord, setOldYCoord] = React.useState<number>(0)
const [topLimit, setTopLimit] = React.useState<number>(0)
const [leftLimit, setLeftLimit] = React.useState<number>(0)
const [isScaling, setIsScaling] = React.useState<boolean>(false)
const [isDragging, setIsDragging] = React.useState<boolean>(false)
const [isFirstPress, setIsFirstPress] = React.useState<boolean>(false)
const [accel, setAccel] = React.useState<number>(1)
const [touchDist, setTouchDist] = React.useState<number>(0)
const [cfg, setCfg] = React.useState<types.ImageConfig>({
img: '',
imgTOP: 0,
imgLEFT: 0,
offsetX: 0,
offsetY: 0,
isFirstPress: true,
isDragging: false,
isScaling: false,
divHeight: 500,
divWidth: 500,
topLimit: 0,
leftLimit: 0,
isLoaded: true,
oldMouseX: 0,
oldMouseY: 0,
touchDist: 0,
})
const setNewImageLimits = () => {
const img = imgRef
let heightLimit: number
let widthLimit: number
console.log(`imgScale is: ${imgScale}`)
//console.log(`current offsets: ${imgLeft}:${imgTop}`)
console.log(`img width/Height: ${img.current?.width}:${img.current?.height}`)
console.log(img)
img.current
? (heightLimit = Math.floor(imgScale * img.current.naturalHeight - cfg.divHeight))
: (heightLimit = 0)
img.current
? (widthLimit = Math.floor(imgScale * img.current.naturalWidth - cfg.divWidth))
: (widthLimit = 0)
setTopLimit(-heightLimit)
setLeftLimit(-widthLimit)
setImgLeft(0)
setImgTop(0)
console.log(
'New Image limits set with topLimit:' + heightLimit + ' and leftLimit:' + widthLimit
)
}
const handleImageLoad = () => {
if (imgRef) {
const img = imgRef
//console.log(imgRef)
let heightLimit: number
let widthLimit: number
img.current ? (heightLimit = img.current.naturalHeight - cfg.divHeight) : (heightLimit = 0)
img.current ? (widthLimit = img.current.naturalWidth - cfg.divWidth) : (widthLimit = 0)
setTopLimit(-heightLimit)
setLeftLimit(-widthLimit)
setNatHeight(img.current ? img.current.naturalHeight : 0)
setNatWidth(img.current ? img.current.naturalWidth : 0)
setSCHeight(img.current ? img.current.naturalHeight : 0)
setSCWidth(img.current ? img.current.naturalWidth : 0)
console.log('Image Loaded with topLimit:' + heightLimit + ' and leftLimit:' + widthLimit)
}
}
React.useEffect(() => {
if (imgRef.current?.complete) {
handleImageLoad()
}
}, [])
React.useEffect(() => {
setNewImageLimits()
console.log(`imgScale is: ${imgScale}`)
console.log('Image has with topLimit:' + topLimit + ' and leftLimit:' + leftLimit)
}, [imgScale])
function distance(e: any) {
let zw = e.touches[0].pageX - e.touches[1].pageX
let zh = e.touches[0].pageY - e.touches[1].pageY
if (zw * zw + zh * zh != 0) {
return Math.sqrt(zw * zw + zh * zh)
} else return 0
}
function setCoordinates(e: any) {
let canMouseX: number
let canMouseY: number
if (e?.nativeEvent?.clientX && e?.nativeEvent?.clientY) {
//console.log(e)
//canMouseX = parseInt(e.clientX - cfg.offsetX)
canMouseX = e.nativeEvent.clientX - cfg.offsetX
canMouseY = e.nativeEvent.clientY - cfg.offsetY
//console.log(`${canMouseX}:${canMouseY}`)
} else if (e?.nativeEvent?.targetTouches) {
canMouseX = e.nativeEvent.targetTouches.item(0)?.clientX - cfg.offsetX
canMouseY = e.nativeEvent.targetTouches.item(0)?.clientY - cfg.offsetY
// This isn't doing anything (noticeable)
// e.preventDefault();
} else return {}
return {
canMouseX,
canMouseY,
}
}
const handleMouseUp = (e: any) => {
let { canMouseX, canMouseY } = setCoordinates(e)
setIsScaling(false)
setIsDragging(false)
setIsFirstPress(true)
setAccel(1)
console.log('Mouse UP Event function')
}
const handleMouseDown = (e: any) => {
const { canMouseX, canMouseY } = setCoordinates(e)
//console.log('Mouse DOWN Event function')
e.preventDefault()
//console.log(`Mouse Down ${canMouseX}:${canMouseY}`)
canMouseX ? setOldXCoord(canMouseX) : setOldXCoord(0)
canMouseY ? setOldYCoord(canMouseY) : setOldYCoord(0)
setIsDragging(true)
setCfg({ ...cfg, isDragging: true })
if (e?.targetTouches) {
e.preventDefault()
if (e?.nativeEvent?.touches?.length > 1) {
// detected a pinch
setTouchDist(distance(e))
setCfg({ ...cfg, touchDist: distance(e), isScaling: true })
setIsScaling(true)
setIsDragging(false)
} else {
// set the drag flag
setIsScaling(false)
setIsDragging(true)
}
}
setIsFirstPress(false)
setCfg({ ...cfg, isFirstPress: true })
}
const handleDoubleClick = (e: any) => {
const { canMouseX, canMouseY } = setCoordinates(e)
if (imgScale === 3) {
setImgScale(1)
} else {
setImgScale(imgScale + 0.5)
}
}
const handleMouseMove = (e: any) => {
let scaling = isScaling
let dragging = isDragging
let tempImgScale: number = 1
const { canMouseX, canMouseY } = setCoordinates(e)
let yDiff: number
let xDiff: number
let newLeft: number
let newTop: number
if (e.targetTouches) {
e.preventDefault()
if (e.touches.length > 1) {
//detected a pinch
setIsScaling(true)
setIsDragging(false)
scaling = true
} else {
setIsScaling(false)
setIsDragging(true)
}
}
//console.log(`isScaling : ${isScaling}`)
if (scaling) {
//...adding rndScaleTest to force processing of scaling randomly
let dist = distance(e)
//Can't divide by zero, so return dist in denom. if touchDist still at initial 0 value
tempImgScale = dist / (touchDist === 0 ? dist : touchDist)
//console.log(`imgScale is: ${imgScale}`)
if (tempImgScale < 1) tempImgScale = 1 //for now no scaling down allowed...
if (tempImgScale > 2) tempImgScale = 2 //...and scaling up limited to 2.5x
setSCHeight(Math.floor(imgScale * natHeight))
setSCWidth(Math.floor(imgScale * natWidth))
setImgScale(tempImgScale)
setTouchDist(dist)
}
// if the drag flag is set, clear the canvas and draw the image
if (isDragging) {
yDiff = canMouseY && oldYCoord ? accel * (canMouseY - oldYCoord) : 0
xDiff = canMouseX && oldXCoord ? accel * (canMouseX - oldXCoord) : 0
if (imgLeft + xDiff <= leftLimit) {
setImgLeft(leftLimit)
} else if (imgLeft + xDiff >= 0) {
setImgLeft(0)
} else setImgLeft(imgLeft + xDiff)
if (imgTop + yDiff <= topLimit) {
setImgTop(topLimit)
} else if (imgTop + yDiff >= 0) {
setImgTop(0)
} else setImgTop(imgTop + yDiff)
if (accel < 4) {
setAccel(accel + 1)
}
}
//console.log('Mouse **MOVE Event function')
setOldXCoord(canMouseX || 0)
setOldYCoord(canMouseY || 0)
}
const handleMouseLeave = (e: any) => {
setIsScaling(false)
setIsDragging(false)
setIsFirstPress(true)
setAccel(1)
console.log('Mouse LEAVE Event function')
}
return (
<div>
<div className="portrait">
<div
ref={divRef}
className="wrapper"
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
onTouchEnd={handleMouseUp}
onMouseDown={handleMouseDown}
onTouchStart={handleMouseDown}
onTouchMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onDoubleClick={handleDoubleClick}
>
<img
ref={imgRef}
src={`data:image/jpeg;base64,${vars.bigImage}`}
style={{
transform: `scale(${imgScale})`,
transformOrigin: `top left`,
objectPosition: `${imgLeft}px ${imgTop}px`,
}}
onLoad={handleImageLoad}
/>
</div>
</div>
<span>{`imgLeft: ${imgLeft}px `}</span>
<span>{`imgTop: ${imgTop}px `}</span>
</div>
)
}
In the end, the problem does lie with the use of Scale(), and I could not rely on it for purposes of tracking position and offset. So I ended up explicitly scaling 'height' and 'width' for the img and keeping the scaling value as a reference when required to calculate new values for these. The parent Div and Img essentially now look like this:
<div
ref={divRef}
className="wrapper"
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
onTouchEnd={handleMouseUp}
onMouseDown={handleMouseDown}
onTouchStart={handleMouseDown}
onTouchMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
onDoubleClick={handleDoubleClick}
>
<img
ref={imgRef}
src={`data:image/jpeg;base64,${vars.bigImage}`}
style={{
transform: `translate(${imgLeft}px, ${imgTop}px)`,
height: `${scHeight}px`,
width: `${scWidth}px)`,
transformOrigin: `top left`,
}}
onLoad={handleImageLoad}
/>
</div>
This works fine, calculated offsets match the expected values, and translation of the image within the parent div behaves as expected.
I have a list of Vectors which represent points on three different floors in three.js.
I am trying to group these vectors according to the floor they belong to. Is there a good formula to do this? Perhaps find height from on vector or something. Not sure how to go about this. Any help would be appreciated.
Thanks,
Rob
As Wilt said... Can't really help you without more info.
Still, if your floors are even and all stand on the xz plane (in my example), You may indeed check the points' height (position.y) against the floors'.
var container, renderer, scene, camera, controls;
var floors = [];
var points = [], materials = [], heights = [];
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
container = document.createElement('div');
document.body.appendChild(container);
container.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera + controls
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 50, 750);
camera.lookAt(scene.position);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.autoRotate = true;
//floors
for(i=0; i<3; i++) {
var planeGeometry = new THREE.BoxGeometry(500, 500, 10, 10);
var planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff * Math.random(),
side: THREE.DoubleSide,
transparent: true,
opacity : 0.3,
depthWrite : false //get rid of coplanar glitches wall/floor
});
materials.push(planeMaterial);
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = Math.PI / 2;
//plane.rotation.y = Math.PI / 8; //Uncomment to see this doesn't work if the floors move, i.e. changing rotation/position. If this is what you need, just raycast from point to floor in the animation loop and count how many floors the ray goes through (intersects.length)
plane.position.y = 75*i;
heights.push(plane.position.y);
floors.push(plane);
scene.add(plane);
}
//wall
var height = heights[2];
var planeGeometry = new THREE.BoxGeometry(500, height+100, 10, 10);
var planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff * Math.random(),
side: THREE.DoubleSide,
transparent: true,
opacity:0.3,
depthWrite : false //get rid of coplanar glitches wall/floor
});
materials.push(planeMaterial);
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.position.y = heights[1]+45;
plane.position.z = -510/2;
scene.add(plane);
// points
for (i=0; i<200; i++) {
var sphereGeometry = new THREE.SphereGeometry(3, 32, 16);
var sphere = new THREE.Mesh(sphereGeometry);
sphere.position.x = Math.random() * 500 - 250;
sphere.position.y = Math.random() * 300 - 100;
sphere.position.z = Math.random() * 500 - 250;
scene.add(sphere);
points.push(sphere);
}
// events
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function addSpheres() {
//addSpheres
for (i=0;i<200;i++) {
var that = points[i].position.y;
points[i].position.y = ( that < heights[0] ) ? 200 : that - 0.5;
if ( that > heights[0] && that < heights[1] ) points[i].material = materials[0];
if ( that < heights[2] && that > heights[1] ) points[i].material = materials[1];
if ( that > heights[2] ) points[i].material = materials[2];
points[i].material.needsUpdate = true;
}
}
function animate() {
controls.update();
addSpheres();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>
Now, feel free to "group" these points according to your needs.
Please note that "vectors" are different from "points". Read more about the difference.
Raycasting would be the way to go if you had a more complex scene (moving floors/different planes, points moving in different directions).
How can I move the camera to a specific position in matter.js?
I want the camera to follow my main character.
I tried this tutorial: http://brm.io/matter-js/demo/#views
but truth be told, I did not understand it.
I found how to make it: http://codepen.io/vanuatu/pen/VeQMpp
We need to control render bounds, like this:
//
hero = bubbleBall
// Follow Hero at X-axis
engine.render.bounds.min.x = centerX + hero.bounds.min.x
engine.render.bounds.max.x = centerX + hero.bounds.min.x + initialEngineBoundsMaxX
// Follow Hero at Y-axis
engine.render.bounds.min.y = centerY + hero.bounds.min.y
engine.render.bounds.max.y = centerY + hero.bounds.min.y + initialEngineBoundsMaxY
// Update Mouse
Mouse.setOffset(mouseConstraint.mouse, engine.render.bounds.min);
I hope that can help someone
An easier way to do it:
Render.lookAt(render, player, {
x: 1080,
y: 1920
});
x and y are the width and height of the render bounds.
First, let's start with this simple example :
// module aliases
var Engine = Matter.Engine,
Render = Matter.Render,
World = Matter.World,
Runner = Matter.Runner,
Bodies = Matter.Bodies;
var engine = Engine.create();
var runner = Runner.create();
// create a renderer
var render = Render.create({
element: document.body,
engine: engine,
runner: runner,
options: {
width: 1280,
height: 720
}
});
var vertices = [{ x : 50, y : 0},
{ x : 63, y : 38},
{ x : 100, y : 38},
{ x : 69, y : 59},
{ x : 82, y : 100},
{ x : 50, y : 75},
{ x : 18, y : 100},
{ x : 31, y : 59},
{ x : 0, y : 38},
{ x : 37, y : 38} ];
// add a star at center
var star = Matter.Vertices.create(vertices);
var starBody = Bodies.fromVertices(640, 360, star, {isStatic: true}, true);
// add all of the bodies to the world
World.add(engine.world, [ starBody]);
Render.run(render);
Runner.run(runner, engine);
You should see a little star at center, if you want to move the camera, your render needs to have bounds, modify the render instanciation as follow :
<pre><code>
var render = Render.create({
element: document.body,
engine: engine,
runner: runner,
options: {
width: 1280,
height: 720,
<b>hasBounds : true</b>
}
});
And use the Bounds module alias :
var Engine = Matter.Engine,
Render = Matter.Render,
World = Matter.World,
Runner = Matter.Runner,
Bodies = Matter.Bodies,
Bounds = Matter.Bounds;
Now you can translate the bounds of your render to move your camera :
let translate = {x : -100, y : -100}
Bounds.translate(render.bounds, translate);
What it does in fact is something like this :
function moveMatterJSCamera(offsetX, offsetY) {
render.bounds.min.x += offsetX;
render.bounds.max.x += offsetX;
render.bounds.min.y += offsetY;
render.bounds.max.y += offsetY;
}
I found that translating everything works too. like transX=-player.position.x and then doing Matter.Body.translate(body,{x:transX,y:0}) with all your bodies including the player
function track(){
Render.lookAt(render, player, {
x: 500,
y: 500
});
}
function repeatOften() {
track()
requestAnimationFrame(repeatOften);
}
requestAnimationFrame(repeatOften);
I have a curve running above or below a sloped line and I want to fill the area just above the line and below the curve, but not the area below the line and above the curve, as illustrated in the left image.
Again, it is easy to fill like right image, but I want to fill like in left image. So, how to get rid of the unwanted fill?
svg.append("path").attr("class", "line").attr("d", peak(pp)).style({ fill: peakColor, opacity: 0.5 });
Okay, this is something I came up with for me in case anybody cares about it.
var slope = (dp.rightY - dp.leftY) / (dp.rightX - dp.leftX); // slope of the line
var pp = [];
pp.push({ x: dp.leftX, y: dp.leftY });
var wasAbove = true; // track if it's above the line
data.forEach(function (d) {
if (d.x >= dp.leftX && d.x <= dp.rightX) {
var yAtLine = (d.x - dp.leftX) * slope + dp.leftY;
if (d.y > yAtLine) {
if (!wasAbove)
pp.push({ x: d.x, y: yAtLine });
pp.push(d);
wasAbove = true;
} else if (wasAbove) {
pp.push({ x: d.x, y: yAtLine });
wasAbove = false;
}
}
});
pp.push({ x: dp.rightX, y: dp.rightY });
var peak = d3.svg.line().x(function (d) { return xScale(d.x) }).y(function (d) { return yScale(d.y) });
svg.append("path").attr("class", "line").attr("d", peak(pp)).style({ fill: peakColor, opacity: 0.5 });
I want to know how to show infowindow on polyline in using Google Maps Api V3? and to appear in the middle of the polyline ?!
Firstly you will need to calculate the middle/center of the polyline. This has been discussed and answered here;
https://stackoverflow.com/a/9090409/787921
Then you will have to open the infowindow;
var infowindow = new google.maps.InfoWindow({
content: "infowindow text content"});
infowindow.setPosition(midLatLng);
infowindow.open(map);
find the middle point and set your custom view .
func showPath(polyStr :String){
polyline?.map = nil
mapView1.reloadInputViews()
pathDraw = GMSPath(fromEncodedPath: polyStr)!
polyline = GMSPolyline(path: pathDraw)
polyline?.strokeWidth = 4.0
polyline?.strokeColor = UIColor.init(red: 247/255.0, green: 55/255.0, blue: 76/255.0, alpha: 1.0)
polyline?.map = mapView1
let poinsCount = pathDraw.count()
let midpoint = pathDraw.coordinate(at: poinsCount)
DispatchQueue.main.async
{
self.addMarkerPin(corrdinate: midCordinate, distance: "10 min")
}
}
func addMarkerPin(corrdinate:CLLocationCoordinate2D, distance: String)
{
let marker = GMSMarker()
marker.position = corrdinate
PathTimeView = PathInfoView.loadFromNib() //here i am load Xib file, you can use your custom view
let DynamicView=PathTimeView
DynamicView.timelbl.text = distance
UIGraphicsBeginImageContextWithOptions(DynamicView.frame.size, false, UIScreen.main.scale)
DynamicView.layer.render(in: UIGraphicsGetCurrentContext()!)
let imageConverted: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
marker.icon = imageConverted
marker.map = self.mapView1
marker.infoWindowAnchor = CGPoint(x: -1900 , y: -2000)
}
First you should got center/middle of polyline and this what works for me
private fun centerPos(points: MutableList<LatLng>): LatLng {
val middleDistance = SphericalUtil.computeLength(points).div(2)
return extrapolate(points, points.first(), middleDistance.toFloat()) ?: points[0]
}
private fun extrapolate(path: List<LatLng>, origin: LatLng, distance: Float): LatLng? {
var extrapolated: LatLng? = null
if (!PolyUtil.isLocationOnPath(
origin,
path,
false,
1.0
)
) { // If the location is not on path non geodesic, 1 meter tolerance
return null
}
var accDistance = 0f
var foundStart = false
val segment: MutableList<LatLng> = ArrayList()
for (i in 0 until path.size - 1) {
val segmentStart = path[i]
val segmentEnd = path[i + 1]
segment.clear()
segment.add(segmentStart)
segment.add(segmentEnd)
var currentDistance = 0.0
if (!foundStart) {
if (PolyUtil.isLocationOnPath(origin, segment, false, 1.0)) {
foundStart = true
currentDistance = SphericalUtil.computeDistanceBetween(origin, segmentEnd)
if (currentDistance > distance) {
val heading = SphericalUtil.computeHeading(origin, segmentEnd)
extrapolated = SphericalUtil.computeOffset(
origin,
(distance - accDistance).toDouble(),
heading
)
break
}
}
} else {
currentDistance = SphericalUtil.computeDistanceBetween(segmentStart, segmentEnd)
if (currentDistance + accDistance > distance) {
val heading = SphericalUtil.computeHeading(segmentStart, segmentEnd)
extrapolated = SphericalUtil.computeOffset(
segmentStart,
(distance - accDistance).toDouble(),
heading
)
break
}
}
accDistance += currentDistance.toFloat()
}
return extrapolated
}
then You can add infoWindow with normal way with your platform at it is differ from each platform