How to display svg markers that contain links to other svgs in HERE maps? - svg

I am trying to display svg markers using the HERE Javascript API. I have followed the documentation, however my twist is that my svg contains links to other svgs. The reason for this is that I would like to display markers that have the same pin shape, but a different icon in the centre of the pin. As the icons will also be used in other places on my website, it makes sense to save the svgs separately so they only need updating in one place.
I can get standard svgs to display as markers, but when I try and nest the svgs, no marker is displayed on the map.
This is my code so far:
pin.svg - This is the basic pin shape that all markers will use
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="46.093765"
height="63.352634">
<g
transform="translate(-8.9327811,-0.45623957)"
id="layer1">
<path
d="M 22.15625,0 A 21.544797,22.053723 0 0 0 0,22.0625 21.544797,22.053723 0 0 0 10.40625,40.9375 c 1.062265,1.795846 11.125,19.40625 11.125,19.40625 l 12.125,-20.0625 A 21.544797,22.053723 0 0 0 43.09375,22.0625 21.544797,22.053723 0 0 0 22.15625,0 z"
transform="translate(10.432788,1.9651232)"
id="path5014"
style="fill:#cccccc;fill-opacity:1;stroke:#6e6e6e;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
d="m 50.723066,23.370655 a 17.218699,16.540131 0 1 1 -34.437397,0 17.218699,16.540131 0 1 1 34.437397,0 z"
transform="matrix(0.94189641,0,0,1.0010512,0.4199413,0.48436132)"
id="path5019"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
</svg>
here-example.svg - This is the example from the HERE site. I am using it as an icon that gets placed inside the pin
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<rect stroke="black" fill="red" x="1" y="1" width="22" height="22" />
<text x="12" y="18" font-size="12pt" font-family="Arial" font-weight="bold" text-anchor="middle" fill="yellow">
C
</text>
</svg>
pin-with-icon.svg - This is my nested svg - it pulls in the pin and the icon and overlaps them
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<image x="20" y="20" width="300" height="80" xlink:href="pin.svg" />
<image x="120" y="32" width="100" height="30" xlink:href="here-example.svg" />
</svg>
index.html - This is where the HERE map is used and I try and display the markers. It makes heavy use of the HERE developer documentation. Replace <App_ID> and <App_Code> with the HERE credentials. In this page I also try and load the problematic svg into a div to prove that it works.
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<script src="http://js.api.here.com/v3/3.0/mapsjs-core.js" type="text/javascript" charset="utf-8"></script>
<script src="http://js.api.here.com/v3/3.0/mapsjs-service.js" type="text/javascript" charset="utf-8"></script>
<script src="http://js.api.here.com/v3/3.0/mapsjs-ui.js" type="text/javascript" charset="utf-8"></script>
<script src="http://js.api.here.com/v3/3.0/mapsjs-mapevents.js" type="text/javascript" charset="utf-8"></script>
<script src="http://code.jquery.com/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<link rel="stylesheet" type="text/css" href="http://js.api.here.com/v3/3.0/mapsjs-ui.css" />
</head>
<body>
<div style="width: 640px; height: 480px" id="mapContainer"></div>
<!-- Display the svg just to prove that the loads correctly -->
<div id="svgPin"></div>
<script>
// Initialize the platform object:
var platform = new H.service.Platform({
'app_id': '<App_ID>',
'app_code': '<App_Code>'
});
// Obtain the default map types from the platform object
var maptypes = platform.createDefaultLayers();
// Instantiate (and display) a map object:
var map = new H.Map(
document.getElementById('mapContainer'),
maptypes.normal.map,
{
zoom: 6,
center: { lng: 13.4, lat: 52.51 }
});
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
// Example with svg pins not showing (svg contains links to other svgs)
var svgMarkupRetrieval = $.get('pin-with-icon.svg', function (svg) {
// Just to prove that the svg is loaded correctly
$('#svgPin').html(svg);
var icon = new H.map.Icon(svg);
// Add the first marker
var marker1 = new H.map.Marker({ lat: 52.4, lng: 13.3 },
{ icon: icon });
map.addObject(marker1);
// Add the second marker.
var marker2 = new H.map.Marker({ lat: 51.45, lng: 13.3 },
{ icon: icon });
map.addObject(marker2);
}, 'text');
// Example with svg pins showing (from HERE developer guide)
var svgMarkupRetrieval = $.get('here-example.svg', function (svg) {
var icon = new H.map.Icon(svg);
// Add the first marker
var marker3 = new H.map.Marker({ lat: 50.4, lng: 13.3 },
{ icon: icon });
map.addObject(marker3);
// Add the second marker.
var marker4 = new H.map.Marker({ lat: 49.45, lng: 13.3 },
{ icon: icon });
map.addObject(marker4);
}, 'text');
</script>
</body>
</html>
To use the code, create each file with the name I specify and put them in a folder in IIS. index.html has to be run through IIS, otherwise a cross origin request error occurs.
The pin-with-icon.svg loads separately (I load it under the map), as do the standard svg markers. But I cannot see why the markers I create using pin-with-icon.svg do not show on the map. Any help would be appreciated.

