Svelte / D3 Function to get Point on a Path - svg

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

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.

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

How to parse SVG element's viewBox x, y, width and height values?

Suppose I have an SVG element :
<svg id="myMap" viewBox="0 0 200 200"></svg>
How would I get get a specific value
of myMap's viewBox? For a simplified example : how to get the "x" value of the viewBox attribute of myMap? (for the above example, the x value is the first zero (0)).
Below is some variation of syntax I've tried :
<script>
var myMap = Snap("#myMap");
alert(myMap.attr("viewBox"));//dislays [object Object]
alert(myMap.attr("viewBox.vbx"));//also dislays [object Object]
alert(myMap.attr("viewBox.x"));//also dislays [object Object]
</script>
All the above examples display [object Object] on the alert box.
I need the proper float value of x, y, width and height of the viewport to implement zoom in and out functions in a map.
You could always just read it straight out of the DOM
alert(document.getElementById("myMap").viewBox.baseVal.width);
<svg id="myMap" viewBox="0 0 200 200"></svg>
The attr() method returns an object instead of a scalar, while alert() needs a scalar. If you use console.log() instead of alert() you can see the contents of the objects in your JavaScript console.
To get x, y, width and height of your svg use
var myMap = Snap("#myMap");
var attrs = myMap.attr("viewBox");
console.log(attr.x);
console.log(attr.y);
console.log(attr.width);
console.log(attr.height);
Thank you, #Robert Longson
alert(document.getElementById("myMap").viewBox.baseVal.width);
<svg id="myMap" viewBox="0 0 200 200"></svg>
alert(document.getElementById("myMap").viewBox.baseVal.width);
<svg id="myMap" viewBox="0 0 200 200"></svg>

Raphael and preserveAspectRatio

With RaphaelJS, this command inserts an image:-
var myImg = paper.image('image.svg', 100, 100, 150,150);
and the SVG output is:-
<image x="100" y="100" width="150" height="150" preserveAspectRatio="none" href="image.svg"/>
Question: How do I directly access preserveAspectRatio attribute and change it to xMidYMid meet - if you examine myImg.attr(), it doesnt show this attribute.
The roundabout way is navigate the SVG DOM tree, and execute svgImg.setAttributeNS(null,"preserveAspectRatio" , "xMidYMid meet" );
Note: Only some images require none while the rest needs the xMidYMid meet tag. Hence I can't set this attribute on parent <svg>
Note2: Chrome doesn't support preserveAspectRatio with SVG images. Use FF or IE to test.
At the source code level, preserveAspectRatio is hardcoded to none
Answer The quickest way to change this:-;
myImg[0].preserveAspectRatio.baseVal.align = 6 (1 = off, 6 = xMidYMid)
myImg[0].preserveAspectRatio.baseVal.meetOrSlice = 1 (1 = meet, 2 = slice)
Update:- jQuery style:-
jQuery(myImg.node).prop('preserveAspectRatio').baseVal.align = 6 ;
jQuery(myImg.node).prop('preserveAspectRatio').baseVal.meetOrSlice = 1 ;
Raphael's docs for Element.node "Gives you a reference to the DOM object, so you can assign event handlers or just mess around. Note: Don’t mess with it."
You can call these parameters on the Raphael canvas as a whole.
First create SVG:
var paper = Raphael('content',xSize,ySize);
Place image in it:
paper.image('image.svg', 100, 100, 150,150);
Then change attributes of svg:
paper.canvas.setAttribute('preserveAspectRatio', 'xMidYMid meet');

Calculating viewBox parameters based on path elements in SVG

I get an XML or JSON with paths only, and I need to recreate the SVG image.
I create an empty
<svg xmlns='http://www.w3.org/2000/svg' version='1.1'></svg>,
I add a <g transform="scale(1 -1)" fill='#aaa' stroke='black' stroke-width='5' ></g> in it, and then in this element I add all of the paths in it (e.g. <path d= ... />).
In the end I get a SVG image, but because I haven't set the viewBox attribute in the SVG element the image isn't properly displayed - when I open it in browser, a part of it is displayed full size.
Can the viewBox be calculated from the values from the paths?
Thank you!
Similar to Martin Spa's answer, but a better way to do get the max viewport area is using the getBBox function:
var clientrect = path.getBBox();
var viewBox = clientrect.x+' '+clientrect.y+' '+clientrect.width+' '+clientrect.height;
You can then set the viewbox to these co-ordinates.
n.b. i think you can change the viewbox of an svg after it's rendered so you may have to re-render the svg.
OK so I solved it the following way:
removed all letters from the paths string and made an array out of it with
var values = pathValue.split('L').join(' ').split('M').join(' ').split('z').join('').split(' ');
found max and min from those values:
var max = Math.max.apply( Math, values );
var min = Math.min.apply( Math, values );
set the viewBox:
viewBox = max min max max
This worked in my case excellent. Hope that it will be helpful to someone else too.

Resources