how to add text in svg object inside javascript - javascript

How can I add a text node inside javascript svg object . I am using this object , but I don't know how to add text into svg .
var circlemarker = {
path: 'M-5,0a5,5 0 1,0 10,0a5,5 0 1,0 -10,0 z',
fillColor:'yellow', //'#f39c13',
fillOpacity: 0.8,
scale: 3,
strokeColor: '#f7692c',
strokeWeight: 2,
data: 'My text' // this is not working
};

First of all, read more about SVG here
TL;DR SVG is just set of elements that represent a geometric figure.
Simple SVG looks like
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
<text x="30" y="40">aaa</text>
</svg>
Notice that <text></text> is an element, so it cannot be used inside <circle></circle> but underneath
So to add text, create new text element like this
var textmarker = {
x: 10,
y: 40,
fontSize: 10,
fontFamily: 'Verdana',
value: 'some text' //this might be tricky, as per docs, text element filled by innerHTML property, try data, text, innerHTML, etc
}

Related

Animating a value of a tag attribute

Is there a way to animate a value of dy attribute of SVG's tspan using JavaScript, ideally using Web Animations API?
The value of dy is known to be animatable using <animate /> element (as seen here):
<svg>
<text x="30" y="30">FairBridge
<animate attributeName="dy" from="0 0 0" to="0 -20 20" dur="1s" repeatCount="indefinite"/>
</text>
</svg>
I am wondering if it's possible to convert that animation to JavaScript.
For context, I've been using KeyframeEffect for all my previous animations, and would prefer to use it to animate the attribute value too in order to keep the animations code consistent.
In case there's absolutely no way to use KeyframeEffect, how would one go about animating a generic value of a tag attribute?
Only the presentation attributes can be animated using Web Animations API, because they are also CSS properties, XML attributes cannot.
In this example I can animate fill and rotate, but not x and dy. When I give the x property the wrong value (like '100' or 100) I get an error in the FireFox console -- strange when the right value has no effect. dy is just ignored.
var text1 = document.getElementById('text1');
var textKeyframes = new KeyframeEffect(
text1, [{
fill: 'red',
rotate: '0deg',
x: '0px',
dy: '0px'
},
{
fill: 'green',
rotate: '90deg',
x: '100px',
dy: '100px'
}
], {
duration: 2000,
fill: 'forwards'
}
);
var textAnimation = new Animation(textKeyframes, document.timeline);
textAnimation.play();
<svg viewBox="0 0 800 200">
<g transform="translate(100 100)">
<text id="text1" text-anchor="middle" dominant-baseline="middle"
font-size="30">FairBridge </text>
</g>
</svg>

SVG HTML5 change pattern attributes in specific instance/usage only

