Currently, I serve a map with an imageOverlay base layer.
Right now, I need to use an svg instead of the current png imageOverlay,
and to alter the svg css (like, changing opacity for the nearest places (paths in the svg) etc), dynamically,
so basically, I need to control the actual map svg style dynamically.
I've decided I need to use an inline svg to alter the style of the svg,
instead of an image with an svg source.
I tried replacing the leaflet created image overlay with an actual inline svg at runtime.
it worked, but the zoom control won't affect the layer anymore,
and switching layers is a bit buggy.
is there any inside capabilities of leaflet to support this kind of requirement?
adding a fiddle to explain it better http://jsfiddle.net/dh5wgu2x/
var map = L.map('map', {
center: [40.75, -74.2],
zoom: 13
});
var imageUrl = 'https://rawgit.com/VengadoraVG/moving-to-gnulinux/master/img/tux.svg',
imageBounds = [
[40.712216, -74.22655],
[40.773941, -74.12544]
];
L.imageOverlay(imageUrl, imageBounds).addTo(map);
setTimeout(() => {
var $img = $('.leaflet-image-layer');
var imgURL = $img.attr('src');
var attributes = $img.prop("attributes");
$.get(imgURL, function(data) {
// Get the SVG tag, ignore the rest
var $svg = $(data).find('svg');
// Remove any invalid XML tags
$svg = $svg.removeAttr('xmlns:a');
// Loop through IMG attributes and apply on SVG
$.each(attributes, function() {
$svg.attr(this.name, this.value);
});
// Replace IMG with SVG
$img.replaceWith($svg);
}, 'xml');
}, 2000);
Related
I'd like to load a SVG image into canvas using Fabric.js and have access to all attributes of it.
Let's assume I have an image like this:
SVG Image
I tried to it using Fabric.js on 3 ways:
1. (Saved as String) - this method is not loading my image, I took other SVG and attributes can be accessed with svgImg.fill.color property.
fabric.loadSVGFromString(svg, function (objects, options) {
var svgImg = fabric.util.groupSVGElements(objects, options);
canvas.add(svgImg);
});
2. Directly from URL:
fabric.loadSVGFromURL(URLsvg, function (objects, options) {
var svgImg = fabric.util.groupSVGElements(objects, options);
canvas.add(svgImg);
});
3.Using fabric.Image:
<img id="Test_Img" src="~/SVG/homer-simpson.svg" style="display: none;" />
var Img = document.getElementById('Test_Img');
var imgInstance = new fabric.Image(Img, {
left: 90,
top: 80
});
How can I access inner attributes of my SVG and change for example color of Homers T-Shirt?
Loaded SVG objects become groups in fabric.js (if they are not simple geometric forms like a rectangle, circle, etc.).
You can access the members of the group with getObjects(), which returns an array.
Here is a simple example. It reads an SVG consisting of 3 arcs and changes the color of the first arc to blue:
var canvas = new fabric.Canvas('canvas', { preserveObjectStacking: true });
canvas.backgroundColor="#fff";
canvas.renderAll();
fabric.loadSVGFromURL("https://media-public.canva.com/MABtmZU4zXQ/1/screen.svg",function(objects,options) {
var loadedObjects = fabric.util.groupSVGElements(objects, options);
loadedObjects.set({
width: 300,
height: 150
});
canvas.add(loadedObjects);
// Change the first arc to blue
loadedObjects.getObjects()[0].set({fill: "blue"});
canvas.renderAll();
});
It runs with fabric.js Version 3.5.
Working example: https://jsfiddle.net/awehring/k7azLb84/9/
I am new to leaflet programming, I have a .svg file and I want to make that clickable on super imposing on open street map.
I have tried making it as an overlay on an open street map by Image-overlay option but svg is not clickable.
Basically I want to get the ID of clicked svg path or element (what ever you call) in leaflet. The map is getting zoom when I clicked.
var imageUrl = 'test2.svg',
imageBounds = [(image bounds)];
L.imageOverlay(imageUrl, imageBounds).addTo(map);
Loading the SVG file
You can load your file through an imageOverlay but you won't get events on individual paths/groups/shapes, you are limited to events on the overlay.
To get events on the components of the SVG, you have to embed it into your DOM nodes, either by inlining it or by loading it and creating the required nodes. Something like this 1:
var url = 'https://upload.wikimedia.org/wikipedia/commons/f/fd/Ghostscript_Tiger.svg';
var req = new XMLHttpRequest();
req.onload = function(resp) {
var xml = this.responseXML;
var importedNode = document.importNode(xml.documentElement, true);
var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
g.appendChild(importedNode);
g.setAttribute('class', 'svglayer');
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.appendChild(g);
svg.addEventListener('click', function(e) {
console.log(e.target.id)
})
document.body.appendChild(svg);
};
req.open("GET", url, true);
req.send();
This snippet will load a SVG file, append it to the DOM et and setup a click event that will log the id of the target element.
Embedding SVG into a map
Armed with this knowledge, you can append the SVG node to a map pane instead of the document.body. Here's a simple example that directly modifies the overlay pane :
map.getPane('overlayPane').appendChild(svg);
An important point to note is that Leaflet disables clicking on individual path elements by setting the CSS property pointer-events to none. You have to alter this to get events on the path nodes, hence the added CSS property:
.leaflet-pane > svg .svglayer path {
pointer-events: auto ;
}
And a demo
var map = L.map(document.getElementById('map'),{
renderer: L.canvas()
}).setView([48.8583736, 2.2922926], 15);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var url = 'https://upload.wikimedia.org/wikipedia/commons/f/fd/Ghostscript_Tiger.svg';
var req = new XMLHttpRequest();
req.onload = function(resp) {
var xml = this.responseXML;
var importedNode = document.importNode(xml.documentElement, true);
var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
g.appendChild(importedNode);
g.setAttribute('class', 'svglayer');
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.appendChild(g);
svg.addEventListener('click', function(e) {
console.log(e.target.id, e.target.tagName)
})
map.getPane('overlayPane').appendChild(svg);
};
req.open("GET", url, true);
req.send();
html, body {padding:0; margin:0; height: 100%}
#map {height: 180px}
.leaflet-pane > svg .svglayer path {
pointer-events: auto ;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.2.0/dist/leaflet.css" integrity="sha512-M2wvCLH6DSRazYeZRIm1JnYyh22purTM+FDB5CsyxtQJYeKq83arPe5wgbNmcFXGqiSH2XR8dT/fJISVA1r/zQ==" crossorigin=""/>
<script src="https://unpkg.com/leaflet#1.2.0/dist/leaflet.js" integrity="sha512-lInM/apFSqyy1o6s89K4iQUKg6ppXEgsVxT35HbzUupEVRh2Eu9Wdl4tHj7dZO0s1uvplcYGmt3498TtHq+log==" crossorigin=""></script>
<div id='map'>
</div>
1 Image by Ghostscript authors (GPL Ghostscript SVN: tiger.eps) [GPL (http://www.gnu.org/licenses/gpl.html)], via Wikimedia Commons
You need to explicitly specify the interactive option on your Image Overlay:
If true, the image overlay will emit mouse events when clicked or hovered.
var map = L.map("map").setView([48.85, 2.35], 12);
var imageBounds = [
[48.8, 2.3],
[48.9, 2.4]
];
var imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/9/9c/Map_of_country_subdivisions_%28states%29.svg';
var imageOverlay = L.imageOverlay(imageUrl, imageBounds, {
interactive: true,
attribution: 'CC-BY-SA 4.0 Wikimedia contributor'
}).addTo(map);
imageOverlay.on('click', function(event) {
alert('clicked on SVG image');
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.2.0/dist/leaflet.css">
<script src="https://unpkg.com/leaflet#1.2.0/dist/leaflet-src.js"></script>
<div id="map" style="height: 200px"></div>
As mentioned previously, I want to use Maquette as a basic hyperscript language. Consequently, I don't want to use the maquette.projector. However, despite being able to append SVG objects made with Maquette to the DOM, the DOM does not seem to be updating. For example, in the code below, I expect to be able to click a button to create a circle. If I inspect the DOM by using Chrome Dev tools, I can the the SVG circle object was added to the DOM, but the DOM hasn't been updated. How I am supposed to update the DOM?
var h = maquette.h;
var dom = maquette.dom;
var svg;
var svgNode;
var root;
var rootDiv;
function addCircle(evt) {
console.log("adding circle");
const c = h('g', [
h('circle#cool.sweet', {cx: '100', cy: '100', r: '100', fill: 'red'}),
]);
const g = dom.create(c).domNode;
svgNode.appendChild(g);
}
document.addEventListener('DOMContentLoaded', function () {
svg = h('svg', {styles: {height: "200px", width: "200px"}})
rootDiv = h('div.sweet', [
svg,
]);
root = dom.create(rootDiv).domNode;
document.body.appendChild(root);
svgNode = root.querySelector("svg");
const button = h('button', {
onclick: addCircle,
}, ["Add circle"]);
const buttonNode = dom.create(button).domNode;
root.appendChild(buttonNode);
});
<script src="//cdnjs.cloudflare.com/ajax/libs/maquette/2.4.1/maquette.min.js"></script>
Why is appendChild not rendering anything?
This was a tough one. The reason the circle does not appear on the screen (although it is added to the DOM) has to do with XML-namespaces.
SVG Elements and descendents thereof should get the SVG XML namespace. When you use maquette to render nodes that start in HTML with embedded SVG, you do not have to worry about this.
But now you start inside an SVG by creating a <g> node. Maquette assumes that you need a 'nonstandard' HTML <g> Node, because it does not know you were inside an SVG.
This is why maquette provides a second argument to its DOM.create method which you can use as follows to fix your problem:
const g = dom.create(c, {namespace: 'http://www.w3.org/2000/svg'}).domNode;
I'm trying to create a new <img> with the <svg> tag with JavaScript every time I click on the button. When I see the result in the console firebug it works correctly, but nothing on screen displays.
What I want is for an image <svg> to appear after the last one every time you click the button.
Thanks in advance.
var svgNS = "http://www.w3.org/2000/svg";
mybtn.addEventListener("click", createCircleSVG);
function createCircleSVG(){
var d = document.createElement('svg');
d.setAttribute('id','mySVG');
document.getElementById("svgContainer").appendChild(d);
createCircle();
}
function createCircle(){
var myCircle = document.createElementNS(svgNS,"circle"); //to create a circle."
myCircle.setAttributeNS(null,"id","mycircle" + opCounter++);
myCircle.setAttributeNS(null,"cx",25);
myCircle.setAttributeNS(null,"cy",25);
myCircle.setAttributeNS(null,"r",100);
myCircle.setAttributeNS(null,"fill","black");
myCircle.setAttributeNS(null,"stroke","blue");
document.getElementById("mySVG").appendChild(myCircle);
}
Create a SVG:
var svg = document.createElementNS(ns, 'svg');
First function:
function createSVG() { ... }
Second function:
function createSVGCircle() { createSVG() ... }
Or separated:
createSVG();
createSVGCircle();
Example
You need to create the svg element in the SVG namespace using createElementNS (like you already do for the circle) e.g.
var d = document.createElementNS(svgNS, 'svg');
giving the SVG element a width and height is also necessary
d.setAttribute("width", "100%");
d.setAttribute("height", "100%");
Note here we can use setAttribute as the attributes are in the null namespace. You can convert the circle setAttributeNS calls too if you want.
I'm trying to build a transform manager for KineticJS that would build a bounding box and allow users to scale, move, and rotate an image on their canvas. I'm getting tripped up with the logic for the anchor points.
http://jsfiddle.net/mharrisn/whK2M/
I just want to allow a user to scale their image proportionally from any corner, and also rotate as the hold-drag an anchor point.
Can anyone help point me in the right direction?
Thank you!
Here is a proof of concept of a rotational control I've made:
http://codepen.io/ArtemGr/pen/ociAD
While the control is dragged around, the dragBoundFunc is used to rotate the content alongside it:
controlGroup.setDragBoundFunc (function (pos) {
var groupPos = group.getPosition()
var rotation = degrees (angle (groupPos.x, groupPos.y, pos.x, pos.y))
status.setText ('x: ' + pos.x + '; y: ' + pos.y + '; rotation: ' + rotation); layer.draw()
group.setRotationDeg (rotation); layer.draw()
return pos
})
I am doing the same thing, and I've posted a question which is allmoast the same, but I found a link where you have the resize and move tool ready developed. So I have used the same. It does not contain the rotate tool however, but this can be a good start for you too, it is very simple and logical. Here is the link: http://www.html5canvastutorials.com/labs/html5-canvas-drag-and-drop-resize-and-invert-images/
I will come back with the rotation tool as well if I manage to get it working perfectly.
I hope I am not late yet for posting this code snippet that I made. I had the same problem with you guys dealing with this kind of task. Its been 3 days since I tried so many workarounds to mimic the fabricjs framework capability when dealing with images and objects. I could use Fabricjs though but it seems that Kineticjs is more faster/consistent to deal with html5.
Luckily, we already have existing plugin/tool that we could easily implement together with kineticjs and this is jQuery Transform tool. SUPER THANKS TO THE AUTHOR OF THIS! Just search this on google and download it.
I hope the code below that I created would help lots of developers out there who is pulling their hair off to solve this kind of assignment.
$(function() {
//Declare components STAGE, LAYER and TEXT
var _stage = null;
var _layer = null;
var simpleText = null;
_stage = new Kinetic.Stage({
container: 'canvas',
width: 640,
height: 480
});
_layer = new Kinetic.Layer();
simpleText = new Kinetic.Text({
x: 60,
y: 55,
text: 'Simple Text',
fontSize: 30,
fontFamily: 'Calbiri',
draggable: false,
name:'objectInCanvas',
id:'objectCanvas',
fill: 'green'
});
//ADD LAYER AND TEXT ON STAGE
_layer.add(simpleText);
_stage.add(_layer);
_stage.draw();
//Add onclick event listener to the Stage to remove and add transform tool to the object
_stage.on('click', function(evt) {
//Remove all objects' transform tool inside the stage
removeTransformToolSelection();
// get the shape that was clicked on
ishape = evt.targetNode;
//Add and show again the transform tool to the selected object and update the stage layer
$(ishape).transformTool('show');
ishape.getParent().moveToTop();
_layer.draw();
});
function removeTransformToolSelection(){
//Search all objects inside the stage or layer who has the name of "objectInCanvas" using jQuery iterator and hide the transform tool.
$.each(_stage.find('.objectInCanvas'), function( i, child ) {
$(child).transformTool('hide');
});
}
//Event listener/Callback when selecting image using file upload element
function handleFileSelect(evt) {
//Remove all objects' transform tool inside the stage
removeTransformToolSelection();
//Create image object for selected file
var imageObj = new Image();
imageObj.onload = function() {
var myImage = new Kinetic.Image({
x: 0,
y: 0,
image: imageObj,
name:'objectInCanvas',
draggable:false,
id:'id_'
});
//Add to layer and add transform tool
_layer.add(myImage);
$(myImage).transformTool();
_layer.draw();
}
//Adding source to Image object.
var f = document.getElementById('files').files[0];
var name = f.name;
var url = window.URL;
var src = url.createObjectURL(f);
imageObj.src = src;
}
//Attach event listener to FILE element
document.getElementById('files').addEventListener('change', handleFileSelect, false);
});