D3.js in action 笔记

因为本书是英文版的,没有中文版,所以基本上这个笔记是为了代码留用,加之其代码格式和内容都比较简洁,然后又提出了很多问题,值得学习。

其中代码的位置:https://github.com/emeeks/d3_in_action_2

书的话我用的第二版(2018)的,可以私我要。

2

2.1 加载数据 Load Data 

2.1.1 分层数据 Hierarchical data

d3.json("tweets.json", data => {
    var tweetData = data.tweets;
    var nestedTweets = d3.nest()
        .key(d => d.user)
        .entries(tweetData); //输入所有数据
});  

2.1.2 <group>的使用

var tweetG = d3.select("svg")
    .selectAll("g")
    .data(incomingData)
    .enter()
    .append("g")
    .attr("transform", (d, i) => "translate(1, 100)")
//此时返回的是g,定义了x,y,所以在之后就不需要设置x,y

//然后直接向group里添加元素
tweetG.append("circle)
    .attr("r", d=>radiusScale(d.impact))

tweetG.append("text")
    .text(d => d.user)

2.1.3 Enter()

var dataset = [3, 6, 9];
var p = d3.select("body").selectAll("p");
var update = p.data(dataset);
var enter = update.enter();
console.log(update);
console.log(enter);
//update部分的处理方法是直接修改内容
update.text(function(d){return d;});
//enter部分的处理方法是添加元素再修改内容
enter.append("p").text(function(d){return d;})

2.1.4 根据键函数的规则绑定数据

<p>3:张三</p>
<p>6:李四</p>
<p>9:王五</p>
var persons = [{id:6, name:"张三"}, {id:9, name:"李四"}, {id:3, name:"王五"}]
p.data(persons, function(d){ return d.id; })
    .text(function(d){
        return d.id + ":"+d.name;
    })
<p>3:王五</p>
<p>6:张三</p>
<p>9:李四</p>

2.1.5 数据更新时的处理模板

var update = p.data(dataset);
var enter = update.enter();
var exit = update.exit();

update.text( function(d) { return d; });
enter.append("p").text( function(d) { return d; });
exit.remove();

2.2 数据格式 Format Data

2.3 测量数据 Measure Data

2.3.1 分类数据scale

var qScale = d3.scaleQuantile().domain(sampleArray).range([0, 1, 2]);

2.3.2 测量数据 Measuring data

d3.csv("cities.csv", data => {
    d3.min(data, d => +d.population);
    d3.max(data, d => +d.population);
    d3.mean(data, d => +d.population);
});

3 交互 Interactive

3.1 事件 Event

.on("click", buttonClick);
//mouseover, mouseout

3.2 颜色 Colour

//darker和brighter使用环境
var teamColor = d3.rgb("#75739F")
teamColor.darker(.75);
teamColor.brighter(.5);

3.2.1 渐变颜色RGB

var newRamp = d3.scaleLinear().domain([500000, 13000000]).range(["blue", "red"]);
newRamp(1000000); //返回"#0a00f5"
newRamp(9000000); //返回"#ad0052"
newRamp.invert("#ad0052"); //invert function only works with a numeric range, return NaN

3.2.2 渐变颜色HSL

HSL(hue色相,saturation饱和度,lightness亮度)

var ybRamp = d3.scaleLinear()
    .interpolate(d3.interpolateHsl)
    .domain([0, maxValue]).range(["yellow", "blue"])

除此之外,还有HCL(hue, chroma, lightness),LAB(lightess, A-B)

一般来说使用interpolated in RGB tend toward muddy and gray, unless you break the color ramp into multiple stops. 所以使用HSL等会比较好;   

3.2.3 调色板

var tenColorScale = d3.scaleOrdinal()
    .domain(["UEFA", "CONMEBOL"])
    .range(d3.schemeCategory10)
    .unkonwn("#c4b9ac")

https://d3js.org/colorbrewer.v1.min.js

var colorQuantize = d3.scaleQuantize()
    .domain([0, maxValue]).range(colorbrewer.Reds[3]);

3.3 预先生成的内容 Pre-generate Content

3.3.1 Images

 d3.selectAll("g.overallG").insert("image", "text")
    .attr("xlink:href"), d => 'images/${d.team}.png')
    .attr("width", "45px").attr("height", "20px)
    .attr("x", -22).attr("y", -10)

使用insert()而不是append()的原因是先确定image和text写入的先后顺序;一定要确定height和width,不然不会显示image;

3.3.2 SVG 

下载地址:http://thenounproject.com/

  • 加载svgIcon
d3.html("../resources/football.svg", loadSVG);
function loadSVG(svgData){
    d3.select(svgData).selectAll("path").each(function() {
        d3.select("svg").node.appendChild(this);
    });
}
  • 若需要将path添加到每个<g>上
function loadSVG(svgData) {
  d3.selectAll("g").each(function() {
    var gParent = this;
    //不使用箭头函数因为需要绑定context和selection;使用this
    d3.select(svgData).selectAll("path").each(function() {
      gParent.appendChild(this.cloneNode(true));
    });
  });
}
  • 若需要将每个path都添加data数据,方便后续调色等;
//对每个path都绑定数据
d3.selectAll("g.overallG").each(function(d) {
  d3.select(this).selectAll("path").datum(d);
});
//可以找到每个path的.region
var fourColorScale = d3.scaleOrdinal()
  .domain(["UEFA", "CONMEBOL", "CONCACAF", "AFC"])
  .range(["#5eafc6", "#FE9922", "#93C464", "#fcbc34" ]);

d3.selectAll("path")
    .style("fill", p => {
      if (p) {
          return fourColorScale(p.region);
        }
    })
    .style("stroke", "black").style("stroke-width", "2px");
}

4 组件 Chart Components

使用<g> 使得整体通过 transform移动group的位置;

4.1 生成器 Generators

D3生成器由一些函数组成,这些函数获取数据并返回必要的SVG绘图代码,以基于该数据创建图形对象。

什么时候用d3.line()?在大多数画形状和线条的时候,无论是否填充或者是否关闭;

什么时候用d3.area()? 形状的底部需要通过形状的上部分来计算;where the bottom of the shape can be calculated based on the top of the shape as you're drawing it.

4.1.1 线 Line

  • d3.line():expects an array of points as data, needs to tell the generator what values constitute the x and y coordinates for each point;need to set specific accessor functions
//如果存在缺失点,不想要直接插值的话(效果是在线之间存在gap draw lines with gap)
d3.line().defined( d => d.y !== null)

