Search code examples
d3.jssvgreusability

What is the d3 structure needed to make a reusable svg entity


I am using API descriptions, converted from DITA (XML) to JSON to create railroad diagrams in d3. I want to define various elements only once (like the sidingL and sidingR in my code, below). I have a siding style. In my global variable declarations I have sidingL (and a variant for sidingR):

var sidingL = d3.svg.path()
    .attr("d", "M50,0 v80 a10,10 0 0  10,10 h20")
    .attr("class", "siding");

Then, after adding the SVG canvas (svgContainer), I add:

    var L = svgContainer.append(sidingL);
    var R = svgContainer.append(sidingR);

which I hoped would instantiate an instance of each of sidingL and sidingR

I've stripped down most of my code for this experiment to just achieve the visible rendering on the SVG canvas of one of each of the sidings. Nothing is rendering and can't see anything helpful in the Firefox Web Developer Toolkit, Console or Debug views!

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Railroad siding</title>
        <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
        <link 
href='http://fonts.googleapis.com/css?family=Lato&subset=latin,latin-ext'
 rel='stylesheet' type='text/css'>
    <style>
  .siding {
    fill: none;
    stroke: black;
    stroke-width: 6px;
    stroke-linecap: round;
    stroke-linejoin: round;

  }
  </style>
</head>
<body>
<script type="text/javascript">

  //set up constants
  var canvasWidth= 4000;
  var canvasHeight=800

  var sidingL = d3.svg.path()
    .attr("d", "M50,0 v80 a10,10 0 0  10,10 h20")
    .attr("class", "siding");

  var sidingR = d3.svg.path()
    .attr("d", "M100,0 v80 a10,10 0 1 10,10 h-20")
    .attr("class", "siding");

  // Get the data
  var data;
  d3.json("APIsyntax.json", function(error, json) {
  //if (error) return console.warn(error);
   // Canvas
  var svgContainer = d3.select("body")
    .append("svg")
    .attr("width", canvasWidth)
    .attr("height", canvasHeight);

  // tryout for option siding path
  var L = svgContainer.append(sidingL);
  var R = svgContainer.append(sidingR);


  });      
    </script>
</body>
</html> 

Solution

  • There are several ways to do it (and, because of that, this question is "too broad"). But, following the way you did in your code, this is the problem:

    var L = svgContainer.append(sidingL);
    

    In which you are trying to append an d3.svg.path(), and this will not work. What you can do is using a variable for creating the elements and then appending them as a returned value of a function:

    var L = svgContainer.append(function(){ return sidingL});
    

    Here is a small snippet:

    var canvasWidth= 200;
    var canvasHeight=200;
    
    var sidingL = document.createElementNS("http://www.w3.org/2000/svg", "path");
    sidingL.setAttribute("d", "M50,0 v80 a10,10 0 0  10,10 h20");
    sidingL.setAttribute("class", "siding");
    
    var sidingR = document.createElementNS("http://www.w3.org/2000/svg", "path");
    sidingR.setAttribute("d", "M100,0 v80 a10,10 0 1 10,10 h-20");
    sidingR.setAttribute("class", "siding");
    
    var svgContainer = d3.select("body")
        .append("svg")
        .attr("width", canvasWidth)
        .attr("height", canvasHeight);
    
    var L = svgContainer.append(function(){ return sidingL});
    var R = svgContainer.append(function(){ return sidingR});
    .siding {
        fill: none;
        stroke: black;
        stroke-width: 6px;
        stroke-linecap: round;
        stroke-linejoin: round;
      }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

    The D3 way

    I don't know if that's what you meant by "reusable", but the D3 way of doing what the code above does is way shorter:

    var paths = ["M50,0 v80 a10,10 0 0  10,10 h20",
    	"M100,0 v80 a10,10 0 1 10,10 h-20"];
      
    var svg = d3.select("body")
    	.append("svg")
      .attr("width", 300)
      .attr("height", 300);
      
    var myPaths = svg.selectAll("path")
    	.data(paths)
      .enter()
      .append("path")
      .attr("class", "siding");
      
    myPaths.attr("d", function(d){ return d});
    .siding {
        fill: none;
        stroke: black;
        stroke-width: 6px;
        stroke-linecap: round;
        stroke-linejoin: round;
      };
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

    We just store the paths in a variable (var paths in this case) and use them as the data to the paths.