I have a SVG Pattern which I use as fill in many other SVGs.
Of course, when I change attributes in the pattern afterwards, it is changed in every SVG using this pattern.
Is there a possibility to modify the pattern attributes in a specific instance/use only?
I would like to avoid creating the pattern in 100 different versions before.
EDIT with sample:
This is a sample pattern. I use this icon as fill for a circle on a country on a world map. The circle inside the pattern is used to set the background color.
<svg width="0" height="0">
<defs>
<pattern id="infantry_svg" patternUnits="objectBoundingBox" width="100% " height="100%">
<circle r="10" fill="transparent"></circle>
<path d="M 19.328,3.097 19.025,2.633 18.35,3.045 17.638,2.148 17.555,3.563 15.188,5.011 C 14.818,4.596 14.555,4.484 14.555,4.484 L 10.343,7.079 10.039,6.959 9.871,6.7 13.072,4.919 C 12.811,4.496 12.29,3.65 12.29,3.65 L 9.295,5.893 9.173,5.968 9.028,5.732 8.342,6.155 8.488,6.393 8.276,6.523 7.838,5.811 7.152,6.234 7.59,6.946 7.293,7.129 7.146,6.891 6.46,7.314 6.606,7.551 6.584,7.566 3.881,8.993 4.556,10.089 7.139,8.339 C 7.26,8.536 7.353,8.687 7.353,8.687 l -1.748,1.074 0.293,0.456 -0.373,1.185 -1.518,1.412 c 0,0 -2.772,1.469 -4.007,2.152 0.308,0.691 0.964,1.57 1.443,2.214 0,0 4.855,-4.879 6.706,-4.765 l 1.837,-1.136 c 0,0 -0.752,-0.704 -0.5,-0.864 1.583,1.752 3.062,1.704 3.062,1.704 0.442,-0.568 0.528,-1.221 0.78,-1.834 -1.195,-0.286 -1.226,-0.103 -2.25,-1.111 l 4.673,-2.88 C 15.821,6.048 15.48,5.467 15.48,5.467 l 3.848,-2.37 z m -9.83,8.165 -1.203,0.742 c 0,0 -0.281,-0.437 -0.435,-0.688 l 0.265,-0.16 c 0.168,0.165 0.502,0.417 0.834,0.213 l -0.124,-0.2 C 8.662,11.275 8.461,11.146 8.333,11.03 l 0.743,-0.453 0.422,0.685 z"
fill="#030104" transform="translate(1,1.1) scale(0.29)" stroke="white" stroke-width="0.4" />
</pattern>
</defs>
</svg>
Now when I set this pattern inside a circle on a country, it looks like this:
The circle on the country is generated like this with Snap.svg:
var circle = xs.circle(x, y, 4).attr({ id: "blabla", fill: "url(#infantry_svg)", stroke: "black", strokeWidth: 1 }).appendTo(xs.select('g'));
Directly after this, I set the background color of the pattern used for this circle:
$("#infantry_svg circle").attr({fill: troopOwner_color]});
Now every circle using "infantry_svg" pattern, has "troopOwner_color" as background color of the circle.
But I would like to change only this single instance of the pattern usage.
Whilst you can't do what you want with the specific requirement of the question, ie using a pattern. I'm not sure why you need to use a pattern.
Why not just use a 'use' element for the defs statement, and have a path & circle in there. Then you can just clone it and change the fill...
<defs>
<g id="infantry_svg" >
<circle cx="30" cy="30" r="40" stroke="red" stroke-width="5"/>
<path>path stuff</path>
</defs>
var g = s.g().use( Snap.select('#infantry_svg') );
var g1 = s.g( g ).attr({ fill: 'yellow' });
var g2 = g.clone().attr({ fill: 'blue', transform: 't200,0' })
var g2 = g.clone().attr({ fill: 'green', transform: 't100,0' })
jsfiddle
Just include the bits that don't change in the defs element, and create the bits that will be unique.

How to use Jointjs and SVG to draw element tools