//完整数据
var tweetLine = d3.line()
  .x(d => xScale(d.day))
  .y(d => yScale(d.tweets));

d3.select("svg")
  .append("path")
    .attr("d", tweetLine(data))
    .attr("fill", "none")
    .attr("stroke", "#fe9a22")
    .attr("stroke-width", 2);
  • curve interpolation:曲线插值 curveCardinal
  • 想要线封闭起来(在d里加"Z",=path里的闭口)
d3.select("svg")
    .sppend("path")
    .attr("d", movieArea(data)+"Z")
    .attr("fill", "none")
    .attr("stroke", "black")
    .attr("stroke-width", 3)

4.1.2 面积 Area

  • d3.area():= d3.line() + provide helper functions to bound the lower and of your path to produce areas in charts;define a.y0() accessor that corresponds to y accessor and determines the shape of the bottom of our area.
Object.keys(data[0]).forEach(key => {
  if (key != "day") {
    var movieArea = d3.area()
      .x(d => xScale(d.day))
      .y0(d => yScale(d[key]))
      .y1(d => yScale(-d[key]))
      .curve(d3.curveCardinal);
    d3.select("svg")
      .append("path")
        .style("id", `${key} Area`)
        .attr("d", movieArea(data));
  };
});
  • 计算stackedArea:其中.every()一旦返回false则停止遍历,其中lineData传入的数据相当于:某一天的所有key+value
function simpleStacking( lineData, lineKey) {
  var newHeight = 0;
  Object.keys(lineData).every(key => {
    if (key !== "day") {
      newHeight += parseInt(lineData[key]);
      if (key === lineKey) {
        return false;
      }
    }
    return true;
  });
  return newHeight;
}
//adding each value until it reaches the current named attribute. As a result.
//it returns the total value for every movie during this day up to the movie we've sent.
  • 使用
var n = 0;
Object.keys(data[0]).forEach(key => {
  if (key != "day") {
    var movieArea = d3.area()
      .x(d => xScale(d.day))
      .y0(d => yScale(simpleStacking(d,key) - d[key]))
      .y1(d => yScale(simpleStacking(d, key)))
      .curve(d3.curveBasis);
    d3.select("svg")
      .append("path")
        .style("id", `${key} Area`)
        .attr("d", movieArea(data))
        .attr("fill", fillScale(key))
        .attr("stroke", "black")
        .attr("stroke-width", 1);
    n++;
  }
});

4.2 为你的图表添加Legend

http://d3-legend.susielu.com

var fillScale = d3.scaleOrdinal()
  .domain(["titanic", "avatar", "akira", "frozen", "deliverance", "avengers"])
  .range(["#fcd88a", "#cf7c1c", "#93c464", "#75734F", "#5eafc6", "#41a368"]);
        
var legendA = d3.legendColor().scale(fillScale);

d3.select("svg")
  .append("g")
    .attr("transform", "translate(500, 0)")
    .call(legendA);

还有其他的表现形式,具体参照第三方git

4.3 plotting 散点图

var xExtent = d3.extent(scatterData, d => d.salary);
var yExtent = d3.extent(scatterData, d => d.friends);
var xScale = d3.scaleLinear().domain(xExtent).range([0,500]);
var yScale = d3.scaleLinear().domain(yExtent).range([0,500]);
      
var xAxis = d3.axisBottom().scale(xScale)
    .tickSize(500).ticks(4);
      
d3.select("svg").append("g").attr("id", "xAxisG").call(xAxis);
      
var yAxis = d3.axisRight().scale(yScale)
    .ticks(16).tickSize(500);
      
d3.select("svg").append("g").attr("id", "yAxisG").call(yAxis);
      
d3.select("svg").selectAll("circle")
    .data(scatterData).enter()
    .append("circle").attr("r", 5)
    .attr("cx", d => xScale(d.salary))
     .attr("cy", d => yScale(d.friends));

4.4 组件 Components

In contrast with generators, which produce the attribute string necessary for a <path> element, components create an entire set of graphical objects necessary for a particular chart component. 

d3.axis()

d3.brush()

 

5 布局 Layouts

D3 layouts can be rather straightforward, as the pie chart layout. Layouts take in one or more arrays of data, and sometimes generators, and append attributes to the data necessary to draw it in certain positions or sizes.

5.1 直方图 Histogram

var histoChart = d3.histogram();

histoChart
  .domain([ 0, 5 ]) //x轴范围
  .thresholds([ 0, 1, 2, 3, 4, 5 ]) //区分
  .value(d => d.favorites.length);

histoData = histoChart(tweetsData);
console.log(histoData);
d3.select("svg")
  .selectAll("rect")
  .data(histoData).enter()
  .append("rect")
    .attr("x", d => xScale(d.x0))
    .attr("y", d => yScale(d.length))
    .attr("width", d => xScale(d.x1 - d.x0) - 2)
    .attr("height", d => 400 - yScale(d.length))
    .style("fill", "#FCD88B");

根据d.favorites.length的大小来分段;d.x0:0, d.x1:1, d.length:4;d.x0:1, d.x1:2, d.length:3;依此类推

数据

{
"tweets": [
{"user": "Al", "content": "I really love seafood.", "timestamp": " Mon Dec 23 2013 21:30 GMT-0800 (PST)", "retweets": ["Raj","Pris","Roy"], "favorites": ["Sam"]},
{"user": "Al", "content": "I take that back, this doesn't taste so good.", "timestamp": "Mon Dec 23 2013 21:55 GMT-0800 (PST)", "retweets": ["Roy"], "favorites": []},
{"user": "Al", "content": "From now on, I'm only eating cheese sandwiches.", "timestamp": "Mon Dec 23 2013 22:22 GMT-0800 (PST)", "retweets": [], "favorites": ["Roy","Sam"]},
{"user": "Roy", "content": "Great workout!", "timestamp": " Mon Dec 23 2013 7:20 GMT-0800 (PST)", "retweets": [], "favorites": []},
{"user": "Roy", "content": "Spectacular oatmeal!", "timestamp": " Mon Dec 23 2013 7:23 GMT-0800 (PST)", "retweets": [], "favorites": []},
{"user": "Roy", "content": "Amazing traffic!", "timestamp": " Mon Dec 23 2013 7:47  GMT-0800 (PST)", "retweets": [], "favorites": []},
{"user": "Roy", "content": "Just got a ticket for texting and driving!", "timestamp": " Mon Dec 23 2013 8:05 GMT-0800 (PST)", "retweets": [], "favorites": ["Sam", "Sally", "Pris"]},
{"user": "Pris", "content": "Going to have some boiled eggs.", "timestamp": " Mon Dec 23 2013 18:23 GMT-0800 (PST)", "retweets": [], "favorites": ["Sally"]},
{"user": "Pris", "content": "Maybe practice some gymnastics.", "timestamp": " Mon Dec 23 2013 19:47  GMT-0800 (PST)", "retweets": [], "favorites": ["Sally"]},
{"user": "Sam", "content": "@Roy Let's get lunch", "timestamp": " Mon Dec 23 2013 11:05 GMT-0800 (PST)", "retweets": ["Pris"], "favorites": ["Sally", "Pris"]}
]
}

