向d3.js动画气泡图添加痕迹 [英] Adding traces to d3.js animated bubble chart

查看:68
本文介绍了向d3.js动画气泡图添加痕迹的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试制作一个动画时间序列图,该图显示移动点后的踪迹"或蜗牛踪迹.我一直在尝试整合KoGor的 http://bl.ocks.org/KoGor/8163022 ,但是还没有运气-我认为问题出在tweenDash()-原始函数是为单个跟踪而设计的-每个公司都有一个跟踪.下面是一个工作示例-时间序列清理和可移动数据标签有效,但跟踪方面无效.

I'm trying to build an animated time series chart which shows a 'trace' or snail trail following the moving dot. I have been trying to integrate KoGor's http://bl.ocks.org/KoGor/8163022 but haven't had luck- I think the problem lies in tweenDash() - The original function was designed for a single trace- this one has one per company. Attached below is a working example- the time series scrubbing and movable data labels work, just not the trace aspect.

谢谢

RL

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="utf-8">
<body bgcolor="#000000"> 
<title>BPS</title>
<style>

@import url(style.css);

#chart {
  margin-left: -40px;
  height: 506px;
  display:inline;
}

#buffer {
	width: 100px;
	height:506px;
	float:left;
}
text {
  font: 10px sans-serif;
  color: #ffffff;

}

.dot {
  stroke: #000;
}

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

.label {
  fill: #777;
}

.year.label {
  font: 900 125px "Helvetica Neue";
  fill: #ddd;
}

.year.label.active {
  fill: #aaa;
}

.overlay {
  fill: none;
  pointer-events: all;
  cursor: ew-resize;
}

</style>


<div>
<div id="buffer"></div><div id="chart"></div>
</div>


<script src="d3.v3.min.js"></script>
<script>

var source = '[{"name":"ABCD","AUM":[[2010,1000.6],[2011,1200.6],[2012,1300.1],[2013,1400.5],[2014,1600.0]],"AUA":[[2010,3000.6],[2011,3300.2],[2012,4000.0],[2013,4500.8],[2014,6000.3]],"marketPercentage":[[2010,40.4],[2011,39.7],[2012,38.5],[2013,37.1],[2014,36.5]],"fill":[[2010,0],[2011,-1],[2012,-1],[2013,-1],[2014,-1]],"xOffset":[[2010,5],[2011,5],[2012,5],[2013,5],[2014,5]],"yOffset":[[2010,-30],[2011,-20],[2012,-20],[2013,-20],[2014,-10]]},{"name":"EFGH","AUM":[[2010,32.8],[2011,43.2],[2012,58.3],[2013,78.8],[2014,92]],"AUA":[[2010,327.3],[2011,439.3],[2012,547.0],[2013,710.0],[2014,824.0]],"marketPercentage":[[2010,1.0],[2011,1.2],[2012,1.5],[2013,1.8],[2014,1.9]],"fill":[[2010,0],[2011,1],[2012,1],[2013,1],[2014,1]],"xOffset":[[2010,5],[2011,5],[2012,5],[2013,5],[2014,5]],"yOffset":[[2010,-10],[2011,-10],[2012,-10],[2013,-10],[2014,-10]]},{"name":"HIJK","AUM":[[2010,0.1],[2011,0.5],[2012,1.2],[2013,2.4],[2014,2.6]],"AUA":[[2010,159.6],[2011,176.7],[2012,199.9],[2013,235.1],[2014,269.0]],"marketPercentage":[[2010,0.1],[2011,0.1],[2012,0.1],[2013,0.1],[2014,0.1]],"fill":[[2010,0],[2011,0],[2012,0],[2013,1],[2014,1]],"xOffset":[[2010,5],[2011,5],[2012,5],[2013,5],[2014,5]],"yOffset":[[2010,-10],[2011,-10],[2012,-10],[2013,-10],[2014,-10]]}]';


// Various accessors that specify the four dimensions of data to visualize.
function x(d) { return d.AUM; }
function y(d) { return d.AUA; }
function xo(d) {return d.xOffset; }
function yo(d) {return d.yOffset; }
function radius(d) { return d.marketPercentage; }
function key(d) { return d.name; }

