简体   繁体   English

SVG rect在过渡时的动画位置

[英]Animate position of svg rect on transition

Edit: here is an example Fiddle: https://jsfiddle.net/3c9dtLyh/6/ 编辑:这是一个小提琴的例子: https : //jsfiddle.net/3c9dtLyh/6/

I have a layout that I am attempting to animate to compare the arrangement on two different dates. 我正在尝试制作一个布局,以比较两个不同日期的安排。 What I would like to accomplish is a transition where items whose x,y position is different on the second date smoothly fly to the new position. 我想要完成的是一个过渡,其中第二个日期的x,y位置不同的项目将平稳地飞到新位置。 I have attempted to do this using an updateData function set to trigger onclick. 我尝试使用设置为触发onclick的updateData函数来执行此操作。

The layout looks like this: 布局如下所示:

在此处输入图片说明

I do not neccesarily expect this approach to work because how would the transition know which (x,y) pairs correspond to the correct item name in the new arrangement. 我并不一定希望这种方法行得通,因为过渡如何知道新安排中的(x,y)对与正确的商品名称相对应。 What am I missing about how these transitions work and how could I improve my approach? 我对这些过渡如何工作以及如何改进自己的方法遗漏了什么?

Here is the code I am using. 这是我正在使用的代码。 It's a relatively simple sequence of appending and svg element, drawing the rectangles, then (failing) to update their position on click. 这是一个相对简单的附加和svg元素序列,先绘制矩形,然后(失败)以更新其单击位置。

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  font: 10px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.dot {
  stroke: #000;
}
</style>
<body>
    <div id = "chart">
    </div>
    <div id = "select_params">

    <input name="updateButton"
           type="button"
           value="Update"
           onclick="updateData()" />

    </div>
</body>

<!-- load js libraries -->
<script src="https://d3js.org/d3.v4.min.js"></script>               <!-- uses v4 of d3 -->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script>       <!-- need to use this older version for tipsy -->
<script type="text/javascript" src="jquery.tipsy.js"></script>  <!-- load from locally hosted source code -->

<!-- build the visualization -->
<script type='text/javascript'>
var item_width = 40, item_height = 60;

var margin = {top: 20, right: 50, bottom: 75, left: 40},
    width = 700 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var x = d3.scaleLinear()
    .range([0, width]);

var y = d3.scaleLinear()
    .range([height, 0]);

var color = d3.scaleOrdinal(d3.schemeCategory10);

var svg = d3.select("#chart").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 + ")");

d3.csv("http://localhost:8080/udacity_test_vis_1/combined_item_pos.csv", function(data) {

  // cast string to numeric
  data.forEach(function(d) {
    d.x_pos = +d.x_pos;
    d.y_pos = +d.y_pos;
    d.sales = +d.sales;
  });

  console.log(data);

  var x_offset = 5, y_offset = 5;

  x.domain(d3.extent(data, function(d) { return d.x_pos; }));        // set the x domain
  y.domain(d3.extent(data, function(d) { return d.y_pos; }));            // set the y domain

  svg.selectAll("g")
      .data(data)
    .enter()
      .append("rect")
      .filter(function(d){ return d.date == '1-20-2017'})
      .attr("class", "dot")
      .attr("width", item_width)
      .attr("height", item_height)
      .attr("x", function(d) { return x(d.x_pos) + x_offset; })              // x position of dots
      .attr("y", function(d) { return y(d.y_pos) + y_offset; })             // y position of dots
      .attr("rx", 5)
      .attr("ry", 5)
      .style("fill", "#1f5fc6")     // color factor variable
      .style("fill-opacity", 0.5);

  svg.selectAll("g")
       .data(data)
    .enter()
       .append("text")
       .filter(function(d){ return d.date == '1-20-2017'})
       .attr("x", function(d) { return x(d.x_pos) + item_width/2 + x_offset; })
       .attr("y", function(d) { return y(d.y_pos) + item_height/2 + y_offset; })
       .attr("font-size", 10)
       .attr("text-anchor", "middle")
       .attr("fill", "black")
       .text(function(d){ return d.item_name});

});