改变绑定:

histoChart.value(d => d.retweets.length);
histoData = histoChart(tweetsData);

d3.histogram 相当于,传入数据,以及分类的规则,比例尺等;它则返回根据你的规则分类的数据;

5.2 饼图 Pie charts(简单数据)

var pieChart = d3.pie();
var yourPie = pieChart([1,1,2]);

var newArc = d3.arc();
newArc.innerRadius(0)
  .outerRadius(100);

d3.select("svg")
  .append("g")
  .attr("transform","translate(250,250)")
  .selectAll("path")
  .data(yourPie)
  .enter()
  .append("path")
  .attr("d", newArc)

4.3 饼图 Pie chart(导入数据)

d3.json("../data/tweets.json", pieChart)

function pieChart(data) {
  
  var nestedTweets = d3.nest()
    .key(d => d.user)
    .entries(data.tweets);
    console.log(nestedTweets);
  nestedTweets.forEach(d => {
    d.numTweets = d.values.length;
    d.numFavorites = d3.sum(d.values, p => p.favorites.length);
    d.numRetweets = d3.sum(d.values, p => p.retweets.length);
  });
  
  var pieChart = d3.pie();
  
  var newArc = d3.arc();
  newArc
    .innerRadius(20)
    .outerRadius(100);
  
  pieChart.value(d => d.numTweets);
  
  var yourPie = pieChart(nestedTweets);
  
  var fillScale = d3.scaleOrdinal()
    .range(["#fcd88a", "#cf7c1c", "#93c464", "#75734F"]);
  
  d3.select("svg")
    .append("g")
      .attr("transform","translate(250,250)")
    .selectAll("path")
    .data(yourPie)
    .enter()
    .append("path")
      .attr("d", newArc)
      .style("fill", (d,i) => fillScale(i))
      .style("stroke", "black")
      .style("stroke-width", "2px");
}

问题一:由于每次调用这个layout,它会根据数据从大到小排序,以便画出一个可读性更强的图表;因此二次调用,更新数据也会重新排序,为了防止图表transition时的distortion,可以设置:pieChart.sort(null); 此时对象在数组里的顺序没有变化;

问题二:由于pie chart在数据由多到少时,即transition时出现了0值,动画效果会干瘪到只剩一条线后再扩张;总之看起来就有很问题,也会报错;解决方案:把起始弧度和结束弧度存下来,利用插值+attrTween()来进行连续动画放映;

nestedTweets.forEach((d,i) => {
  d.tweetsSlice = tweetsPie[i];
  d.retweetsSlice = retweetsPie[i];
});

//调用
.attr("d", d => newArc(d.tweetsSlice))

d3.selectAll("path")
  .transition()
    .duration(1000)
    .attrTween("d", arcTween);

function arcTween(d) {
  return t => {
    var interpolateStartAngle = d3.interpolate(d.tweetsSlice.startAngle, d.retweetsSlice.startAngle);
    var interpolateEndAngle = d3.interpolate(d.tweetsSlice.endAngle, d.retweetsSlice.endAngle);
    d.startAngle = interpolateStartAngle(t);
    d.endAngle = interpolateEndAngle(t);
    return newArc(d);
  };
}

5.4 堆叠面积图 Stack layout

5.4.1 Stack layout 设置

stackLayout = d3.stack()
  .offset(d3.stackOffsetSilhouette)//居中
  .order(d3.stackOrderInsideOut)//上面最小,下面最大

5.4.2 原始堆叠面积图

这里的数据非常适合默认的stack()函数,所以直接调用即可,它会自己生成堆叠数据;
至于生成什么样的堆叠数据,就相当于,layout 根据x(天数),创造了一个包含了[y0, y1]的数组,来表示某一个的top和bottom

stackLayout = d3.stack()
  .keys(movies);

var stackArea = d3.area()
  .x((d, i) => xScale(i))
  .y0(d => yScale(d[0]))
  .y1(d => yScale(d[1]));

d3.select("svg").selectAll("path")
  .data(stackLayout(data))
  .enter().append("path")
    .style("fill", d => fillScale(d.key))
    .attr("d", d => stackArea(d));

5.4.3 堆叠柱形图

d3.select("svg").selectAll("g.bar")
  .data(stackLayout(data))
  .enter()
  .append("g")
    .attr("class", "bar")
    .each(function(d) {
      d3.select(this).selectAll("rect")
        .data(d)
        .enter()
        .append("rect")
          .attr("x", (p,q) => xScale(q) + 30)
          .attr("y", p => yScale(p[1]))
          .attr("height", p => heightScale(p[1] - p[0]))
          .attr("width", 40)
          .style("fill", fillScale(d.key));
    });

效果:

5.5 桑基图 Sankey diagram

网址:http://github.com/d3/d3-sankey

数据集:

{"nodes":[
{"name":"index"},
{"name":"about"},
{"name":"contact"},
{"name":"store"},
{"name":"cheese"},
{"name":"yogurt"},
{"name":"milk"}
],
"links":[
{"source":0,"target":1,"value":25},
{"source":0,"target":2,"value":10},
{"source":0,"target":3,"value":40},
{"source":1,"target":2,"value":10},
{"source":3,"target":4,"value":25},
{"source":3,"target":5,"value":10},
{"source":3,"target":6,"value":5},
{"source":4,"target":6,"value":5},
{"source":4,"target":5,"value":15}
]
}
var sankey = d3.sankey()
  .nodeWidth(20)
  .nodePadding(200)
  .size([460, 460])
  .nodes(data.nodes)
  .links(data.links)
  .layout(200);

d3.select("svg").append("g")
    .attr("transform", "translate(20,20)").attr("id", "sankeyG");

