草庐IT

javascript - Array.map() 与 d3.selectAll().data.enter()

coder 2025-03-05 原文

我试图了解使用 d3.selectAll.data.enter() 循环遍历数据集并绘制它的好处。

  var data = [4, 8, 15, 16, 23, 42];

  var x = d3.scale.linear()
      .domain([0, d3.max(data)])
      .range([0, 420]);

  let chartsvg = d3.select(".chart").append("svg");

  chartsvg.selectAll("rect")
    .data(data)
    .enter()
    .append("rect")
    .attr("x", 0)
    .attr("y", function(d, i) {
    return 25*i;
  })
    .attr("width", function(d) {
    return x(d);
  })
    .attr("height", 20)
    .attr("fill", "#f3b562");

我看到 d3 的功能(如比例、轴等)有很多好处。但感觉就像使用 Array.map() 循环数据集一样,我可以用更简洁的代码和更少的行来实现相同的功能,尤其是当我在创建一个更复杂的可视化,而不是像这样简单的准系统条形图。
  var data = [4, 8, 15, 16, 23, 42];

  var x = d3.scale.linear()
      .domain([0, d3.max(data)])
      .range([0, 420]);

  let chartsvg = d3.select(".chart").append("svg");

  data.map(function(d, i){
    chartsvg.append("rect")
      .attr("x", 0)
      .attr("y", 25*i)  
      .attr("width", x(d))
      .attr("height", 20)
      .attr("fill", "#f3b562");
  });

最佳答案