// Chart dimensions.


var margin = {top: 19.5, right: 19.5, bottom: 19.5, left: 39.5},
    width = 960 - margin.right,
    height = 500 - margin.top - margin.bottom;

// Various scales. These domains make assumptions of data, naturally.
var xScale = d3.scale.linear().domain([0, 2000]).range([0, width]),
    yScale = d3.scale.linear().domain([0, 5000]).range([height, 0]),
    radiusScale = d3.scale.sqrt().domain([0, 500]).range([0, 40]),
    colorScale = d3.scale.category10();

// The x & y axes.
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).ticks(12, d3.format(",d")),
    yAxis = d3.svg.axis().scale(yScale).orient("left");

// Create the SVG container and set the origin.
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 + ")");

// Add the x-axis.
svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
	.style("fill", "#FFFFFF")
    .call(xAxis);

// Add the y-axis.
svg.append("g")
    .attr("class", "y axis")
	.style("fill", "#FFFFFF")
    .call(yAxis);

// Add an x-axis label.
svg.append("text")
    .attr("class", "x label")
    .attr("text-anchor", "end")
	.style("fill", "#FFFFFF") 
    .attr("x", width)
    .attr("y", height - 6);
    //.text("income per capita, inflation-adjusted (dollars)");

// Add a y-axis label.
svg.append("text")
    .attr("class", "y label")
    .attr("text-anchor", "end")
    .attr("y", 6)
    .attr("dy", ".75em")
				.style("fill", "#FFFFFF") 

    .attr("transform", "rotate(-90)")
 //   .text("life expectancy (years)")
 	;

// Add the year label; the value is set on transition.
var label = svg.append("text")
    .attr("class", "year label")
    .attr("text-anchor", "end")
    .attr("y", height - 24)
    .attr("x", width)
    .text(2010);



//d3.json("investments_v04ANON.json", function(companies) {
	
companies = JSON.parse(source)

  // A bisector since many company's data is sparsely-defined.
  var bisect = d3.bisector(function(d) { return d[0]; });

  // Add a dot per company. Initialize the data at 2010, and set the colors.
  var dot = svg.append("g")
      .attr("class", "dots")
    .selectAll(".dot")
      .data(interpolateData(2010))
    .enter().append("circle")
      .attr("class", "dot")
//      .style("fill", function(d) { return colorScale(color(d)); })
      .style("fill", function(d) {return colorScale(interpolateData(2010)) })
      .call(position)
      .sort(order);
	  
	  
  var lineTraces = svg.append("path")
  		.attr("class", "lineTrace")
		.selectAll(".traces")
		.attr("stroke-width", 2)
		.attr("stroke", "grey")
		.data(interpolateData(2010));


   //yields a mouseover label - "title" precludes need for separate mouseover event.
//  dot.append("title")
//  	.text(function(d) { return d.name; });
//.text(function(d) {return d.AUM});
	  
var theLabel = svg.append("g")
	  .attr("class", "texts")
	  .selectAll(".theLabel")
	  .data(interpolateData(2010))
	  .enter().append("text")
	  .attr("class", "text")
	  .text("hey")
	  .call(position2);

  // Add an overlay for the year label.
  var box = label.node().getBBox();

  var overlay = svg.append("rect")
        .attr("class", "overlay")
        .attr("x", box.x)
        .attr("y", box.y)
        .attr("width", box.width)
        .attr("height", box.height)
        .on("mouseover", enableInteraction);

  // Start a transition that interpolates the data based on year.
  svg.transition()
      .duration(30000)
      .ease("linear")
      .tween("year", tweenYear)
	  .attrTween("stroke-dasharray", tweenDash)
      .each("end", enableInteraction);

  // Positions the dots based on data.
function position(dot) {
    dot .attr("cx", function(d) { return xScale(x(d)); })
        .attr("cy", function(d) { return yScale(y(d)); })
        .attr("r", function(d) { return radiusScale(radius(d)); })
		.style("fill", function(d) {return d.fill>0 ? "green" : "red"} );//{return d.fill});
  }
    

