定义自定义D3符号

时间:2018-08-23 09:59:33

标签: javascript d3.js svg html5-canvas

我正在尝试在D3中开发自定义形状,但是我有点绿色的Javascript智慧,并且不确定D3-Shapes库在幕后的作用。

尤其是,我不确定symbol.js中的default函数在做什么或如何使用。从一点点JS中,我确实知道它似乎是在描述原型或可能的类。可能有人可以使用symbol.type类/函数来注册其他形状,但是我不确定应该如何称呼它,例如var symbol = symbol("SYMBOL").type(SYMBOL)或可能的var SYMBOL = symbol().type(SYMBOL)或其他组合。 Issue 23讨论了形状库与原始实现之间的变化,我认为该实现否定了SO: answer。我还搜寻了D3-Symbol-Extra的线索,但没有骰子。

到目前为止,对于超椭圆,我具有以下形状定义,该形状与D3 Shapes库中的符号定义匹配,例如circlediamond

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();
  }
};

我可以根据默认尺寸绘制形状,但是我无法通过设置r属性来调整尺寸。在这种情况下,可以通过设置nhw属性来实现。

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 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>

1 个答案:

答案 0 :(得分:2)

您声称自己...

  

无法通过设置r属性来调整尺寸。在这种情况下,可以通过设置n,h和w属性。

好吧,在您的情况下,您可以使用symbol.size()传递一个属性。例如,100

.attr("d", d3.symbol().type(superellipse).size(100))

但是,问题是symbol.size()仅传递一个值:您不能尝试传递多个参数,数组,对象或类似的东西。因此,在上面的示例中,whn将是100

如果您检查symbol.size()中的source code,则会看到:

symbol.size = function(_) {
    return arguments.length ? 
        (size = typeof _ === "function" ? _ : constant(+_), symbol) : size;
};

一元加号向我们表明,如果您尝试传递数组或对象,则会得到NaN,并且如果传递多个值,它们将被忽略。

那么,我们该如何解决呢?

正确的任务工具

最后,在我看来,您的问题仅仅是因为您为此任务使用了错误的通行费。您不需要符号生成器,可以使用通用函数来生成d元素的<path>属性。

例如:

function superellipse (w, h, n){
    return "M" + (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)))
        + " L" + (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)))
        + " L" + (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)))
        + " L" + (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)))
        + " L" + (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)))
        + " L" + (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)))
        + " L" + (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)))
        + " L" + (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)))
        + " L" + (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)))
        + " L" + (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)));
};

通过这种方式,您可以获得在基准对象中定义的whn属性,并将它们毫无问题地传递给函数。

以下是包含您的数据的演示:

function superellipse (w, h, n){
	return "M" + (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)))
		+ " L" + (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)))
		+ " L" + (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)))
		+ " L" + (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)))
		+ " L" + (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)))
		+ " L" + (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)))
		+ " L" + (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)))
		+ " L" + (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)))
		+ " L" + (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)))
		+ " L" + (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)));
};

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 + "," + d.y + ")"
    });

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

  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": 300,
      "y": 100,
      "w": 200,
      "n": 3,
      "h": 100,
      "color": "red"
    },
  ]
};
visualize(json);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>