Use H.map.DomMarker and H.map.DomIcon

Related

Material Design Icon (MDI) inside SVG graphic?

In my application I need to use the same icon in different places.
in v-card-action's button
in a SVG graphic
For the button it is as explained in vuetify documentation:
<v-card-actions>
<v-btn value="previous" color="red" >
<span class="hidden-sm-and-down">Previous</span>
<v-icon right>mdi-arrow-left-circle</v-icon>
</v-btn>
</v-card-actions>
But now, how to use the exact same icon (using it's name) in a custom SVG
<svg viewBox="0 0 100 100">
<rec x="0" y="0" width="100" height="100" stroke="grey" />
<???> mdi-arrow-left-circle </???>
</svg>
First, do i need to use SVG <img>, <text> or <path> primitive ?
Second, how do i get the proper icon from it's name mdi-arrow-left-circle ?
I had the exact same question. This link came in handy when putting this together:
How do I include a font awesome icon in my svg?
Disclaimer: I'm using TS components in Vue and have added Vuetify.
in the template I have a SVG:
<svg>
<text
x="100"
y="100"
class="licon"
fill="red">
{{ content('mdi-close') }}
</text>
</svg>
The content method does this:
content(cls: string): string {
// this copies the content from the pseudo element :before as it's needed to show the icon from material design
const ele = document.querySelector('.' + cls);
if(ele) {
const styles = window.getComputedStyle(ele, ':before');
return styles.content.replaceAll('"', "");
}
return '';
}
The last piece needed was to make sure to use the correct font (include in your stylesheet/etc):
.licon {
font: bold 300px 'Material Design Icons';
}
Hopefully this helps someone else.

Vue can not attach event listener

I have an svg with some elements in it, the complete code is in here capture event, the aircraft image is positioned via transform attribute in such a way that it falls into the image with href2. The problem is Vue is unable to detect the click event on the aircraft image.
I can't seem to find a way to go around this. I want to be able to attach an event listener to the aircraft image regardless of where on the screen is located.
In jQuery solving these kind of situations is like a breeze of air, but with Vue seems to be a different story.
Here is the HTML
<div id="app">
<svg xmlns="http://www.w3.org/2000/svg" width="1015px" height="580px" viewBox="-50 -50 1015 580" preserveAspectRatio="xMidYMid meet" version="1.1" id="svg">
<g #click="showFlightCard" v-for="(ge, index) in this.gEl" :key="index">
<path :id="index+1" d="M 400 100 L 150 150" stroke="red" stroke-width="3" fill="none" />
<image :id="index" :href="href1" width="48" height="24" transform="translate(251,143)"></image>
</g>
<image x="250" y="10" width="522" height="402.452" id="e4_image" preserveAspectRatio="xMidYMid meet" :href="href2" style="stroke:black;stroke-width:1px;fill:khaki;"/>
</svg>
</div>
Here is the JS, for clarity I avoided the href in here due to 64 bit encoding, which is too long, please look at the jsfiddle which contains the href as well.
var app = new Vue({
el: '#app',
methods: {
showFlightCard: function (e) {
console.log('Click')
}
},
data: {
message: 'Hello Vue!',
gEl: ['A', 'B', 'C'],
href1: 'look at the jsfiddle',
href2: 'look at the jsfiddle'
}
})
You can attach the click listener on the path of the svg itself, or on the parent element of the svg. A click listener on the svg tag itself doesn't seem to fire events.

How can i make a svg <rect> box dynamic with changeable <text>?

How can i make a svg rect box dynamic with changeable text ? Like in my code, if the text "Hello" will more than 30 character ?
<svg version="1.1" viewBox="0 0 500 500" preserveAspectRatio="xMinYMin meet" class="svg-content">
<g>
<rect x="0" y="0" width="100" height="100" stroke-width="5" stroke="#000000" fill="none"></rect> ?
<text x="0" y="50" font-family="Verdana" font-size="35" fill="blue">Hello</text>
</g>
</svg>
Probably the best approach would be to have the rect preset at specific width. Then create tspans to fill the text element, and dynamically resize the rect height as the characters exceed the preset width.
Below is an example:
<!DOCTYPE HTML>
<html>
<head>
<title>Wrap Text Rectangle</title>
</head>
<body onload=wrapTextRect()>
Place this text:<br>
<textarea id="myTextValue" style='width:400px;height:60px;'>
Hello!
</textarea><br>
<button onClick=wrapTextRect()> Wrap text in rect</button>
<div id="svgDiv" style='background-color:lightgreen;width:400px;height:400px;'>
<svg id="mySVG" width="400" height="400">
<rect id=myRect x=50 y=50 rx=10 ry=10 width=100 fill="#4682b4" stroke='black' stroke-width=5 opacity=.5 />
<text id=myText font-size=14 font-family="arial" fill="white" />
</svg>
</div>
SVG Source:<br>
<textarea id=sourceValue style=width:500px;height:300px></textarea>
<script>
var NS="http://www.w3.org/2000/svg"
//---onload and button---
function wrapTextRect()
{
//---clear previous---
for(var k=myText.childNodes.length-1;k>=0;k--)
myText.removeChild(myText.childNodes.item(k))
var padding=10
var width=+myRect.getAttribute("width")-padding
var x=+myRect.getAttribute("x")
var y=+myRect.getAttribute("y")
var fontSize=+myText.getAttribute("font-size")
var text=myTextValue.value
var words = text.split(' ');
var text_element = document.getElementById('myText');
var tspan_element = document.createElementNS(NS, "tspan"); // Create first tspan element
var text_node = document.createTextNode(words[0]); // Create text in tspan element
tspan_element.setAttribute("x", x+padding);
tspan_element.setAttribute("y", y+padding+fontSize);
tspan_element.appendChild(text_node); // Add tspan element to DOM
text_element.appendChild(tspan_element); // Add text to tspan element
//---[EDIT] a single word that exceeds preset rect with---
if(words.length==1)
{
var len = tspan_element.getComputedTextLength()
if(len>+myRect.getAttribute("width"))
myRect.setAttribute("width", len+2*padding)
}
//---end [EDIT]------------------
for(var i=1; i<words.length; i++)
{
var len = tspan_element.firstChild.data.length // Find number of letters in string
tspan_element.firstChild.data += " " + words[i]; // Add next word
if (tspan_element.getComputedTextLength() > width-padding)
{
tspan_element.firstChild.data = tspan_element.firstChild.data.slice(0, len); // Remove added word
var tspan_element = document.createElementNS(NS, "tspan"); // Create new tspan element
tspan_element.setAttribute("x", x+padding);
tspan_element.setAttribute("dy", fontSize);
text_node = document.createTextNode(words[i]);
tspan_element.appendChild(text_node);
text_element.appendChild(tspan_element);
}
}
var height = text_element.getBBox().height +2*padding; //-- get height plus padding
myRect.setAttribute('height', height); //-- change rect height
//---show svg source---
sourceValue.value=svgDiv.innerHTML
}
</script>
</body>
</html>

SVG use tag and ReactJS

So normally to include most of my SVG icons that require simple styling, I do:
<svg>
<use xlink:href="/svg/svg-sprite#my-icon" />
</svg>
Now I have been playing with ReactJS as of late evaluating it as a possible component in my new front-end development stack however I noticed that in its list of supported tags/attributes, neither use or xlink:href are supported.
Is it possible to use svg sprites and load them in this way in ReactJS?
MDN says that xlink:href is deprecated in favor of href. You should be able to use the href attribute directly. The example below includes both versions.
As of React 0.14, xlink:href is available via React as the property xlinkHref. It is mentioned as one of the "notable enhancements" in the release notes for 0.14.
<!-- REACT JSX: -->
<svg>
<use xlinkHref='/svg/svg-sprite#my-icon' />
</svg>
<!-- RENDERS AS: -->
<svg>
<use xlink:href="/svg/svg-sprite#my-icon"></use>
</svg>
Update 2018-06-09: Added info about href vs xlink:href attributes and updated example to include both. Thanks #devuxer
Update 3: At time of writing, React master SVG properties can be found here.
Update 2: It appears that all svg attributes should now be available via react (see merged svg attribute PR).
Update 1: You may want to keep an eye on the svg related issue on GitHub for additional SVG support landing. There are developments in the works.
Demo:
const svgReactElement = (
<svg
viewBox="0 0 1340 667"
width="100"
height="100"
>
<image width="667" height="667" href="https://i.imgur.com/w7GCRPb.png"/>
{ /* Deprecated xlink:href usage */ }
<image width="667" height="667" x="673" xlinkHref="https://i.imgur.com/w7GCRPb.png"/>
</svg>
);
var resultHtml = ReactDOMServer.renderToStaticMarkup(svgReactElement);
document.getElementById('render-result-html').innerHTML = escapeHtml(resultHtml);
ReactDOM.render(svgReactElement, document.getElementById('render-result') );
function escapeHtml(unsafe) { return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.1/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.1/umd/react-dom-server.browser.development.js"></script>
<h2>Render result of rendering:</h2>
<pre><svg
viewBox="0 0 1340 667"
width="100"
height="100"
>
<image width="667" height="667" href="https://i.imgur.com/w7GCRPb.png"/>
{ /* Deprecated xlink:href usage */ }
<image width="667" height="667" x="673" xlinkHref="https://i.imgur.com/w7GCRPb.png"/>
</svg></pre>
<h2><code>ReactDOMServer.renderToStaticMarkup()</code> output:</h2>
<pre id="render-result-html"></pre>
<h2><code>ReactDOM.render()</code> output:</h2>
<div id="render-result"></div>
Update september 2018: this solution is deprecated, read Jon’s answer instead.
--
React doesn’t support all SVG tags as you say, there is a list of supported tags here. They are working on wider support, f.ex in this ticket.
A common workaround is to inject HTML instead for non-supported tags, f.ex:
render: function() {
var useTag = '<use xlink:href="/svg/svg-sprite#my-icon" />';
return <svg dangerouslySetInnerHTML={{__html: useTag }} />;
}
If you encounter xlink:href, then you can get the equivalent in ReactJS by removing the colon and camelcasing the added text: xlinkHref.
You'll probably eventually be using other namespace-tags in SVG, like xml:space, etc.. The same rule applies to them (i.e., xml:space becomes xmlSpace).
As already said in Jon Surrell's answer, use-tags are supported now. If you are not using JSX, you can implement it like this:
React.DOM.svg( { className: 'my-svg' },
React.createElement( 'use', { xlinkHref: '/svg/svg-sprite#my-icon' }, '' )
)
I created a little helper that works around this issue: https://www.npmjs.com/package/react-svg-use
first npm i react-svg-use -S then simply
import Icon from 'react-svg-use'
React.createClass({
render() {
return (
<Icon id='car' color='#D71421' />
)
}
})
and this will then generate the following markup
<svg>
<use xlink:href="#car" style="fill:#D71421;"></use>
</svg>
I had problems with showing SVG in Gutenberg block, by referencing it with xlink:href. We used xlinkHref property in react, but after compiling, instead to render as xlink:href it was rendered to xlinkhref, and SVG was not displayed. After a lot of examining, I found out that xlink:href is deprecated (although it worked if we add it in html, or directly in chrome dev tools), and that href should be used instead. So after changing it to href it worked.
"SVG 2 removed the need for the xlink namespace, so instead of xlink:href you should use href." https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
This is the code I used
SVG file
<svg id="svg-source" style="display: none;" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<symbol id="svg-arrow-accordion" viewBox="0 0 15 24" fill="none">
<path id="Path_1662" data-name="Path 1662" d="M15.642,14.142h-3V1.5H0v-3H15.642Z" transform="translate(2 2) rotate(45)" fill="currentColor"></path>
</symbol>
</svg>
React file
<svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="15" height="24">
<use href="#svg-arrow-accordion"></use>
</svg>
This is svg Component.
const SvgComponent = () => {
return <svg width="0" height="0">
<defs>
<symbol id="test" viewBox="0 0 100 100">
<line x1='0' y1='50' x2='100' y2='50' strokeWidth='8' stroke="#000" />
</symbol>
</defs>
</svg>
}
export default SvgComponent
use component
import SvgComponent from './SvgComponent';
export default function App() {
return (
<>
<SvgComponent/>
<svg>
<use xlinkHref="#test"></use>
</svg>
</>
);
}

Pure SVG way to fit text to a box

Box size known. Text string length unknown. Fit text to box without ruining its aspect ratio.
After an evening of googling and reading the SVG spec, I'm pretty sure this isn't possible without JavaScript. The closest I could get was using the textLength and lengthAdjust text attributes, but that stretches the text along one axis only.
<svg width="436" height="180"
style="border:solid 6px"
xmlns="http://www.w3.org/2000/svg">
<text y="50%" textLength="436" lengthAdjust="spacingAndGlyphs">UGLY TEXT</text>
</svg>
I am aware of SVG Scaling Text to fit container and fitting text into the box
I didn't find a way to do it directly without Javascript, but I found a JS quite easy solution, without for loops and without modify the font-size and fits well in all dimensions, that is, the text grows until the limit of the shortest side.
Basically, I use the transform property, calculating the right proportion between the desired size and the current one.
This is the code:
<?xml version="1.0" encoding="UTF-8" ?>
<svg version="1.2" viewBox="0 0 1000 1000" width="1000" height="1000" xmlns="http://www.w3.org/2000/svg" >
<text id="t1" y="50" >MY UGLY TEXT</text>
<script type="application/ecmascript">
var width=500, height=500;
var textNode = document.getElementById("t1");
var bb = textNode.getBBox();
var widthTransform = width / bb.width;
var heightTransform = height / bb.height;
var value = widthTransform < heightTransform ? widthTransform : heightTransform;
textNode.setAttribute("transform", "matrix("+value+", 0, 0, "+value+", 0,0)");
</script>
</svg>
In the previous example the text grows until the width == 500, but if I use a box size of width = 500 and height = 30, then the text grows until height == 30.
first of all: just saw that the answer doesn't precisely address your need - it might still be an option, so here we go:
you are rightly observing that svg doesn't support word-wrapping directly. however, you might benefit from foreignObject elements serving as a wrapper for xhtml fragments where word-wrapping is available.
have a look at this self-contained demo (available online):
<?xml version="1.0" encoding="utf-8"?>
<!-- SO: http://stackoverflow.com/questions/15430189/pure-svg-way-to-fit-text-to-a-box -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="20cm" height="20cm"
viewBox="0 0 500 500"
preserveAspectRatio="xMinYMin"
style="background-color:white; border: solid 1px black;"
>
<title>simulated wrapping in svg</title>
<desc>A foreignObject container</desc>
<!-- Text-Elemente -->
<foreignObject
x="100" y="100" width="200" height="150"
transform="translate(0,0)"
>
<xhtml:div style="display: table; height: 150px; overflow: hidden;">
<xhtml:div style="display: table-cell; vertical-align: middle;">
<xhtml:div style="color:black; text-align:center;">Demo test that is supposed to be word-wrapped somewhere along the line to show that it is indeed possible to simulate ordinary text containers in svg.</xhtml:div>
</xhtml:div>
</xhtml:div>
</foreignObject>
<rect x="100" y="100" width="200" height="150" fill="transparent" stroke="red" stroke-width="3"/>
</svg>
I've developed #Roberto answer, but instead of transforming (scaling) the textNode, we simply:
give it font-size of 1em to begin with
calculate the scale based on getBBox
set the font-size to that scale
(You can also use 1px etc.)
Here's the React HOC that does this:
import React from 'react';
import TextBox from './TextBox';
const AutoFitTextBox = TextBoxComponent =>
class extends React.Component {
constructor(props) {
super(props);
this.svgTextNode = React.createRef();
this.state = { scale: 1 };
}
componentDidMount() {
const { width, height } = this.props;
const textBBox = this.getTextBBox();
const widthScale = width / textBBox.width;
const heightScale = height / textBBox.height;
const scale = Math.min(widthScale, heightScale);
this.setState({ scale });
}
getTextBBox() {
const svgTextNode = this.svgTextNode.current;
return svgTextNode.getBBox();
}
render() {
const { scale } = this.state;
return (
<TextBoxComponent
forwardRef={this.svgTextNode}
fontSize={`${scale}em`}
{...this.props}
/>
);
}
};
export default AutoFitTextBox(TextBox);
This is still an issue in 2022. There is no way to define bounds and get text to scale in a pure scalable vector graphic. Adjusting the font size manually is still the only solution it seems, and the examples given are quite buggy. Has anybody figured out a clean solution that works? Judging by the svg spec it looks like a pure solution doesn't exist.
And to provide some sort of answer myself, this resource is the best I've found, is hacky, but works much more robustly: fitrsvgtext - storybook | fitrsvgtext - GitHub
I don't think its the solution for what you want to do but you can use textLength
with percentage ="100%" for full width.
<svg width="436" height="180"
style="border:solid 6px"
xmlns="http://www.w3.org/2000/svg">
<text x="0%" y="50%" textLength="100%">blabla</text>
</svg>
you can also add text-anchor="middle" and change the x position to center perfectly your text
this will not change the fontsize and you will have weird space letterspacing...
JSFIDDLE DEMO

Resources