"Flight Animation" in d3.js v3 - javascript
Im currently trying to wrap my head around connecting points around the map and animating them like - HERE:
http://www.tnoda.com/flightanimation
I want the points to connect using information in my CSV File:
Specifically 'where' field which will tell us is the place a destination or an origin of the flight:
code,city,country,lat,lon,where
ZNZ,ZANZIBAR,TANZANIA,-6.13,39.31,dest
TYO,TOKYO,JAPAN,35.68,139.76,dest
AKL,AUCKLAND,NEW ZEALAND,-36.85,174.78,orgin
BKK,BANGKOK,THAILAND,13.75,100.48,orgin
DEL,DELHI,INDIA,29.01,77.38,orgin
SIN,SINGAPORE,SINGAPOR,1.36,103.75,orgin
BSB,BRASILIA,BRAZIL,-15.67,-47.43,orgin
RIO,RIO DE JANEIRO,BRAZIL,-22.90,-43.24,orgin
YTO,TORONTO,CANADA,43.64,-79.40,orgin
IPC,EASTER ISLAND,CHILE,-27.11,-109.36,orgin
SEA,SEATTLE,USA,47.61,-122.33,orgin
(I know i spelled origin wrong its intentionally like that)
Now this is my HTML Code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.13/d3.min.js"></script>
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<style>
path {
stroke: limegreen;
stroke-width: 0.25px;
fill: black;
margin: 0 auto;
}
body {
background-color:darkgrey;
margin: 0 auto;
}
.packet {
max-height: height: 50px;
max-width: 50px;
fill: limegreen;
}
</style>
<body>
<script type="text/javascript" src="http://gc.kis.v2.scr.kaspersky-labs.com/3F7B1EB8-32BF-7449-968C-CB1318D27635/main.js" charset="UTF-8"></script><link rel="stylesheet" crossorigin="anonymous" href="http://gc.kis.v2.scr.kaspersky-labs.com/53672D8131BC-C869-9447-FB23-8BE1B7F3/abn/main.css"/><script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script>
var width = 960,
height = 500;
var projection = d3.geo.mercator()// Creating our projection for our map
var svg = d3.select("body").append("svg")//Append svg to body
.attr("width", width)
.attr("height", height);
var path = d3.geo.path()//create a path for the projection
.projection(projection);
var g = svg.append("g"); //create an empty space to append
var packet = svg.append("path")
.attr("width","50px")
.attr("d", "M612.074,132.141v-2.38c0-8.849-4.016-19.26-11.229-26.473l-0.818-0.818c0,0-0.818,0-0.818-0.818 c-1.636-1.636-3.198-2.38-4.833-4.016c-0.818,0-0.818-0.818-1.636-0.818c-1.636-0.818-4.016-1.636-5.652-2.38 c-0.818,0-0.818-0.818-1.636-0.818c-2.38-0.818-4.833-1.636-7.213-1.636c-0.818,0-0.818,0-1.636,0c-2.38,0-5.651-0.818-8.849-0.818 H43.427c-3.198,0-6.395,0-9.667,0.818c-0.818,0-1.636,0-2.38,0.818c-2.38,0.818-4.834,0.818-6.395,1.636 c-0.818,0-0.818,0.818-1.636,0.818c-1.636,0.818-4.016,1.636-5.652,2.38l-0.818,0.818c-1.636,0.818-3.198,2.38-4.834,3.198 c-0.818,0.818-1.636,1.636-2.38,2.38C4.016,110.428,0.818,117.715,0,125.746c0,0.818,0,0.818,0,1.636v357.384 c0,0.818,0,0.818,0,1.636c1.636,11.229,7.213,20.896,15.244,26.473c7.213,4.833,16.062,8.031,26.473,8.031H569.39c0,0,0,0,0.818,0 l0,0c2.38,0,5.651,0,8.031-0.818c0.818,0,0.818,0,1.636,0c2.38-0.818,4.834-0.818,6.395-1.636h0.818 c17.698-6.395,24.911-21.714,24.911-36.14v-2.38v-0.818v-0.818V134.521c0-0.818,0-0.818,0-1.636 C612.074,132.959,612.074,132.959,612.074,132.141z M560.69,120.913l-252.98,246.51l-57.854-56.218l0,0L51.459,120.838H560.69 V120.913z M29.819,475.099V140.991l187.095,179.882L29.819,475.099z M299.679,491.905H56.292l182.336-149.393l58.597,57.036 c2.38,2.38,4.834,3.198,7.213,4.016h0.818c0.818,0,0.818,0,1.636,0l0,0c0.818,0,1.636,0,1.636,0h0.818 c2.38-0.818,5.651-1.636,7.213-4.016l55.4-53.838l183.079,146.196H299.679z M582.329,475.843L394.417,324.07L582.329,140.99 V475.843z");
var route = svg.append("path");
// load and display the World
d3.json("world-110m2.json", function(error, topology) {//Load in the world map ( LOW RES)
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")//append path
.attr("d", path)//path is d3.geo.path which is the projection
//Loading the countries inside the world load display function to speed up the loading on local server and faster or client
//Loading the countries here also prevents the dots to be under the map instead on top of it!
d3.csv("countries.csv", function(error, data) {
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];})
.attr("r", 5)
.style("fill", "red");
//Writing out the Cities name
g.selectAll("text")
.attr("class","names")
.data(data)
.enter()
.append("text") // append text
.attr("x", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("y", function(d) {
return projection([d.lon, d.lat])[1];
})
.attr("dy", -7) // set y position of bottom of text
.style("fill", "limegreen") // fill the text with the colour black
.attr("text-anchor", "middle") // set anchor y justification
.text(function(d) {return d.city;}); // define the text to display
//Test
route.selectAll("path")
.datum({type: "LineString", coordinates:
[
function(d) {
if (d.where === origin){
return projection(d.lat,d.lon)
}},
function(d) {
if (d.where === dest){
return projection(d.lat,d.lon)
}}
]
})
.attr("class", "route")
.attr("d", path);
});
//Animating path
// Map Zooimng
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("path")
.attr("d", path.projection(projection));
});
svg.call(zoom)
});
function transition(packet, route) {
var l = route.node().getTotalLength();
packet.transition()
.duration(5000)
.attrTween("transform", delta(route.node()));
}
function delta(path) {
var l = path.getTotalLength();
return function(i) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
}
}
}
transition(packet, route);
</script>
</body>
</html>
Also if anyone can tell me why is my SVG element not resizing that would be great but i'd say i will be able to figure it out.
If someone can explain to me why is the code not working - point out my mistake in my logic and correct me it would be really helpful.
I'm doing this because Data Visualisation has conquered my interest now.
Cheers!
Also hope my comments can be helpful for people that might have had the same problem and are trying to wrap their head around the code!
Related
Data points and ticks in the scaleBand axis are not aligned
My data points and the values in the scaleBand y axis are not aligned. I am not able to align them properly, when I read the documentation, saw that by default the alignment is 0.5 and that's why my data points are plotted between the two points in the axis. But I tried to override the alignment my giving the alignment as 0, but there seems to be no change. The following is my code. <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <title>D3 v4 - linechart</title> <style> #graph { width: 900px; height: 500px; } .tick line { stroke-dasharray: 2 2 ; stroke: #ccc; } .y path{ fill: none; stroke: none; } </style> </head> <body> <div id="graph"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.1.1/d3.min.js"></script> <script> !(function(){ "use strict" var width,height var chartWidth, chartHeight var margin var svg = d3.select("#graph").append("svg") var axisLayer = svg.append("g").classed("axisLayer", true) var chartLayer = svg.append("g").classed("chartLayer", true) var xScale = d3.scaleLinear() var yScale = d3.scaleBand() var align = 0 //d3.tsv("data1.tsv", cast, main) d3.json("http://localhost/d32.json",cast) //データの方変換 function cast(data) { console.log("got it"); data.forEach(function(data) { console.log(data.Letter); data.Letter = data.Letter; data.Freq = +data.Freq; }); main(data); } function main(data) { console.log("in main"); setSize(data) drawAxis() drawChart(data) } function setSize(data) { width = document.querySelector("#graph").clientWidth height = document.querySelector("#graph").clientHeight margin = {top:40, left:100, bottom:40, right:0 } chartWidth = width - (margin.left+margin.right+8) chartHeight = height - (margin.top+margin.bottom) svg.attr("width", width).attr("height", height) axisLayer.attr("width", width).attr("height", height) chartLayer .attr("width", chartWidth) .attr("height", chartHeight) .attr("transform", "translate("+[margin.left, margin.top]+")") xScale.domain([0, d3.max(data, function(d) { return d.Freq; })]).range([0,chartWidth]) yScale.domain(data.map(function(d) { return d.Letter; })).range([chartHeight, 0]).align(1) } function drawChart(data) { console.log("in drawChart"); var t = d3.transition() .duration(8000) .ease(d3.easeLinear) .on("start", function(d){ console.log("transiton start") }) .on("end", function(d){ console.log("transiton end") }) var lineGen = d3.line() .x(function(d) { return xScale(d.Freq) }) .y(function(d) { return yScale(d.Letter) }) .curve(d3.curveStepAfter) var line = chartLayer.selectAll(".line") .data([data]) line.enter().append("path").classed("line", true) .merge(line) .attr("d", lineGen) .attr("fill", "none") .attr("stroke", "blue") .attr("stroke-width","2px") .attr("stroke-dasharray", function(d){ return this.getTotalLength() }) .attr("stroke-dashoffset", function(d){ return this.getTotalLength() }) chartLayer.selectAll(".line").transition(t) .attr("stroke-dashoffset", 0) chartLayer.selectAll("circle").classed("circle",true) .data(data) .enter().append("circle") .attr("class", "circle") .attr("fill","none") .attr("stroke","black") .attr("cx", function(d) { return xScale(d.Freq); }) .attr("cy", function(d) { return yScale(d.Letter); }) .attr("r", 4); chartLayer.selectAll(".logo").transition(t) .attr("stroke-dashoffset", 0) } function drawAxis(){ var yAxis = d3.axisLeft(yScale) .tickSizeInner(-chartWidth) axisLayer.append("g") .attr("transform", "translate("+[margin.left, margin.top]+")") .attr("class", "axis y") .call(yAxis); var xAxis = d3.axisBottom(xScale) axisLayer.append("g") .attr("class", "axis x") .attr("transform", "translate("+[margin.left, chartHeight+margin.top]+")") .call(xAxis); } }()); </script> </body> </html> The output is shown here:
The band scale is the wrong tool in this situation. The main reason is that a band scale has an associated bandwidth. You can tweak the paddingInner and paddingOuter values of the band scale to give you the expected result. However, the easiest solution is using a point scale instead. Point scales: ...are a variant of band scales with the bandwidth fixed to zero. (emphasis mine) So, it should be: var yScale = d3.scalePoint()
Animate position of svg rect on transition
Edit: here is an example Fiddle: 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. I have attempted to do this using an updateData function set to trigger onclick. 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. 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. <!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. This aims to demonstrate a couple things: The proper use of the enter, update, exit pattern. Removed cut / paste duplicate code. The proper way to use g to group elements and position them together. 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/ 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. Therefore, you only need to pass d3 data for a certain date, versus passing d3 ALL the data and then filtering. 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. 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. 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 This is because i associated each rectangle with an item_name. D3s data function can take an optional 2nd parameter that specifies HOW the data is bound to the rectangles. 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. 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). 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. Note that even though i'm talking about rectangles, i bound data to the g element, which contains the rectangle and the text. Feel free to ask any questions, i made a lot of changes that i didn't discuss.
D3 js tooltip issue for Choropleth Map
i am new to D3.js. I have been trying to add tooltip to the existing d3 chloropleth Map by Michelle Chandra. However i am unable to make any progress, the tooltip doesnt seem to appear. Where am i doing wrong? Any Help will be appreciated. http://bl.ocks.org/michellechandra/0b2ce4923dc9b5809922. Thanks <style type="text/css"> /* On mouse hover, lighten state color */ path:hover { fill-opacity: .7; } /* Style for Custom Tooltip */ div.tooltip { position: absolute; text-align: center; width: 60px; height: 28px; padding: 2px; font: 12px sans-serif; background: white; border: 0px; border-radius: 8px; pointer-events: none; } /* Legend Font Style */ body { font: 11px sans-serif; } /* Legend Position Style */ .legend { position:absolute; left:800px; top:350px; } </style> </head> <body> <script type="text/javascript"> /* This visualization was made possible by modifying code provided by: Scott Murray, Choropleth example from "Interactive Data Visualization for the Web" https://github.com/alignedleft/d3-book/blob/master/chapter_12/05_choropleth.html Malcolm Maclean, tooltips example tutorial http://www.d3noob.org/2013/01/adding-tooltips-to-d3js-graph.html Mike Bostock, Pie Chart Legend http://bl.ocks.org/mbostock/3888852 */ //Width and height of map var w = 900; var h = 600; // D3 Projection var projection = d3.geo.albersUsa() .translate([w/2, h/2]) // translate to center of screen .scale([1000]); // scale things down so see entire US // Define path generator var path = d3.geo.path() // path generator that will convert GeoJSON to SVG paths .projection(projection); // tell path generator to use albersUsa projection // Define linear scale for output var color = d3.scale.linear() .range(["rgb(24,143,95)","rgb(51,188,196)","rgb(155,226,183)","rgb(217,91,67)"]); var legendText = ["Cities Lived", "States Lived", "States Visited", "States Not Visited Yet"]; //Create SVG element and append map to the SVG var svg = d3.select("body") .append("svg") .attr("width", w) .attr("height", h); // Append Div for tooltip to SVG var div = d3.select("body") .append("div") .attr("class", "tooltip") .style("opacity", 0); // Load in my states data! d3.csv("stateslived.csv", function(data) { color.domain([0,1,2,3]); // setting the range of the input data // Load GeoJSON data and merge with states data d3.json("us-states.json", function(json) { // Loop through each state data value in the .csv file for (var i = 0; i < data.length; i++) { // Grab State Name var dataState = data[i].state; // Grab data value var dataValue = data[i].visited; // Find the corresponding state inside the GeoJSON for (var j = 0; j < json.features.length; j++) { var jsonState = json.features[j].properties.name; if (dataState == jsonState) { // Copy the data value into the JSON json.features[j].properties.visited = dataValue; // Stop looking through the JSON break; } } } // Bind the data to the SVG and create one path per GeoJSON feature svg.selectAll("path") .data(json.features) .enter() .append("path") .attr("d", path) .style("stroke", "#fff") .style("stroke-width", "1") .style("fill", function(d) { // Get data value var value = d.properties.visited; if (value) { //If value exists… return color(value); } else { //If value is undefined… return "rgb(213,222,217)"; } }); // Map the cities I have lived in! d3.csv("cities-lived.csv", function(data) { svg.selectAll("circle") .data(data) .enter() .append("circle") .attr("cx", function(d) { return projection([d.lon, d.lat])[0]; }) .attr("cy", function(d) { return projection([d.lon, d.lat])[1]; }) .attr("r", function(d) { return Math.sqrt(d.years) * 4; }) .style("fill", "rgb(217,91,67)") .style("opacity", 0.85) // add browser tooltip of city name //.append("title") //.text(function(d) { // return d.place; //}); // Modification of custom tooltip code provided by Malcolm Maclean, "D3 Tips and Tricks" // http://www.d3noob.org/2013/01/adding-tooltips-to-d3js-graph.html .on("mouseover", function(d) { d3.select(this).transition().duration(300).style("opacity", 1); div.transition().duration(200) .style("opacity", .9); div.text(d.properties.visited) .style("left", (d3.event.pageX) + "px") .style("top", (d3.event.pageY - 28) + "px"); }) // fade out tooltip on mouse out .on("mouseout", function(d) { div.transition() .duration(500) .style("opacity", 0); }); }); // Modified Legend Code from Mike Bostock: http://bl.ocks.org/mbostock/3888852 var legend = d3.select("body").append("svg") .attr("class", "legend") .attr("width", 140) .attr("height", 200) .selectAll("g") .data(color.domain().slice().reverse()) .enter() .append("g") .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); legend.append("rect") .attr("width", 18) .attr("height", 18) .style("fill", color); legend.append("text") .data(legendText) .attr("x", 24) .attr("y", 9) .attr("dy", ".35em") .text(function(d) { return d; }); }); }); /* This code generate paths without mapping to other data // Load JSON file and generate path for each state d3.json("us-states.json", function(json) { // file path, callback function called when data loaded svg.selectAll("path") // creates empty references to all the paths .data(json.features) // loop through our data (the states in the array) and bind to paths .enter() // create placeholder to reference the new elements .append("path") // add to the DOM! .attr("d", path) // generate paths for each state .style("fill", "steelblue"); // make the states blue! }); */ </script>
I'm not sure what this statement is supposed to do: d3.select(this).transition().duration(300).style("opacity", 1); It appears to select the window object. One would need to look at your json file to see if you're getting the right data into the div. Shameless plug. Take a look at foxToolTip.js. I think its easier and more flexible than d3tip. https://github.com/MichaelRFox/foxToolTip.js
Once div is a div, you have to use html, not text: div.transition().duration(200) .style("opacity", .9); div.html(d.properties.visited) .style("left", (d3.event.pageX) + "px") .style("top", (d3.event.pageY - 28) + "px");
d3.js - connecting shapes with lines (without using force or other layouts)
I have a bunch of static circles and I want to connect them with lines (it's a dependency graph). All the examples I see are done with d3's ready-made layouts and I'm not sure how to approach this efficiently. I also want to highlight lines related to a node when I mouse-over that node, as well as fade any other shapes/lines. This is what I have for now: (it just draws evenly spaced and sized circles according to area size given) <!DOCTYPE html> <html> <head> <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script> </head> <body style="overflow: hidden;"> <div id="drawarea" style="overflow: hidden;"></div> <script type="text/javascript"> var dataset = [], i = 0; for(i=0; i<45; i++){ dataset.push(Math.round(Math.random()*100)); } var width = 5000, height = 3000; var svg = d3.select("#drawarea").append("svg") .attr("width", width) .attr("height", height) .call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom)) .append("g"); var div_area = width*height, num_nodes = dataset.length, node_area = div_area/num_nodes*0.7, node_to_padding_ratio = 0.50, node_dia_inc_pad = Math.sqrt(node_area), node_radius_wo_pad = node_dia_inc_pad/2*node_to_padding_ratio, node_padding = node_dia_inc_pad/2*(1-node_to_padding_ratio), nodes_in_width = parseInt(width/(node_dia_inc_pad)), nodes_in_height = parseInt(height/(node_dia_inc_pad)); svg.selectAll("circle") .data(dataset) .enter().append("circle") .style("stroke", "gray") .style("fill", "white") .attr("r", node_radius_wo_pad) .attr("cx", function(d, i){ return 2*node_radius_wo_pad+i%nodes_in_width*node_dia_inc_pad;}) .attr("cy", function(d, i){ return 2*node_radius_wo_pad+(parseInt(i/nodes_in_width))*node_dia_inc_pad}) .on("mouseover", function(){d3.select(this).style("fill", "aliceblue");}) .on("mouseout", function(){d3.select(this).style("fill", "white");}) function zoom() { svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); } </script> </body> </html> EDIT: My revised code: <!DOCTYPE html> <html> <head> <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script> </head> <body style="overflow: hidden;"> <div id="canvas" style="overflow: hidden;"></div> <script type="text/javascript"> var graph = { "nodes":[ {"name":"Myriel","group":1}, {"name":"Napoleon","group":1} ], "links":[ {"source":1,"target":0,"value":1} ] } var width = 2000, height = 1000; var svg = d3.select("#canvas").append("svg") .attr("width", width) .attr("height", height) .call(d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoom)) .append("g"); var div_area = width*height, num_nodes = graph.nodes.length, node_area = div_area/num_nodes, node_to_padding_ratio = 0.50, node_dia_inc_pad = Math.sqrt(node_area), node_radius_wo_pad = node_dia_inc_pad/2*node_to_padding_ratio, node_padding = node_dia_inc_pad/2*(1-node_to_padding_ratio), nodes_in_width = parseInt(width/(node_dia_inc_pad)), nodes_in_height = parseInt(height/(node_dia_inc_pad)); var xScale = d3.scale.linear() .domain([0,nodes_in_width]) .range([node_radius_wo_pad,width-node_radius_wo_pad]); var yScale = d3.scale.linear() .domain([0,nodes_in_height]) .range([node_radius_wo_pad,height-node_radius_wo_pad]); var lines = svg.attr("class", "line") .selectAll("line").data(graph.links) .enter().append("line") .attr("x1", function(d) { return xScale(d.source%nodes_in_width); }) .attr("y1", function(d) { return yScale(parseInt(d.source/nodes_in_width)); }) .attr("x2", function(d) { return xScale(d.target%nodes_in_width); }) .attr("y2", function(d) { return yScale(parseInt(d.target/nodes_in_width)); }) .attr("src", function(d) { return d.source; }) .attr("trgt", function(d) { return d.target; }) .style("stroke", "grey"); var circles = svg.selectAll("circle") .data(graph.nodes) .enter().append("circle") .style("stroke", "gray") .style("fill", "white") .attr("r", node_radius_wo_pad) .attr("cx", function(d, i){ return xScale(i%nodes_in_width);}) .attr("cy", function(d, i){ return yScale(parseInt(i/nodes_in_width));}) .attr("index", function(d, i){return i;}) .on("mouseover", function(){ var that = this; lines.filter(function() { return d3.select(this).attr("src") == d3.select(that).attr("index"); }).style("stroke", "red"); lines.filter(function() { return d3.select(this).attr("trgt") == d3.select(that).attr("index"); }).style("stroke", "green"); lines.filter(function() { return (d3.select(this).attr("trgt") != d3.select(that).attr("index") && d3.select(this).attr("src") != d3.select(that).attr("index")); }).style("display", "none"); d3.select(this).style("fill", "aliceblue"); }) .on("mouseout", function(){ lines.style("stroke", "grey") .style("display", "block"); d3.select(this).style("fill", "white"); }); function zoom() { svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); } </script> </body> </html> What I want to do now is have the circles the lines point to and from be colored similarly. I'm not sure how to make the reference to them from the "mouseover" event of a circle though. Will do some testing...
You haven't specified how your nodes are connected, so I'm assuming that everything is connected to everything. The principle is the same as for any other layout -- you take the data you have that determines the links and pass it to .data(). In your code, the coordinates aren't part of the data, which makes it a bit more verbose, but still quite straightforward. To add the links, I'm using a nested selection -- I'm adding a g element for each node and underneath the connections to all the other nodes. var lines = svg.selectAll("g.line").data(dataset) .enter().append("g").attr("class", "line") .selectAll("line").data(dataset) .enter().append("line") .attr("x1", function(d, i) { return 2*node_radius_wo_pad+i%nodes_in_width*node_dia_inc_pad; }) .attr("y1", function(d, i) { return 2*node_radius_wo_pad+(parseInt(i/nodes_in_width))*node_dia_inc_pad; }) .attr("x2", function(d, i, j) { return 2*node_radius_wo_pad+j%nodes_in_width*node_dia_inc_pad; }) .attr("y2", function(d, i, j) { return 2*node_radius_wo_pad+(parseInt(j/nodes_in_width))*node_dia_inc_pad; }); This adds a line for every pair of nodes. Note that it will add links between the same nodes (which you won't be able to see) and 2 links between each pair of nodes -- once starting at one node and once at the other. I haven't filtered out these cases here to keep the code simple. In your particular application, I'm guessing that the connections are determined in another way anyway. To highlight the links that are connected a particular node on highlight, I'm using the links variable that contains all of them and filtering out the ones whose start coordinates are different from the coordinates of the circle. The filtered selection is then painted red. .on("mouseover", function(){ var that = this; lines.filter(function() { return d3.select(this).attr("x1") == d3.select(that).attr("cx") && d3.select(this).attr("y1") == d3.select(that).attr("cy"); }).style("stroke", "red"); d3.select(this).style("fill", "aliceblue"); }) If the coordinates are part of the data, everything will become a bit easier and look more like the examples you may have seen for the force layout for example. I would recommend to create a data structure much like what's used there for your links, with source and target attributes that determine the source and target nodes. Complete example here.
D3.js tree with odd number of vertices, edges not shown
I have the following JavaScript code that uses the D3.js library to draw a tree (it follows the standard structure one can find in the various online tutorials): var json = { "name": "A", "children": [{ "name": "B" }] }; var tree = d3.layout.tree().size([200, 200]); var nodes = tree.nodes(json); var vis = d3.select("#chart").attr("width", 300) .attr("height", 300) .append("svg:g") .attr("transform", "translate(40, 40)"); var diagonal = d3.svg.diagonal(); var link = vis.selectAll("path.link").data(tree.links(nodes)).enter() .append("svg:path") .attr("class", "link") .attr("d", diagonal); var node = vis.selectAll("g.node").data(nodes).enter().append("svg:g") .attr("class", "node") .attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; }); node.append("svg:circle").attr("r", 10); node.append("svg:text").attr("dx", function (d) { return 10; }) .attr("dy", 10) .attr("text-anchor", function (d) { return "start"; }) .text(function (d) { return d.name; }); JSFIDDLE It works mostly fine, except for trees in which a vertex has an odd number of children (1, 3, ...); in this case, the edge for the odd vertex will not be drawn (e.g., in the above example, the edge between A and B is not displayed). What am I missing?
You are missing the style for the node links. Something variation of this: <style> .link { fill: none; stroke: #ccc; stroke-width: 4.5px; } </style> Or, set it on the link itself: .attr("d", diagonal).attr({ 'fill': 'none', 'stroke': 'grey', 'stroke-width': 4 }); It depends on odd vs. even number because by default a path gets no stroke and a fill color of black. So a straight line doesn't show up but the curved ones get filled.