I'm new to javascript and SVG and I have no graphical programming background and this is my first project using all of those. So I started to make a custom element just like Mike Goodwin answer proposed and I ended with this code after editing it:
joint.shapes.tools.tooledElement = joint.shapes.basic.Generic.extend({
toolMarkup: [
'<g class="element-tools">',
'<g class="element-tool-remove"><circle fill="red" r="11" stroke="black" stroke-width="1"/>',
'<path transform="scale(.7) translate(-16, -16)" stroke="black" stroke-width="1" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z"/>',
'<title>Remove this element from the model</title>',
'</g>',
'<g class="element-tool-link"><circle fill="green" r="11" cx="160" cy="40" stroke="black" stroke-width="1"/>',
'<path transform="scale(.7) translate(-16, -16)"/>',
'<title>creates a new link</title>',
'</g>',
'</g>'
].join(''),
defaults: joint.util.deepSupplement({
attrs: {
text: { 'font-weight': 400, 'font-size': 'small', fill: 'black', 'text-anchor': 'middle', 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle' }
}
}, joint.shapes.basic.Generic.prototype.defaults)
});
Which works properly. Now I would like to draw some line on the green circle and to make the red circle into a red square. To achieve this I looked at this tutorial on paths to draw and this tutorial on basic shapes. But if I try to make a line on the green circle like this:
'<path transform="scale(.7) translate(-16, -16)" stroke="black" stroke-width="1" d="x y L 10 10 " />'
it won't draw anything. They do say " If your cursor already was somewhere on the page, no line is drawn to connect the two places." and that's why I omitted the "M" from path.
So here comes the first question: How can I draw the line on the center of the green circle without starting from the previous last point defined on any other path?
To make the red square I tried the exactly example from the second tutorial changing the fill (as a test):
//first line to test
<rect x="10" y="10" width="30" height="30" stroke="black" fill="red" stroke-width="5"/>
or
//second line to test
<rect x="60" y="10" rx="10" ry="10" width="30" height="30" stroke="black" fill="red" stroke-width="5"/>
The result of the first line would be the element at which the tools are being used to be draw again above itself like this:
And the second line would end up on nothing being show.
So here are the next questions:
Why did the results from the first line got like that?
and
How can I change the red circle into any other shape?
UPDATE:
About the line draw:
'<path transform="scale(.7) translate(-16, -16)" stroke="black" stroke-width="1" d="M150 150 H 5 V 5 H 5 z" />'
If I use this code for example, this is the result:
'<path transform="scale(.7) translate(-16, -16)" stroke="black" stroke-width="1" d="M150 150 H 5 V 5 H 5 z" />'
If I use this other code then the result will be:
The tutorial led me to believe that M is defining the start point but changing the translate(-16, -16) to something else made the correct start point be possible. So its the translate attribute combined with M that set the starting point.
The first question as been answered on update. As for the second question (concerning the red square conflicting with the element shape inside a rect) this was my work around:
Use the path to draw the tools elements instead of using basic shapes, that way it won't conflict with the element shape.
Example:
'<g class="element-tool-link"><circle fill="green" r="11" cx="160" cy="40" stroke="black" stroke-width="1"/>',
'<path transform="scale(.7) translate(-16, -16)"/>',
'<title>creates a new link</title>',
'</g>'
would turn into:
'<g class="element-tool-link">',
'<path d="M33 0 a 11 11 0 1 0 0.0001 0z " stroke="black" fill="lightblue" stroke-width="1"/>',
'<path transform="scale(.7) " stroke="black" stroke-width="1" d="M58,16 H 37 "/>',
'<title>creates a new link</title>',
'</g>'
where '<path d="M33 0 a 11 11 0 1 0 0.0001 0z " stroke="black" fill="lightblue" stroke-width="1"/>' defines a circle shape.

Can not set startOffset attribute on textpath with Snap.svg

I am trying to render text in the middle of an arc with Snap.svg.
This is possible with SVG like this:
<defs>
<path id="path1" d="M30 40 C 50 10, 70 10, 120 40 S 150 0, 200 40"/>
</defs>
<text text-anchor="middle">
<textPath xlink:href="#path1" startOffset="50%">
Text on a curved path
</textPath>
</text>
What I get is this (please ignore the specific coordinates):
<path d="M 352.5 14.1 A 338.4 338.4 0 0 1 645.5 183.3" id="path1"></path>
<text x="0" y="0" style="text-anchor: middle;">
<textPath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#path1">
Lorem Ipsum
</textPath>
</text>
when I do this in Snap.svg:
var labelArc = paper
.path('M 352.5 14.1 A 338.4 338.4 0 0 1 645.5 183.3')
.attr('startOffset': '50%'});
paper
.text(0, 0, 'Lorem Ipsum')
.attr({
'text-anchor' : 'middle',
'textpath' : labelArc
});
The problem is that the startOffset attribute is not passed to the textpath.
Setting this attribute via CSS did not work either.
Am I doing something wrong or does it require some fancy workaround?
Ok, I figured out a way to do it...
Simply assign the text to a variable and set startOffset like so:
var label = paper
.text(0, 0, 'Lorem Ipsum')
.attr({
'text-anchor' : 'middle',
'textpath' : labelArc
});
label.textPath.attr({ startOffset: '50%' });
There might be a more elegant way of doing it, but this is at least working.

Clippath not working if created using Javascript but working perfectly if it is from HTML tags

