Hmmm. Now that I've figured out how to wrap SVG text dynamically using TSPANs (see Auto line-wrapping in SVG text), trying to animate it has me stumped. I'm basing it off the Zoomable Treemap example from Mike Bostock.
My text wrapping code is invoked thus:
g.append("text")
.attr("dy", ".75em")
.text(function(d) { return d.name; })
// .call(text) // Mike's line
.each(function (d,i) { // My line
wraptorect(this,this.previousSibling,6,2,2);
});
Putting the old Mike line back works fine but removes the text wrapping:
function text(text) {
text.attr("x", function(d) { return x(d.x) + 6; })
.attr("y", function(d) { return y(d.y) + 6; });
}
I'd have thought that you'd just need to animate the parent TEXT element but I'm getting the text moving in weird directions in Chrome (and even worse behaviour in IE9 where the text doesn't want to wrap yet). I suspect it's something to do with the TSPANs having x attributes but can't see the way forward beyond that...
Single line
<text dy=".75em" x="252" y="2">Other purposes which could be interesting</text>
Wrapped lines
<text dy=".75em" x="252" y="2">
<tspan x="252" dy="15">Other purposes </tspan>
<tspan x="252" dy="15">which could be </tspan>
<tspan x="252" dy="15">interesting </tspan>
</text>
The JS code is quite long so here's the fiddle link: http://jsfiddle.net/gHdR6/6/
If TSPANs are positioned absolutely (ie. they have x and / or y attributes) then you can't move then by moving the parent TEXT. You can either (a) position them relatively (using dx and dy), or (b) move the entire text block by using a transform on the TEXT or a wrapper G. I found inconsistencies in how IE and Chrome render font-widths so used (b) to good effect.
See http://jsfiddle.net/gHdR6/15/ for the updated demo. Here's the SVG structure which works:
<text transform="translate(772,439)">
<tspan x="0" dy="15">Transport and </tspan>
<tspan x="0" dy="15">communication </tspan>
</text>
Your zoom (or animation) code then needs to update the translate for these nodes instead of x and y.
Related
My question: Can svg <marker> elements inherit color from the <line> they are referenced on?
The background: I have a D3 graph that has different styled lines, and I want my lines to have arrows at the end.
So at the top of my <svg> I have const defs = svg.append('defs'); and then further along I generate my defs using a generator function:
function makeDefs(defs: Selection<SVGDefsElement, unknown, null, undefined>, color: string, status: string) {
const markerSize = 3
const start = defs.append
.append('marker')
.attr('id', `arrow-start-${color}-${status}`)
.attr('viewBox', '-5 -10 20 20')
.attr('markerWidth', markerSize)
.attr('markerHeight', markerSize)
.attr('orient', 'auto-start-reverse');
start
.append('path')
.attr(
'd',
status === 'PUBLISHED' ? customPaths.arrowLarge : customPaths.arrowSmall
)
.attr('stroke', color)
.attr('fill', color);
}
And use it like so:
makeDefs(defs, 'red', 'DRAFT');
And then I add the markers to my lines with:
// d3 code to draw the lines etc
line.attr(
'marker-start',
(d) =>
`url(
#arrow-start-${d.color}-${d.status}
)`
);
This all works great, my arrows have lines. But my current setup feels burdensome and clunky. I have about 20 colors and 3 statuses. With my current setup that would be 60 different:
makeDefs(defs, 'one-of-20-colors', 'one-of-3-statues');
My understanding of markers is that they can inherit color using the currentcolor attribute. Currently my <defs> sit up top underneath my main <svg> so any color inherited is inherited directly from that top level svg which is not what I want. The issue in my case is my <line> elements are the elements who's color I want to inherit, but according to the MDN docs <line>s cannot have <defs> as children, thus leaving me with the only option, of defining all my <defs> up front all at once.
Is there a trick or some attribute I'm missing here?
Any way to pass color to my marker when doing:
line.attr(
'marker-start',
(d) =>
`url(
#arrow-start-${d.color}-${d.status}
)`
);
?
For what is is worth, I'm currently wrapping all my <line>s in <g>. I suppose I could wrap them in <svg>s instead, and apply the fill and stroke there, and then define my <defs> per svg container? I tried this briefly and swapping the <g> for an <svg> broke a lot, but I'm not even sure if it would work, or be better for that matter.
Is there a way to create a line-break on some text in an SVG text block, without using tspan's?
My client wants to be able to add content into an Advanced Custom Field editor in Wordpress, add a < br > or < p >, and it add that line-break to the text in an SVG diagram?
I know line-breaks can be added with < tspan > but the client doesn't want to have to code that into dynamic content that is pulled into the SVG diagram via PHP.
For example, currently I have:
<text transform="matrix(1 0 0 1 355.2294 118.67)" class="st6">
<tspan x="0" y="0" class="st3 st4 st5">SOME</tspan>
<tspan x="-24.8" y="12" class="st3 st4 st5">TEXT</tspan>
</text>
Which outputs:
SOME
TEXT
But I need to make that text editable in Wordpress via a Custom field, so the client would enter the text in the WYSIWG with the line break, and it display the same in the SVG. (I don't want to have two separate fields for "social" and "Entrepreneurs" as he may change it to a single line in the future)...
So something like (but I know it wouldn't work):
Field: Some < br > Text
<text transform="matrix(1 0 0 1 355.2294 118.67)" class="st6">
<?php the_field('text'); ?>
</text>
No, there is not. SVG is a graphics format and the only way to get html formated text in it is to use a <foreignObject>. The only way to get the line breaks as you would like, is to process the input and generate those <tspan> elements.
But the code required to generate those <tspan> elements isn't that complicated.
assuming that $lines is an array of strings you can:
$tspans = [];
$lineheight = 10;
for ($i = 0; i < count($lines); $++) {
$y = $i * $lineheight;
$tspans[] = "<tspan x=\"0\" y=\"" . $y . "\">" . $line . "</tspan>";
}
$result = implode('', $tspans);
In other words, just multiply the linenumber by the line height and generate the resulting y value;
I have some code that outputs an SVG as string.
Eg
foo = """
<svg xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" height="100" width="200" style="fill: blue"/>
</svg>
"""
I would like to display it in the cell output as an image.
I've seen several libraries do this.
How is it done?
display(mimetype, x)
display has a form that takes a mime-type as the first argument.
IJulia uses this information to determine how to display that object
So the code to display your foo, is display("image/svg+xml", foo)
Which will draw a nice blue rectangle.
In the newer versions of julia the accepted answer is no longer true. See this discussion instead you must save the string to a file, then read it. Something like:
f = open("images/animage.svg", "r")
s = read(f) #Don't force Julia to read a "String"
close(f) #Don't forget to close your file!
#show typeof(s); flush(stdout) #FYI: So you can understand what type is read back
display(MIME("image/svg+xml"), s)
The above code was taken from this discussion
I want to put a rectangle around a text in SVG.
The height of the text is known to me (the font-size attribute of the text element). But the width is dependent on the actual content. Using getBBox() or getComputedTextLength() should work. But this only works after rendering.
Is there a way to specify that in an other way? For example defining the x and width attributes relative to other values? I didn't find anything like that in the SVG Spec.
Figuring where text ends presumably requires roughly the same underlying code path as the rendering itself implements - going through the width of each character based on the font and style, etc... As I am not aware the SVG standards define a method for directly getting this information without doing the actual full rendering, till such methods emerge or are reported here by others, the approach should be to render invisibly before doing the actual rendering.
You can do that in a hidden layer (z-index, opacity and stuff) or outside the visible viewport, whichever works best in experimentation. You just need to get the browser do the rendering to find out, so you render invisibly for that sake, then use getComputedTextLength()
I know this is old, but a few ideas:
If you can choose a mono-spaced font, you know your width by a simple constant multiplication with glyph count
If you are bound to proportional fonts, you can find an average glyph size, do the math as with mono-space, and leave enough padding. Alternatively you can fill the padding with text element textLength attribute. If the constant is chosen carefully, the results are not very displeasing.
EDIT: As matanster found it to be hacky
Predetermine glyph widths with getComputedTextLength() and build a lookup table. Downside is that it does not account for kerning, but if your cache size is not a problem, you can append glyph-pair widths to this lookup.
Going beyond that is to find some way to do server side rendering: Is there a way to perform server side rendering of SVG graphics using React?
It is possible using canvas with measureText():
// Get text width before rendering
const getTextWidth = (text, font) => {
const element = document.createElement('canvas');
const context = element.getContext('2d');
context.font = font;
return context.measureText(text).width;
}
// Demo
const font = '16px serif';
const text = 'My svg text';
const textWidth = getTextWidth(text, font);
document.body.innerHTML = `
<svg>
<text x="0" y="20" font="${font}">${text}</text>
<rect x="0" y="30" width="${textWidth}" height="4" fill="red" />
</svg>
`;
Adapted from https://stackoverflow.com/a/31305410/1657101
I want to add text to the centre of a path and align it horizontally, NOT align along the path.
I have the following text running along the path at the centre, but I want to display it so that it is horizontal, no matter what angle the path is heading.
<text text-anchor="middle">
<textPath xlink:href="#SomePath" startOffset="50%">Some Text</textPath>
</text>
If I understand correctly you are after each individual letter being straight (i.e. pointing North) but following the path. Something like this:
Looking at the current SVG standard this does not seem to be possible.
For horizontal text layout flows, the
reference orientation for a given
glyph is the vector that starts at the
intersection point on the path to
which the glyph is attached and which
points in the direction 90 degrees
counter-clockwise from the angle of
the curve at the intersection point.
The image above is generated from SVG but this was achieved (as you can see from the imperfections) by applying individual kerning (rotation) to each letter by applying the rotate attribute:
<text id="text2897">
<textPath xlink:href="#path2890" id="textPath3304">
<tspan
rotate="69.238731 53.737518 40.30315 24.801943 358.96658 358.96658 4.1336575 357.93317 351.73267 351.73267 351.73267 348.63242 343.46533 343.46533 346.56558 347.599 347.599 347.599 347.599 347.599 347.599 346.56558 345.53217 344.49875 343.46533"
id="tspan2899">
Some sample text for path
</tspan>
</textPath>
</text>
You can calculate the necessary adjustments in rotation in script quite easily:
var tp = document.getElementById("textpath");
var rotate = "";
for(var i = 0; i < tp.getNumberOfChars(); i++)
{
rotate += -tp.getRotationOfChar(i) + " ";
}
tp.setAttribute("rotate", rotate);