The best way to import complex SVG graphics into D3 charts - javascript

Assume, I have some relatively complex SVG graphics as a set of files, for example, looking like these icons:
I want to use them in my D3-powered charts. So, I can go with <defs> and <use> tags and inject them as symbols. But I want to make them less solid monolithic assets, but more like fully active and editable graphic elements. I know I can manually import all paths from icons SVG code like this:
svg.append('path').attr('d', 'M7.5,5.809c-0.869,0-1.576-0.742-1.576-1.654c0-0.912,0.707-1.653,1.576-1.653 c0.87,0,1.577,0.742,1.577,1.653C9.077,5.067,8.369,5.809,7.5,5.809z')
But this doesn't seem to be a quick to run scenario as I need to build some script to convert icons to code like that or do it manually, but I want to have some simple workflow similar to just editing an icon Illustrator, saving, importing.
As a result, I want to have full control on the all the shapes and paths inside each icon.
How you think it can be done in the most straightforward and D3 way?

You can import your SVGs using d3.xml and then insert the xml directly into your document. In the example code below I create a g in a svg element and then insert an image into that:
<script>
var height = 500;
var width = 700;
var vis = d3.select("#vis").append("svg")
.attr("width", width).attr("height", height)
var g = vis.append("g").attr("id", "image");
d3.xml("drawing.svg", "image/svg+xml", function(xml) {
g.each(function() {
this.appendChild(xml.documentElement);
});
});
</script>
I guess you could also select separate icons from one SVG document containing multiple icons and insert each separately. I expect styling will be lost when the SVG document stores it's styling in a stylesheet and not directly in the elements.

Related

snap svg translate fragment