D3 代表数据驱动文档
D3 中最强大的功能(也就是库的名称)是将数据绑定(bind)到 DOM 元素的能力。通过这样做,您可以通过多种方式根据绑定(bind)数据操作这些 DOM 元素,例如(但不限于):

  • 排序
  • 筛选
  • 翻译
  • 款式
  • 追加
  • 删除

  • 等等...
    如果您不将数据绑定(bind)到 DOM 元素,例如使用 map()在您的问题中使用方法(与 forEach() 相同),您可能会在开始时节省几行,但最终会得到一个笨拙的代码来处理。让我们来看看它:
    map() 方法
    这是一个非常简单的代码,使用您的大部分代码段,使用 map() 创建条形图方法:

    var h = 250,
      w = 500,
      p = 40;
    var svg = d3.select("body")
      .append("svg")
      .attr("width", w)
      .attr("height", h);
    
    var data = [{
      group: "foo",
      value: 14,
      name: "A"
    }, {
      group: "foo",
      value: 35,
      name: "B"
    }, {
      group: "foo",
      value: 87,
      name: "C"
    }, {
      group: "foo",
      value: 12,
      name: "D"
    }, {
      group: "bar",
      value: 84,
      name: "E"
    }, {
      group: "bar",
      value: 65,
      name: "F"
    }, {
      group: "bar",
      value: 34,
      name: "G"
    }, {
      group: "baz",
      value: 98,
      name: "H"
    }, {
      group: "baz",
      value: 12,
      name: "I"
    }, {
      group: "baz",
      value: 43,
      name: "J"
    }, {
      group: "baz",
      value: 66,
      name: "K"
    }, {
      group: "baz",
      value: 42,
      name: "L"
    }];
    
    
    var color = d3.scaleOrdinal(d3.schemeCategory10);
    
    var xScale = d3.scaleLinear()
      .range([0, w - p])
      .domain([0, d3.max(data, function(d) {
        return d.value
      })]);
    
    var yScale = d3.scaleBand()
      .range([0, h])
      .domain(data.map(function(d) {
        return d.name
      }))
      .padding(0.1);
    
    data.map(function(d, i) {
      svg.append("rect")
        .attr("x", p)
        .attr("y", yScale(d.name))
        .attr("width", xScale(d.value))
        .attr("height", yScale.bandwidth())
        .attr("fill", color(d.group));
    });
    
    var axis = d3.axisLeft(yScale);
    var gY = svg.append("g").attr("transform", "translate(" + p + ",0)")
      .call(axis);
    <script src="https://d3js.org/d3.v4.min.js"></script>

    这似乎是一个不错的结果,酒吧都在那里。但是,有 没有数据绑定(bind)到那些矩形。保留此代码,我们将在下面的挑战中使用它。
    输入选择
    现在让我们尝试相同的代码,但使用惯用的“enter”选择:

    var h = 250,
      w = 500,
      p = 40;
      
    var svg = d3.select("body")
      .append("svg")
      .attr("width", w)
      .attr("height", h);
    
    var data = [{
      group: "foo",
      value: 14,
      name: "A"
    }, {
      group: "foo",
      value: 35,
      name: "B"
    }, {
      group: "foo",
      value: 87,
      name: "C"
    }, {
      group: "foo",
      value: 12,
      name: "D"
    }, {
      group: "bar",
      value: 84,
      name: "E"
    }, {
      group: "bar",
      value: 65,
      name: "F"
    }, {
      group: "bar",
      value: 34,
      name: "G"
    }, {
      group: "baz",
      value: 98,
      name: "H"
    }, {
      group: "baz",
      value: 12,
      name: "I"
    }, {
      group: "baz",
      value: 43,
      name: "J"
    }, {
      group: "baz",
      value: 66,
      name: "K"
    }, {
      group: "baz",
      value: 42,
      name: "L"
    }];
    
    
    var color = d3.scaleOrdinal(d3.schemeCategory10);
    
    var xScale = d3.scaleLinear()
      .range([0, w - p])
      .domain([0, d3.max(data, function(d) {
        return d.value
      })]);
    
    var yScale = d3.scaleBand()
      .range([0, h])
      .domain(data.map(function(d) {
        return d.name
      }))
      .padding(0.1);
    
    svg.selectAll(null)
      .data(data, function(d) {
        return d.name
      })
      .enter()
      .append("rect")
      .attr("x", p)
      .attr("y", function(d) {
        return yScale(d.name)
      })
      .attr("width", function(d) {
        return xScale(d.value)
      })
      .attr("height", yScale.bandwidth())
      .attr("fill", function(d) {
        return color(d.group)
      });
    
    var axis = d3.axisLeft(yScale);
    var gY = svg.append("g").attr("transform", "translate(" + p + ",0)")
      .call(axis);
    <script src="https://d3js.org/d3.v4.min.js"></script>

    可以看到,比之前的map()长一点方法,2行更长。
    但是,这实际上绑定(bind)了 数据 到那些矩形。如果您 console.log 选择其中一个矩形的 D3,您会看到类似这样的内容(在 Chrome 中):
    > Selection
      > _groups: Array(1)
        > 0: Array(1)
          > 0: rect
            > __data__: Object
              group: "bar"
              name: "G"
              value: 34
    
    由于此代码实际上将数据绑定(bind)到 DOM 元素,因此您可以使用 map() 以一种很麻烦(至少可以说)的方式操作它们。方法。我将在下一个片段中展示这一点,该片段将用于提出挑战。
    挑战
    由于您的问题涉及更简洁的代码和更少的行,因此这对您来说是一个挑战。
    我创建了 3 个按钮,data 中的每个组一个数组(所有组的第四个)。当您单击该按钮时,它会过滤数据并相应地更新图表:

    var h = 250,
      w = 500,
      p = 40;
    var svg = d3.select("body")
      .append("svg")
      .attr("width", w)
      .attr("height", h);
    
    var g1 = svg.append("g")
    var g2 = svg.append("g")
    
    var data = [{
      group: "foo",
      value: 14,
      name: "A"
    }, {
      group: "foo",
      value: 35,
      name: "B"
    }, {
      group: "foo",
      value: 87,
      name: "C"
    }, {
      group: "foo",
      value: 12,
      name: "D"
    }, {
      group: "bar",
      value: 84,
      name: "E"
    }, {
      group: "bar",
      value: 65,
      name: "F"
    }, {
      group: "bar",
      value: 34,
      name: "G"
    }, {
      group: "baz",
      value: 98,
      name: "H"
    }, {
      group: "baz",
      value: 12,
      name: "I"
    }, {
      group: "baz",
      value: 43,
      name: "J"
    }, {
      group: "baz",
      value: 66,
      name: "K"
    }, {
      group: "baz",
      value: 42,
      name: "L"
    }];
    
    
    var color = d3.scaleOrdinal(d3.schemeCategory10);
    
    var xScale = d3.scaleLinear()
      .range([0, w - p])
      .domain([0, d3.max(data, function(d) {
        return d.value
      })]);
    
    var yScale = d3.scaleBand()
      .range([0, h])
      .domain(data.map(function(d) {
        return d.name
      }))
      .padding(0.1);
    
    
    var axis = d3.axisLeft(yScale);
    var gY = g2.append("g").attr("transform", "translate(" + p + ",0)")
      .call(axis);
    
    draw(data);
    
    function draw(data) {
    
      yScale.domain(data.map(function(d) {
        return d.name
      }))
    
      var rects = g1.selectAll("rect")
        .data(data, function(d) {
          return d.name
        })
    
      rects.enter()
        .append("rect")
        .attr("x", p)
        .attr("y", function(d) {
          return yScale(d.name)
        })
        .attr("width", 0)
        .attr("height", yScale.bandwidth())
        .attr("fill", function(d) {
          return color(d.group)
        })
        .transition()
        .duration(1000)
        .attr("width", function(d) {
          return xScale(d.value)
        });
    
      rects.transition()
        .duration(1000)
        .attr("x", p)
        .attr("y", function(d) {
          return yScale(d.name)
        })
        .attr("width", function(d) {
          return xScale(d.value)
        })
        .attr("height", yScale.bandwidth())
        .attr("fill", function(d) {
          return color(d.group)
        });
    
      rects.exit()
        .transition()
        .duration(1000)
        .attr("width", 0)
        .remove();
    
      gY.transition().duration(1000).call(axis);
    };
    
    d3.selectAll("button").on("click", function() {
    
      var thisValue = this.id;
    
      var newData = thisValue === "all" ? data : data.filter(function(d) {
        return d.group === thisValue;
      });
    
      draw(newData)
    });
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <button id="foo">Foo</button>
    <button id="bar">Bar</button>
    <button id="baz">Baz</button>
    <button id="all">All</button>
    <br>
    <br>

    更简洁的代码在某种程度上是基于意见的,但我们可以轻松测量大小。
    因此,挑战在于:尝试创建一个执行相同操作的代码,但使用 map()方法,即不绑定(bind)任何数据。做我在这里做的所有转换。您将尝试重新创建的代码是 on("click") 中的所有代码。功能。
    之后,我们将比较您的代码大小和惯用的“进入”、“更新”和“退出”选择的大小。
    挑战#2
    当涉及到绑定(bind)数据时,展示 D3 的功能可能会更有趣,第 2 个挑战。
    在这个新代码中,我在 1 秒后对原始数据数组进行排序,并重新绘制图表。然后,点击“更新”按钮,我正在绑定(bind)另一个数据数组到条形图。
    这里的好处是关键函数,它将每个条与每个数据点相关联,在这种情况下,使用 name属性(property):
    .data(data, function(d) {
        return d.name
    })
    
    这是代码,请等待1秒后再点击“更新”:

    var h = 250,
      w = 500,
      p = 40;
    
    var svg = d3.select("body")
      .append("svg")
      .attr("width", w)
      .attr("height", h);
    
    var data2 = [{
      group: "foo",
      value: 10,
      name: "A"
    }, {
      group: "foo",
      value: 20,
      name: "B"
    }, {
      group: "foo",
      value: 30,
      name: "C"
    }, {
      group: "foo",
      value: 40,
      name: "D"
    }, {
      group: "bar",
      value: 50,
      name: "E"
    }, {
      group: "bar",
      value: 60,
      name: "F"
    }, {
      group: "bar",
      value: 70,
      name: "G"
    }, {
      group: "baz",
      value: 80,
      name: "H"
    }, {
      group: "baz",
      value: 85,
      name: "I"
    }, {
      group: "baz",
      value: 90,
      name: "J"
    }, {
      group: "baz",
      value: 95,
      name: "K"
    }, {
      group: "baz",
      value: 100,
      name: "L"
    }];
    
    var data = [{
      group: "foo",
      value: 14,
      name: "A"
    }, {
      group: "foo",
      value: 35,
      name: "B"
    }, {
      group: "foo",
      value: 87,
      name: "C"
    }, {
      group: "foo",
      value: 12,
      name: "D"
    }, {
      group: "bar",
      value: 84,
      name: "E"
    }, {
      group: "bar",
      value: 65,
      name: "F"
    }, {
      group: "bar",
      value: 34,
      name: "G"
    }, {
      group: "baz",
      value: 98,
      name: "H"
    }, {
      group: "baz",
      value: 12,
      name: "I"
    }, {
      group: "baz",
      value: 43,
      name: "J"
    }, {
      group: "baz",
      value: 66,
      name: "K"
    }, {
      group: "baz",
      value: 42,
      name: "L"
    }];
    
    var color = d3.scaleOrdinal(d3.schemeCategory10);
    
    var xScale = d3.scaleLinear()
      .range([0, w - p])
      .domain([0, d3.max(data, function(d) {
        return d.value
      })]);
    
    var yScale = d3.scaleBand()
      .range([0, h])
      .domain(data.map(function(d) {
        return d.name
      }))
      .padding(0.1);
    
    svg.selectAll(".bars")
      .data(data, function(d) {
        return d.name
      })
      .enter()
      .append("rect")
      .attr("class", "bars")
      .attr("x", p)
      .attr("y", function(d) {
        return yScale(d.name)
      })
      .attr("width", function(d) {
        return xScale(d.value)
      })
      .attr("height", yScale.bandwidth())
      .attr("fill", function(d) {
        return color(d.group)
      })
    
    var axis = d3.axisLeft(yScale);
    var gY = svg.append("g").attr("transform", "translate(" + p + ",0)")
      .call(axis);
    
    setTimeout(function() {
    
      data.sort(function(a, b) {
        return d3.ascending(a.value, b.value)
      });
    
      yScale.domain(data.map(function(d) {
        return d.name
      }));
    
      svg.selectAll(".bars").data(data, function(d) {
          return d.name
        })
        .transition()
        .duration(500)
        .attr("y", function(d) {
          return yScale(d.name)
        })
        .attr("width", function(d) {
          return xScale(d.value)
        });
    
      gY.transition().duration(1000).call(axis);
    
    }, 1000)
    
    d3.selectAll("button").on("click", function() {
    
      svg.selectAll(".bars").data(data2, function(d) {
          return d.name
        })
        .transition()
        .duration(500)
        .attr("y", function(d) {
          return yScale(d.name)
        })
        .attr("width", function(d) {
          return xScale(d.value)
        });
    
      gY.transition().duration(1000).call(axis);
    })
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <button>Update</button>
    <br>
    <br>

    您的挑战是相同的:更改 .on("click") 中的代码,仅此而已...
    svg.selectAll(".bars").data(data2, function(d) {
            return d.name
        })
        .transition()
        .duration(500)
        .attr("y", function(d) {
            return yScale(d.name)
        })
        .attr("width", function(d) {
            return xScale(d.value)
        });
    
    gY.transition().duration(1000).call(axis);
    
    ... 到执行 的代码一样的 ,但对于您的map()方法。
    请记住,由于我对条形图进行了排序,因此您不能再通过数据数组的索引来更改它们!
    结论map()当您第一次绘制元素时,方法可能会为您节省 2 行。但是,这会使以后的事情变得非常麻烦。

    关于javascript - Array.map() 与 d3.selectAll().data.enter(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44354851/

    有关javascript - Array.map() 与 d3.selectAll().data.enter()的更多相关文章

    1. ruby - 在 Ruby 中实现 `call_user_func_array` - 2

      我怎样才能完成http://php.net/manual/en/function.call-user-func-array.php在ruby中?所以我可以这样做:classAppdeffoo(a,b)putsa+benddefbarargs=[1,2]App.send(:foo,args)#doesn'tworkApp.send(:foo,args[0],args[1])#doeswork,butdoesnotscaleendend 最佳答案 尝试分解数组App.send(:foo,*args)

    2. Ruby Koans about_array_assignment - 非平行与平行分配歧视 - 2

      通过ruby​​koans.com,我在about_array_assignment.rb中遇到了这两段代码你怎么知道第一个是非并行赋值,第二个是一个变量的并行赋值?在我看来,除了命名差异之外,代码几乎完全相同。4deftest_non_parallel_assignment5names=["John","Smith"]6assert_equal["John","Smith"],names7end45deftest_parallel_assignment_with_one_variable46first_name,=["John","Smith"]47assert_equal'John

    3. ruby-on-rails - 更好的替代方法 try( :output). try( :data). try( :name)? - 2

      “输出”是一个序列化的OpenStruct。定义标题try(:output).try(:data).try(:title)结束什么会更好?:) 最佳答案 或者只是这样:deftitleoutput.data.titlerescuenilend 关于ruby-on-rails-更好的替代方法try(:output).try(:data).try(:name)?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c

    4. arrays - 这是 Ruby 中 Array.fill 方法的错误吗? - 2

      这个问题在这里已经有了答案:Arraysmisbehaving(1个回答)关闭6年前。是否应该这样,即我误解了,还是错误?a=Array.new(3,Array.new(3))a[1].fill('g')=>[["g","g","g"],["g","g","g"],["g","g","g"]]它不应该导致:=>[[nil,nil,nil],["g","g","g"],[nil,nil,nil]]

    5. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

      我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

    6. Ruby on Rails regexp equals-tilde 与 array include 用于检查选项列表 - 2

      我正在使用Rails3.2.3和Ruby1.9.3p0。我发现我经常需要确定某个字符串是否出现在选项列表中。看来我可以使用Ruby数组.includemethod:或正则表达式equals-tildematchshorthand用竖线分隔选项:就性能而言,一个比另一个好吗?还有更好的方法吗? 最佳答案 总结:Array#include?包含String元素,在接受和拒绝输入时均胜出,对于您的示例只有三个可接受的值。对于要检查的更大的集合,看起来Set#include?和String元素可能会获胜。如何测试我们应该根据经验对此进行测试

    7. ruby - 在 ruby​​ 中使用 .try 函数和 .map 函数 - 2

      我需要从json记录中获取一些值并像下面这样提取curr_json_doc['title']['genre'].map{|s|s['name']}.join(',')但对于某些记录,curr_json_doc['title']['genre']可以为空。所以我想对map和join()使用try函数。我试过如下curr_json_doc['title']['genre'].try(:map,{|s|s['name']}).try(:join,(','))但是没用。 最佳答案 你没有正确传递block。block被传递给参数括号外的方法

    8. ruby - 在 Ruby 中,为什么 Array.new(size, object) 创建一个由对同一对象的多个引用组成的数组? - 2

      如thisanswer中所述,Array.new(size,object)创建一个数组,其中size引用相同的object。hash=Hash.newa=Array.new(2,hash)a[0]['cat']='feline'a#=>[{"cat"=>"feline"},{"cat"=>"feline"}]a[1]['cat']='Felix'a#=>[{"cat"=>"Felix"},{"cat"=>"Felix"}]为什么Ruby会这样做,而不是对object进行dup或clone? 最佳答案 因为那是thedocumenta

    9. ruby - 不能将 `each` 的所有或大多数情况替换为 `map` 吗? - 2

      Enumerable#each和Enumerable#map的区别在于返回的是接收者还是映射后的结果。回到接收者是微不足道的,你通常不需要在each之后继续一个方法链,比如each{...}.another_method(我可能没见过这样的案例。即使你想回到接收者那里,你也可以通过tap来实现)。所以我认为所有或者大部分使用Enumerable#each的情况都可以用Enumerable#map代替。我错了吗?如果我是对的,each的目的是什么?map是否比each慢?编辑:我知道当您对返回值不感兴趣时​​使用each是一种常见的做法。我对这种做法是否存在不感兴趣,但感兴趣的是,除了从

    10. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

      我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

    随机推荐