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.
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.
This is what React SVG currently supports: http://facebook.github.io/react/docs/tags-and-attributes.html#svg-attributes
I'm trying to figure out how to make a shape I drew using the SVG path clickable.
If there is another way to draw a shape that can be made clickable, that works too.
Thanks!
I wrap my SVG with a div and apply any attributes that I desire (click handlers, fill colors, classes, width, etc..), like so (fiddle link):
import React, { PropTypes } from 'react'
function XMark({ width, height, fill, onClick }) {
return (
<div className="xmark-container" onClick={onClick}>
<svg className='xmark' viewBox="67 8 8 8" width={width} height={height} version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink">
<polygon stroke="none" fill={fill} fillRule="evenodd" points="74.0856176 9.4287633 71.5143809 12 74.0856176 14.5712367 73.5712367 15.0856176 71 12.5143809 68.4287633 15.0856176 67.9143824 14.5712367 70.4856191 12 67.9143824 9.4287633 68.4287633 8.91438245 71 11.4856191 73.5712367 8.91438245 74.0856176 9.4287633 74.0856176 9.4287633 74.0856176 9.4287633" />
</svg>
</div>
)
}
XMark.propTypes = {
width: PropTypes.number,
height: PropTypes.number,
fill: PropTypes.string,
onClick: PropTypes.func,
}
XMark.defaultProps = {
width: 8,
height: 8,
fill: '#979797',
onClick: null,
}
export default XMark
You can of course ditch the wrapper and apply the onClick to the svg element as well, but I've found this approach works well for me!
(I also try and use pure functions when possible https://hackernoon.com/react-stateless-functional-components-nine-wins-you-might-have-overlooked-997b0d933dbc)
This worked for me.
svg {
pointer-events: none;
}
path{
pointer-events: auto;
}
Then we can add on click event on path. worked!! thanks
I did it this way:
Just using polyline for example, it could be any SVG element.
export default class Polyline extends React.Component {
componentDidMount() {
this.polyline.addEventListener('click', this.props.onClick);
}
componentWillUnmount(){
this.polyline.removeEventListener('click', this.props.onClick);
}
render() {
const {points, style, markerEnd} = this.props;
return <polyline points={points}
ref={ref => this.polyline = ref}
style={style}
markerEnd={markerEnd}/>;
}
}
get ref in ref callback
on componentDidMount add click event listener to the ref
remove event listener in componentWillUnmount
I had similar problem with react, I was trying to handle onclick event for svg.
Simple css solved problem for me:
svg {
pointer-events: none;
}
You can use onClick as you do with other DOM elements.
Two major ways to do this:
You put an HTML event listener on the 'path' tag in the svg code. You will have to escape your code properly if you choose this method.
The following example features a star shape cut in two paths each of which logs "Hello" in the console ( console.log("Hello") )
Example:
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 225 213.6" style="enable-background:new 0 0 225 213.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4D4D4D;}
.st1{fill:#494949;}
</style>
<g>
<path id="right" onmouseover="console.log("Hello")" class="st0" d="M43.5,131c4.4,4.2,6.3,8.7,5,15.1
c-3.4,17.2-6,34.6-9,51.9c-1.5,8.5,1.6,14.4,9.1,15.5c3.1,0.5,6.8-0.9,9.8-2.4c14.9-7.6,29.8-15.4,44.5-23.5c3-1.6,5.8-2.5,8.6-2.7
V0.1c-1.1,0.2-2.1,0.5-3.2,1.1c-2.7,1.4-5.2,4.2-6.7,7c-7.8,15.2-15.6,30.5-22.8,46C75.6,61,71,64.7,63.5,65.7
c-16.6,2.2-33.1,5.1-49.7,6.9C6.4,73.5,2.4,77.2,0,83.5c0,0.7,0,1.3,0,2c3.5,4.5,6.7,9.4,10.7,13.4C21.4,109.8,32.5,120.4,43.5,131
z"/>
<path id="left" onmouseover="console.log("Hello")" class="st1" d="M206.4,71.9c-15.9-2.1-31.8-4.7-47.7-6.9c-5.3-0.7-8.8-3.5-11-8.2c-8-16.2-16-32.5-24.1-48.7
c-2.9-5.8-7.3-8.8-12-8v184.8c3.5-0.2,6.9,0.7,10.6,2.7c14.7,8.1,29.6,15.8,44.5,23.5c2.9,1.5,6.7,2.9,9.8,2.4
c7.3-1,10.5-6.8,9.2-15c-3-17.8-5.9-35.6-9.2-53.3c-1.1-5.6,0.7-9.8,4.5-13.4c11.2-10.9,22.6-21.7,33.6-32.8c4-4,7.1-8.9,10.7-13.4
c0-0.7,0-1.3,0-2C222.3,74.1,214.5,72.9,206.4,71.9z"/>
</g>
</svg>
Example: https://svgshare.com/i/aex.svg
In Adobe Illustrator there is (currently called) SVG Iteractivity tool.
You can find it under the Window top menu.
Then select the path you need with direct selection tool, choose the HTML event from the SVG Interactivity widow and write your Javascript Code below.
Then click 'Export Selection' from the file menu or Save As... and save all as .svg
The result will be .svg file with automatically properly escaped code and HTML event on the 'path' tag.
Update: as of February 2014, Meteor supports reactive SVG, so no workaround is necessary.
Meteor 0.5.9
I would like to create a group of shapes, one for each document in the collection. I can create shapes one at a time in a template, but not inside of an {{#each loop}}.
This works:
<Template name="map">
<svg viewBox="0 0 500 600" version="1.1">
<rect x="0" y="0" width="100" height="100" fill={{color}}/>
</svg>
</Template>
Template.map.color = function() {
return "green";
};
This does not:
<Template name="map">
<svg viewBox="0 0 500 600" version="1.1">
{{#each colors}}
<rect x="0" y="0" width="100" height="100" fill={{color}}/>
{{/each}}
</svg>
</Template>
Template.map.colors = function() {
return [{color: "red"}, {color: "blue"}];
}
Anything I try to create inside of using {{#each}} just doesn't show up, even though I can create them manually, even with attributes inserted by Meteor through the template.
I also tried just sending a single object {color: "red"} to the template and using {{#with colors}}, and that does not work either. In addition to the SVG, I've also put plain s into the templates to make sure information gets to the template correctly, and those are all working as expected, with {{#each}} and with {{#with}}.
Should I be able to do what I'm trying to do?
(Updated April 1, 2013)
Found a way that combines Handlebars with insertion by Javascript. Have to give credit to this blog entry for figuring this one out:
http://nocircleno.com/blog/svg-and-handlebars-js-templates/
I created the following two files, placed them inside the client folder of a new Meteor directory and I got the html successfully.
Testing.js:
<head>
<title>testing</title>
</head>
<body>
</body>
<template name="map">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
{{#each colors}}
<rect x="0" y="{{yPosition}}" width="100" height="100" fill="{{color}}"/>
{{/each}}
</svg>
</template>
Testing.html:
(function () {
var count = 0;
Template.map.yPosition = function() {
count++;
return (count-1) * 100;
};
Template.map.colors = function() {
return [{color: "red"}, {color: "blue"}];
};
Meteor.startup(function() {
var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgElement.width = 500;
svgElement.height = 600;
document.getElementsByTagName("body")[0].appendChild(svgElement);
var svgFragment = new DOMParser().parseFromString(Template.map(), "text/xml");
svgElement.appendChild(svgFragment.documentElement);
});
})();
I came across the same problem experimenting with Meteor and SVG elements and discovered that you can add elements and get them to show up with the two methods below. One option is to just wrap the elements in the each loop in an <svg></svg>, like this:
<svg viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
{{#each pieces}}
<svg xmlns="http://www.w3.org/2000/svg"><circle cx="{{x}}" cy="{{y}}" r="1" fill="{{color}}"></circle></svg>
{{/each}}
</svg>
Another options is to (on template render) create an svg element with jQuery that contains the element you want to insert, then use jQuery to grab that inner element and insert it into the svg element already in the DOM, like so (in coffeescript):
for piece in Pieces.find().fetch()
$el = $("<svg><circle cx='#{piece.x}' cy='#{piece.y}' r='1' class='a'></circle></svg>")
$el.find('circle').appendTo #$('svg')
You could also use something like d3 or RaphaelJS to do the inserting. You can even make the individual elements reactive to your Collection and animate easily by using a library like d3 in the Collection observer callbacks like so (again, coffeescript):
Pieces.find().observe {
added: (piece)=>
# using jquery (could use d3 instead)
$el = $("<svg><circle cx='#{piece.x}' cy='#{piece.y}' r='1' fill='#{piece.color}' data-id='#{piece._id}'></circle></svg>")
$el.find('circle').appendTo #$('svg')
changed: (newPiece, oldPiece)=>
# using d3 to animate change
d3.select("[data-id=#{oldPiece._id}]").transition().duration(1000).attr {
cx: newPiece.x
cy: newPiece.y
fill: newPiece.color
}
removed: (piece)=>
#$("[data-id=#{piece._id}]").remove()
}
These methods seem to work in latest Chrome, Safari, Firefox browsers on Mac, but I haven't tested in others.
According to the Using Blaze page, Meteor will have first class support of SVG when Blaze is released.
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