I am facing a issue in SVG clipping.I have one graph which need to be drawn dynamically from the javascript and the values is coming from the JSON. Now in one of the configuration I have to clip the curve if it is going out of range,otherwise I have to change the value range for both x and y axis to fit the curve in the graph window.
I did some POC before implementation of the clipping functionality and I tried in conventional HTML tags and Its working fine. bellow is the code of the POC:
<svg>
<defs>
<clippath id="Clippath1">
<rect id="Rect1" x="0" y="0" width="940.5" height="300">
</rect>
</clippath>
<marker id="Marker1" viewbox="0 0 10 10" refx="5" refy="5" markerwidth="10" markerheight="10" orient="auto">
<circle cx="5" cy="5" r="1" stroke="red" stroke-width="1" fill="black">
</circle>
</marker>
</defs>
<g>
<polyline points="0,0 140,125 160,140 180,220 220,240 300,280 400,450 500,500 900,900"
style="fill: none; stroke: red; stroke-width: 5" clip-path="url(#clip)" stroke="blue"
stroke-width="1" marker-start="url(#point)" marker-mid="url(#point)" marker- end="url(#point)" />
</g>
</svg>
It is working fine. I have one marker to show the point and have one inside the tag and I have applied the clippath to my polyline.
Now in my project I need to perform the same thing from the javascript(creating all the tags) from the javascript. and It is not working.
parentSVGGroup = _currTrendWin.getSVG();
//Create a defs tag
defs = PWC.HmiAC.svgPolyline.CreateSvgElement('defs', { 'id': 'drawableTrendArea_defs', 'appendTo': parentSVGGroup });
//creating the clippath
clipPath = PWC.HmiAC.svgPolyline.CreateSvgElement('clippath', { 'id': 'drawableTrendArea_clippath', 'appendTo': defs });
//creating the rectangle for the defining the drawable rectangle
clipRect = PWC.HmiAC.svgPolyline.CreateSvgElement('rect',
{ 'id': 'drawableTrendAreaRect',
'x': _currTrendWin.getRect().left,
'y': _currTrendWin.getRect().top,
'width': _currTrendWin.getRect().width,
'height': _currTrendWin.getRect().height,
'appendTo': clipPath
});
markerConfig =
{
'id': 'point',
'viewBox': '0 0 10 10',
'refX': 5,
'refY': 5,
'markerWidth': 10,
'markerHeight': 10,
'orient': 'auto',
'appendTo': defs
};
marker = PWC.HmiAC.svgPolyline.CreateSvgElement('marker', markerConfig);
PointStyleConfig =
{
'cx': 5,
'cy': 5,
'r': 1,
'stroke': 'red',
'stroke-width': 1,
'fill': 'black',
'appendTo': marker
};
pointStyle = PWC.HmiAC.svgPolyline.CreateSvgElement('circle', PointStyleConfig);
polyLine = {
'points': points.trim(),
'id': _name,
'fill': 'none',
'stroke': 'blue',
'stroke-width': 1,
'marker-start': 'url(#point)',
'marker-mid': 'url(#point)',
'marker-end': 'url(#point)',
'clip-path': 'url(#drawableTrendArea_clippath)',
'appendTo': parentSVGGroup
};
poly = document.getElementById(_name);
if (poly) {
poly.parentNode.removeChild(poly);
}
poly = PWC.HmiAC.svgPolyline.CreateSvgElement('polyline', polyLine);
This is the code stuff for the creation of the same logic from the javascript.But if I copy the generated html file from the browser and put the whole html tag into a new file it is working as expected.
Sorry I forgot to add that function..its bellow:
_createSvgElement = function (type, attributes) {
var svgElement = document.createElementNS(_svgNS, type),
attr, value;
if (attributes) {
for (attr in attributes) {
if (attr) {
value = attributes[attr];
if (attr === 'appendTo') {
value.appendChild(svgElement);
}
else {
svgElement.setAttributeNS(null, attr, value);
}
}
}
}
return svgElement;
};
could u ppl can help me out from the problem..
Thanks
Arijit
it is very late to the game. I am having the same issue, the problem seems to come from the fact that javascript/jquery both convert the elementname to all lower case and clippath actually needs to be clipPath.
I am currently trying to find a work around for this or if you did would you please share.
The PITA way I was able to get it to work was creating an HTML string and then using jquery to append the clipPaths to my defs section.
Not clean, not the way I would like to do it, but it got it done.

Categories

Resources