function updateData() {

    // grab the data again
    d3.csv("http://localhost:8080/udacity_test_vis_1/combined_item_pos.csv", function(data) {

      // cast string to numeric
      data.forEach(function(d) {
        d.x_pos = +d.x_pos;
        d.y_pos = +d.y_pos;
        d.sales = +d.sales;
      });

      var svg = d3.select("#chart").transition();

      svg.selectAll("g")
          .data(data)
            .enter()
          .append("rect")
          .filter(function(d){ return d.date == '2-10-2017'})
          .attr("class", "dot")
          .attr("width", item_width)
          .attr("height", item_height)
          .attr("x", function(d) { return x(d.x_pos) + x_offset; })              // x position of dots
          .attr("y", function(d) { return y(d.y_pos) + y_offset; })             // y position of dots
          .attr("rx", 5)
          .attr("ry", 5)
          .style("fill", "#1f5fc6")     // color factor variable
          .style("fill-opacity", 0.5);

    svg.selectAll("g")
        .data(data)
          .enter()
        .append("text")
        .filter(function(d){ return d.date == '2-10-2017'})
        .attr("x", function(d) { return x(d.x_pos) + item_width/2 + x_offset; })
        .attr("y", function(d) { return y(d.y_pos) + item_height/2 + y_offset; })
        .attr("font-size", 10)
        .attr("text-anchor", "middle")
        .attr("fill", "black")
        .text(function(d){ return d.item_name});


});
}

</script>

Here is my data: 这是我的数据:

