2

I’m trying to develop a custom shape in D3, but I’m a little green Javascript wise and am not sure what the D3-Shapes library is doing under the hood.

In particular, I’m not sure what the default function in symbol.js is doing or how it is used. From the little bit of JS that I do know it appears to be describing a prototype or possibly a class. Supposedly one may register additional shapes using the symbol.type class/function but I’m not sure how I should be calling this e.g. var symbol = symbol("SYMBOL").type(SYMBOL) or possibly var SYMBOL = symbol().type(SYMBOL) or some other combination. Issue 23 discusses how the shapes library has changed from the original implementation which I think negates this SO: answer. I also scrounged the D3-Symbol-Extra for clues but no dice.

So far I have the following shape definition for a superellipse that matches up to the symbol definitions in the D3 Shapes library, e.g. circle, diamond, etc.

var superellipse = {
 draw : function (context, size) {
  var w = size;
  var h = size;
  var n = 2/4;
  context.moveTo(w/2*Math.sign(Math.cos( 0/9*2*Math.PI))*Math.pow(Math.abs(Math.cos( 0/9*2*Math.PI)), n), h/2*Math.sign(Math.sin( 0/9*2*Math.PI))*Math.pow(Math.abs(Math.sin( 0/9*2*Math.PI)), n));
  context.lineTo(w/2*Math.sign(Math.cos( 1/9*2*Math.PI))*Math.pow(Math.abs(Math.cos( 1/9*2*Math.PI)), n), h/2*Math.sign(Math.sin( 1/9*2*Math.PI))*Math.pow(Math.abs(Math.sin( 1/9*2*Math.PI)), n));
  context.lineTo(w/2*Math.sign(Math.cos( 2/9*2*Math.PI))*Math.pow(Math.abs(Math.cos( 2/9*2*Math.PI)), n), h/2*Math.sign(Math.sin( 2/9*2*Math.PI))*Math.pow(Math.abs(Math.sin( 2/9*2*Math.PI)), n));
  context.lineTo(w/2*Math.sign(Math.cos( 3/9*2*Math.PI))*Math.pow(Math.abs(Math.cos( 3/9*2*Math.PI)), n), h/2*Math.sign(Math.sin( 3/9*2*Math.PI))*Math.pow(Math.abs(Math.sin( 3/9*2*Math.PI)), n));
  context.lineTo(w/2*Math.sign(Math.cos( 4/9*2*Math.PI))*Math.pow(Math.abs(Math.cos( 4/9*2*Math.PI)), n), h/2*Math.sign(Math.sin( 4/9*2*Math.PI))*Math.pow(Math.abs(Math.sin( 4/9*2*Math.PI)), n));
  context.lineTo(w/2*Math.sign(Math.cos( 5/9*2*Math.PI))*Math.pow(Math.abs(Math.cos( 5/9*2*Math.PI)), n), h/2*Math.sign(Math.sin( 5/9*2*Math.PI))*Math.pow(Math.abs(Math.sin( 5/9*2*Math.PI)), n));
  context.lineTo(w/2*Math.sign(Math.cos( 6/9*2*Math.PI))*Math.pow(Math.abs(Math.cos( 6/9*2*Math.PI)), n), h/2*Math.sign(Math.sin( 6/9*2*Math.PI))*Math.pow(Math.abs(Math.sin( 6/9*2*Math.PI)), n));
  context.lineTo(w/2*Math.sign(Math.cos( 7/9*2*Math.PI))*Math.pow(Math.abs(Math.cos( 7/9*2*Math.PI)), n), h/2*Math.sign(Math.sin( 7/9*2*Math.PI))*Math.pow(Math.abs(Math.sin( 7/9*2*Math.PI)), n));
  context.lineTo(w/2*Math.sign(Math.cos( 8/9*2*Math.PI))*Math.pow(Math.abs(Math.cos( 8/9*2*Math.PI)), n), h/2*Math.sign(Math.sin( 8/9*2*Math.PI))*Math.pow(Math.abs(Math.sin( 8/9*2*Math.PI)), n));
  context.lineTo(w/2*Math.sign(Math.cos( 9/9*2*Math.PI))*Math.pow(Math.abs(Math.cos( 9/9*2*Math.PI)), n), h/2*Math.sign(Math.sin( 9/9*2*Math.PI))*Math.pow(Math.abs(Math.sin( 9/9*2*Math.PI)), n));
  context.closePath();
  }
};

I can get the shape to be drawn according to the default size, but I can’t tweak the dimensions as one might for a circle by setting the r attribute; in this case by setting the n, h and w attributes.

var svg = d3.select("svg")
    .attr("width", 500)
    .attr("height", 500);

