Edit: I have edited the question, earlier values were not reflecting on the x axis now that got resolved .Now as the slider moves the data doesn't change in the graph and i can see changing x values.
I'm trying to implement a jquery slider in d3 following this link. The example is generating data of some random numbers while my data is in the below format:
{storeid: 5722646637445120, peoplesum: 87, date: "2018-06-03"}
{storeid: 5722646637445120, peoplesum: 90, date: "2018-06-04"}
{storeid: 5722646637445120, peoplesum: 114, date: "2018-06-05"}
I'm able to trigger events when i move my slider but the values are not reflecting on the graph now. So that mean my view is not updating the SVG correctly which is my assumption. I could be wrong here as well.
My x axis should have the date range and y should be peoplesum and it could be a single or a multiline graph.
Updated Code:
private initSvg() {
d3.select("svg").remove();
this.svg = d3.select("#d3Id")
.append("svg")
.attr("width", this.width + this.margin.left + this.margin.right)
.attr("height", this.height + this.margin.top + this.margin.bottom)
.append("g")
.attr("transform",
"translate(" + this.margin.left + "," + this.margin.top + ")")
.attr("stroke-width", 2);
}
private initAxis() {
// Parse the date / time
var parseDate = timeParse("%b %Y");
this.formatTime = timeParse("%e %B");
// Set the ranges
this.x = d3Scale.scaleTime().range([0, this.width]);
this.y = d3Scale.scaleLinear().range([this.height, 0]);
}
private drawAxis() {
var X = this.x;
var Y = this.y;
this.xAxis = d3.axisBottom(this.x)
.ticks(5);
// var xAxis = d3.axisBottom(this.x).tickSize(-this.height).ticks(3);
// // Add the x-axis.
// this.svg.append("svg:g")
// .attr("class", "x axis")
// .attr("transform", "translate(0," + this.height + ")")
// .call(xAxis);
this.yAxis = d3.axisLeft(this.y)
.ticks(5);
// Define the line
this.priceline = d3Shape.line()
.x(function (d) { return X(new Date(d['date'])); })
.y(function (d) { return Y(d['peoplesum']); });
}
private drawLine() {
if ( this.data[0]['d3_parameter_maker'] === true)
{
this.x.domain([0, d3.max(this.data, function (d) { return d['date']; })]);
}
else if ( this.data[0]['d3_parameter_maker'] === undefined)
{
//console.log("aksdad")
var mindate = new Date(this.dashboard_date['startTime']),
maxdate = new Date(this.dashboard_date['endTime']);
this.x.domain([mindate,maxdate]);
}
console.log(new Date(this.dashboard_date['startTime']));
// Scale the range of the data
var svgVar = this.svg;
var pricelineVar = this.priceline;
var margin = this.margin;
var height = this.height;
let thisObj = this;
this.mouseOver = [];
let X = this.x;
let Y = this.y;
if ( this.mouseFlag < 0) {
for ( let i = 0; i < this.peopleInSumArr.length; i++) {
this.mouseOver[i] = true;
}
} else {
for (let i = 0; i < this.peopleInSumArr.length; i++) {
if (i !== this.mouseFlag) {
this.mouseOver[i] = false;
}
}
this.mouseOver[this.mouseFlag] = true;
}
this.y.domain([0, d3.max(this.data, function (d) { return d['peoplesum']; })]);
// Nest the entries by symbol
var dataNest = d3.nest()
.key(function (d) { return d['storeid']; })
.entries(this.data);
// set the colour scale
var color = d3.scaleOrdinal(d3.schemeCategory10);
var legendSpace = this.width / dataNest.length; // spacing for the legend
var div1 = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
dataNest.forEach(function (data, i) {
thisObj.svg.append("path")
.attr("class", "line")
.style("fill", "none")
.attr("d", thisObj.priceline(data.values))
.attr('opacity', thisObj.mouseOver !== undefined && thisObj.mouseOver[i] === true ? 1 : 0.2)
.style('cursor', 'pointer')
.style("stroke", function () { // Add the colours dynamically
return data['color'] = color(data.key);
})
.attr("stroke-width", 3)
.on('click', function () { // on mouse in show line, circles and text
thisObj.mouseFlag = i;
thisObj.initSvg();
thisObj.initAxis();
thisObj.drawAxis();
thisObj.drawLine();
});
// Add the scatterplot
thisObj.svg.selectAll("dot")
.data(data.values)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function (d) { return thisObj.x(new Date(d.date)); })
.attr("cy", function (d) { return thisObj.y(d.peoplesum); })
.style('cursor', 'pointer')
.on("mouseover", function (d) {
div1.transition()
.duration(200)
.style("opacity", .9);
// tslint:disable-next-line:no-unused-expression
div1.html("<b>Date: </b>" + d.date + "<br/>" + "<b>Sum: </b>" + d.peoplesum.toFixed(2))
.style('position', 'absolute')
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px")
.style('text-align', 'center')
.style('width', '100px')
.style('height', '30px')
.style('padding', '2px')
.style('font', '12px sans-serif')
.style('background-color', 'lightsteelblue')
.style('border', '0px')
.style('border-radius', '8px')
.style('cursor', 'pointer')
.style('pointer-events', 'none');
})
.on("mouseout", function (d) {
div1.transition()
.duration(500)
.style("opacity", 0);
});
// Add the X Axis
// Add the Y Axis
thisObj.svg.append("text")
.attr("x", (legendSpace / 2) + i * legendSpace) // space legend
.attr("y", height + (margin.bottom / 2) + 5)
.attr("class", "legend") // style the legend
.style('cursor', 'pointer')
.style("fill", function () { // Add the colours dynamically
return data['color'] = color(data.key);
})
.text(data.key)
.attr("stroke-width", 3)
.on('click', function () { // on mouse in show line, circles and text
thisObj.mouseFlag = i;
thisObj.initSvg();
thisObj.initAxis();
thisObj.drawAxis();
thisObj.drawLine();
});
});
// Add the X Axis
let xAxisSelection= this.svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + this.height + ")")
xAxisSelection.call(d3.axisBottom(this.x));
// Add the Y Axis
let yAxisLeft =this.svg.append("g")
.attr("class", "y axis")
yAxisLeft.call(d3.axisLeft(this.y));
var clip = this.svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr("width", this.width)
.attr("height", this.height);
// Add the line by appending an svg:path element with the data line we created above
// do this AFTER the axes above so that the line is above the tick-lines
var path = this.svg.append("svg")
.attr("class","path")
.attr("clip-path", "url(#clip)")
.attr( thisObj.priceline(this.data));
function zoom(begin, end) {
thisObj.x.domain([begin, end - 1]);
var t = thisObj.svg.transition().duration(0);
var size = moment(moment(end).toDate()).diff(moment(begin).toDate(), 'days');
console.log("size",size);
var step = size / 10;
var ticks = [];
const startDate = new Date(moment(begin).toDate());
for (var i = 0; i <= 10; i++) {
var xAxisDate = new Date(moment(begin).toDate())
// Add a day
xAxisDate.setDate(startDate.getDate() + i)
ticks.push(xAxisDate);
}
xAxisSelection.call(d3.axisBottom(thisObj.x.domain(d3.extent(ticks))));
}
//console.log("this.data)",this.data)
$(function() {
$( "#slider-range" ).slider({
range: true,
min: new Date(mindate).getTime() / 1000,
max: new Date(maxdate).getTime() / 1000,
step: 86400,
values: [ new Date(mindate).getTime() / 1000, new Date(maxdate).getTime() / 1000 ],
slide: function( event, ui ) {
//console.log("ui.values[0]",ui.values)
var begin = d3.min([(new Date(ui.values[ 0 ] *1000).toDateString() ), thisObj.data.length]);
var end = new Date(ui.values[ 1 ] *1000).toDateString(); // 0]);
//console.log("begin:", moment(begin).toDate(), "end:", moment(end).format('yyyy-mm-dd'), thisObj.data);
console.log(begin);
console.log(end);
zoom(begin, end);
}
});
});
}
}
Please find below the screenshot of the graph
Adding the clip path functionality because data was going beyond y axis.
private initSvg() {
d3.select("svg").remove();
this.svg = d3.select("#d3Id")
.append("svg")
.attr("width", this.width + this.margin.left + this.margin.right)
.attr("height", this.height + this.margin.top + this.margin.bottom)
.append("g")
.attr("transform",
"translate(" + this.margin.left + "," + this.margin.top + ")")
.attr("stroke-width", 2);
}
private initAxis() {
// Parse the date / time
// var parseDate = timeParse("%b %Y");
// this.formatTime = timeParse("%e %B");
// Set the ranges
this.x = d3Scale.scaleTime().range([0, this.width]);
this.y = d3Scale.scaleLinear().range([this.height, 0]);
}
private drawAxis() {
var X = this.x;
var Y = this.y;
this.xAxis = d3.axisBottom(this.x)
.ticks(5);
// var xAxis = d3.axisBottom(this.x).tickSize(-this.height).ticks(3);
// // Add the x-axis.
// this.svg.append("svg:g")
// .attr("class", "x axis")
// .attr("transform", "translate(0," + this.height + ")")
// .call(xAxis);
this.yAxis = d3.axisLeft(this.y)
.ticks(5);
// Define the line
this.priceline = d3Shape.line()
.x(function(d) {
return X(new Date(d['date']));
})
.y(function(d) {
return Y(d['peoplesum']);
});
}
private drawLine() {
if (this.data[0]['d3_parameter_maker'] === true) {
this.x.domain([1, d3.max(this.data, function(d) {
return parseInt(d['date']);
})]);
} else if (this.data[0]['d3_parameter_maker'] === undefined) {
var mindate = new Date(this.dashboard_date['startTime']),
maxdate = new Date(this.dashboard_date['endTime']);
this.x.domain([mindate, maxdate]);
}
console.log(new Date(this.dashboard_date['startTime']));
// Scale the range of the data
var svgVar = this.svg;
var pricelineVar = this.priceline;
var margin = this.margin;
var height = this.height;
let thisObj = this;
this.mouseOver = [];
let X = this.x;
let Y = this.y;
if (this.mouseFlag < 0) {
for (let i = 0; i < this.peopleInSumArr.length; i++) {
this.mouseOver[i] = true;
}
} else {
for (let i = 0; i < this.peopleInSumArr.length; i++) {
if (i !== this.mouseFlag) {
this.mouseOver[i] = false;
}
}
this.mouseOver[this.mouseFlag] = true;
}
this.y.domain([0, d3.max(this.data, function(d) {
return d['peoplesum'];
})]);
var clip = thisObj.svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr("width", this.width)
.attr("height", this.height);
// Nest the entries by symbol
var dataNest = d3.nest()
.key(function(d) {
return d['storeid'];
})
.entries(this.data);
// set the colour scale
var color = d3.scaleOrdinal(d3.schemeCategory10);
var legendSpace = this.width / dataNest.length; // spacing for the legend
var div1 = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
dataNest.forEach(function(data, i) {
thisObj.svg.append("path")
.attr("class", "line")
.style("fill", "none")
.datum(data)
.attr("d", function(d) {
return thisObj.priceline(d.values);
})
.attr('opacity', thisObj.mouseOver !== undefined && thisObj.mouseOver[i] === true ? 1 : 0.2)
.style('cursor', 'pointer')
.style("stroke", function() { // Add the colours dynamically
return data['color'] = color(data.key);
})
.attr("stroke-width", 3)
.attr("clip-path", "url(#clip)")
.on('click', function() { // on mouse in show line, circles and text
thisObj.mouseFlag = i;
thisObj.initSvg();
thisObj.initAxis();
thisObj.drawAxis();
thisObj.drawLine();
});
// Add the scatterplot
thisObj.svg.selectAll("dot")
.data(data.values)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) {
return thisObj.x(new Date(d.date));
})
.attr("cy", function(d) {
return thisObj.y(d.peoplesum);
})
.style('cursor', 'pointer')
.on("mouseover", function(d) {
div1.transition()
.duration(200)
.style("opacity", .9);
// tslint:disable-next-line:no-unused-expression
div1.html("<b>Date: </b>" + d.date + "<br/>" + "<b>Sum: </b>" + d.peoplesum.toFixed(2))
.style('position', 'absolute')
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px")
.style('text-align', 'center')
.style('width', '100px')
.style('height', '30px')
.style('padding', '2px')
.style('font', '12px sans-serif')
.style('background-color', 'lightsteelblue')
.style('border', '0px')
.style('border-radius', '8px')
.style('cursor', 'pointer')
.style('pointer-events', 'none');
})
.on("mouseout", function(d) {
div1.transition()
.duration(500)
.style("opacity", 0);
});
// Add the X Axis
// Add the Y Axis
thisObj.svg.append("text")
.attr("x", (legendSpace / 2) + i * legendSpace) // space legend
.attr("y", height + (margin.bottom / 2) + 5)
.attr("class", "legend") // style the legend
.style('cursor', 'pointer')
.style("fill", function() { // Add the colours dynamically
return data['color'] = color(data.key);
})
.text(data.key)
.attr("stroke-width", 3)
.on('click', function() { // on mouse in show line, circles and text
thisObj.mouseFlag = i;
thisObj.initSvg();
thisObj.initAxis();
thisObj.drawAxis();
thisObj.drawLine();
});
});
// Add the X Axis
let xAxisSelection = this.svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + this.height + ")")
xAxisSelection.call(d3.axisBottom(this.x));
// Add the Y Axis
this.svg.append("g")
.attr("class", "axis")
.call(d3.axisLeft(this.y));
if (this.data[0]['d3_parameter_maker'] === undefined)
{
this.daily_Data_slider(mindate,xAxisSelection,maxdate)
}
else
{
this.hourly_Data_slider(xAxisSelection)
}
}
private daily_Data_slider(mindate,xAxisSelection,maxdate){
var svgVar = this.svg;
var pricelineVar = this.priceline;
var margin = this.margin;
var height = this.height;
let thisObj = this;
function zoom(begin, end) {
thisObj.x.domain([new Date(begin), new Date(end)]);
var t = thisObj.svg.transition().duration(0);
var size = moment(moment(end).toDate()).diff(moment(begin).toDate(), 'days');
console.log("size", size);
var step = size / 10;
var ticks = [];
const startDate = new Date(moment(begin).toDate());
for (var i = 0; i <= 10; i++) {
var xAxisDate = new Date(moment(begin).toDate())
// Add a day
xAxisDate.setDate(startDate.getDate() + i)
ticks.push(xAxisDate);
}
xAxisSelection.call(d3.axisBottom(thisObj.x.domain(d3.extent(ticks))));
// Redraw the line:
d3.selectAll("circle")
.attr("cx", function(d) {
return thisObj.x(new Date(d.date));
})
.attr("cy", function(d) {
return thisObj.y(d.peoplesum);
})
d3.selectAll(".line").attr("d", function(d) {
return thisObj.priceline(d.values);
})
}
//console.log("this.data)",this.data)
$(function() {
$("#slider-range").slider({
range: true,
min: new Date(mindate).getTime() / 1000,
max: new Date(maxdate).getTime() / 1000,
step: 86400,
values: [new Date(mindate).getTime() / 1000, new Date(maxdate).getTime() / 1000],
slide: function(event, ui) {
//console.log("ui.values[0]",ui.values)
var begin = d3.min([(new Date(ui.values[0] * 1000).toDateString()), thisObj.data.length]);
var end = new Date(ui.values[1] * 1000).toDateString(); // 0]);
//console.log("begin:", moment(begin).toDate(), "end:", moment(end).format('yyyy-mm-dd'), thisObj.data);
console.log("begin", begin);
console.log("end", end);
if (new Date(moment(moment(moment(begin).toDate()).add(10, 'days')).format("YYYY-MM-DD")) <= new Date(end)) {
zoom(begin, end);
}
}
});
});
}
Related
I have a vertical line graph in d3 and I'm trying to add a tooltip in the form of a line and circle that follows the cursor movement in the vertical. The latter works but the circle fails to follow the path - I've tried different variations but the circle never follows the path, it's currently just following the y-axis of the graph (see attached image for example). I've achieved the same effect for a horizontal plot but when I try to adapt the code for a vertical graph I just can't get the circle to work properly.
I've brought an example together with the code below, still very new to javascript so code is a bit of a mess.
Screenshot of graph with circle (red) failing to follow the path:
function test(test_data) {
// setup params
var margin_ = {top: 30, right: 60, bottom: 30, left: 20},
width_ = 300
height_ = 700
// Add svg
var line_graph = d3.select("#my_dataviz_test")
.append("svg")
.attr("width", width_ + 100)
.attr("height", height_)
.append("g")
.attr("transform",
"translate(" + margin_.left + "," + margin_.top + ")");
d3.csv(test_data,
function(d){
return { output_time_ref: d.output_time_ref = +d.output_time_ref,
output_time: d3.timeParse("%d/%m/%Y %H:%M")(d.output_time),
prediction: d.prediction = +d.prediction,
}
},
function(data) {
// Add x axis
var x_test = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.prediction; })])
.range([ 0, width_ ]);
line_graph.append("g")
.attr("transform", "translate(" + 0 + "," + height_ + ")")
.call(d3.axisBottom(x_test).tickSizeOuter(0).tickSizeInner(0).ticks(2))
.select(".domain").remove();
// Add Y axis
var y_test = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.output_time_ref; })])
.range([ height_, 0 ]);
line_graph.append("g")
.call(d3.axisLeft(y_test).tickSizeOuter(0).tickSizeInner(0).ticks(5))
.select(".domain").remove();
// Add the line
path_test = line_graph.append("path")
.datum(data)
.attr("fill", "none")
.attr("fill", "steelblue")
.attr("fill-opacity", 0.2)
.attr("stroke", "steelblue")
.attr("stroke-width", 1)
.attr("d", d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x_test(d.prediction) })
.y(function(d) { return y_test(d.output_time_ref) })
)
var mouseG2 = line_graph
.append("g")
.attr("class", "mouse-over-effects");
mouseG2
.append("path")
.attr("class", "mouse-line2")
.style("stroke", "#393B45")
.style("stroke-width", "0.5px")
.style("opacity", 0.75)
mouseG2.append("text")
.attr("class", "mouse-text2")
var totalLength2 = path_test.node().getTotalLength();
var mousePerLine2 = mouseG2.selectAll('.mouse-per-line2')
.data(data)
.enter()
.append("g")
.attr("class", "mouse-per-line2");
mousePerLine2.append("circle")
.attr("r", 8)
.style("stroke", 'red')
.style("fill", "none")
.style("stroke-width", "2px")
.style("opacity", "0");
mouseG2
.append('svg:rect')
.attr('width', width_)
.attr('height', height_)
.attr('fill', 'none')
// .attr('opacity', 0.2)
.attr('pointer-events', 'all')
.on('mouseout', function() {
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2 circle")
.style("opacity", "0"); })
var mouseover = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "1")
.select(".mouse-text2")
.style("opacity", "1")
.select(".mouse-per-line2 circle")
.style("opacity", "1");
///////////////////////////////////////////////////
d3.select("#my_dataviz_test")
var mouse2 = d3.mouse(this);
d3.select("#my_dataviz_test")
.select(".mouse-text2")
.attr("y", mouse2[1])
.attr("transform", "translate(" + (mouse2[1]+60) + "," + (mouse2[1]+5) + ") rotate(90)")
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.attr("d", function() {
var d = "M" + width_ + "," + mouse2[1];
d += " " + 0 + "," + mouse2[1];
return d;
})
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2")
.attr("transform", function(d, i) {
var beginning2 = 0,
end2 = totalLength2
target2 = null;
while (true){
target2 = Math.floor((beginning2 + end2) / 2);
var pos2 = path_test.node().getPointAtLength(target2);
if ((target2 === end2 || target2 === beginning2) && pos2.y !== mouse2[1]) {
break;
}
if (pos2.y > mouse2[1]) { end2 = target2; }
else if (pos2.y < mouse2[1]) { beginning2 = target2; }
else {break};
}
d3.select("#my_dataviz_test").select('circle')
.style("opacity", 1)
return "translate(" + (pos2.x) + "," + mouse2[1] +")";
});
///////////////////////////////////////////////////
}
var mouseleave = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "0")
d3.select("#my_dataviz_test")
.select(".circle")
.style("opacity", "0")
}
line_graph
.on("mouseover", mouseover)
.on("mouseleave", mouseleave)
})
}
test_data.csv:
output_time_ref,output_time,prediction
0,04/01/2013 00:00,0
1,04/01/2013 00:30,0
2,04/01/2013 01:00,0
3,04/01/2013 01:30,0
4,04/01/2013 02:00,0
5,04/01/2013 02:30,0
6,04/01/2013 00:00,0
7,04/01/2013 03:30,0
8,04/01/2013 04:00,0
9,04/01/2013 04:30,8.17E-05
10,04/01/2013 05:00,0.002014463
11,04/01/2013 05:30,0.01322314
12,04/01/2013 06:00,0.033264463
13,04/01/2013 06:30,0.059607438
14,04/01/2013 07:00,0.098553719
15,04/01/2013 07:30,0.145661157
16,04/01/2013 08:00,0.186983471
17,04/01/2013 08:30,0.225206612
18,04/01/2013 09:00,0.267561983
19,04/01/2013 09:30,0.314049587
20,04/01/2013 10:00,0.334710744
21,04/01/2013 10:30,0.350206612
22,04/01/2013 11:00,0.359504132
23,04/01/2013 11:30,0.375
24,04/01/2013 12:00,0.393595041
25,04/01/2013 12:30,0.396694215
26,04/01/2013 13:00,0.393595041
27,04/01/2013 13:30,0.385330579
28,04/01/2013 14:00,0.367768595
29,04/01/2013 14:30,0.344008264
30,04/01/2013 15:00,0.320247934
31,04/01/2013 15:30,0.297520661
32,04/01/2013 16:00,0.273760331
33,04/01/2013 16:30,0.254132231
34,04/01/2013 17:00,0.216942149
35,04/01/2013 17:30,0.167355372
36,04/01/2013 18:00,0.123966942
37,04/01/2013 18:30,0.080785124
38,04/01/2013 19:00,0.041115702
39,04/01/2013 19:30,0.015805785
40,04/01/2013 20:00,0.002489669
41,04/01/2013 20:30,2.67E-05
42,04/01/2013 21:00,1.24E-05
43,04/01/2013 21:30,0
44,04/01/2013 22:00,0
45,04/01/2013 22:30,0
46,04/01/2013 23:00,0
47,04/01/2013 23:30,0
You can precisely compute x by y using the input data:
const y = d3.event.layerY - margin_.top;
const curY = y_test.invert(y);
const minY = Math.floor(curY);
const maxY = Math.ceil(curY);
if (data[minY] && data[maxY]) {
const yDelta = curY - minY;
const minP = data[minY].prediction;
const maxP = data[maxY].prediction;
const curP = minP + (maxP - minP) * yDelta;
const xPos = x_test(curP)
...
}
See it's working in the snippet:
const csvData = `output_time_ref,output_time,prediction
0,04/01/2013 00:00,0
1,04/01/2013 00:30,0
2,04/01/2013 01:00,0
3,04/01/2013 01:30,0
4,04/01/2013 02:00,0
5,04/01/2013 02:30,0
6,04/01/2013 00:00,0
7,04/01/2013 03:30,0
8,04/01/2013 04:00,0
9,04/01/2013 04:30,8.17E-05
10,04/01/2013 05:00,0.002014463
11,04/01/2013 05:30,0.01322314
12,04/01/2013 06:00,0.033264463
13,04/01/2013 06:30,0.059607438
14,04/01/2013 07:00,0.098553719
15,04/01/2013 07:30,0.145661157
16,04/01/2013 08:00,0.186983471
17,04/01/2013 08:30,0.225206612
18,04/01/2013 09:00,0.267561983
19,04/01/2013 09:30,0.314049587
20,04/01/2013 10:00,0.334710744
21,04/01/2013 10:30,0.350206612
22,04/01/2013 11:00,0.359504132
23,04/01/2013 11:30,0.375
24,04/01/2013 12:00,0.393595041
25,04/01/2013 12:30,0.396694215
26,04/01/2013 13:00,0.393595041
27,04/01/2013 13:30,0.385330579
28,04/01/2013 14:00,0.367768595
29,04/01/2013 14:30,0.344008264
30,04/01/2013 15:00,0.320247934
31,04/01/2013 15:30,0.297520661
32,04/01/2013 16:00,0.273760331
33,04/01/2013 16:30,0.254132231
34,04/01/2013 17:00,0.216942149
35,04/01/2013 17:30,0.167355372
36,04/01/2013 18:00,0.123966942
37,04/01/2013 18:30,0.080785124
38,04/01/2013 19:00,0.041115702
39,04/01/2013 19:30,0.015805785
40,04/01/2013 20:00,0.002489669
41,04/01/2013 20:30,2.67E-05
42,04/01/2013 21:00,1.24E-05
43,04/01/2013 21:30,0
44,04/01/2013 22:00,0
45,04/01/2013 22:30,0
46,04/01/2013 23:00,0
47,04/01/2013 23:30,0`;
var margin_ = {top: 30, right: 60, bottom: 30, left: 20},
width_ = 300
height_ = 700
// Add svg
var line_graph = d3.select("#my_dataviz_test")
.append("svg")
.attr("width", width_ + 100)
.attr("height", height_)
.append("g")
.attr("transform",
"translate(" + margin_.left + "," + margin_.top + ")");
const point = line_graph.append('circle')
.attr('r', 5)
.style('fill', 'red');
const data = d3.csvParse(csvData).map(d => ({
output_time_ref: +d.output_time_ref,
output_time: d3.timeParse("%d/%m/%Y %H:%M")(d.output_time),
prediction: +d.prediction,
}));
console.log(data);
// Add x axis
var x_test = d3.scaleLinear()
.domain([0, d3.max(data, d => d.prediction)])
.range([ 0, width_ ]);
line_graph.append("g")
.attr("transform", "translate(" + 0 + "," + height_ + ")")
.call(d3.axisBottom(x_test).tickSizeOuter(0).tickSizeInner(0).ticks(2))
.select(".domain").remove();
// Add Y axis
var y_test = d3.scaleLinear()
.domain([0, d3.max(data, d => +d.output_time_ref)])
.range([ height_, 0 ]);
line_graph.append("g")
.call(d3.axisLeft(y_test).tickSizeOuter(0).tickSizeInner(0).ticks(5))
.select(".domain").remove();
// Add the line
path_test = line_graph.append("path")
.datum(data)
.attr("fill", "none")
.attr("fill", "steelblue")
.attr("fill-opacity", 0.2)
.attr("stroke", "steelblue")
.attr("stroke-width", 1)
.attr("d", d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x_test(d.prediction) })
.y(function(d) { return y_test(d.output_time_ref) })
)
line_graph.on('mousemove', () => {
const y = d3.event.layerY - margin_.top;
const curY = y_test.invert(y);
const minY = Math.floor(curY);
const maxY = Math.ceil(curY);
if (data[minY] && data[maxY]) {
const yDelta = curY - minY;
const minP = data[minY].prediction;
const maxP = data[maxY].prediction;
const curP = minP + (maxP - minP) * yDelta;
const xPos = x_test(curP)
// console.log(xPos);
point
.attr('cx', xPos)
.attr('cy', y)
}
// line_graph
// y_test
})
/*
var mouseG2 = line_graph
.append("g")
.attr("class", "mouse-over-effects");
mouseG2
.append("path")
.attr("class", "mouse-line2")
.style("stroke", "#393B45")
.style("stroke-width", "0.5px")
.style("opacity", 0.75)
mouseG2.append("text")
.attr("class", "mouse-text2")
var totalLength2 = path_test.node().getTotalLength();
var mousePerLine2 = mouseG2.selectAll('.mouse-per-line2')
.data(data)
.enter()
.append("g")
.attr("class", "mouse-per-line2");
mousePerLine2.append("circle")
.attr("r", 8)
.style("stroke", 'red')
.style("fill", "none")
.style("stroke-width", "2px")
.style("opacity", "0");
mouseG2
.append('svg:rect')
.attr('width', width_)
.attr('height', height_)
.attr('fill', 'none')
// .attr('opacity', 0.2)
.attr('pointer-events', 'all')
.on('mouseout', function() {
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2 circle")
.style("opacity", "0"); })
var mouseover = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "1")
.select(".mouse-text2")
.style("opacity", "1")
.select(".mouse-per-line2 circle")
.style("opacity", "1");
d3.select("#my_dataviz_test")
var mouse2 = d3.mouse(this);
d3.select("#my_dataviz_test")
.select(".mouse-text2")
.attr("y", mouse2[1])
.attr("transform", "translate(" + (mouse2[1]+60) + "," + (mouse2[1]+5) + ") rotate(90)")
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.attr("d", function() {
var d = "M" + width_ + "," + mouse2[1];
d += " " + 0 + "," + mouse2[1];
return d;
})
d3.select("#my_dataviz_test")
.selectAll(".mouse-per-line2")
.attr("transform", function(d, i) {
var beginning2 = 0,
end2 = totalLength2
target2 = null;
while (true){
target2 = Math.floor((beginning2 + end2) / 2);
var pos2 = path_test.node().getPointAtLength(target2);
if ((target2 === end2 || target2 === beginning2) && pos2.y !== mouse2[1]) {
break;
}
if (pos2.y > mouse2[1]) { end2 = target2; }
else if (pos2.y < mouse2[1]) { beginning2 = target2; }
else {break};
}
d3.select("#my_dataviz_test").select('circle')
.style("opacity", 1)
return "translate(" + (pos2.x) + "," + mouse2[1] +")";
});
}
var mouseleave = function(d) {
d3.select("#my_dataviz_test")
.select(".mouse-line2")
.style("opacity", "0")
d3.select("#my_dataviz_test")
.select(".circle")
.style("opacity", "0")
}
line_graph
.on("mouseover", mouseover)
.on("mouseleave", mouseleave);
*/
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="my_dataviz_test" />
I have a double nested data set in d3. I want to create a scatter plot to be updated for each value of the first key ("time" variable), but the data of each point is bound to the values of he second key ("space" variable). So, to be clear, each point should be translated to new coordinates and its radius must be updated too.
Here is a data sample (in file "prosperLoanData.csv")
BorrowerState,LoanOriginationDate,LoanOriginalAmount,LenderYield
AK,2007-01-01 00:00:01,1000,0.1
AK,2007-01-01 00:00:01,2000,0.11
AK,2007-01-01 00:00:01,1500,0.09
AK,2007-01-01 00:00:01,500,0.1
AK,2008-01-01 00:00:01,2500,0.07
AK,2008-01-01 00:00:01,3000,0.06
AK,2008-01-01 00:00:01,3500,0.0652
OK,2007-01-01 00:00:01,4000,0.08
OK,2007-01-01 00:00:01,4100,0.081
OK,2007-01-01 00:00:01,4500,0.0812
OK,2007-01-01 00:00:01,4600,0.0799
OK,2007-01-01 00:00:01,3900,0.08
OK,2008-01-01 00:00:01,5000,0.05
And here is my code. I though that deleting the YearGroups1.exit().remove() was enough, but it is not. I can not properly select the circles and rebind them to the new dataset (at the second nested level). Can you help me?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
div.tooltip {
position: absolute;
text-align: center;
width: 180px;
height: 45px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<script type="text/javascript">
</script>
</head>
<body>
<script type="text/javascript">
/*
Use D3 to load the loan data
*/
var parseTime = d3.timeParse("%Y-%m-%d %H:%M:%S")
// load data
d3.csv("prosperLoanData.csv").then(function(data) {
// Setting global parameters
"use strict";
// ----> Margin & size
var margin = 75,
width = 1400 - margin,
height = 600 - margin,
op = 0.5,
years = [2007, 2008];
// ----> Format for strings
var formatDec = d3.format(".0f"),
formatper = d3.format(".2%"),
formatM = d3.format("\$.2s");
// change string (from CSV) into number format
data.forEach( function(d) {
d["Year"] = parseTime(d.LoanOriginationDate).getYear() + 1900;
d["LoanOriginalAmount"] = +d["LoanOriginalAmount"];
d["LenderYield"] = +d["LenderYield"];
// debugger
return d; });
// Function definition
// function update() {};
function key_func(d) {
return d['key'];
}
// FIXED PART
// Define the svg element
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin)
.attr("height", height + margin);
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Data preprocessing - grouping
var nested_year = d3.nest()
.key( function(d) { return d.Year } )
.key( function(d) { return d.BorrowerState })
.rollup( function(points) {
// debugger
var mean_loan = d3.mean(points, function(d) { return d.LoanOriginalAmount; } );
var mean_lenderyield = d3.mean(points, function(d) { return d.LenderYield; } );
// var std_loan = d3.deviation(points, function(d) { return d.LoanOriginalAmount; } );
var sum_loan = d3.sum(points, function(d) { return d.LoanOriginalAmount; } );
var max_loan = d3.max(points, function(d) { return d.LoanOriginalAmount; } );
var min_loan = d3.min(points, function(d) { return d.LoanOriginalAmount; } );
var max_ly = d3.max(points, function(d) { return d.LenderYield; } );
var min_ly = d3.min(points, function(d) { return d.LenderYield; } );
// debugger
return {
"meanLoan" : mean_loan,
"meanLenderyield" : mean_lenderyield,
// "stdLoan" : std_loan,
"sumLoan" : sum_loan,
};
}
)
.entries(data);
// Determining X/Y Max & Min
var LOA_E1 = d3.min(nested_year, function(d) {return d3.min(d.values, function(da) { return da.value.meanLoan; });})
var LOA_E2 = d3.max(nested_year, function(d) {return d3.max(d.values, function(da) { return da.value.meanLoan; });})
var LY_E1 = d3.min(nested_year, function(d) {return d3.min(d.values, function(da) { return da.value.meanLenderyield; });})
var LY_E2 = d3.max(nested_year, function(d) {return d3.max(d.values, function(da) { return da.value.meanLenderyield; });})
var LenderYield_Extent = [LY_E1 , LY_E2 ];
var LoanOriginalAmount_Extent = [LOA_E1 , LOA_E2];
// Creating a scale
var XScale = d3.scaleLinear()
.range([margin , width])
.domain([ 1.05 * LenderYield_Extent[0] - 0.05 * LenderYield_Extent[1] ,
1.05 * LenderYield_Extent[1] - 0.05 * LenderYield_Extent[0] ] );
// debugger
var YScale = d3.scaleLinear()
.range([height , margin])
.domain([ 0, 1.025 * LoanOriginalAmount_Extent[1]]);
var SUM_LOAN_Extent = [70E3 , 1.2E7]; // d3.extent(red_data.value, d => d.value.sumLoan);
var radius = d3.scaleSqrt()
.domain(SUM_LOAN_Extent)
.range([3,0.375 * margin/2]);
// Creating axis
var x_axis = d3.axisBottom()
.scale(XScale)
.tickFormat(d3.format(".2%"));
var y_axis = d3.axisLeft()
.scale(YScale)
.tickFormat(d3.format(".0f"));
svg.append('g')
.attr('transform', "translate(0," + height + ")")
.call(x_axis);
svg.append('g')
.attr('transform', "translate(" + margin + ",0)")
.call(y_axis);
// Text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2 + margin) + " ," +
(height + margin/2) + ")")
.style("text-anchor", "middle")
.style("font-size", 20)
.text("Mean lender yield (percentage)");
// Text label for the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-size", 20)
.text("Mean loan original amount [US dollars]");
// Creating gridlines
function make_x_gridlines() {
return d3.axisBottom(XScale)
.ticks(5);
};
function make_y_gridlines() {
return d3.axisLeft(YScale)
.ticks(5);
};
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_gridlines()
.tickSize(-(height - 1 * margin ))
.tickFormat(""));
svg.append("g")
.attr("class", "grid")
.attr('transform', "translate(" + margin + ",0)")
.call(make_y_gridlines()
.tickSize(-(width - 1 * margin ))
.tickFormat(""));
// Add legend
var DELTA = SUM_LOAN_Extent[1] - SUM_LOAN_Extent[0];
var valuesToShow = [SUM_LOAN_Extent[0] + 0.00 * DELTA, SUM_LOAN_Extent[0] + 0.25 * DELTA ,
SUM_LOAN_Extent[0] + 0.50 * DELTA, SUM_LOAN_Extent[0] + 0.75 * DELTA ,
SUM_LOAN_Extent[0] + 1.00 * DELTA ];
var xCircle = width + 0.35 * margin;
var xLabel = 200;
var yCircle = 150;
var legend = svg.append("g")
.attr("class","legend")
.attr("trasform","translate(" + (width - 100) + "," + 20 + ")" )
.selectAll("g")
.data(valuesToShow)
.enter()
.append("g");
legend.append("circle")
.attr("cy", function(d,i) {
return (i+1)* 0.4 * yCircle + radius(d)/2; })
.attr("cx", xCircle)
.attr("r", function(d) {
return radius(d); })
.attr("stroke","black")
.attr("stroke-width",1.)
.attr("opacity", op);
// Add legend: labels
svg.selectAll("legend")
.data(valuesToShow)
.enter()
.append("text")
.attr('x', function(d,i){ return width + 0.55 * margin; } )
.attr('y', function(d,i){ return (i+1)* 0.4 * yCircle + radius(d)/2 } )
.text( function(d){ return formatM(d) } )
.style("font-size", 10)
.attr('alignment-baseline', 'middle');
svg.append("text")
.attr('x', 0.98 * xCircle )
.attr('y', 0.15 * yCircle )
.text( "Size = " )
.style("font-size", 12.5)
.attr('alignment-baseline', 'middle');
svg.append("text")
.attr('x', 0.98 * xCircle )
.attr('y', 0.15 * yCircle + 10 )
.text( "Total loan" )
.style("font-size", 12.5)
.attr('alignment-baseline', 'middle');
svg.append("text")
.attr('x', 0.98 * xCircle )
.attr('y', 0.15 * yCircle + 20)
.text( "amount (US \$)" )
.style("font-size", 12.5)
.attr('alignment-baseline', 'middle');
// Add color legend
var colors = d3.scaleOrdinal()
.domain([0, 1, 2])
.range(["blue", "green" , "black"]);
var stat = d3.scaleOrdinal()
.domain([0, 1, 2])
.range(["CA", "TX" , "Others"]);
var aaa = [ SUM_LOAN_Extent[0] + 1.00 * DELTA , SUM_LOAN_Extent[0] + 1.00 * DELTA, SUM_LOAN_Extent[0] + 1.00 * DELTA];
var legend1 = svg.append("g")
.attr("class","legend")
.attr("trasform","translate(" + (width - 100) + "," + 20 + ")" )
.selectAll("g")
.data(aaa)
.enter()
.append("g");
legend1.append("circle")
.attr("cy", function(d,i) { //debugger
return (6+i)* 0.4 * yCircle + radius(d)/2; })
.attr("cx", xCircle)
.attr("r", function(d) {
return radius(d); })
.attr("fill", function(d,i){ return colors(i)})
.attr("opacity", op);
// Add legend: labels
svg.selectAll(".legend1")
.data(aaa)
.enter()
.append("text")
.attr('x', function(d,i){ return width + 0.55 * margin; } )
.attr('y', function(d,i){ return (6+i)* 0.4 * yCircle + radius(d)/2 } )
.text( function(d,i){ return stat(i) } )
.style("font-size", 10)
.attr('alignment-baseline', 'middle');
// Appending first circles
// Accessing 1st group
var YearGroups = svg.selectAll(".YearGroups")
.data(nested_year, key_func)
.enter()
.append("g")
.attr("class", "YearGroups");
// Accessing 2nd group
var circles = YearGroups.selectAll("circle")
.data(function(d){
return d.values
})
.enter()
.append("circle")
.transition()
.duration(500)
.attr("cx", function(d) { return XScale(d.value.meanLenderyield) } )
.attr("cy", function(d) { return YScale(d.value.meanLoan ) } )
.attr("r" , function(d) { return radius(d.value.sumLoan ) } )
.attr("fill" , function(d) { if
(d.key == "CA") {return "blue"}
if (d.key == "TX") {return "green"}
else
{ return "black" }})
.attr("opacity" , op);
// debugger
// VARIABLE PART
function update(year) {
var filt = nested_year.filter(function(d) {return d.key == year;} );
var YearGroups1 = svg.selectAll(".YearGroups")
.data(filt, key_func);
YearGroups1.exit().remove();
var circles = YearGroups1.enter().append('g')
.attr("class", "YearGroups").selectAll('circle')
.data(function(d){ return d.values });
var CircPl = circles.enter()
.append("circle")
.transition()
.duration(500)
.attr("cx", function(d) { // debugger
return XScale(d.value.meanLenderyield); })
.attr("cy", function(d) { return YScale(d.value.meanLoan ); })
.attr("r" , function(d) { return radius(d.value.sumLoan ); })
.attr("fill" , function(d) { if
(d.key == "DC") {return "blue"}
if (d.key == "AR") {return "green"}
else
{ return "black" }})
.attr("opacity" , op)
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("Lender yield : " + formatper(d.value.meanLenderyield) + "<br/>" +
"Loan original amount : " + formatDec(d.value.meanLoan) + " $ <br/>" +
"State : " + d.key)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
CircPl
// debugger
// Chart Title
svg.selectAll(".Title").remove()
svg
.append("text")
.attr("class","Title")
.attr("x", (margin + width) / 2)
.attr("y", margin / 2)
.attr("font-weight", "bold")
.attr("text-anchor", "middle")
.style("font-size", "32px")
.text("US Loans per State in " + year); // Title updated
}
var year_idx = 0;
var year_interval = setInterval(function() {
update(years[year_idx]);
year_idx++;
if(year_idx >= years.length) {
clearInterval(year_interval);
}
}, 1000);
});
</script>
</body>
</html>
I got the solution for the issue (thanks Myles C. from Udacity Mentor team).
In fact, the data to be accessed where easily stored in a dummy variable that has been used for data binding afterwards and the .merge() statement is used to update both to all circles.
function update_video(year) {
var filt = nested_year.filter(function(d) {
return d.key == year;
});
var data = filt[0].values
// Selection and Bind Data
const update = YearGroups.selectAll("circle")
.data(data);
var upd = update.enter()
.select("circle")
.merge(update)
.transition()
.duration(1500)
.attr("class", "node_circle")
.attr("cx", function(d) {
return XScale(d.value.meanLenderyield);
})
.attr("cy", function(d) {
return YScale(d.value.meanLoan);
})
.attr("r", function(d) {
return radius(d.value.sumLoan);
})
.attr("fill", function(d) {
if (d.key == "CA") { return "blue";}
if (d.key == "TX") { return "green";}
else { return "black"}
});
// Command necessary to remove States that are not available in the
// database for that year
update.exit().remove();
// Chart Title
svg.selectAll(".Title").remove();
svg.append("text")
.attr("class", "Title")
.attr("x", (margin + width) / 2)
.attr("y", margin / 2)
.attr("font-weight", "bold")
.attr("text-anchor", "middle")
.style("font-size", "32px")
.text("US Loans per State in " + year); // Title update
};
With this function, each circle is updated, regardless of the state (second key of nested data); the .exit().remove() command has been used to remove all points not bound to data.
Afterward, a second function is used to let the user choose a year and update the circles without transitions, but adding tooltips to get data information hoovering the points with the mouse.
function update_with_tooltip(year) {
var filt = nested_year.filter(function(d) {
return d.key == year;
});
var data = filt[0].values;
// Selection and Bind Data
const update = YearGroups.selectAll("circle")
.data(data);
// Update and merge
var upd = update.enter()
.select("circle")
.merge(update)
.attr("class", "node_circle")
.attr("cx", function(d) {
return XScale(d.value.meanLenderyield);
})
.attr("cy", function(d) {
return YScale(d.value.meanLoan);
})
.attr("r", function(d) {
return radius(d.value.sumLoan);
})
.attr("fill", function(d) {
if (d.key == "CA") { return "blue";}
if (d.key == "TX") { return "green";}
else { return "black"}
})
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("Lender yield : " + formatper(d.value.meanLenderyield) +
"<br/>" + "Loan original amount : " + formatDec(d.value.meanLoan)
+ " $ <br/>" + "State : " + d.key)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
// Command necessary to remove States that are not available in the
// database for that year
update.exit().remove();
// Chart Title
svg.selectAll(".Title").remove();
svg.append("text")
.attr("class", "Title")
.attr("x", (margin + width) / 2)
.attr("y", margin / 2)
.attr("font-weight", "bold")
.attr("text-anchor", "middle")
.style("font-size", "32px")
.text("US Loans per State in " + year); // Title update
}
I am begginer to d3.js and getting errors, I customized the example for my needs but gettings these in the console. Please help. Thank you in Advance.
Error: Invalid value for <path> attribute d="M-1.8369701987210297e-14,-100A100,100 0 1,1 NaN,NaNLNaN,NaNA60,60 0 1,0 -1.1021821192326178e-14,-60Z"a # d3.v3.min.js:1
d3.v3.min.js:1 Error: Invalid value for <path> attribute d="MNaN,NaNA100,100 0 1,1 NaN,NaNLNaN,NaNA60,60 0 1,0 NaN,NaNZ"
var getData = function () {
var size = 3;
var data =[];
var text = "";
data=[
{
label:"Saved",
value:200,
},{
label:"Ordered",
value:1255,
},{
label:"Shipped",
value:760,
},{
label:"Backordered",
value:150,
},
{
label:"Cancelled",
value:250,
},
];
d3.select("#data").html(text);
return data;
};
console.log(getData());
var chart = donut(250,200,16,40)
.$el(d3.select("#chart"))
.data(getData())
.render();
d3.select("#refresh").on("click", function () {
chart.data(getData()).render();
});
function donut(width,height,label_font_size,value_font_size) {
// Default settings
var $el = d3.select("body")
var data = {};
// var showTitle = true;
var width = width,
height = height,
radius = Math.min(width, height) / 2;
//var arc = d3.svg.arc().outerRadius(radius);
var arcOver = d3.svg.arc()
.innerRadius(radius + 20);
var currentVal;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null)
.value(function (d) {
console.log(d);
return d.value.value;
});
var svg, g, arc;
var object = {};
// Method for render/refresh graph
object.render = function () {
if (!svg) {
arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(radius - (radius / 2.5));
svg = $el.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
g = svg.selectAll(".arc")
.data(pie(d3.entries(data)))
.enter().append("g")
.attr("class", "arc");
g.append("path")
// Attach current value to g so that we can use it for animation
.each(function (d) {
//this._current = d;
})
.attr("d", arc)
.style("fill", function (d,i) {
return color(i);
});
/*g.append("text")
.attr("transform", function (d,i) {
return "translate(" + arc.centroid(d,i) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle");
g.select("text").text(function (d) {
//return d.data.key;
});
*/
svg.append("text")
.datum(data)
.attr("x", 0)
.attr("y", 0)
.attr("class", "text-tooltip")
.style("text-anchor", "middle")
.attr("font-weight", "bold")
.style("font-size", radius / 2.5 + "px");
g.on("mouseover", function (obj) {
var tooltipText=svg.select("text.text-tooltip");
tooltipText.attr("fill", function (d,i) {
return color(i);
});
tooltipText.append("tspan")
.attr("dy", -15)
.attr("x",0)
.attr("class", "text-tooltip-label")
.style("font-size", label_font_size + "px")
.text(function(d) {return d[obj.data.key].label;});
tooltipText.append("tspan")
.attr("dy", ".9em") // offest by 1.2 em
.attr("x",0)
.attr("class", "text-tooltip-value")
.style("font-size", value_font_size + "px")
.text(function(d) {return d[obj.data.key].value;});
d3.select(this)
.attr("stroke","white")
.transition()
.duration(1000)
.attr("d", arcOver)
.attr("stroke-width",6);
});
g.on("mouseout", function (obj) {
svg.select("text.text-tooltip").text("");
d3.select(this).transition()
.attr("d", arc)
.attr("stroke","none");
});
} else {
g.data(pie(d3.entries(data))).exit().remove();
g.select("path")
.transition().duration(200)
.attrTween("d", function (a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function (t) {
return arc(i(t));
};
});
g.select("text")
.attr("transform", function (d,i) {
return "translate(" + arc.centroid(d,i) + ")";
});
svg.select("text.text-tooltip").datum(data);
}
return object;
};
// Getter and setter methods
object.data = function (value) {
if (!arguments.length) return data;
data = value;
return object;
};
object.$el = function (value) {
if (!arguments.length) return $el;
$el = value;
return object;
};
object.width = function (value) {
if (!arguments.length) return width;
width = value;
radius = Math.min(width, height) / 2;
return object;
};
object.height = function (value) {
if (!arguments.length) return height;
height = value;
radius = Math.min(width, height) / 2;
return object;
};
return object;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
I have a sunburst for which I would like to initiate zoom ability via dropdown. That is when a country name is selected from the drop down menu its part in the sunburst zooms exactly like when its clicked.
js fiddle: http://jsfiddle.net/8wd2xt9n/7/
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var b = {
w: 130, h: 30, s: 3, t: 10
};
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var changeArray = [100,80,60,0, -60, -80, -100];
var colorArray = ["#67000d", "#b73e43", "#d5464a", "#f26256", "#fb876e", "#fca78e", "#fcbba1", "#fee0d2", "#fff5f0"];
var svg = d3.select("#diagram").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function(d) { return d.Total; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
console.log(arc)
function checkIt(error, root){
initializeBreadcrumbTrail();
//intilaize dropdown
if (error) throw error;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.style("fill", function(d) { var color;
if(d.Total_Change > 100)
{color = colorArray[0]
}
else if(d.Total_Change > 0 && d.Total_Change < 100)
{color = colorArray[1]
}
else
{
color = colorArray[2]
}
d.color = color;
return color})
.on("click", click)
.on("mouseover", mouseover);
var tooltip = d3.select("body")
.append("div")
.attr("id", "tooltips")
.style("position", "absolute")
.style("background-color", "#fff")
.style("z-index", "10")
.style("visibility", "hidden");
g.on("mouseover", function(d){return tooltip.style("visibility", "visible")
.html("<div class=" + "tooltip_container>"+"<h4 class=" + "tooltip_heading>" +d.name.replace(/[_-]/g, " ") +"</h4>" +"<br>" + "<p> Emissions 2013:" + " " + "<br>" + d.Total + " " +"<span>in Million Tons</span></p>"+ "<br>"+ "<p> Change in Emissions: <span>" + (d.Total_Change/d.Total*100).toPrecision(3) + "%" + "</span></p>"+"</div>" );})
.on("mousemove", function(){return tooltip.style("top",(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
//creating a dropdown
var dropDown = d3.select("#dropdown_container")
.append("select")
.attr("class", "selection")
.attr("name", "country-list");
var nodeArr = partition.nodes(root);
var options = dropDown.selectAll("option")
.data(nodeArr)
.enter()
.append("option");
options.text(function (d) {
var prefix = new Array(d.depth + 1);
var dropdownValues = d.name.replace(/[_-]/g, " ");
return dropdownValues;
}).attr("value", function (d) {
var dropdownValues = d.name;
return dropdownValues;
});
// transition on click
function click(d) {
// fade out all text elements
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function(d) { return y(d.y); });
}
});
};
d3.select(".selection").on("change", function changePie() {
var value = this.value;
var index = this.selectedIndex;
var dataObj = nodeArr[index];
path[0].forEach(function(p){
var data = d3.select(p).data();//get the data from the path
if (data[0].name === value){
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function(d) { return y(d.y); });
}
});
}
});
console.log(this.value + " :c " + dataObj["Iron and steel"] + " in " + (dataObj.parent && dataObj.parent.name));
});
};
d3.json("https://gist.githubusercontent.com/heenaI/cbbc5c5f49994f174376/raw/55c672bbca7991442f1209cfbbb6ded45d5e8c8e/data.json", checkIt);
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
function initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select("#sequence").append("svg:svg")
.attr("width", width)
.attr("height", 50)
.attr("id", "trail");
// Add the label at the end, for the percentage.
trail.append("svg:text")
.attr("id", "endlabel")
.style("fill", "#000");
}
function breadcrumbPoints(d, i) {
var points = [];
points.push("0,0");
points.push(b.w + ",0");
points.push(b.w + b.t + "," + (b.h / 2));
points.push(b.w + "," + b.h);
points.push("0," + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + "," + (b.h / 2));
}
return points.join(" ");
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g")
.data(nodeArray, function(d) { return d.name.replace(/[_-]/g, " ") + d.Total; });
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", "#d3d3d3");
entering.append("svg:text")
.attr("x", (b.w + b.t) / 2)
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name.replace(/[_-]/g, " "); });
// Set position for entering and updating nodes.
g.attr("transform", function(d, i) {
return "translate(" + i * (b.w + b.s) + ", 0)";
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
d3.select("#trail").select("#endlabel")
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle");
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
function mouseover(d) {
var sequenceArray = getAncestors(d);
updateBreadcrumbs(sequenceArray);}
Add this one line and it will work :)
Inside your selection change add this var d = data[0]; as shown below:
d3.select(".selection").on("change", function changePie() {
var value = this.value;
var index = this.selectedIndex;
var dataObj = nodeArr[index];
path[0].forEach(function(p){
var data = d3.select(p).data();//get the data from the path
if (data[0].name === value){
var d = data[0];//this line is missing
path.transition()
Working code here
Other option is to call the click function on selection change like below
d3.select(".selection").on("change", function changePie() {
var value = this.value;
var index = this.selectedIndex;
var dataObj = nodeArr[index];
path[0].forEach(function (p) {
var data = d3.select(p).data(); //get the data from the path
if (data[0].name === value) {
console.log(data)
click(data[0]);//call the click function
}
Working code here
Hope this helps!
hi I created a spiral chart in d3.js, and I want to add circle to different position of the spiral lines.according to there values.
circle closes to the center will have highest priority.
any idea how to do that.
here is the code which i wrote
var width = 400,
height = 430
num_axes = 8,
tick_axis = 1,
start = 0
end = 4;
var theta = function(r) {
return -2*Math.PI*r;
};
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(2*Math.PI);
var radius = d3.scale.linear()
.domain([start, end])
.range([0, d3.min([width,height])/2-20]);
var angle = d3.scale.linear()
.domain([0,num_axes])
.range([0,360])
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + (height/2+8) +")");
var pieces = d3.range(start, end+0.001, (end-start)/1000);
var spiral = d3.svg.line.radial()
.interpolate("cardinal")
.angle(theta)
.radius(radius);
//svg.append("text")
// .text("And there was much rejoicing!")
// .attr("class", "title")
// .attr("x", 0)
// .attr("y", -height/2+16)
// .attr("text-anchor", "middle")
//svg.selectAll("circle.tick")
// .data(d3.range(end,start,(start-end)/4))
// .enter().append("circle")
// .attr("class", "tick")
// .attr("cx", 0)
// .attr("cy", 0)
// .attr("r", function(d) { return radius(d); })
svg.selectAll(".axis")
.data(d3.range(num_axes))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + -angle(d) + ")"; })
.call(radial_tick)
.append("text")
.attr("y", radius(end)+13)
.text(function(d) { return angle(d) + "°"; })
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "rotate(" + -90 + ")" })
svg.selectAll(".spiral")
.data([pieces])
.enter().append("path")
.attr("class", "spiral")
.attr("d", spiral)
.attr("transform", function(d) { return "rotate(" + 90 + ")" });
function radial_tick(selection) {
selection.each(function(axis_num) {
d3.svg.axis()
.scale(radius)
.ticks(5)
.tickValues( axis_num == tick_axis ? null : [])
.orient("bottom")(d3.select(this))
d3.select(this)
.selectAll("text")
.attr("text-anchor", "bottom")
.attr("transform", "rotate(" + angle(axis_num) + ")")
});
}
please see the second solution for my implementation. Help me with connecting the circle with the center
Here is a model for the technique you seem to be looking for...
var width = 400,
height = 430,
num_axes = 8,
tick_axis = 1,
start = 0,
end = 4,
testValue = 2;
var theta = function (r) {
return -2 * Math.PI * r;
};
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(2 * Math.PI);
var radius = d3.scale.linear()
.domain([start, end])
.range([0, (d3.min([width, height]) / 2 - 20)]);
var angle = d3.scale.linear()
.domain([0, num_axes])
.range([0, 360]);
var chart = d3.select("#chart")
.style("width", width + "px");
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 8) + ")");
var pieces = d3.range(start, end + 0.001, (end - start) / 500);
var spiral = d3.svg.line.radial()
.interpolate("linear")
.angle(theta)
.radius(radius);
svg.append("text")
.text("Title")
.attr("class", "title")
.attr("x", 0)
.attr("y", -height/2+16)
.attr("text-anchor", "middle")
svg.selectAll("circle.tick")
.data(d3.range(end,start,(start-end)/4))
.enter().append("circle")
.attr("class", "tick")
.style({fill: "black", opacity: 0.1})
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d) { return radius(d); })
svg.selectAll(".axis")
.data(d3.range(num_axes))
.enter().append("g")
.attr("class", "axis")
.attr("transform", function (d) { return "rotate(" + -angle(d) + ")"; })
.call(radial_tick)
.append("text")
.attr("y", radius(end) + 13)
.text(function (d) { return angle(d) + "°"; })
.attr("text-anchor", "middle")
.attr("transform", function (d) { return "rotate(" + -90 + ")" })
svg.selectAll(".axis path")
.style({fill: "none", stroke: "black"})
.attr("stroke-dasharray", "5 5")
svg.selectAll(".spiral")
.data([pieces])
.enter().append("path")
.attr("class", "spiral")
.attr("fill", "none")
.attr("stroke", "black")
.attr("d", spiral)
.attr("transform", function (d) { return "rotate(" + 90 + ")" });
function radial_tick(selection) {
selection.each(function (axis_num) {
d3.svg.axis()
.scale(radius)
.ticks(5)
.tickValues(axis_num == tick_axis ? null : [])
.tickSize(1)
.orient("bottom")(d3.select(this))
d3.select(this)
.selectAll("text")
.attr("text-anchor", "bottom")
.attr("transform", "rotate(" + angle(axis_num) + ")")
});
}
function radialScale(x) {
var t = theta(x), r = radius(x);
d3.select(this)
.attr("cx", r * Math.cos(t))
.attr("cy", r * Math.sin(t))
}
slider = SliderControl("#circleSlider", "data", update, [start, end], ",.3f");
function update(x) {
if (typeof x != "undefined") testValue = x;
var circles = svg.selectAll(".dataPoints")
.data([testValue]);
circles.enter().append("circle");
circles.attr("class", "dataPoints")
.style({ fill: "black", opacity: 0.6 })
.attr("r", 10)
.each(radialScale)
circles.exit().remove();
return testValue
}
function SliderControl(selector, title, value, domain, format) {
var accessor = d3.functor(value), rangeMax = 1000,
_scale = d3.scale.linear().domain(domain).range([0, rangeMax]),
_$outputDiv = $("<div />", { class: "slider-value" }),
_update = function (value) {
_$outputDiv.css("left", 'calc( '
+ (_$slider.position().left + _$slider.outerWidth()) + 'px + 1em )')
_$outputDiv.text(d3.format(format)(value));
$(".input").width(_$outputDiv.position().left + _$outputDiv.outerWidth() - _innerLeft)
},
_$slider = $(selector).slider({
value: _scale(accessor()),
max: rangeMax,
slide: function (e, ui) {
_update(_scale.invert(ui.value));
accessor(_scale.invert(ui.value));
}
}),
_$wrapper = _$slider.wrap("<div class='input'></div>")
.before($("<div />").text(title + ":"))
.after(_$outputDiv).parent(),
_innerLeft = _$wrapper.children().first().position().left;
_update(_scale.invert($(selector).slider("value")))
return d3.select(selector)
};
.domain {
stroke-width: 1px;
}
<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart">
<div id="circleSlider"></div>
</div>