,x_pos,y_pos,item_name,sales,date
0,1,1,S8221,2022,1-20-2017
1,2,1,NLC11,518,1-20-2017
2,3,1,35UUY,1614,1-20-2017
3,4,1,PPTNV,1059,1-20-2017
4,5,1,G0CWS,2183,1-20-2017
5,6,1,3JHUA,2513,1-20-2017
6,7,1,4HXGA,2251,1-20-2017
7,8,1,RYM9K,2330,1-20-2017
8,9,1,T8PUB,1476,1-20-2017
9,10,1,PLULW,1225,1-20-2017
10,1,2,YJ6S0,2403,1-20-2017
11,2,2,E9RGD,1361,1-20-2017
12,3,2,E2SW4,1131,1-20-2017
13,4,2,BZPGX,698,1-20-2017
14,5,2,0K682,1855,1-20-2017
15,6,2,D8UZW,2371,1-20-2017
16,7,2,USKY7,1851,1-20-2017
17,8,2,D0L0Y,1767,1-20-2017
18,9,2,P1AGP,1025,1-20-2017
19,10,2,9LT7O,1380,1-20-2017
20,1,3,1J184,1108,1-20-2017
21,2,3,RJDEG,2106,1-20-2017
22,3,3,LTSLR,1980,1-20-2017
23,4,3,ET3DF,2700,1-20-2017
24,5,3,42W1W,2194,1-20-2017
25,6,3,5QTJN,958,1-20-2017
26,7,3,O8XKY,2381,1-20-2017
27,8,3,LS9NW,516,1-20-2017
28,9,3,0MPZ7,2198,1-20-2017
29,10,3,R4E3J,2494,1-20-2017
30,1,4,WFPPY,2349,1-20-2017
31,2,4,MT2DB,2525,1-20-2017
32,3,4,6DRYS,600,1-20-2017
33,4,4,NVV0S,1556,1-20-2017
34,5,4,ODGZ2,912,1-20-2017
35,6,4,E3NLS,931,1-20-2017
36,7,4,9FFZ7,722,1-20-2017
37,8,4,UKZGF,2170,1-20-2017
38,9,4,XXORI,896,1-20-2017
39,10,4,QYU9Q,1104,1-20-2017
40,1,5,4KQPU,1562,1-20-2017
41,2,5,S3AYK,2298,1-20-2017
42,3,5,5W3CE,2580,1-20-2017
43,4,5,T0S7H,1677,1-20-2017
44,5,5,02SJG,1972,1-20-2017
45,6,5,GBMNZ,1845,1-20-2017
46,7,5,2Y7KH,982,1-20-2017
47,8,5,3WMOL,1952,1-20-2017
48,9,5,93KLU,2240,1-20-2017
49,10,5,K80OQ,2467,1-20-2017
50,1,6,2SIJS,1788,1-20-2017
51,2,6,5ZJ7V,2277,1-20-2017
52,3,6,HTL99,873,1-20-2017
53,4,6,C06QP,2185,1-20-2017
54,5,6,2S1YI,580,1-20-2017
55,6,6,IQ0L8,2395,1-20-2017
56,7,6,PEE2Y,2299,1-20-2017
57,8,6,6DEWK,2019,1-20-2017
58,9,6,9FY5B,1517,1-20-2017
59,10,6,NZQ54,2624,1-20-2017
60,1,7,C4SVV,1823,1-20-2017
61,2,7,Q4C4I,2339,1-20-2017
62,3,7,996OQ,1621,1-20-2017
63,4,7,PISK6,895,1-20-2017
64,5,7,KOKHE,1315,1-20-2017
65,6,7,6P4FT,1467,1-20-2017
66,7,7,3FY75,2085,1-20-2017
67,8,7,9YCNB,992,1-20-2017
68,9,7,NXXK1,2080,1-20-2017
69,10,7,4RDHV,2031,1-20-2017
0,6,1,9FFZ7,592,2-10-2017
1,1,6,E2SW4,622,2-10-2017
2,6,7,PLULW,1699,2-10-2017
3,8,3,ET3DF,784,2-10-2017
4,9,4,KOKHE,1092,2-10-2017
5,2,6,5ZJ7V,1691,2-10-2017
6,4,5,9FY5B,630,2-10-2017
7,9,4,G0CWS,1523,2-10-2017
8,9,2,PISK6,1778,2-10-2017
9,6,4,35UUY,2107,2-10-2017
10,3,5,5QTJN,1751,2-10-2017
11,6,6,NLC11,526,2-10-2017
12,8,2,C06QP,2308,2-10-2017
13,8,3,XXORI,1453,2-10-2017
14,5,1,E9RGD,1864,2-10-2017
15,7,2,HTL99,1222,2-10-2017
16,3,3,PEE2Y,2050,2-10-2017
17,9,7,GBMNZ,1941,2-10-2017
18,3,1,T8PUB,1440,2-10-2017
19,5,1,3WMOL,2692,2-10-2017
20,7,7,S3AYK,523,2-10-2017
21,1,5,BZPGX,2245,2-10-2017
22,2,1,S8221,2241,2-10-2017
23,9,7,IQ0L8,566,2-10-2017
24,8,5,D8UZW,1769,2-10-2017
25,3,1,RYM9K,1044,2-10-2017
26,4,6,4HXGA,2650,2-10-2017
27,2,2,WFPPY,2203,2-10-2017
28,2,4,93KLU,2289,2-10-2017
29,7,3,P1AGP,1084,2-10-2017
30,4,3,3JHUA,1364,2-10-2017
31,1,4,9LT7O,1198,2-10-2017
32,4,6,4RDHV,771,2-10-2017
33,10,7,T0S7H,873,2-10-2017
34,3,6,NXXK1,2391,2-10-2017
35,8,2,2SIJS,811,2-10-2017
36,8,4,LTSLR,1670,2-10-2017
37,6,7,02SJG,1880,2-10-2017
38,9,3,0MPZ7,2090,2-10-2017
39,2,6,E3NLS,2350,2-10-2017
40,7,6,QYU9Q,1092,2-10-2017
41,6,3,0K682,894,2-10-2017
42,1,5,LS9NW,1928,2-10-2017
43,7,7,NVV0S,951,2-10-2017
44,9,4,996OQ,670,2-10-2017
45,7,6,USKY7,706,2-10-2017
46,10,4,Q4C4I,2270,2-10-2017
47,4,2,UKZGF,1691,2-10-2017
48,10,3,RJDEG,597,2-10-2017
49,10,2,1J184,1921,2-10-2017
50,2,3,5W3CE,2604,2-10-2017
51,5,5,3FY75,1260,2-10-2017
52,1,1,6DEWK,2491,2-10-2017
53,7,5,9YCNB,1743,2-10-2017
54,4,7,6DRYS,2450,2-10-2017
55,5,2,MT2DB,1292,2-10-2017
56,8,5,C4SVV,1395,2-10-2017
57,3,7,ODGZ2,2685,2-10-2017
58,10,4,2S1YI,2617,2-10-2017
59,1,2,YJ6S0,1611,2-10-2017
60,6,3,2Y7KH,2188,2-10-2017
61,5,4,4KQPU,1413,2-10-2017
62,10,1,D0L0Y,2291,2-10-2017
63,5,1,NZQ54,1405,2-10-2017
64,5,2,6P4FT,1885,2-10-2017
65,3,1,PPTNV,1442,2-10-2017
66,1,5,K80OQ,2140,2-10-2017
67,4,5,42W1W,1697,2-10-2017
68,2,7,O8XKY,1007,2-10-2017
69,10,6,R4E3J,887,2-10-2017

