Documentation Index
Fetch the complete documentation index at: https://mintlify.com/spatialillusions/milsymbol/llms.txt
Use this file to discover all available pages before exploring further.
D3 operates directly on the SVG DOM, making it a natural fit for milsymbol’s asDOM() method which returns a live SVGElement. Unlike map integrations that need geographic anchor correction, D3 visualizations typically position symbols by translating a parent <g> element — so you negate getOctagonAnchor() on the group’s transform to make the octagon center land exactly at the node’s layout position. milsymbol also works with D3’s image elements via toDataURL() when you need raster output inside an SVG.
Two approaches
symbol.asDOM() returns an SVGElement that can be appended to a D3-created element via this.appendChild() inside an .each() callback. This is the most natural D3 pattern — the SVG lives entirely in the DOM, reacts to CSS, and is inspectable.// Append a milsymbol SVG element into a D3 selection
svg.append("g")
.attr("class", "symbol")
.attr("transform", function (d) {
d.data.symbol = new ms.Symbol(d.data.sidc, { size: 15 });
return (
"translate(" +
-d.data.symbol.getOctagonAnchor().x +
"," +
-d.data.symbol.getOctagonAnchor().y +
")"
);
})
.each(function (d) {
// Append the live SVG DOM element
this.appendChild(d.data.symbol.asDOM());
});
The each callback receives the DOM element as this. Calling this.appendChild(symbol.asDOM()) inserts the milsymbol SVG as a child of the <g> element that D3 just created.When you need a symbol inside an SVG <image> element — for example when working with a canvas-backed D3 renderer — use toDataURL():svg.selectAll("image.symbol")
.data(units)
.enter()
.append("image")
.attr("class", "symbol")
.attr("href", function (d) {
return new ms.Symbol(d.sidc, { size: 20 }).toDataURL();
})
.attr("x", function (d) {
var sym = new ms.Symbol(d.sidc, { size: 20 });
return d.x - sym.getAnchor().x;
})
.attr("y", function (d) {
var sym = new ms.Symbol(d.sidc, { size: 20 });
return d.y - sym.getAnchor().y;
})
.attr("width", function (d) { return new ms.Symbol(d.sidc, { size: 20 }).getSize().width; })
.attr("height", function (d) { return new ms.Symbol(d.sidc, { size: 20 }).getSize().height; });
Pre-compute and attach the symbol object to each datum to avoid creating it multiple times per element. For example: units.forEach(function(u) { u.symbol = new ms.Symbol(u.sidc, { size: 20 }); }); — then reference d.symbol in each accessor.
Full working example: collapsible ORBAT tree
The following example is adapted from the milsymbol D3 ORBAT list example included in the repository. It builds a hierarchy from GeoJSON data, lays it out with d3.tree, and renders each node’s milsymbol using asDOM(). The symbol is created in the transform attribute accessor, stored on the datum for reuse, then appended in the each callback.
var duration = 300;
var margin = { top: 15, right: 20, bottom: 10, left: 30 };
var svg = d3
.select("#orbat")
.append("svg")
.attr("width", 500)
.attr("height", 500)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var d3cluster = d3.tree().nodeSize([0, 20]);
var d3hirarcy = d3.hierarchy(structure, function (d) { return d.suborgs; });
d3hirarcy.x0 = 0;
d3hirarcy.y0 = 0;
update(svg, d3cluster, d3hirarcy, d3hirarcy);
function update(svg, d3cluster, d3hirarcy, source) {
var barHeight = 25;
var nodes = d3cluster(d3hirarcy);
// Lay out nodes top-to-bottom in list order
var i = 0;
nodes.eachBefore(function (d) { d.x = i++ * barHeight; });
// Enter new nodes
var node = svg
.selectAll("g.node")
.data(d3hirarcy.descendants(), function (d) {
return d.id || (d.id = ++i);
});
var newNode = node
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.style("opacity", 1e-6);
newNode
.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.y + "," + d.x + ")";
})
.style("opacity", 1);
// Append the milsymbol icon for each new node
newNode
.append("g")
.attr("class", "symbol")
.attr("transform", function (d) {
if (d.data.sidc)
d.data.symbol = new ms.Symbol(d.data.sidc, { size: 15 });
return (
"translate(" +
-d.data.symbol.getOctagonAnchor().x +
"," +
-d.data.symbol.getOctagonAnchor().y +
")"
);
})
.each(function (d) {
this.appendChild(d.data.symbol.asDOM());
});
// Append the unit label
newNode
.append("text")
.attr("class", "node-text")
.attr("dy", 3)
.attr("x", 20)
.style("text-anchor", "start")
.text(function (d) { return d.data.name; });
// Remove exiting nodes
node
.exit()
.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + source.y + "," + source.x + ")";
})
.style("opacity", 1e-6)
.remove();
// Stash current positions for the next transition
nodes.each(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
Anchor translation for D3
Military symbols have variable dimensions — a brigade symbol with text fields is taller than a team symbol without them. D3 tree layouts assign a single (x, y) point per node, so you must translate the symbol’s <g> element so that the meaningful center of the symbol (the octagon anchor) lands on that point.
| Method | When to use | Transform formula |
|---|
getOctagonAnchor() | ORBAT diagrams, node-link trees | translate(-anchor.x, -anchor.y) |
getAnchor() | Map overlays, geo-referenced SVG | translate(-anchor.x, -anchor.y) |
// Using getOctagonAnchor — centers the symbol frame on the layout point
var symbol = new ms.Symbol(sidc, { size: 15 });
var anchor = symbol.getOctagonAnchor(); // { x, y }
// Store on datum then append in each()
d3.select(parentG).append("g")
.attr("transform", "translate(" + (-anchor.x) + "," + (-anchor.y) + ")")
.each(function () { this.appendChild(symbol.asDOM()); });
asDOM() returns a fresh SVGElement each time it is called. If you need to update a symbol in place — for example when selection state changes the fill color — it is simpler to remove the old child and append a new one than to modify the existing SVG node’s attributes directly.
The D3 ORBAT example in the milsymbol repository uses D3 v7 and d3.hierarchy / d3.tree. The pattern shown above is compatible with D3 v5 and later. For D3 v4, replace d3.tree() with d3.cluster() if you want a dendrogram layout.