Skip to main content

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.

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.
MethodWhen to useTransform formula
getOctagonAnchor()ORBAT diagrams, node-link treestranslate(-anchor.x, -anchor.y)
getAnchor()Map overlays, geo-referenced SVGtranslate(-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.

Build docs developers (and LLMs) love