Chains of functions in the D3.js plugin

I have a plugin written for D3.js called d3-marcon

that implements Mike Bostock's Brand Conventions . For example, instead of writing:

var margin = {top: 10, bottom: 10, left: 10, right: 10},
    width = window.innerWidth - margin.left - margin.right,
    height = window.innerHeight - margin.top - margin.bottom,
    svg = d3.select("body").append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
        .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

      

You can write:

var setup = d3.marcon({top: 10, bottom: 10, left: 10, right: 10, width: window.innerWidth, height: window.innerHeight}),
    margin = setup.margin,
    width = setup.width,
    height = setup.height,
    svg = setup.svg;

      

As you can see, it works by passing the options object to the function marcon()

. If you don't specify any options, it defaults 0

to all margins, 900

for width, 600

for height, and adds an svg element to "body"

. This way, you can start up quickly and quickly with one line of code var setup = d3.marcon()

, and then pass parameters later when you want to change your variation.

This is useful, but it still doesn't look like a real D3 feature. Real D3 functions allow you to bundle functions together rather than passing option objects. So instead of d3.marcon({element: ".viz"})

D3 code looks like d3.marcon().element(".viz")

.

D3 code also allows you to pass additional functions to chained functions (for example d3.method().chainedFunction(function(d) { return d.value; })

), so you can update the object's attributes based on the data.

Obviously my plugin doesn't do any of this. I've spent a few hours looking for existing D3 modules to try and figure out how they work, but I'm not going anywhere. Can anyone suggest how to get my code to work as a suitable D3 module? Or, if it isn't, a good tutorial to read?

I linked to the repository above, but here it is again . And here's a block showing how it works.

+3


source to share


2 answers


This is my suggestion, based on Nick Zhu's approach in his book Data Visualization since D3 4.x.

Basically, we are creating a function with an object ...

function marcon() {
    var instance = {};
}

      

... and set each method individually:

instance.top = function(d) {
    if (!arguments.length) return top;
    top = d;
    return instance;
};

      

At the end, you call the rendering part with render()

:

marcon().top(10)
    .left(10)
    //etc...
    .render();

      

The good thing about this approach is that, as you requested, it allows chaining. For example, you can create your SVG like this:

var mySvg = marcon();

mySvg.top(20)
    .left(10)
    .right(10)
    .bottom(10)
    .height(200)
    .width(200)
    .element("body")
    .render();

      

Here's a demo:



function marcon() {
  var instance = {};
  var top = 10,
    bottom = 0,
    left = 0,
    right = 0,
    width = 900,
    height = 600,
    element = "body",
    innerWidth, innerHeight, svg;

  instance.top = function(d) {
    if (!arguments.length) return top;
    top = d;
    return instance;
  };

  instance.left = function(d) {
    if (!arguments.length) return left;
    left = d;
    return instance;
  };

  instance.right = function(d) {
    if (!arguments.length) return right;
    right = d;
    return instance;
  };

  instance.bottom = function(d) {
    if (!arguments.length) return bottom;
    bottom = d;
    return instance;
  };

  instance.width = function(d) {
    if (!arguments.length) return width;
    width = d;
    return instance;
  };

  instance.height = function(d) {
    if (!arguments.length) return height;
    height = d;
    return instance;
  };

  instance.element = function(d) {
    if (!arguments.length) return element;
    element = d;
    return instance;
  };

  instance.innerWidth = function() {
    return innerWidth;
  };

  instance.innerHeight = function() {
    return innerHeight;
  };

  instance.svg = function() {
    return svg;
  };

  instance.render = function() {
    innerWidth = width - left - right;
    innerHeight = height - top - bottom;
    svg = d3.select(element)
      .append("svg")
      .attr("width", innerWidth + left + right)
      .attr("height", innerHeight + top + bottom)
      .append("g")
      .attr("transform", "translate(" + left + ", " + top + ")");
  }

  return instance;
}

var mySvg = marcon();
mySvg.top(20)
  .left(10)
  .right(10)
  .bottom(20)
  .height(200)
  .width(200)
  .element(".testDiv")
  .render();

var rect = mySvg.svg()
  .append("rect")
  .attr("width", mySvg.innerWidth())
  .attr("height", mySvg.innerHeight())
  .style("fill", "teal")
      

svg {
  background-color: tan;
}
      

<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="testDiv"></div>
      

Run codeHide result


In addition, it allows you to set default values. If you have not installed a setter, the specified values ​​will be provided by default. Here, if we don't specify the width, it defaults to 900:

