2

我最近试图为 d3.js 编写一个插件,并且被一些可能微不足道的东西弄糊涂了。d3 的网站上有关于如何创建可重用图表的解释。模式看起来像这样(只有最重要的细节,完整的代码在这里):

设计模式 1:来自 d3 网站

function timeSeriesChart() {
  var margin = {top: 20, right: 20, bottom: 20, left: 20},
      ...
      area = d3.svg.area().x(X).y1(Y),
      line = d3.svg.line().x(X).y(Y);

  function chart(selection) {
    selection.each(function(data) {

      // Convert data to standard representation greedily;
      // this is needed for nondeterministic accessors.
      data = data.map(function(d, i) {
        return [xValue.call(data, d, i), yValue.call(data, d, i)];
      });

      // Update the x-scale.
      ...

      // Update the y-scale.
      ...

      // Select the svg element, if it exists.
      var svg = d3.select(this).selectAll("svg").data([data]);
      ...

      // Otherwise, create the skeletal chart.
      var gEnter = svg.enter().append("svg").append("g");
      ...    
  }

  // The x-accessor for the path generator; xScale ∘ xValue.
  function X(d) {      }

  // The x-accessor for the path generator; yScale ∘ yValue.
  function Y(d) {      }

  chart.margin = function(_) {
    if (!arguments.length) return margin;
    margin = _;
    return chart;
  };

  chart.width = function(_) {
    if (!arguments.length) return width;
    width = _;
    return chart;
  };

  chart.height = function(_) {
    if (!arguments.length) return height;
    height = _;
    return chart;
  };

  chart.x = function(_) {
    if (!arguments.length) return xValue;
    xValue = _;
    return chart;
  };

  chart.y = function(_) {
    if (!arguments.length) return yValue;
    yValue = _;
    return chart;
  };

  return chart;
}

我毫不怀疑这种模式是健壮的,尤其是因为它是由 d3 的创建者本人提出的。但是,在我遇到该帖子之前,我已经使用以下模式一段时间没有问题(类似于一般如何创建插件):

设计模式 2:创建插件的一般方法

(function() {
    var kg = {
        version: '0.1a'
    };

    window.kg = kg;

    kg.chart = {};

    // ==========================================================
    // CHART::SAMPLE CHART TYPE
    // ==========================================================
    kg.chart.samplechart = {

        // ----------------------------------------------------------
        // CONFIGURATION PARAMETERS
        // ----------------------------------------------------------
        WIDTH: 900,
        HEIGHT: 500,
        MARGINS: {
            top: 20,
            right: 20,
            bottom: 20,
            left: 60,
            padding: 40
        },
        xRange: d3.time.scale(),
        yRange: d3.scale.linear(),
        xAxis: d3.svg.axis(),
        yAxis: d3.svg.axis(),
        data: {},

        // ----------------------------------------------------------
        // INIT FUNCTION
        // ----------------------------------------------------------
        init: function() {
            // Initialize and add graph to the given div
            this.update();
        },

        // ----------------------------------------------------------
        // Redraws the graph
        // ----------------------------------------------------------
        update: function() {
            var parentThis = this;
            var newData = parentThis.data;

            // Continue with adding points/lines to the chart


        },
        // ----------------------------------------------------------
        // Gets random data for graph demonstration
        // ----------------------------------------------------------
        getRandomData: function() {
            // Useful for demo purposes  

        }
    };

    // ==========================================================
    // HELPER FUNCTIONS
    // ==========================================================

}());


// EXAMPLE: This renders the chart. 
kg.chart.samplechart.vis = d3.select("#visualization");
kg.chart.samplechart.data = kg.chart.samplechart.getRandomData();
kg.chart.samplechart.init();​    

我已经使用Design Pattern 2一段时间了,没有任何问题(我同意它不是超级干净,但我正在努力)。看了Design Pattern 1之后,我只是觉得它有太多的冗余。例如,查看使内部变量可访问(chart.margin = function(_) {}等)的最后代码块。

也许这是一种很好的做法,但它使维护变得很麻烦,因为必须为每种不同的图表类型重复此操作(如这里在一个名为NVD3的库中看到的,目前正在开发中)并且增加了开发工作和错误风险。

我想知道如果我继续我的模式会面临什么样的严重问题,或者我的模式如何可以改进或更接近设计模式 1的精神。在这一点上,我试图避免改变模式,因为这将需要完全重写,并将新的错误引入到一个有点稳定的迷你库中。有什么建议么?

4

1 回答 1

4

事实上,您可以在 d3 源代码中找到您的第二种模式。

(function(){
...
d3 = {version: "2.9.6"}; // semver
...

d3.svg = {};
d3.svg.arc = function() {
  var innerRadius = d3_svg_arcInnerRadius,
      outerRadius = d3_svg_arcOuterRadius,
...

但是组件和生成器,如比例、轴、区域和布局,倾向于使用我们可以称之为“具有 getter-setter 方法的图表作为闭包”或“通过可配置函数进行高阶编程”的模式。您可以在Google Group 线程上关注此讨论以了解基本原理。

就个人而言,我也不喜欢这种冗余,即使它有用且可读性强。所以我使用自定义函数自动生成这些 getter 和 setter:

d3.helper.createAccessors = function(visExport) {
    for (var n in visExport.opts) {
        if (!visExport.opts.hasOwnProperty(n)) continue;
        visExport[n] = (function(n) {
            return function(v) {
                return arguments.length ? (visExport.opts[n] = v, this) : visExport.opts[n];
            }
        })(n);
    }
};

我在图表模块的末尾这样使用它:

d3.helper.createAccessors(chart, opts);

其中 opts 是所有公共函数的名称:

var opts = {
            width: 200,
            margin: [5, 0, 20, 0],
            height: 200,
            showPoints: true,
            showAreas: false,
            enableTooltips: true,
            dotSize: 4
        };

这是一个完整的例子:http: //jsfiddle.net/christopheviau/YPAYz/

于 2012-07-31T14:51:22.403 回答