d3.select("#sankeyG").selectAll(".link")
  .data(data.links)
  .enter().append("path")
    .attr("class", "link")
    .attr("d", sankey.link())
    .style("stroke-width", d => d.dy) //这里的dy是指每条连接线的宽度
    .style("stroke-opacity", .5)
    .style("fill", "none")
    .style("stroke", d => intensityRamp(d.value))
    .sort((a, b) => b.dy - a.dy)
    .on("mouseover", function() {
      d3.select(this).style("stroke-opacity", .8);
    })
    .on("mouseout", () => {
      d3.selectAll("path.link").style("stroke-opacity", .5);
    });

d3.select("#sankeyG").selectAll(".node")
  .data(data.nodes)
  .enter().append("g")
    .attr("class", "node")
    .attr("transform", d => `translate(${d.x} , ${d.y} )`);//这里的d.x,d.y是指矩形的位置

d3.selectAll(".node").append("rect")
    .attr("height", d => d.dy)//这里的dy只指矩形的宽
    .attr("width", 20)
    .style("fill", "#93c464")
    .style("stroke", "gray");

设置node为圆形:

sankey.ondeWidth(1);
d3.selectAll(".node").append("circle")
    .attr("height", d => d.dy)
    .attr("r", d => d.dy / 2)
    .attr("cy", d => d.dy / 2)
    .style("fill", "#93c464");  

设置runMoreLayouts() 用来设置更新数据时,从a->b通过变化多少次;(position optimization)

function moreLayouts() {
  numLayouts += 20;
  sankey.layout(numLayouts);
  d3.selectAll(".link")
    .transition()
      .duration(500)
      .attr("d", sankey.link());
  d3.selectAll(".node")
    .transition()
      .duration(500)
      .attr("transform", d => `translate(${d.x} , ${d.y} )`);
}

5.6 词云 Word cloud

网址:https://github.com/sly7-7/d3-cloud

文档:www.jasondavies.com/wordcloud/about

d3.csv("../data/worddata.csv", dataViz);

function dataViz(data) {
  
  var wordScale=d3.scaleLinear().domain([0,75]).range([10,160]);
  randomRotate=d3.scaleLinear().domain([0,1]).range([-20,20]);
  
  d3.cloud()
    .size([500, 500])
    .words(data)
    .rotate( () => randomRotate(Math.random()))
    .fontSize(d => wordScale(d.frequency))
    .on("end", draw)
    .start();
  
  function draw(words) {
    var wordG = d3.select("svg").append("g")
        .attr("id", "wordCloudG")
        .attr("transform","translate(250,250)");
    
    wordG.selectAll("text")
      .data(words)
      .enter()
      .append("text")
        .style("font-size", d => d.size + "px")
        .style("fill", "#4F442B")
        .attr("text-anchor", "middle")
        .attr("transform", d =>
          `translate(${[d.x, d.y]})rotate(${d.rotate})`)
        .text(d => d.text);
  }
}

5.x 其他的D3 plugins

网址:https://github.com/d3/d3-plugins

6 分层可视化

6.1数据处理

6.1.1 分层数据 hierarchical data