//function  from: http://bl.ocks.org/KoGor/8163022
  function tweenDash() {
    var i = d3.interpolateString("0," + 5, 5 + "," + 5); // interpolation of stroke-dasharray style attr
 //   var l = path.node().getTotalLength();
//    var i = d3.interpolateString("0," + l, l + "," + l); // interpolation of stroke-dasharray style attr
    
	return function(t) {
      var marker = d3.select(".dots");
//      var p = path.node().getPointAtLength(t * l);
      var p = lineTraces.node().getPointAtLength(t * 5);
      marker.attr("transform", "translate(" + p.x + "," + p.y + ")");//move marker
      return i(t);
    }
  }

	
function position2(theLabel) {
theLabel.attr("x", function(d) { return xScale(x(d)) + xo(d); })
        .attr("y", function(d) { return yScale(y(d)) + yo(d); })
		.attr("text-anchor", "end")
		.style("fill", "#FFFFFF")
		.text(function(d) { return d.name + ": AUM:" + Math.round(d.AUM) + ", AUA: " + Math.round(d.AUA) });//{return d.fill});
  }

  // Defines a sort order so that the smallest dots are drawn on top.
function order(a, b) {
    return radius(b) - radius(a);
  }

  // After the transition finishes, you can mouseover to change the year.
  function enableInteraction() {
    var yearScale = d3.scale.linear()
        .domain([2010, 2014])
        .range([box.x + 10, box.x + box.width - 10])
        .clamp(true);

    // Cancel the current transition, if any.
    svg.transition().duration(0);

    overlay
        .on("mouseover", mouseover)
        .on("mouseout", mouseout)
        .on("mousemove", mousemove)
        .on("touchmove", mousemove);

    function mouseover() {
      label.classed("active", true);
   }

    function mouseout() {
      label.classed("active", true);
      label.classed("active", false);
    }

    function mousemove() {
      displayYear(yearScale.invert(d3.mouse(this)[0]));
    }
  }

  // Tweens the entire chart by first tweening the year, and then the data.
  // For the interpolated data, the dots and label are redrawn.
  function tweenYear() {
    var year = d3.interpolateNumber(2010, 2014);
    return function(t) { displayYear(year(t)); };
  }

  // Updates the display to show the specified year.
  function displayYear(year) {
    dot.data(interpolateData(year), key).call(position).sort(order);
	theLabel.data(interpolateData(year), key).call(position2).sort(order);
    label.text(Math.round(year));
  }

  // Interpolates the dataset for the given (fractional) year.
  function interpolateData(year) {
    return companies.map(function(d) {
      return {
//		name: d.name + ": AUM:" + interpolateValues(d.AUM, year) + ", AUA: " + interpolateValues(d.AUA, year),
//		name: d.name + ": AUM:" + d.AUM + ", AUA: " + d.AUA, 
//        name: interpolateValues(d.AUM, year),
        name: d.name,
        AUM: interpolateValues(d.AUM, year),
        marketPercentage: interpolateValues(d.marketPercentage, year),
        AUA: interpolateValues(d.AUA, year),
		fill: interpolateValues(d.fill, year),
		xOffset: interpolateValues(d.xOffset, year),
		yOffset: interpolateValues(d.yOffset, year)
      };
    });
  }

  // Finds (and possibly interpolates) the value for the specified year.
  function interpolateValues(values, year) {
    var i = bisect.left(values, year, 0, values.length - 1),
        a = values[i];
    if (i > 0) {
      var b = values[i - 1],
          t = (year - a[0]) / (b[0] - a[0]);
      return a[1] * (1 - t) + b[1] * t;
    }
    return a[1];
  };
//});

</script>

标记-您构建的第二个版本效果很好.我现在正尝试解决各个线段.我添加了一个属性"toggleSwitch",但是下面的代码运行1x,并且仅捕获对象的初始状态.