function marcon() {
  var instance = {};
  var top = 10,
    bottom = 0,
    left = 0,
    right = 0,
    width = 900,
    height = 600,
    element = "body",
    innerWidth, innerHeight, svg;

  instance.top = function(d) {
    if (!arguments.length) return top;
    top = d;
    return instance;
  };

  instance.left = function(d) {
    if (!arguments.length) return left;
    left = d;
    return instance;
  };

  instance.right = function(d) {
    if (!arguments.length) return right;
    right = d;
    return instance;
  };

  instance.bottom = function(d) {
    if (!arguments.length) return bottom;
    bottom = d;
    return instance;
  };

  instance.width = function(d) {
    if (!arguments.length) return width;
    width = d;
    return instance;
  };

  instance.height = function(d) {
    if (!arguments.length) return height;
    height = d;
    return instance;
  };

  instance.element = function(d) {
    if (!arguments.length) return element;
    element = d;
    return instance;
  };

  instance.innerWidth = function() {
    return innerWidth;
  };

  instance.innerHeight = function() {
    return innerHeight;
  };

  instance.svg = function() {
    return svg;
  };

  instance.render = function() {
    innerWidth = width - left - right;
    innerHeight = height - top - bottom;
    svg = d3.select(element)
      .append("svg")
      .attr("width", innerWidth + left + right)
      .attr("height", innerHeight + top + bottom)
      .append("g")
      .attr("transform", "translate(" + left + ", " + top + ")");
  }

  return instance;
}

var mySvg = marcon();
mySvg.top(20)
  .left(10)
  .right(10)
  .bottom(20)
  .height(200)
  .element("body")
  .render();

var rect = mySvg.svg()
  .append("rect")
  .attr("width", mySvg.innerWidth())
  .attr("height", mySvg.innerHeight())
  .style("fill", "teal")
      

svg {
  background-color: tan;
}
      

<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="testDiv"></div>
      

Run codeHide result


Finally, you can use getters, for example:

marcon().top();

      

What gives you value. Here is a demo, take a look at the console:

function marcon() {
  var instance = {};
  var top = 10,
    bottom = 0,
    left = 0,
    right = 0,
    width = 900,
    height = 600,
    element = "body",
    innerWidth, innerHeight, svg;

  instance.top = function(d) {
    if (!arguments.length) return top;
    top = d;
    return instance;
  };

  instance.left = function(d) {
    if (!arguments.length) return left;
    left = d;
    return instance;
  };

  instance.right = function(d) {
    if (!arguments.length) return right;
    right = d;
    return instance;
  };

  instance.bottom = function(d) {
    if (!arguments.length) return bottom;
    bottom = d;
    return instance;
  };

  instance.width = function(d) {
    if (!arguments.length) return width;
    width = d;
    return instance;
  };

  instance.height = function(d) {
    if (!arguments.length) return height;
    height = d;
    return instance;
  };

  instance.element = function(d) {
    if (!arguments.length) return element;
    element = d;
    return instance;
  };

  instance.innerWidth = function() {
    return innerWidth;
  };

  instance.innerHeight = function() {
    return innerHeight;
  };

  instance.svg = function() {
    return svg;
  };

  instance.render = function() {
    innerWidth = width - left - right;
    innerHeight = height - top - bottom;
    svg = d3.select(element)
      .append("svg")
      .attr("width", innerWidth + left + right)
      .attr("height", innerHeight + top + bottom)
      .append("g")
      .attr("transform", "translate(" + left + ", " + top + ")");
  }

  return instance;
}

var mySvg = marcon();
mySvg.top(20)
  .left(10)
  .right(10)
  .bottom(20)
  .height(200)
  .width(200)
  .element("body")
  .render();

var rect = mySvg.svg()
  .append("rect")
  .attr("width", mySvg.innerWidth())
  .attr("height", mySvg.innerHeight())
  .style("fill", "teal");
  
console.log("The height is " + mySvg.height())
      

svg {
  background-color: tan;
}
      

<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="testDiv"></div>
      

Run codeHide result


+3


source


There are several different ways to do what you want. First, you should read this excellent tutorial on reuse with d3

. Taking these ideas with your code would look something like this:



<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>

<body>
  <script>
    function marcon() {
      
      var top = 0,
      	bottom = 0,
      	left = 0,
      	right = 0,
      	width = 900,
      	height = 600,
      	svg;

      function self(selection) {
        
    		var w = width - left - right,
    		    h = height - top - bottom;
    		
    		svg = selection.append("svg")
    		  .attr("width", width + left + right)
    			.attr("height", height + top + bottom)
    			.append("g")
    			.attr("transform", "translate(" + left + ", " + top + ")");
    	}

      self.top = function(value) {
        if (!arguments.length) return top;
        top = value;
        return self;
      };

      self.bottom = function(value) {
        if (!arguments.length) return bottom;
        bottom = value;
        return self;
      };
      
      self.left = function(value) {
        if (!arguments.length) return left;
        left = value;
        return self;
      };
      
      self.right = function(value) {
        if (!arguments.length) return right;
        right = value;
        return self;
      };
      
      self.width = function(value) {
        if (!arguments.length) return width;
        width = value;
        return self;
      };
      
      self.height = function(value) {
        if (!arguments.length) return height;
        height = value;
        return self;
      };
      
      self.svg = function(value){
        if (!arguments.length) return svg;
        svg = value;
        return self;
      }


      return self;
    }
    
    var m = marcon()
      .width(100)
      .height(100)
      .top(50)
      .left(50);
    
    d3.select('body')
      .call(m);
      
    m.svg()
      .append("text")
      .text("Hi Mom!");
    
  </script>
</body>

</html>
      

Run codeHide result


+2


source







All Articles