So, I took a few minutes to completely refactor your code into proper d3 style. 因此,我花了几分钟时间将代码完全重构为正确的d3样式。 This aims to demonstrate a couple things: 目的是演示几件事情:

  1. The proper use of the enter , update , exit pattern. 正确使用enterupdateexit模式。
  2. Removed cut / paste duplicate code. 删除剪切/粘贴重复的代码。
  3. The proper way to use g to group elements and position them together. 使用g分组元素并将它们放置在一起的正确方法。
  4. How to add the transitions. 如何添加过渡。

Here is the code running . 这是运行代码

Commented code: 注释代码:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  body {
    font: 10px sans-serif;
  }

  .axis path,
  .axis line {
    fill: none;
    stroke: #000;
    shape-rendering: crispEdges;
  }

  .dot {
    stroke: #000;
  }
</style>

<body>
  <div id="chart">
  </div>
  <div id="select_params">

    <input name="updateButton" type="button" value="Update" onclick="updateData()" />

  </div>
</body>

<!-- load js libraries -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<!-- uses v4 of d3 -->

<!-- build the visualization -->
<script type='text/javascript'>
  var item_width = 40,
    item_height = 60;

  var margin = {
      top: 20,
      right: 50,
      bottom: 75,
      left: 40
    },
    width = 700 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

  var x = d3.scaleLinear()
    .range([0, width]);

  var y = d3.scaleLinear()
    .range([height, 0]);

  var color = d3.scaleOrdinal(d3.schemeCategory10);

  var svg = d3.select("#chart").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 + ")");

  // a single function to draw 
  function draw(data, someDate) {

    data.forEach(function(d) {
      d.x_pos = +d.x_pos;
      d.y_pos = +d.y_pos;
      d.sales = +d.sales;
    });

    // pre-filter data
    data = data.filter(function(d) {
      return d.date === someDate
    });

    var x_offset = 5,
      y_offset = 5;

    x.domain(d3.extent(data, function(d) {
      return d.x_pos;
    })); // set the x domain
    y.domain(d3.extent(data, function(d) {
      return d.y_pos;
    })); // set the y domain

    // create an update selection with a key function
    var g_sel = svg.selectAll("g")
      .data(data, function(d) {
        return d.item_name;
      });

    // get rid of those leaving the update
    g_sel.exit().remove();

    // our entering g
    var g_ent = g_sel.enter()
      .append("g");

    // add our rects to our g
    g_ent.append("rect")
      .attr("class", "dot")
      .attr("width", item_width)
      .attr("height", item_height)
      .attr("rx", 5)
      .attr("ry", 5)
      .style("fill", "#1f5fc6") // color factor variable
      .style("fill-opacity", 0.5);

    // add our text to our g
    g_ent.append("text")
      .attr("font-size", 10)
      .attr("text-anchor", "middle")
      .attr("fill", "black")
      .attr("dx", item_width / 2)
      .attr("dy", item_height / 2)
      .text(function(d) {
        return d.item_name
      });

    // UPDATE + ENTER selection
    g_sel = g_ent.merge(g_sel);

    // move them into position with transition
    g_sel
      .transition()
      .attr("transform", function(d) {
        return "translate(" + (x(d.x_pos) + x_offset) + "," + (y(d.y_pos) + y_offset) + ")";
      });
  }

  d3.csv("test.csv", function(data) {

    draw(data, '1-20-2017');

  });


  function updateData() {

    d3.csv("test.csv", function(data) {
      draw(data, '2-10-2017');
    });

  }
