//create svg container + inner drawing space using the d3 conventional margin var margin = {top: 20, right: 80, bottom: 30, left: 30}, width = 960 - margin.left - margin.right, height = 400 - margin.top - margin.bottom; //create time formatting function var parseDate = d3.time.format("%Y-%m-%d").parse; //create a time scaling function for the x-axis data, from 0 to width of the inner drwg space var x = d3.time.scale() .range([0, width]); //create a linear scaling function for the y-axis data, from height of inner drwg space to 0 var y = d3.scale.linear() .range([height, 0]); //create ordinal scaling function for the colours var color = d3.scale.category10(); //******testing var places = d3.scale.ordinal(); //createe x- & y-axes functions, bring in scaling functions & orientate var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left"); //create d3 path generation function var line = d3.svg.line() .interpolate("basis") .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.pindex); }); //create svg drawing container & inner drwg space 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 + ")"); //asynchrous callback function to retrieve data from tsv file d3.tsv("data.tsv", function(error, data) { //look at the 1st data element, gets the object keys, //data[0] returns the 1st js object, d3.keys functionality looks at eack key:value pair in the object //& returns only the keys, as an array. Then the js array.filter method is applied to each element in the //the array and returns the element as long as it does not equal "date". This array of keys is used to set //the domain of the ordinal scale function. //data is an array of 106 objects in the form: // [{ // series1: "100", // series2: "100", // series3: "100", // series4: "100", // series5: "100", // series6: "100", // series7: "100", // series8: "100", // Date: "2005-01-01", // } ..... 105 other objects for each date value..] // //console.log(data); places.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; })); //iterate thru the js objects & for each object convert the date str into a js date object //& assign it back to the d.date key data.forEach(function(d) { d.date = parseDate(d.date); }); //reorganise data for use by the d3 pattern: i.e. Nesting data //1. apply a function to every element in the ordinal domain i.e. each pindex //2. return an object with 2 key:value pairs //3. end up with an array of 8 objects for each pindex series, consisting of an array // of names and a value, where the value is an array of 106 objects, consisting of a //date and a pindex value: // [{ // name: "series1", // values: [{date: "2005-01-01", // pindex: "100"}, // {date: "2005-02-01" // pindex: "100.4"}, ...etc for all 106 data points i.e. dates // ] // }, // ....+7 more objects for each series // ] // var cities = places.domain().map(function(name) { return { name: name, values: data.map(function(d) { return {date: d.date, pindex: +d[name]}; }) }; }); //console.log(cities); //create an array using d3.extent, & use it to set the x-scale fuction's input domain, // returns an array of two elements - min & max dates x.domain(d3.extent(data, function(d) { return d.date; })); //as with x-axis, create an array to setup the y-scale function input domain, //as this is a nested array we need the max & min values for the entire data set //but also for each individual series y.domain([ d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.pindex; }); }), d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.pindex; }); }) ]); //create a svg group element for the x axis & for the y axis, append labels svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("House Price Index¹"); //create an svg g-element for each of the 8 series var city = svg.selectAll(".city") .data(cities) .enter().append("g") .attr("class", "city"); //and generate a path for each series using the line function city.append("path") .attr("class", "line") .attr("d", function(d) { return line(d.values); }) //****testing // .style("stroke", "lightgrey") //...testing //.style("stroke-dasharray", (3,3) ) .style("stroke", function(d){ if (d.name === "series1") {return "black"} else {return "lightgrey"} ;}) //set an mouseover event listener for when the mouseover occurs on any give path g-element //using d3.select(this) //and also select the corresponding text g-element, //in both selections make path and text go orange .on("mouseover", function(d){ //console.log(d.name); d3.select(this) .style("stroke", "orange"); //use the following to select the specific path g-element & bring it to the top //of the svg drawing container this.parentNode.parentNode.appendChild(this.parentNode); //selection for grabbing corresponding text d3.select('#text-' + d.name) .attr("visibility","visible") .style("stroke", "orange") .text(function(d) {return d.name;}); }) //when mouseout occurs revert the path to grey & the text to invisible .on("mouseout", function(d){ d3.select(this) .style("stroke", function(d){ if (d.name === "series1") {return "black"} else {return "lightgrey"} ;}) d3.select('#text-' + d.name) .attr("visibility","hidden") }) //create an id for the path DOM element that has the name of the data series in it .attr("id", function(d, i) { return "path-" + d.name; }) ; //create the text label for each data series, //find the position the apppend it to at the end each path using the array.length method city.append("text") .datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; }) .attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.pindex) + ")"; }) .attr("x", 5) .attr("dy", ".35em") //.attr("visibility","hidden") //****testing .attr("visibility",function(d){ if (d.name === "series1") {return "visible"} else {return "hidden"} ;}) //as with the path mouseover event above, set an event listener for the text. .on("mouseover", function(d){ d3.select('#path-' + d.name) .style("stroke", "orange") this.parentNode.parentNode.appendChild(this.parentNode); d3.select(this) .attr("visibility", "visible") .style("stroke", "orange"); }) .on("mouseout", function(d) { d3.select('#path-' + d.name) //.style("stroke", "lightgrey") .style("stroke", function(d){ if (d.name === "series1") {return "black"} else {return "lightgrey"} ;}) d3.select(this) .attr("visibility",function(d){ if (d.name == "series1") {return "visible"} else {return "hidden"} ;}) }) .text(function(d) { return d.name; }) .attr("id", function(d, i) { return "text-" + d.name; }); //filter the "series1" series //and append "points of interest" i.e. circles visible at points: 1.max, 2.min, 3.first, 4.last, //& set them as mouseover points for further explanations var filtered = city .filter(function(d){ return d.name == "series1" }) filtered .selectAll('circle') .data( function(d){return d.values} ) .enter().append('circle') .attr({ cx: function(d,i){ return x(d.date) }, cy: function(d,i){ return y(d.pindex) }, r: 5 }) //at this point all the data points have circles appended //but need to set all visibility attrs to "hidden" //except for : 1.max, 2.min, 3.first, 4.last /*.attr("visibility", function(f) { // if (d.pindex == d3.max(filtered, function(v) {return d.pindex;}) {return "visible"} //if (d.pindex == d3.max(d3.values(filtered))) {return "visible"} if (+f.pindex == d3.max(filtered, function(d,i) {return +d.pindex;})) {return "visible"} // <== max // if (d.pindex == d3.min(filtered) {return "visible"} // <== min else { return "hidden" } // <== Add these ;})*/ .style("fill", "orange") //mouseover cirlces .on('mouseover', function(d){ filtered.append("text") .attr({ x: function(dd){ return x(d.date) }, y: function(dd){ return y(d.pindex) }, dx:-3, dy:".35em", "text-anchor":"end" }) .style("fill", "black") .text( function(dd){ var formatDate = d3.time.format("%d-%B-%Y") return 'Date:' + formatDate(d.date) + ',index:' + d.pindex } ) }) .on('mouseout', function(dd){ d3.select(this.parentElement) .selectAll('text').remove() }) ; });