var root = {id: "All Tweets", children: [
{id: "Al's Tweets", children: [{id: "tweet1"}, {id: "tweet2"}]},
{id: "Roy's Tweets", children: [{id: "tweet1"}, {id: "tweet2"}]}...
var root = d3.hierarchy(root, function(d) {return d.children});

6.1.2 对象数组 Object array

//an array of objects that looked like this:
{
    cell: "gifs everywhere", 
    country: "Norway",
    metric: "recommendations",
    significance: 0.07408233813305311,
    subscription: "deluxe",
    value: 0.4472930460902817
}
var nestedDataset = d3.nest()
    .key(d => d.cell)
    .key(d => d.metric)
    .key(d => d.subscription)
    .key(d => d.country)
    .entries(dataset)

6.1.3 表格数据 in tabular format

child, parent
you, your mom
you, your dad
your dad, your paternal grandfather
your dad, your maternal grandmother
your mom, your maternal grandfahter
your mom, your maternal grandmother
, you
d3.stratify()
    .parentID(d => d.child)
    .id(d => d.parent)

在这里让parentID设为child的原因是:因为root节点必须是单个节点;

6.2 Pack layout

数据:tweets.json

{
"tweets": [
{"user": "Al", "content": "I really love seafood.", "timestamp": " Mon Dec 23 2013 21:30 GMT-0800 (PST)", "retweets": ["Raj","Pris","Roy"], "favorites": ["Sam"]},
{"user": "Al", "content": "I take that back, this doesn't taste so good.", "timestamp": "Mon Dec 23 2013 21:55 GMT-0800 (PST)", "retweets": ["Roy"], "favorites": []},
{"user": "Al", "content": "From now on, I'm only eating cheese sandwiches.", "timestamp": "Mon Dec 23 2013 22:22 GMT-0800 (PST)", "retweets": [], "favorites": ["Roy","Sam"]},
{"user": "Roy", "content": "Great workout!", "timestamp": " Mon Dec 23 2013 7:20 GMT-0800 (PST)", "retweets": [], "favorites": []},
{"user": "Roy", "content": "Spectacular oatmeal!", "timestamp": " Mon Dec 23 2013 7:23 GMT-0800 (PST)", "retweets": [], "favorites": []},
{"user": "Roy", "content": "Amazing traffic!", "timestamp": " Mon Dec 23 2013 7:47  GMT-0800 (PST)", "retweets": [], "favorites": []},
{"user": "Roy", "content": "Just got a ticket for texting and driving!", "timestamp": " Mon Dec 23 2013 8:05 GMT-0800 (PST)", "retweets": [], "favorites": ["Sam", "Sally", "Pris"]},
{"user": "Pris", "content": "Going to have some boiled eggs.", "timestamp": " Mon Dec 23 2013 18:23 GMT-0800 (PST)", "retweets": [], "favorites": ["Sally"]},
{"user": "Pris", "content": "Maybe practice some gymnastics.", "timestamp": " Mon Dec 23 2013 19:47  GMT-0800 (PST)", "retweets": [], "favorites": ["Sally"]},
{"user": "Sam", "content": "@Roy Let's get lunch", "timestamp": " Mon Dec 23 2013 11:05 GMT-0800 (PST)", "retweets": ["Pris"], "favorites": ["Sally", "Pris"]}
]
}

代码

d3.json("../data/tweets.json", viz);

function viz(data) {
  
  var depthScale = d3.scaleOrdinal()
    .range(["#5EAFC6", "#FE9922", "#93c464", "#75739F"]);
  
  var nestedTweets = d3.nest()
    .key(d => d.user)
    .entries(data.tweets);
  
  var packableTweets = {id: "All Tweets", values: nestedTweets};//相当于把所有的数据存进去
  var packChart = d3.pack();
  
  packChart.size([500,500]);
  
  var root = d3.hierarchy(packableTweets, d => d.values)//创建函数
    .sum(() => 1);
  //console.log(packChart(root).descendants()); //可以看函数输出的数据效果
  d3.select("svg")
    .append("g")
      .attr("transform", "translate(100,20)")
    .selectAll("circle")
    .data(packChart(root).descendants())
    .enter()
    .append("circle")
      .attr("r", d => d.r)
      .attr("cx", d => d.x)
      .attr("cy", d => d.y)
      .style("fill", d => depthScale(d.depth))
      .style("stroke", "black");
}

实现效果

问题1:中间有一个绿色的圈圈其实应该有父节点的,但是因为其大小是一样的所以覆盖了,在这里可以添加padding

packChart.padding(10);

效果

问题2:将sum()函数从:

  var root = d3.hierarchy(packableTweets, d => d.values)
    .sum(() => 1);

修改为:

var root = d3.hierarchy(packableTweets, d => d.values)
    .sum(d => d.retweets ? d.retweets.length + d.favorites.length + 1 : undefined);

其中修改的部分是绿色的圈圈的大小,大小从固定不变的1,变成了由retweets和favorites共同确定的size;

Pack Layout适用场景:trying to focus on the thing at the bottom of the circle pack, the leaf nodes, and how they're sorted by the various categories you've nested them by.(关注叶子节点)map well to individual things of the same type and that we don't think of as varying in size.(适合分类而不是通过尺寸大小)

6.3 系统树图 Dendrogram

数据如上处理

6.3.1 d3.tree()

代码:

d3.json("../data/tweets.json", viz);

function viz(data) {
  
  var depthScale = d3.scaleOrdinal()
    .range(["#5EAFC6", "#FE9922", "#93c464", "#75739F"]);
  
  var nestedTweets = d3.nest()
    .key(d => d.user)
    .entries(data.tweets);
  
  var packableTweets = {id: "All Tweets", values: nestedTweets};
  
  var root = d3.hierarchy(packableTweets, d => d.values);
  
  var treeChart = d3.tree();
  treeChart.size([500,500]);
  
  var treeData = treeChart(root).descendants();
  
  d3.select("svg")
    .append("g")
      .attr("id", "treeG")
      .attr("transform", "translate(60,20)")
    .selectAll("g")
    .data(treeData)
    .enter()
    .append("g")
      .attr("class", "node")
      .attr("transform", d => `translate(${d.x},${d.y})`);
  
  d3.selectAll("g.node")
    .append("circle")
      .attr("r", 10)
      .style("fill", d => depthScale(d.depth))
      .style("stroke", "white")
      .style("stroke-width", "2px");
  
  d3.selectAll("g.node")
    .append("text")
      .style("text-anchor", "middle")
      .style("fill", "#4f442b")
      .text(d => d.data.id || d.data.key || d.data.content);
  
  d3.select("#treeG").selectAll("line")
    .data(treeData.filter(d => d.parent))
    .enter().insert("line","g")
      .attr("x1", d => d.parent.x)
      .attr("y1", d => d.parent.y)
      .attr("x2", d => d.x)
      .attr("y2", d => d.y)
      .style("stroke", "black");
}

效果:

问题1:字体遮挡

解决方案:将d.x,d.y对调

.attr("transform", d => `translate(${d.y},${d.x})`);

若想把图形改成radial graph(放射性)图,使用Projection

d3.select("#treeG").selectAll("line")
  .data(treeData.filter(d => d.parent))
  .enter().insert("line","g")
    .attr("x1", d => project(d.parent.x, d.parent.y)[0])
    .attr("y1", d => project(d.parent.x, d.parent.y)[1])
    .attr("x2", d => project(d.x, d.y)[0])
    .attr("y2", d => project(d.x, d.y)[1])
    .style("stroke", "black");

function project(x, y) {
  var angle = x / 90 * Math.PI;
  var radius = y;
  return [radius * Math.cos(angle), radius * Math.sin(angle)];
}

效果:

6.3.2 d3.cluster vs d3.tree

左图的在同一level的结点都放在了同一层,但是这样会浪费很多空间和线条,不推荐这样做,如果要这样的话,最好有很值得的理由。

Dendrograms的使用场景:should be used when each parent and child is of the same type (like a word or sentence fragment in a word tree) and the focus is on paths and forks in the path.(父节点和子节点的类型一致,关注连接或者连接点)

6.4 冰锥图 icicle chart

6.4.1 长方形

function viz(data) {
  
  var depthScale = d3.scaleOrdinal()
    .range(["#5EAFC6", "#FE9922", "#93c464", "#75739F"]);
  
  var nestedTweets = d3.nest()
    .key(d => d.user)
    .entries(data.tweets);
  
  var packableTweets = {id: "All Tweets", values: nestedTweets};
  
  var root = d3.hierarchy(packableTweets, d => d.values)
    .sum(d => d.retweets ? d.retweets.length + d.favorites.length + 1 : undefined);
  
  var partitionLayout = d3.partition()
    .size([500,300]);
  
  partitionLayout(root);
  
  d3.select("svg")
    .selectAll("rect")
    .data(root.descendants())
    .enter()
    .append("rect")
      .attr("x", d => d.x0)
      .attr("y", d => d.y0)
      .attr("width", d => d.x1 - d.x0) 
      .attr("height", d => d.y1 - d.y0)
      .style("fill", d => depthScale(d.depth))
      .style("stroke", "black");
}

6.4.2 圆形

  var partitionLayout = d3.partition()
    .size([2 * Math.PI,250]);
  
  partitionLayout(root);
  
  var arc = d3.arc()
    .innerRadius(d => d.y0)
    .outerRadius(d => d.y1);
  
  d3.select("svg")
    .append("g")
      .attr("transform", "translate(255,255)")
    .selectAll("path")
    .data(root.descendants())
    .enter()
    .append("path")
      .attr("d", d => arc({
        y0: d.y0,
        y1: d.y1,
        startAngle: d.x0,
        endAngle: d.x1
      }))
      .style("fill", d => depthScale(d.depth))
      .style("stroke", "black");

6.5 树图 Treemap

function viz(data) {
  
  var depthScale = d3.scaleOrdinal()
    .range(["#5EAFC6", "#FE9922", "#93c464", "#75739F"]);
  
  var nestedTweets = d3.nest()
    .key(d => d.user)
    .entries(data.tweets);
  
  var packableTweets = {id: "All Tweets", values: nestedTweets};
  
  var root = d3.hierarchy(packableTweets, d => d.values)
    .sum(d => d.retweets ? d.retweets.length + d.favorites.length + 1 : undefined);
  
  var treemapLayout = d3.treemap()
    .size([500,500])
    .padding(d => d.depth * 5 + 5);
  
  treemapLayout(root);
  
  d3.select("svg")
    .selectAll("rect")
    .data(root.descendants(),
      d => d.data.content || d.data.user || d.data.key)
    .enter()
    .append("rect")
      .attr("x", d => d.x0)
      .attr("y", d => d.y0)
      .attr("width", d => d.x1 - d.x0)
      .attr("height", d => d.y1 - d.y0)
      .style("fill", d => depthScale(d.depth))
      .style("stroke", "black");
}

7 网络可视化 Network visualization

  .grid {
    stroke: #9A8B7A;
    stroke-width: 1px;
    fill: #CF7D1C;
  }
  .arc {
    stroke: #9A8B7A;
    fill: none;
  }
  .node {
    fill: #EBD8C1;
    stroke: #9A8B7A;
    stroke-width: 1px;
  }
  text {
    font-size: 8px;
  }
  circle.active {
    fill: #FE9922;
  }
  path.active {
    stroke: #FE9922;
  }

7.1 邻接矩阵 Adjacency matrix

The most effective network visualization has no connecting lines at all. Instead, the adjacency matrix uses a grid to represent connections between nodes, with the graphical rules of the chart.(最有效的表达网络的图像,其实是没有连线的,通过邻接矩阵来表示)

但是d3并没有提供相应的Layout,所以https://bost.ocks.org/mike/miserables/(tql)

连接方向:y-axis 上的点是 source, x-axis 上的点是 target.

7.1.1 Promise

异步:廖雪峰 JavaScript Promise

在这里为什么要使用异步?因为此时需要加载两个csv文件,一个点集,一个边集,后续的操作需要等两个数据集都加载成功才能进行;

如何使用异步:

1.We need to wrap our calls in promises to use promise.all

2.Promise.all returns an array of results in order of the promises that were sent.

var PromiseWrapper = d => new Promise(resolve => d3.csv(d, p => resolve(p)));

Promise
  .all([
    PromiseWrapper("../data/nodelist.csv"),
    PromiseWrapper("../data/edgelist.csv")
  ])
  .then(resolve => {
    createAdjacencyMatrix(resolve[0], resolve[1]);
  });

7.1.2 Adjacency matrix function

效果:

function createAdjacencyMatrix(nodes, edges) {
  var edgeHash = {};
  edges.forEach(edge => {
    var id = `${edge.source}-${edge.target}`;
    edgeHash[id] = edge;
  });
  
//这里的a,b均为点的index,其中a为source的index,b为target的index
  var matrix = [];
  nodes.forEach((source, a) => {
    nodes.forEach((target, b) => {
      var grid = {
        id: `${source.id}-${target.id}`,
        x: b,
        y: a,
        weight: 0
      };
      if (edgeHash[grid.id]) {
        grid.weight = edgeHash[grid.id].weight;
      }
      matrix.push(grid);
    });
  });
  
  d3.select("svg")
    .append("g")
      .attr("transform", "translate(50,50)")
      .attr("id", "adjacencyG")
    .selectAll("rect")
    .data(matrix)
    .enter()
    .append("rect")
      .attr("class", "grid")
      .attr("width", 25)
      .attr("height", 25)
      .attr("x", d => d.x * 25)
      .attr("y", d => d.y * 25)
      .style("fill-opacity", d => d.weight * .2);
  
  d3.select("svg")
    .append("g")
      .attr("transform", "translate(50,45)")
    .selectAll("text")
    .data(nodes)
    .enter()
    .append("text")
      .attr("x", (d,i) => i * 25 + 12.5)
      .text(d => d.id)
      .style("text-anchor", "middle");
  
  d3.select("svg")
    .append("g")
      .attr("transform", "translate(45,50)")
    .selectAll("text")
    .data(nodes)
    .enter()
    .append("text")
      .attr("y", (d,i) => i * 25 + 12.5)
      .text(d => d.id)
      .style("text-anchor", "end");
}

7.1.3 添加交互效果(高亮行和列)

d3.selectAll("rect.grid").on("mouseover", gridOver);
function gridOver(d) {
  d3.selectAll("rect")
    .style("stroke-width", function (p) {
      return p.x == d.x || p.y == d.y ? "4px" : "1px";
    });
}

7.2 圆弧图 Arc diagram

鉴于邻接矩阵可以让你快速看到边的动态,而弧度图可以让你快速的看到点的动态;

注意!在结点平面(node plane)上方表示A->B,下方则表示B->A;

7.2.1 arc diagram code

function createArcDiagram(nodes,edges) {
  var nodeHash = {};
  nodes.forEach((node, x) => {
    nodeHash[node.id] = node
    node.x = parseInt(x) * 30
  });
  
  edges.forEach(edge => {
    edge.weight = parseInt(edge.weight)
    edge.source = nodeHash[edge.source]
    edge.target = nodeHash[edge.target]
  });
  
  var arcG = d3.select("svg").append("g").attr("id", "arcG")
      .attr("transform", "translate(50,250)");
  
  arcG.selectAll("path")
    .data(edges)
    .enter()
    .append("path")
      .attr("class", "arc")
      .style("stroke-width", d => d.weight * 2)
      .style("opacity", .25)
      .attr("d", arc);
  
  arcG.selectAll("circle")
    .data(nodes)
    .enter()
    .append("circle")
      .attr("class", "node")
      .attr("r", 10)
      .attr("cx", function (d) {return d.x;});
  
  function arc(d,i) {
    var draw = d3.line().curve(d3.curveBasis);
    var midX = (d.source.x + d.target.x) / 2;
    var midY = (d.source.x - d.target.x);
    //画三个点
    return draw([[d.source.x,0],[midX,midY],[d.target.x,0]]);
  }
}

7.1.2 交互 Arc diagram interactivity

d3.selectAll("circle").on("mouseover", nodeOver);
d3.selectAll("path").on("mouseover", edgeOver);
//高亮该点,以及与该点相连的所有边
function nodeOver(d) {
  d3.selectAll("circle").classed("active", p => p === d);
  d3.selectAll("path").classed("active", p => p.source === d
    || p.target === d);
}
//高亮与该边,与该边相连的点
function edgeOver(d) {
  d3.selectAll("path").classed("active", p => p === d);
  d3.selectAll("circle")
    .classed("source", p => p === d.source)
    .classed("target", p => p === d.target);
}

更深入的学习:hive plot

github:hive plot、展示:bost.ocks hive

7.3 力导向图 Force-directed layout

7.3.1 三种forece-directed算法

attraction/repulsion,gravity,and link attraction.

7.3.2 力导向图模拟 An initial force simulation

将所有的点给一个圆形,通过simulation让它们全部都处于一个中心;

var roleScale = d3.scaleOrdinal()
  .range(["#75739F", "#41A368", "#FE9922"]);

var sampleData = d3.range(9).map((d,i) => ({r: 50 - i * 5}));
console.log(sampleData);
var manyBody = d3.forceManyBody().strength(10);
var center = d3.forceCenter().x(250).y(250);

var force = d3.forceSimulation()
  .force("charge", manyBody)
  .force("center", center)
  .nodes(sampleData)
  .on("tick", updateNetwork);

d3.select("svg")
  .selectAll("circle")
  .data(sampleData)
  .enter()
  .append("circle")
    .attr("class", "node")
    .style("fill", (d, i) => roleScale(i))
    .attr("r", d => d.r);

function updateNetwork() {
  d3.selectAll("circle")
      .attr("cx", d => d.x)
      .attr("cy", d => d.y);
}

7.3.3 碰撞检测 An initial force simulation with collision detection     

 

var force = d3.forceSimulation()
  .force("charge", manyBody)
  .force("center", center)
  .nodes(sampleData)
  .on("tick", updateNetwork)
  .force("collision", d3.forceCollide(d => d.r));

7.3.4 蜂蜡图 Beeswrm plot

var sampleData = d3.range(300).map(() =>
  ({r: 2, value: 200 + d3.randomNormal()() * 50}));

var force = d3.forceSimulation()
  .force("collision", d3.forceCollide(d => d.r))
  .force("x", d3.forceX(100))//其余的点尽量往x=100靠
  .force("y", d3.forceY(d => d.value).strength(3))//y的位置由value的值来确定
  .nodes(sampleData)
  .on("tick", updateNetwork);

通过保持每个圆点看出分布情况。

7.3.5 力导向图 Force-directed network diagram

var roleScale = d3.scaleOrdinal()
  .domain(["contractor", "employee", "manager"])
  .range(["#75739F", "#41A368", "#FE9922"]);

var PromiseWrapper = d => new Promise(resolve => d3.csv(d, p => resolve(p)));

Promise
  .all([
    PromiseWrapper("../data/nodelist.csv"),
    PromiseWrapper("../data/edgelist.csv")
  ])
  .then(resolve => {
    createForceLayout(resolve[0], resolve[1]);
  });

function createForceLayout(nodes,edges) {
  var nodeHash = {};
  nodes.forEach(node => {
    nodeHash[node.id] = node;
  });
  
  edges.forEach(edge => {
    edge.weight = parseInt(edge.weight);
    edge.source = nodeHash[edge.source];
    edge.target = nodeHash[edge.target];
  });
  
  var linkForce = d3.forceLink();
  
  var simulation = d3.forceSimulation()
    .force("charge", d3.forceManyBody().strength(-40))//负数表示斥力,正数表示吸引力
    .force("center", d3.forceCenter().x(300).y(300))
    .force("link", linkForce)
    .nodes(nodes)
    .on("tick", forceTick);
  
  simulation.force("link").links(edges);
  
  d3.select("svg").selectAll("line.link")
    .data(edges, d => `${d.source.id}-${d.target.id}`)
    .enter()
    .append("line")
      .attr("class", "link")
      .style("opacity", .5)
      .style("stroke-width", d => d.weight);
  
  var nodeEnter = d3.select("svg").selectAll("g.node")
    .data(nodes, d => d.id)
    .enter()
    .append("g")
      .attr("class", "node");
  nodeEnter.append("circle")
    .attr("r", 5)
    .style("fill", d => roleScale(d.role));
  nodeEnter.append("text")
    .style("text-anchor", "middle")
    .attr("y", 15)
    .text(d => d.id);
  
  function forceTick() {
    d3.selectAll("line.link")
      .attr("x1", d => d.source.x)
      .attr("x2", d => d.target.x)
      .attr("y1", d => d.source.y)
      .attr("y2", d => d.target.y);
    d3.selectAll("g.node")
      .attr("transform", d => `translate(${d.x},${d.y})`);
  }
}

问题:上面这个图应该是一个有向图,但是因为它的边是半透明的,两个点的两个方向的边因为互相遮挡看上去像无向图。

解答:如果想要给边一个箭头?在svg:defs中定义一个marker,并将这个marker和你想画的内容联系起来;这个marker可以是静态html,也可以是SVG元素;

var marker = d3.select("svg").append('defs')
  .append('marker')
    .attr("id", "Triangle")
    .attr("refX", 12)
    .attr("refY", 6)
    .attr("markerUnits", 'userSpaceOnUse')//stroke-width和父节点相同
    .attr("markerWidth", 12)
    .attr("markerHeight", 18)
    .attr("orient", 'auto')
  .append('path')
    .attr("d", 'M 0 0 12 6 0 12 3 6');

d3.selectAll("line").attr("marker-end", "url(#Triangle)");//可以选择,marker-end, marker-start, marker-mid来选择这个marker在线的哪个位置

附加:其他的方式来表达方向:使用curved edges,or grow fatter on one end than the other. 如果想要实现这样的效果的话,需要使用<path>而不是的简单的<line>。

7.3.6 力导向布局设置 Force layout settings

  • Charge:Charge sets the rate at which nodes push(repulsion) each other away or attract each other. default -30
  • Gravity:independent x- and y- constraints, to visually center it using the center constraint.
  • Link Force:Attraction between nodes is determined by setting the strength property of the "link" force. 在Force layout中,是通过物理模拟最优的布局方式;

7.4  网络图因素 Network measures

了解详情:excellent introduction to networks and networks analysis

更多配置:upphiminn/jLouvain

  • edge weight:(边的权重)
  • centrality:(中心)the nodes in a system are which ones are more important than the others, referred to as centrality.
  • degree:(出入度) Degree is a rough measure of the importance of a node in a network because you assume that people or things with more connections have more power or influence in a network.
nodes.forEach(d => {
  d.degreeCentrality = edges
    .filter(p => p.source === d || p.target === d)
    .length;
});
  • clustering(内聚):whether nodes are interconnected
  • modularity(耦合):whether certain nodes are more connected to each other than to the rest of the network.

根据community membership 或者 module 而画出的力导向图:bl.ocks.org/emeeks

7.5 更新网络 Updating the network

7.5.1 stopping and restarting the layout

  • stop()
  • restart()
  • tick()
  • force.drag():当进行交互的时候,将点固定;
var drag = d3.drag();

drag
  .on("drag", dragging);

d3.selectAll("g.node").call(drag);

function dragging(d) {
  var e = d3.event;
  d.fx = e.x;
  d.fy = e.y;
  if (simulation.alpha() < 0.1) {
    simulation.alpha(0.1);
    simulation.restart();
  }
}

7.5.2 移除结点/添加结点 Removing and adding nodes and links

1.stop() 2.remove nodes 3.rebind data

移除某些结点:

d3.select("svg")
  .on("click", filterNetwork);

function filterNetwork() {
  simulation.stop();
  var originalNodes = simulation.nodes();
  var originalLinks = simulation.force("link").links();
  
  var influentialNodes = originalNodes.filter(d => d.role === "employee");
  var influentialLinks = originalLinks.filter(d =>
    influentialNodes.indexOf(d.source) > -1 &&
    influentialNodes.indexOf(d.target) > -1);
  
  d3.selectAll("g.node")
    .data(influentialNodes, d => d.id)
    .exit()
    .transition()
      .duration(10000)
      .style("opacity", 0)
      .remove();
  
  d3.selectAll("line.link")
    .data(influentialLinks, d => `${d.source.id}-${d.target.id}`)
    .exit()
    .transition()
      .duration(10000)
      .style("opacity", 0)
      .remove();
  
  simulation
    .nodes(influentialNodes);
  simulation.force("link")
    .links(influentialLinks);
  simulation.alpha(0.1);
  simulation.restart();
}

添加某个连线:

function addEdge() {
  simulation.stop();
  var links = simulation.force("link").links();
  var nodes = simulation.nodes();
  var newEdge = {source: nodes[0], target: nodes[8], weight: 5};
  links.push(newEdge);
  simulation.force("link").links(links);
  d3.select("svg").selectAll("line.link")
    .data(links, d => `${d.source.id}-${d.target.id}`)
    .enter()
    .insert("line", "g.node")
      .attr("class", "link")
      .style("stroke", "#FE9922")
      .style("stroke-width", 5);
  
  simulation.alpha(0.1);
  simulation.restart();
}

添加点+线:

function addNodesAndEdges() {
  simulation.stop();
  var oldEdges = simulation.force("link").links();
  var oldNodes = simulation.nodes();
  var newNode1 = {id: "Mike", role: "contractor", team: "none"};
  var newNode2 = {id: "Noah", role: "contractor", team: "none"};
  var newEdge1 = {source: oldNodes[5], target: newNode1, weight: 5};
  var newEdge2 = {source: oldNodes[5], target: newNode2, weight: 5};
  oldEdges.push(newEdge1,newEdge2);
  oldNodes.push(newNode1,newNode2);
  simulation.force("link").links(oldEdges);
  simulation.nodes(oldNodes);
  d3.select("svg").selectAll("line.link")
    .data(oldEdges, d => `${d.source.id}-${d.target.id}`)
    .enter()
    .insert("line", "g.node")
      .attr("class", "link")
      .style("stroke", "#FE9922")
      .style("stroke-width", 5);
  
  var nodeEnter = d3.select("svg").selectAll("g.node")
    .data(oldNodes, d => d.id)
    .enter()
    .append("g")
      .attr("class", "node");
  nodeEnter.append("circle")
      .attr("r", 5)
      .style("fill", "#FCBC34");
  nodeEnter.append("text")
      .style("text-anchor", "middle")
      .attr("y", 15)
      .text(d => d.id);
  simulation.alpha(0.1);
  simulation.restart();
}

注意:其中的vx, vy表示rent velocity(速度?雾),如果不更新的话,下次再start的时候,所有的点会从之前的点开始移动。感觉这会是之后遇到的bug,这这里记录一下。qaq

d3.selectAll("g.node").each(d => {
  d.x = xScale(d.degreeCentrality);
  d.vx = 0;
  d.y = yScale(d.salary);
  d.vy = 0;
});

7.5.3 优化 Optimization

普遍意义上来说,不要超过500个结点;但是随着浏览器的性能提升,这个限制也会有变化;但是要展示多结点网络时,可以使用forceManyBody.chargeDistance()来设置一个maximum distance when computing the repulsive charge for each node(当结点之间是排斥力的时候)

 

第八章见博客:Geospatial information visualization 

地址:https://blog.csdn.net/qq_37430374/article/details/102642013

 

第九章之后(Advanced techniques)见博客:

 

 

 

 

 

 

 

 

 

 

I’ve always loved making games. Board games, role-playing games, computer games— I just love abstracting things into rules, numbers, and categories. As a natural conse- quence, I’ve always loved data visualization. Damage represented as a bar, spells repre- sented with icons, territory broken down into hexes, treasure charted out in a variety of ways. But it wasn’t until I started working with maps in grad school that I became aware of the immeasurable time and energy people have invested in understanding how to best represent data. I started learning D3 after having worked with databases, map data, and network data in a number of different desktop packages, and also coding in Flash. So I was nat- urally excited when I was introduced to D3 , a JavaScript library that deals not only with information visualization generally, but also with the very specific domains of geospa- tial data and network data. The fact that it lives in the DOM and follows web standards was a bonus, especially because I’d been working with Flash, which wasn’t known for that kind of thing. Since then, I’ve used D3 for everything, including the creation of UI elements that you’d normally associate with jQuery. When I was approached by Manning to write this book, I thought it would be the perfect opportunity for me to look deeply at D3 and make sure I knew how every little piece of the library worked, while writing a book that didn’t just introduce D3 but really dived into the different pieces of the library that I found so exciting, like mapping and networks, and tied them together. As a result, the book ended up being much longer than I expected and covers everything from the basics of generating lines and areas to using most of the layouts that come to mind when you think of data visualization. It also devotes some space to maps, networks, mobile, and optimization. In the end, I tried to give readers a broad approach to data visualization tools, whether that means maps or networks or pie charts.
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值