</script>

Heres my attempt: https://jsfiddle.net/guanzo/3c9dtLyh/10/ 这是我的尝试: https : //jsfiddle.net/guanzo/3c9dtLyh/10/

There are multiple data points that share the same position, which is why some rectangles are overlapping. 有多个共享相同位置的数据点,这就是为什么某些矩形重叠的原因。 I made a lot of changes to your code that resulted in less repetition. 我对您的代码进行了很多更改,从而减少了重复。

Your data contains duplicate item_names with different dates/positions, but in your visualization you seem to want to only show items at a single date. 您的数据包含重复的item_names ,它们具有不同的日期/位置,但是在您的可视化中,您似乎只想显示单个日期的项目。 Therefore, you only need to pass d3 data for a certain date, versus passing d3 ALL the data and then filtering. 因此,您只需要传递特定日期的d3数据,而不是传递d3所有数据然后进行过滤。

Your code: 您的代码:

svg.selectAll("g")
      .data(data)
    .enter()
      .append("rect")
      .filter(function(d){ return d.date == '1-20-2017'})

My code: 我的代码:

var firstDateData = data.filter(d=>d.date == '1-20-2017');

  var groups = svg.selectAll("g")
      .data(firstDateData, d=> d.item_name)

The difference between these 2 is that in my example, D3 is only aware of a single set of item_names on date 1-20-2017 . 这两个之间的区别在于,在我的示例中,D3仅知道日期为1-20-2017的一组item_names Therefore when i update the date with item_names on date 2-10-2017 , D3 will automatically move all updated rectangles to their new position. 因此,当我使用item_names更新日期为2-10-2017日期2-10-2017 ,D3将自动将所有更新的矩形移动到其新位置。 How? 怎么样?

Here is where your question comes into play: 这是您的问题发挥作用的地方:

I do not neccesarily expect this approach to work because how would the transition know which (x,y) pairs correspond to the correct item name in the new arrangement 我并不一定希望这种方法行得通,因为过渡如何知道新安排中的(x,y)对对应于正确的商品名称

This is because i associated each rectangle with an item_name . 这是因为我将每个矩形与item_name关联。 D3s data function can take an optional 2nd parameter that specifies HOW the data is bound to the rectangles. D3s data函数可以采用可选的第二个参数,该参数指定如何将数据绑定到矩形。 This is called the key function. 这称为按键功能。

svg.selectAll("g").data(firstDateData, d=> d.item_name)

In this case, i told d3 that each group (rectangles and their text) is bound to item_name . 在这种情况下,我告诉d3,每个组(矩形及其文本)都绑定到item_name Therefore, the next time i pass data to D3, it tries to match existing elements (that are associated with an item_name ) to the data (which contains item_names ). 因此,下一次我将数据传递给D3时,它将尝试将现有元素(与item_name关联)与数据(包含item_names )进行匹配。 If in my new data, i pass an item_name that corresponds to an existing element, and the data contains a new x and y position, D3 will move to element to that new position. 如果在我的新数据中,我传递了与现有元素相对应的item_name ,并且数据包含新的x和y位置,则D3将移动到该元素到该新位置。

Note that even though i'm talking about rectangles, i bound data to the g element, which contains the rectangle and the text. 请注意,即使我在谈论矩形,我也会将数据绑定到g元素,该元素包含矩形和文本。

Feel free to ask any questions, i made a lot of changes that i didn't discuss. 随时问任何问题,我做了很多我没有讨论过的更改。

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

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