function visualize(json) {
    var nodes = svg.selectAll("g nodetext")
        .data(json.nodes);

    var entryPoint = nodes.enter()
        .append("g")
        .attr("class", "node")
        .attr("transform", function(d) {
            return "translate(" + d.x + ",80)"
        });

    // var ellipse = entryPoint.append("circle")
    //               .attr("r", 50);

    var nodeShape = entryPoint.append("path")
        .attr("d", d3.symbol().type(superellipse))
        .style("stroke", "gray")
        .attr("fill", "pink")
        // .n(function(d) {return d.n;})
        .attr("w", function(d) {
            return d.w;
        })
        .attr("h", function(d) {
            return d.h;
        });

    var nodeText = entryPoint.append("text")
        .text(function(d) {
            return d.text;
        })
        .attr({
            "text-anchor": "middle"
        });
};

To be explicit, I’m invoking this as follows :

var superellipse = {
  draw: function(context, size) {
    var w = size;
    var h = size;
    var n = 2 / 4;
    context.moveTo(w / 2 * Math.sign(Math.cos(0 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.cos(0 / 9 * 2 * Math.PI)), n), h / 2 * Math.sign(Math.sin(0 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.sin(0 / 9 * 2 * Math.PI)), n));
    context.lineTo(w / 2 * Math.sign(Math.cos(1 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.cos(1 / 9 * 2 * Math.PI)), n), h / 2 * Math.sign(Math.sin(1 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.sin(1 / 9 * 2 * Math.PI)), n));
    context.lineTo(w / 2 * Math.sign(Math.cos(2 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.cos(2 / 9 * 2 * Math.PI)), n), h / 2 * Math.sign(Math.sin(2 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.sin(2 / 9 * 2 * Math.PI)), n));
    context.lineTo(w / 2 * Math.sign(Math.cos(3 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.cos(3 / 9 * 2 * Math.PI)), n), h / 2 * Math.sign(Math.sin(3 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.sin(3 / 9 * 2 * Math.PI)), n));
    context.lineTo(w / 2 * Math.sign(Math.cos(4 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.cos(4 / 9 * 2 * Math.PI)), n), h / 2 * Math.sign(Math.sin(4 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.sin(4 / 9 * 2 * Math.PI)), n));
    context.lineTo(w / 2 * Math.sign(Math.cos(5 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.cos(5 / 9 * 2 * Math.PI)), n), h / 2 * Math.sign(Math.sin(5 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.sin(5 / 9 * 2 * Math.PI)), n));
    context.lineTo(w / 2 * Math.sign(Math.cos(6 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.cos(6 / 9 * 2 * Math.PI)), n), h / 2 * Math.sign(Math.sin(6 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.sin(6 / 9 * 2 * Math.PI)), n));
    context.lineTo(w / 2 * Math.sign(Math.cos(7 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.cos(7 / 9 * 2 * Math.PI)), n), h / 2 * Math.sign(Math.sin(7 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.sin(7 / 9 * 2 * Math.PI)), n));
    context.lineTo(w / 2 * Math.sign(Math.cos(8 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.cos(8 / 9 * 2 * Math.PI)), n), h / 2 * Math.sign(Math.sin(8 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.sin(8 / 9 * 2 * Math.PI)), n));
    context.lineTo(w / 2 * Math.sign(Math.cos(9 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.cos(9 / 9 * 2 * Math.PI)), n), h / 2 * Math.sign(Math.sin(9 / 9 * 2 * Math.PI)) * Math.pow(Math.abs(Math.sin(9 / 9 * 2 * Math.PI)), n));
    context.closePath();
  }
};

var svg = d3.select("svg")
  .attr("width", 500)
  .attr("height", 500);

function visualize(json) {
  var nodes = svg.selectAll("g nodetext")
    .data(json.nodes);

  var entryPoint = nodes.enter()
    .append("g")
    .attr("class", "node")
    .attr("transform", function(d) {
      return "translate(" + d.x + ",80)"
    });

  // var ellipse = entryPoint.append("circle")
  //               .attr("r", 50);

  var nodeShape = entryPoint.append("path")
    .attr("d", d3.symbol().type(superellipse))
    .style("stroke", "gray")
    .attr("fill", "pink")
    // .n(function(d) {return d.n;})
    .attr("w", function(d) {
      return d.w;
    })
    .attr("h", function(d) {
      return d.h;
    });

  var nodeText = entryPoint.append("text")
    .text(function(d) {
      return d.text;
    })
    .attr({
      "text-anchor": "middle"
    });
}

var json = {
  "nodes": [{
      "text": "test A",
      "x": 100,
      "y": 100,
      "w": 200,
      "n": 0.5,
      "h": 200,
      "color": "red"
    },
    {
      "text": "test B",
      "x": 200,
      "y": 200,
      "w": 200,
      "n": 3,
      "h": 100,
      "color": "red"
    },
  ]
};
visualize(json);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>