This is probably the best way, but I want to embed an external SVG and move it into position. I'm using the following code to load the external SVG image, but I can't seem to apply any transformation to the fragment.
var svg = new Snap('#svg');
Snap.load('logo.svg', function (fragment) {
svg.append(fragment);
});
It's not clear what you are really trying to transform, as you don't have any transform method in your snippet of code.
The normal route, is to append the fragment to a group element, and put the transform on that (as the svg element itself doesn't support transforms). So something like...
var svg = new Snap('#svg');
var g = svg.g();
Snap.load('logo.svg', function (fragment) {
g.append(fragment);
g.transform('t100,100');
});

Is there a way to use any element as a canvas?

Examples I see using <canvas> always have an actual canvas element in the HTML. Say I for example already have a <header> element that I would like to draw on, is there a way to use it as a canvas directly? Or do I need to add that canvas element and make sure it's filling the header?
I was skeptical at first, but there is one way to achieve almost that, by using css and getCSSCanvasContext() with the webkit. This allows you to display a canvas as a background so you can control it like a canvas.
Ex:
HTML
<header></header>
CSS
header { background: -webkit-canvas(fooBar); }
JavaScript (when document ready / onload)
function fillCanvas(w, h) {
var ctx = document.getCSSCanvasContext('2d', 'fooBar', w, h);
// draw into canvas
}
A complete example and more specs are given here.
Hope this is convenient.

having problems selecting svg child elements using d3

I have an svg that I'm trying to access and modify using d3.js. The svg file name is us-map.svg. I've included a reference to the svg in my html like this:
<object id="imgMap" data="us-map.svg" type="image/svg+xml">
</object>
I can select imgMap in my chrome watch panel like this:
var imgMap = d3.select('#imgMap')
However, I'm not able to select child elements. For example, my imgMap svg has several child <g> elements but the child elements are not returned with this function:
d3.select('#imgMap').selectAll('g')
Am I missing something here? I was thinking that d3 could be used to traverse and manipulate an existing svg?
I was thinking that d3 could be used to traverse and manipulate an existing SVG
This is pretty much what d3 does best. But when you write:
d3.select('#imgMap')
You are not selecting the SVG (unless you have an SVG with id = "imgMap", which is not your case). You're using an <object>. Thus, you have to write:
var mySVG = d3.select(document.getElementById("imgMap").contentDocument);
And then select your groups using mySVG.
var myGroups = mySVG.selectAll("g");
Have in mind that this selection only works after the object has been loaded.
Source: https://benfrain.com/selecting-svg-inside-tags-with-javascript/
EDIT:
As requested by the OP, this is a basic working demo: https://plnkr.co/edit/RJOznJROiqTpo5dm9M7L?p=preview
In this plunkr, "mysvg.svg" is an external file (in your code, you'll have to provide the correct path). The code finds the SVG:
var mySVG = d3.select(document.getElementById("imgMap").contentDocument);
And then selects the blue circle inside the SVG, moving it to the right:
var myCircle = mySVG.select("#blueCircle");
myCircle.transition().duration(2000).attr("cx", 180);
Pay attention to this: I set a setTimeout of 1000ms, just to make sure that the object is loaded before the code runs.

Directed graph - node level CSS styles

I found this excellent example and sample code for drawing directed graphs - http://bl.ocks.org/cjrd/6863459
However, the graph-creator.css file defines a global style for all nodes. What if I want to assign different styles to certain "special" nodes from all other nodes (I want them to be different shapes and also differently colored and if possible also a different transparency). How would I modify this code to add these node specific effects?
You can choose to append different shapes here based on different scenario:
// append new elements in <g> element in scenario X
// you can pass different parameters for specific styling here
// for example, user select "red" color rect in setting filters
newGs.append("circle")
.attr("r", String(consts.nodeRadius));
// alternatively append rect
newGs.append("rect")
.attr({
"x": // mouse event info as circles in the demo
"y": // mouse event info as circles in the demo
"width": String(consts.rectWidth),
"height": String(consts.rectHeight)
})
.attr("class", "myRectStyle") // set styles in css
.attr("fill", "red")
.attr("rx",5)
.attr("ry",5)
In order to achieve the goal, first you must understand the CSS concepts. First of all you can place a CSS for a HTML/SVG markup in 3 places.
External CSS file,
Same HTML File with a <style> block
Inline CSS inside the tag eg. <circle> <li> <line> etc.
In your case, if you want to give different styles for different nodes, then you can give them specific css class/id selectors and have styles in any of the 3 methods I have mentioned previously.
Let's say you want to make certain circles transparent, then just give the circles a class "trCircles" and specify the CSS in a external CSS file and link the file with <link>
d3.select('g')
.append('circle')
.attr('class', 'trCircle')
...
in the CSS file you can have.
.trCircle{
fill : transparent;
}
Orr if you want to apply them in the d3 level. You can specify it when you create the circle.
d3.select('g')
.append('circle')
.attr('cx' , '100')
.....
.style('fill','transparent')
;
Hope you get the idea.

Get the real size of a SVG/G element

Is there any accurate way to get the real size of a svg element that includes stroke, filters or other elements contributing to the element's real size from within Javascript?
I have tried pretty much everything coming to my mind and now I feel I'm coming to a dead end :-(
Updated question to add more context (Javascript)
You can't get the values directly. However, you can get the dimensions of the bounding rectangle:
var el = document.getElementById("yourElement"); // or other selector like querySelector()
var rect = el.getBoundingClientRect(); // get the bounding rectangle
console.log( rect.width );
console.log( rect.height);
It is supported at least in the actual versions of all major browser.
Check fiddle
Both raphael js http://dmitrybaranovskiy.github.io/raphael/ and d3 js http://d3js.org/ have various methods to find the size of an svg object or sets of svg object. It depends on if it's a circle, square, path, etc... as to which method to use.
I suspect you are using complex shapes, so in that case bounding box would be your best bet http://raphaeljs.com/reference.html#Element.getBBox
(Edit: updated reference site.) http://dmitrybaranovskiy.github.io/raphael/reference.html#Element.getBBox
Here is an example using D3.js:
Starting with a div:
<div style="border:1px solid lightgray;"></div>
The javascript code looks like this:
var myDiv = d3.select('div');
var mySvg = myDiv.append('svg');
var myPath = mySvg.append('path');
myPath.attr({
'fill': '#F7931E',
'd': 'M37,17v15H14V17H37z M50,0H0v50h50V0z'
});
// Get height and width.
console.log(myPath.node().getBBox());
If it is an SVG used as a CSS background image and you're using React you can use background-image-size-hook.
import { useBackgroundImageSize } from 'background-image-size-hook'
const App = () => {
const [ref, svg] = useBackgroundImageSize()
console.log(svg) // { width, height, src }
return <SVGBackgroundImageComponent ref={ref} />
}
You didn't specify any programming language. So I can suggest to use Inkscape.
In the file menu you find document's properties and in the first page there's "resize page to content" command. In this way you remove all the white space around your draw and you see the real size. After width and height values apprear inside the header of svg.
I know that Inkscape supports scripting and command line operations but I don't know if it's possible to do the trimming operatation in this way. But if it's possible you can do that from every programming language.

Categories