SVG: add a line-break to text without tspan - svg

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;

Related

Best way to dynamically style svg marker elements

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.

Svelte / D3 Function to get Point on a Path

I'd like to create a function that gets the coordinates of the nth point along an SVG path, while using Svelte and D3.
Note, the blue dot is not part of the code and there just for illustration.
I've tried this...
// the path generator
const pathLine = line()
.x(d => xScale(d.age))
.y(d => yScale(d.temp))
.curve(curveBasis);
function pointFromPath(position){
pos = pathLine(data).getPointAtLength(position);
return pos
}
$: console.log(pointFromPath(2))
...
<svg viewBox="0 0 100 100">
{#if (show)}
<path transition:draw={{duration: 2000}}
d={pathLine(data)} />
{/if}
</svg>
But it doesn't work and I'm not sure what to try next. My redproducable code is here
https://svelte.dev/repl/189b1ff0485f4697b3061dd29daf1d3a?version=3.32.3
The problem is that pathLine(data) is returning a string representing the "d" and not the DOM-element itself.
See a working example here:
https://svelte.dev/repl/76d1d7d0a38b4f6c9f02f9ffb0200ea5?version=3.32.3

Showing an SVG that I have as a string

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

Animating wrapped text in d3

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.

Using SVG, is it possible to add text to the centre of a path and align it horizontally?

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);

Resources