On Github AnnieCannons / ac-introduction-d3
Slides : bit.ly/d3-codeclass
Aysegul Yonet / @AysegulYonet
<circle cx="250" cy="100" r="30" fill="#25B0B0"> </circle>Let's take a look at circle. Notice we are calculating an area of a circle each time we draw a circle using these attributes. That means we have to be aware of the performance issues.
<rect x="200" y="50" width="100" height="100" rx="10" ry="0" fill="#25B0B0"> </rect>Interesting fact: if a properly specified value is provided for rx but not for ry (or the opposite), then the browser will consider the missing value equal to the defined one.
<g transform="translate(100, 100)"> <circle cx="20" cy="10" r="30" fill="rgb(255, 0, 0)"> </circle> <circle cx="100" cy="10" r="30" fill="#25B0B0"> </circle> </g>Any transformations (positioning, scaling, rotating) that are applied to the group element will also be applied to each of the child elements.
<line x1="10" y1="10" x2="100" y2="100" stroke="blue" stroke-width="100"> </line>
<path d=" M50,100 L131,66 L259,115 L339,50 L400,98 M350,150 L100,150" fill="none" stroke="#25B0B0"> </path>
<svg> <text x="100" y="100" fill="red" font-family="arial" font-size="16"> Look at me, I am a text. </text> </svg>Unless you set the style of the SVG text, it inherits font-styles from CSS.
var paragraphs = $("div p");
var paragraphs = d3.select("div").selectAll("p");D3 uses css selectors to define a selection. The selection can be based on tag, class, identifier, attribute, or place in the hierarchy. Child nodes might not exist at the time of our selection. One thing to note, D3 selector object only contains the elements that matched the selection rule when the selection was first created.
var p = d3.select("body").append('p'); p.html('Hello CodeClass!').attr('style', 'color: red;');Once we have a selection, we can operate on our nodes.
d3.selectAll("p") .data([0, 1, 1, 2, 3, 5, 8, 13, 21, 34])Most importantly we can bind data to our selections. When data is bound to a selection, each element in the data array is paired with the corresponding DOM node in the selection. We can use the data to define the attributes on that node
d3.select("body").selectAll("p") .data([3, 7, 21, 31, 35, 42]) .enter();If there are fewer nodes than data, the extra data elements form the enter selection, which you can instantiate by appending to the enter selection.
d3.select("body").selectAll("p") .data([3, 7, 21, 31, 35, 42]) .enter().append("p") .text(function(d) { return "I’m number " + d + "!"; }) .style("color", "black");D3 methods returns the selection they act upon, and we can apply multiple operations to the same selection. Here we are able to add text to our newly appended selection.
var bars = d3.select('body').selectAll('p').data(newData); //if we have more data, add new 'p' for those data items bars.enter().append('p') //if we have less data points, remove the 'p' that no longer have a data pairing. bars.exit().remove();What if we have less data points? Using D3’s exit and remove, you can get rid of outgoing nodes that are no longer needed. Like the enter method, exit selects the extra nodes that doesn't have data matching and remove deletes the nodes from our DOM.
// Update… var p = d3.select("body").selectAll("p") .data([3, 7, 21, 31, 35, 42]); // Enter… p.enter().append("p") // Exit… p.exit().remove();This is what we call the update pattern. If you forget about the enter and exit selections, you will automatically select only the elements for which there is corresponding data. By handling these three cases separately, you specify precisely which operations run on which nodes.
d3.select("body").selectAll("p") .data([3, 7, 21, 31, 35, 42]) .enter().append("p") .text(function(d){return d;}) .exit().remove();If you have been to the d3's website, I am sure you came accross the chained methods. Every chained method operates on the previous selection, and most operations return the same selection. Some methods return a new one! For example, selection.append returns a new selection containing the new elements. This conveniently allows you to chain operations into the new elements. Since method chaining can only be used to descend into the document hierarchy, use var to keep references to selections and go back up.
circles.attr("r", "0") .attr({ 'cx': function(d) {return (d * 2); }, 'cy': 10 }) .transition() .duration(750) .delay(function(d, i) { return i * 100; }) .attr("r", 10);Transition will happen between what you have assigned before and after the transion is called.
var scale = d3.scale.linear();Examples we saw so far calculates the position of our SVG elements by using data. D3 comes with scale methods to make these mappings easy. Scales transform a dataset in a certain interval (called the domain) into a number in another interval (called the range). For ex, transforms a domain in miles to a range in pixels.
var xScale = d3.scale.linear() .domain([0, d3.max(data)])// your data minimum and maximum .range([0, 420]);//the pixels to map to d3.select(".chart") .selectAll("div") .data([3, 7, 21, 31, 35, 42]) .enter().append("div") .style("width", function(d) { return xScale(d) + "px"; })JSFiddle Scale func creates a new instance of a scale. On our new scale, we can create new properties that persists. Quantitative: numarical.
var xScale = d3.scale.linear() .domain([0, 1000]) .range([0, 200]); console.log(xScale(500)); //100 console.log(xScale.domain());//[0, 1000]Domain and range is a setter and getter. If you don't pass any arguments it is returning the values.
var xScale = d3.scale.linear() .domain([2, 3, 5, 7, 11, 13, 17, 19, 23, 29]) .range([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
var xScale = d3.scale.linear() .domain([2, 3, 5, 7, 11, 13, 17, 19, 23, 29]) .range([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) .clamp(true);Defaults to false.
Ordinal Scale have a discrete domain, such as a set of names or categories.
var xScale = d3.scale.ordinal() .domain(["Bob", "Stuart", "Kevin", "Scarlet"]) .rangePoints([0, 100]); console.log(xScale("Stuart"));//33.333333333333336 xScale.range(); // [0, 33.333333333333336, 66.66666666666667, 100]You need to have the same number of arguments. If you have more in range, that is OK. RangePoints subdivides everything in between the values you give to it.
var o = d3.scale.ordinal() .domain([1, 2, 3]) .rangeBands([0, 100]); o.rangeBand(); // 33.333333333333336 o.range(); // [0, 33.333333333333336, 66.66666666666667] o.rangeExtent(); // [0, 100]Sets the output range from the specified continuous interval. This interval is subdivided into n evenly-spaced bands, where n is the number of (unique) values in the input domain. The bands may be offset from the edge of the interval and other bands according to the specified padding, which defaults to zero.
var x = d3.scale.ordinal() .domain(["A", "B", "C", "D", "E", "F"]) .rangeRoundBands([0, width], .1);Like rangeBands, except guarantees that range values and band width are integers so as to avoid antialiasing artifacts.
d3.scale.category10()
Constructs a new ordinal scale with a range of ten categorical colors:d3.time.scale() .domain([dates]) .range([values]);Constructs a new time scale with the default domain and range; the ticks and tick format are configured for local time. TODO: Dates and times can be represented using numbers for days, years,... or specific date-time encoding. ISO 8601.
d3.time.format("%Y-%m-%d"); //2015-08-26 d3.format("+,%"); //+2,400%JSFiddle d3.time.format - creates a new local time formatter for a given specifier.
d3.time.scale - constructs a linear time scale.
var xScale = d3.time.scale() .domain([2009-07-13T00:02, 2009-07-13T23:48]) .rangeRound([0, width]); //rangeRound does the same thing as range but rounds the values to integers or begining of dates.JSFiddle
d3.time.intervals - a time interval in local time.
var svg = d3.select('body').append('svg') .attr({ 'width': 250, 'height': 250 }); var xScale = d3.scale.linear() .domain([0, 5]) .range([0, 200]);
var render = function(data, r, color){ //Bind data var circles = svg.selectAll('circle').data(data); //Enter and update circles.enter().append('circle') .attr({ 'cx': function(d){return xScale(d)}, 'cy': 30, 'r': r, 'fill': function(){return color || 'red'} }); }
var oldData = [1, 2, 3]; var newData = [1, 2, 3, 4, 5]; render(oldData, 10); render(newData, 15, 'blue');How many people think the circles all should be blue?
var render = function(data, r, color){ //Bind Data var circles = svg.selectAll('circle').data(data); //Enter circles.enter().append('circle') //Update circles.attr({ 'cx': function(d){return xScale(d)}, 'cy': 30, 'r': r, 'fill': function(){return color || 'red'} }) }I am using a function to return a default value if the color is not defined.
var oldData = [1, 2, 3, 4, 5]; var newData = [1, 2, 3]; render(oldData, 10); render(newData, 15, 'blue');
var render = function(data, r, color){ //Bind Data var circles = svg.selectAll('circle').data(data); //Enter circles.enter().append('circle') //Update circles.attr({ 'cx': function(d){return xScale(d)}, 'cy': 30, 'r': r, 'fill': function(){return color || 'red'} }) //Remove circles.exit().remove(); }
var line = d3.svg.line() .interpolate("basis") .x(function(d) { return x(d.year); }) .y(function(d) { return y(d.stat); });
var xAxis = d3.svg.axis() .scale(xScale) .orient("bottom");
xAxis.ticks(5); xAxis.tickValues([1,2,4]); xAxis.ticks(d3.time.month, 2);
day,donut Monday,91 Tuesday,23 Wednesday,7 Thursday,4 Friday,82 Saturday,39 Sunday,27
var x = "day"; var y = "donut"; var accessor = function(d) { d[y] = parseInt(d[y]); return d; } d3.csv("data/data.csv", accessor, function(d) { console.log(d);//Array of objects [{day: "Monday", donut: 91},...] render(d); });
[0, 1, 2, 3].reduce(function(a, b) { return a - b; });//-6 [0, 1, 2, 3].reduceRight(function(a, b) { return a - b; });//0left-to-right
var donutData = [ {day: 'Monday', donut: 34, variety: "plain"}, {day: 'Tuesday', donut: 41, variety: "glazed"} ];
var nestedData = d3.nest() .key(function(d){ return d.day;}) .entries(donutData); [{ key: "Monday", values: [ {day: 'Monday', donut: 34, variety: "plain"} ]}, { key: "Tuesday", values: [ {day: 'Tuesday', donut: 41, variety: "glazed"} ]} ]
var nestedData = d3.nest() .key(function(d){ return d.day;}) .key(function(d){ return d.variety;}) .entries(donutData); [{ key: "Monday", values: [ { key: "plain", values: [{day: 'Monday', donut: 34, variety: "plain"}]} ]}, { key:"Tuesday", values: [ { key: "glazed", values: [{day: 'Tuesday', donut: 41, variety: "glazed"}]} ] } ]
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[125.6, 10.1], [124.4, 9.2],...] }, "properties": { "name": "Dinagat Islands" } } ] }GeoJSON is a format for encoding a variety of geographic data structures. It is designed to represent discrete geometry objects grouped into feature collections of name/value pairs. Each feature in a feature collection is a JSON object that stores the border data in a coordinates array. Also some metadata in a properties object.
<script src="http://d3js.org/topojson.v1.min.js"></script>TopoJSON is an extension of GeoJSON that encodes topology. Rather than representing geometries discretely, geometries in TopoJSON files are stitched together from shared line segments called arcs. TopoJSON eliminates redundancy, offering much more compact representations of geometry than with GeoJSON; typical TopoJSON files are 80% smaller than their GeoJSON equivalents. In addition, TopoJSON facilitates applications that use topology, such as topology-preserving shape simplification, automatic map coloring, and cartograms.
var projection = d3.geo.albersUsa() .scale(500) .translate([width / 2, height / 2]);Scale and transform is different for every projections type.
var worldProjection = d3.geo.mercator(); var geoPath = d3.geo.path().projection(worldProjection);
d3.select('svg').selectAll('path').data(countries.features) .enter() .append('path') .attr('d', geoPath) .attr('class', 'countries');main.js
d3.select('svg').selectAll('path').data(topojson.feature(us, us.objects.states).features);Returns the GeoJSON MultiLineString geometry object representing the mesh for the specified object in the given topology. This is useful for rendering strokes in complicated objects efficiently, as edges that are shared by multiple features are only stroked once.
<script src="http://d3js.org/queue.v1.min.js"></script> queue() .defer(d3.json, 'states.json') .defer(d3.json, 'cities.json') .await(makeMyMap);
var centered; g.on("click", clicked); function clicked(d) { var x, y, k; if (d && centered !== d) { var centroid = path.centroid(d); x = centroid[0]; y = centroid[1]; k = 4; centered = d; } else { x = width / 2; y = height / 2; k = 1; centered = null; } g.selectAll("path") .classed("active", centered && function(d) { return d === centered; }); g.transition() .duration(750) .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")") .style("stroke-width", 1.5 / k + "px"); }TODO zooming in/out, panning.
Slides : bit.ly/d3-codeclass