Mark- the second version you built works very well. I'm now trying to address the individual line segments. I've added an attribute 'toggleSwitch' but the below code runs 1x and captures only the initial state of the object.

  var lineTraces = svg.append("g")
        .selectAll(".traces")
        .data([0,1,2,4,5,6,7,8,9,10,11,12])
        .enter()
        .append("path")
            .attr("stroke-width", 2)
        .attr("stroke", "grey")
        .attr("class", "lineTrace")
        .attr("d", line)
        .each(function(d,i){
          d3.select(this)
            .datum([someData[i]])
            .attr("nothing", function(i) {console.log(i[0])})
              .attr("d", line)
              .style("stroke-dasharray", function(i) {return (i[0]["toggleSwitch"]<0 ? "0,0": "3,3")})
        });

控制台日志,每个对象一个:

console log, one per object:

Object { name: "TheName", Impact: 120, bubbleSize: 30.4, YoY: 11, toggleSwitch: 0, xOffset: 5, yOffset: -30 }

推荐答案

您链接到的示例具有一个预先建立的路径,然后在其上插入"stroke-dasharray".您的第一个问题是,您需要为每个公司建立该路径.然后就可以补间.

The example you linked to had a pre-established path and then attrTweened the "stroke-dasharray" on it. Your first problem is that you need to establish that path for each company. Then you can tween it.

// set up a line to create the path
var line = d3.svg.line()
  .x(function(d) { return xScale(x(d)); })
  .y(function(d) { return yScale(y(d)); })
  .interpolate("basis");

// for each company add the path
var lineTraces = svg.append("g")
  .selectAll(".traces")
  .attr("fill","red")
  .data([0,1,2]) // 3 companies
  .enter()
  .append("path")
  .attr("stroke-width", 2)
  .attr("stroke", "grey")
  .attr("class", "lineTrace")
  .each(function(d,i){
     // get the line data and add path
     var lineData = [interpolateData(2010)[i],interpolateData(2011)[i],
                     interpolateData(2012)[i],interpolateData(2013)[i],interpolateData(2014)[i]];
      d3.select(this)
        .datum(lineData)
        .attr("d", line);
    });

现在在每个路径上设置过渡:

Now set up the transitions on each path:

lineTraces.each(function(){
  var path = d3.select(this);
  path.transition()
    .duration(30000)
    .ease("linear")
    .attrTween("stroke-dasharray", tweenDash)
});

tweenDash在哪里:

Where tweenDash is:

function tweenDash() {
  var l = lineTraces.node().getTotalLength();
  var i = d3.interpolateString("0," + l, l + "," + l); // interpolation of stroke-dasharray style attr    
  return function(t) {
    var p = lineTraces.node().getPointAtLength(t);
    return i(t);
  }
}

这是一个示例.

您会发现它并不完美,计时已到.如果我有更多时间,我会尝试再将其弄直.

You'll see it's not perfect, the timings are off. If I get a bit more time, I'll try and come back and straighten it out.

编辑

昨晚经过一些思考后,我发现,有一种更简单,更简洁的添加痕迹的方法.无需预先定义路径,然后 attrTween "stroke-dasharray",而只需构建路径即可:

Gave this some thought last night and it dawned on me that there's an easier, more succinct way to add the trace. Instead of pre-defining the path and then attrTweening the "stroke-dasharray", just build the path as you go:

var someData = interpolateData(2010);
// add the paths like before
var lineTraces = svg.append("g")
  .selectAll(".traces")
  .data([0,1,2])
  .enter()
  .append("path")
  .attr("stroke-width", 2)
  .attr("stroke", "grey")
  .attr("class", "lineTrace")
  .attr("d", line)
  .each(function(d,i){
    d3.select(this)
      .datum([someData[i]])
      .attr("d", line);
  });

// Tweens the entire chart by first tweening the year, and then the data.
// For the interpolated data, the dots and label are redrawn.
function tweenYear() {
  var year = d3.interpolateNumber(2010, 2014);
  // added "addTrace" function
  return function(t) { addTrace(year(t)); displayYear(year(t)); };
}

// append the data and draw the path
function addTrace(year){
  var thisData = interpolateData(year);
  lineTraces.each(function(d,i){
    var trace = d3.select(this);
    trace.datum().push(thisData[i]);
    trace.attr("d", line);
  });
}

会产生更好的结果.

这篇关于向d3.js动画气泡图添加痕迹的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