简体   繁体   English

扩展本机d3组件(如d3.svg.axis())的惯用方法是什么?

[英]What's the idiomatic way to extend a native d3 component like d3.svg.axis()?

For a time series visualization in d3, I want to highlight years on the axis. 对于d3中的时间序列可视化,我想在轴上突出显示年份。 I've accomplished this by making my own xAxis renderer, which invokes the native axis function and then implements my own custom logic to format the ticks that it renders. 我通过制作我自己的xAxis渲染器来实现这一点,该渲染器调用本机axis函数,然后实现我自己的自定义逻辑来格式化它呈现的刻度。

截图 This is how I've done it ( see working example on jsbin ): 我就是这样做的( 参见jsbin上的工作示例 ):

  xAxis = d3.svg.axis()
    .scale(xScale)

  customXAxis = function(){
    xAxis(this);
    d3.selectAll('.tick', this)
      .classed("year", isYear);
  };

  ...

  xAxis.ticks(10);

  xAxisElement = canvas.append("g")
    .classed("axis x", true)
    .call(customXAxis);

This gets the job done, but feels wrong; 这可以完成工作,但感觉不对劲; and it hasn't really extended the axis, it's only wrapped it. 并没有真正扩展轴,它只包裹它。 Ideally my customXAxis would inherit the properties of d3's axis component, so I would be able to do things like this: 理想情况下,我的customXAxis将继承d3的axis组件的属性,所以我可以做这样的事情:

customXAxis.ticks(10)

Thanks to @meetamit and @drakes for putting this together. 感谢@meetamit和@drakes把它放在一起。 Here's what I've ended up with: http://bl.ocks.org/HerbCaudill/ece2ff83bd4be586d9af 这就是我最终得到的: http//bl.ocks.org/HerbCaudill/ece2ff83bd4be586d9af

Yep, you can do all that. 是的,你可以做到这一切。 Following mbostock's suggestions here in conjunction with `d3.rebind' you get: 按照mbostock的建议'd3.rebind'一起获得:

// This outer function is the thing that instantiates your custom axis.
// It's equivalent to the function d3.svg.axis(), which instantiates a d3 axis.
function InstantiateCustomXAxis() {
  // Create an instance of the axis, which serves as the base instance here
  // It's the same as what you named xAxis in your code, but it's hidden
  // within the custom class. So instantiating customXAxis also
  // instantiates the base d3.svg.axis() for you, and that's a good thing.
  var base = d3.svg.axis();

  // This is just like you had it, but using the parameter "selection" instead of
  // the "this" object. Still the same as what you had before, but more
  // in line with Bostock's teachings...
  // And, because it's created from within InstantiateCustomXAxis(), you
  // get a fresh new instance of your custom access every time you call
  // InstantiateCustomXAxis(). That's important if there are multiple
  // custom axes on the page.
  var customXAxis = function(selection) {
    selection.call(base);

    // note: better to use selection.selectAll instead of d3.selectAll, since there
    // may be multiple axes on the page and you only want the one in the selection
    selection.selectAll('.tick', this)
      .classed("year", isYear);
  }

  // This makes ticks() and scale() be functions (aka methods) of customXAxis().
  // Calling those functions forwards the call to the functions implemented on
  // base (i.e. functions of the d3 axis). You'll want to list every (or all)
  // d3 axis method(s) that you plan to call on your custom axis
  d3.rebind(customXAxis, base, 'ticks', 'scale');// etc...

  // return it
  return customXAxis;
}

To use this class, you just call 要使用此课程,您只需致电

myCustomXAxis = InstantiateCustomXAxis();

You can now also call 你现在也可以打电话

myCustomXAxis
  .scale(d3.scale.ordinal())
  .ticks(5)

And of course the following will continue to work: 当然,以下内容将继续有效:

xAxisElement = canvas.append("g")
  .classed("axis x", true)
  .call(myCustomXAxis);

In summary 综上所述

That's the idiomatic way to implement classes within d3. 这是在d3中实现类的惯用方法。 Javascript has other ways to create classes, like using the prototype object, but d3's own reusable code uses the above method — not the prototype way. Javascript有其他方法来创建类,比如使用prototype对象,但是d3自己的可重用代码使用上面的方法 - 而不是原型方法。 And, within that, d3.rebind is the way to forward method calls from the custom class to what is essentially the subclass. 而且,在其中, d3.rebind是将方法调用从自定义类转发到本质上是子类的方法。

After a lot of code inspection and hacking, and talking with experienced d3 people, I've learned that d3.svg.axis() is a function (not an object nor a class) so it can't be extended nor wrapped. 经过大量的代码检查和黑客攻击,并与经验丰富的d3人交谈后,我了解到d3.svg.axis()是一个函数(不是对象也不是类),所以它不能扩展也不能包装。 So, to "extend" it we will create a new axis, run a selection on the base axis() to get those tick marks selected, then copy over all the properties from the base axis() in one fell swoop, and return this extended-functionality version. 因此,为了“扩展”它,我们将创建一个新轴,在基axis()上运行选择以选择那些刻度线,然后一次性从基axis()复制所有属性,并返回此扩展功能版本。

var customXAxis = (function() {
  var base = d3.svg.axis();

  // Select and apply a style to your tick marks
  var newAxis = function(selection) {
    selection.call(base);
    selection.selectAll('.tick', this)
      .classed("year", isYear);
  };

  // Copy all the base axis methods like 'ticks', 'scale', etc.
  for(var key in base) {
    if (base.hasOwnProperty(key)) {
       d3.rebind(newAxis, base, key);
    }
  }

  return newAxis;
})();

customXAxis now fully "inherits" the properties of d3's axis component. customXAxis现在完全“继承”d3轴组件的属性。 You can safely do the following: 您可以安全地执行以下操作:

customXAxis
.ticks(2)
.scale(xScale)
.tickPadding(50)
.tickFormat(dateFormatter);

canvas.append("g").call(customXAxis);

*With the help of @HerbCaudill's boilerplate code, and inspired by @meetamit's ideas. *在@HerbCaudill的样板代码的帮助下,受@ meetamit的想法启发。

Demo: http://jsbin.com/kabonokeki/5/ 演示: http//jsbin.com/kabonokeki/5/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM