I have this bubble chart and want to zoom in to be able to see the very small bubbles. I tried this code by Mike Bostock but I have not succeeded getting good zooming functions, I think it because I have another chart concept.
Other examples online apply zoom on charts that have axes but my chart has no axes.
Here is my Code:
d3.json("Data/New/Treemap_source.json", function (error, data) {
if (error) throw error;
var diameter = 693;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var format = function (d){ return "BTC " + d3.format(",.2f")(d); }
var bubble = d3.pack(data)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("#bubblediv")
.append("svg")
.attr("width", diameter + margin.left + margin.right)
.attr("height", diameter + margin.top + margin.bottom)
.attr("class", "bubble");
var nodes = d3.hierarchy(data)
.sum(function(d) { return d.VolumeBTC; });
var node = svg.selectAll(".node")
.data(bubble(nodes).descendants())
.enter()
.filter(function(d){
return !d.children
})
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + (d.x + margin.left)+ "," + (d.y + margin.top+20) + ")";
});
node.on("click", function (d) {
alert("This bubble contains: " + d.data.Symbol);
// var sel = d.data.Symbol;
d3.select('#my-select').property('value', d.data.Symbol);
// print_filter(d3.select('#my-select').property('value', d.data.Symbol));
// d3.select('#my-select').property('value', d.data.Symbol);
});
node.append("title")
.text(function(d) {
return d.data.Symbol + ": " + format(d.value);
});
d3.select("svg").append("text")
.attr("transform", "translate(" + (diameter / 2 -20) + " ,30)")
.attr('class','chartlabel')
.style("text-anchor", "middle")
.text("Altcoins Trading in BTC");
node.append("circle")
.attr("r", function(d) {
return d.r;
})
.style('stroke', '#263432')
.style('stroke-width', '1.5')
.style("fill", function(d,i) {
return color(i);
});
node.append("text")
.attr("dy", ".2em")
.style("text-anchor", "middle")
.text(function(d) {
return d.data.Symbol.substring(0, d.r / 3);
})
.attr("font-family", "sans-serif")
.attr("font-size", function(d){
return d.r/5;
})
.attr("fill", "white");
node.append("text")
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.text(function(d) {
return format(d.data.VolumeBTC);
})
.attr("font-family", "Gill Sans", "Gill Sans MT")
.attr("font-size", function(d){
return d.r/6;
})
.attr("fill", "white");
d3.select(self.frameElement)
.style("height", diameter + "px");
});
As of d3 v4, you can zoom on any svg with the following:
function zoomed() {
svg.attr("transform", d3.event.transform);
}
var zoom = d3.zoom().on("zoom", zoomed);
svg.call(zoom);
To get it to work exactly as you want, you'll need to use d3-zoom:
https://github.com/d3/d3-zoom
Lasly, you'll probably want to have a button to reset the zoom, which can be done like this:
d3.select('#zoom-reset-button').on("click", function() {
zoom.transform(svg, d3.zoomIdentity);
});
Related
In the code below, I need to make the bubbles with the highest values float to the left of the screen, but I have no deep knowledge of D3.js and I can't find a way to do this.
My code
<script type="text/javascript">
dataset = {
"children": [{"Name":"Olives","Count":10},
{"Name":"Tea","Count":8},
{"Name":"Mashed Potatoes","Count":6},
{"Name":"Boiled Potatoes","Count":5},
{"Name":"Milk","Count":4},
{"Name":"Chicken Salad","Count":4},
{"Name":"Vanilla Ice Cream","Count":2},
{"Name":"Cocoa","Count":7}];
var diameter = 600;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var bubble = d3.pack(dataset)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body")
.append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
var nodes = d3.hierarchy(dataset)
.sum(function(d) { return d.Count; });
var node = svg.selectAll(".node")
.data(bubble(nodes).descendants())
.enter()
.filter(function(d){
return !d.children
})
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) {
return d.Name + ": " + d.Count;
});
node.append("circle")
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d,i) {
return color(i);
});
node.append("text")
.attr("dy", ".2em")
.style("text-anchor", "middle")
.text(function(d) {
return d.data.Name.substring(0, d.r / 3);
})
.attr("font-family", "sans-serif")
.attr("font-size", function(d){
return d.r/5;
})
.attr("fill", "white");
node.append("text")
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.text(function(d) {
return d.data.Count;
})
.attr("font-family", "Gill Sans", "Gill Sans MT")
.attr("font-size", function(d){
return d.r/5;
})
.attr("fill", "white");
d3.select(self.frameElement)
.style("height", diameter + "px");
</script>
Edit in JS Fiddle
The code produces this result
I need the bubbles with the highest values to be on the left, as in the image below.
What property or function of D3.js can I use to control the severity of the bubbles as I need them?
Thanks!
There's not a function specifically in d3.pack for this. d3.force allows you specify x and y positions based on the data's value, and could achieve the result you are looking for.
The force simulation has an .x and .y function that is based on the data's count, and then to avoid overlaps, the .collide function adjusts the circles positions based on their radius (plus a small paddding of 3px).
var simulation = d3.forceSimulation(nodes)
.force("forceX", d3.forceX().strength(.051).x(d => xScale(d.Count)))
.force("forceY", d3.forceY().strength(.051).y(d => yScale(d.Count)))
.force('collision', d3.forceCollide().radius(d => rScale(d.Count) + 3))
dataset = {
"children": [{"Name":"Olives","Count":10},
{"Name":"Tea","Count":8},
{"Name":"Mashed Potatoes","Count":6},
{"Name":"Boiled Potatoes","Count":5},
{"Name":"Milk","Count":4},
{"Name":"Chicken Salad","Count":4},
{"Name":"Vanilla Ice Cream","Count":2},
{"Name":"Cocoa","Count":7}]
}
let nodes = dataset.children
var width = 600;
var height = 600;
var margin = 50
var color = d3.scaleOrdinal(d3.schemeCategory20);
let extentCount = d3.extent(nodes, d => d.Count)
let maxRadius = 100
let yScale = d3.scaleLinear()
.domain(extentCount)
.range([height - maxRadius, maxRadius])
let xScale = d3.scaleLinear()
.domain(extentCount)
.range([(width - maxRadius), maxRadius])
let rScale = d3.scaleSqrt()
.domain(extentCount)
.range([5, maxRadius])
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin + margin)
.attr("height", height + margin + margin)
.attr("class", "bubble");
var g = svg.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
var simulation = d3.forceSimulation(nodes)
.force("forceX", d3.forceX().strength(.051).x(d => xScale(d.Count)))
.force("forceY", d3.forceY().strength(.051).y(d => yScale(d.Count)))
.force('collision', d3.forceCollide().radius(d => rScale(d.Count) + 3))
.on("tick", function(d){
node
.attr("cx", function(d){ return d.x; })
.attr("cy", function(d){ return d.y; })
})
.stop()
for (var i = 0; i < 120; i++) {
simulation.tick()
}
var node = g.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) {
return d.Name + ": " + d.Count;
});
node.append("circle")
.attr("r", d => rScale(d.Count))
.style("fill", function(d,i) {
return color(i);
});
node.append("text")
.attr("dy", ".2em")
.style("text-anchor", "middle")
.text(function(d) {
return d.Name.substring(0, rScale(d.Count) / 3);
})
.attr("font-family", "sans-serif")
.attr("font-size", function(d){
return rScale(d.Count)/5;
})
.attr("fill", "white");
node.append("text")
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.text(function(d) {
return d.Count;
})
.attr("font-family", "Gill Sans", "Gill Sans MT")
.attr("font-size", function(d){
return d.r/5;
})
.attr("fill", "white");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
Trying to add the textual data above the path in this Zoomable Sunburst graph: Zoomable Sunburst
I have this code:
d3.json(jsonUrl, function(error, data) {
if (error) {
throw error;
}
svg.selectAll("g")
.data(partition.nodes(data))
.enter()
.append("g")
.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", self.click)
.append("text")
.text(function(d) {
//console.dir(d);
return d.name + "\n" + formatNumber(d.value);
});
});
But it doesn't work. No text is shown. I know it is something like that but I make something wrong. I am using D3js version 3 at the moment.
Some ideas?
i just found this Donut Chart example and changed some parts in my code.
Though it looks cluttered it does the job. Maybe with some polishing I could get decent looks:
Here is my code:
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")"),
g = null;
d3.json(dataFile, function(error, data) {
if (error) {
throw error;
}
g = svg.selectAll(".arc")
.data(partition.nodes(data))
.enter()
.append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", self.click);
g.append("text")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.text(function (d) {
return d.name + "\n" + formatNumber(d.value);
});
});
I want to highlight every term/word that the user has clicked on with an background box for this particular term/word. I am using the on click function, I can access and set the color of the word itself, but I am not able to set the background of this word. How can I achieve that?
Here is my draw function:
function draw(words) {
d3.select("#interests").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + [w >> 1, h >> 1] + ")")
.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.style("cursor", "pointer")
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; })
.on("click", function(d) {
// d3.select(this).style("fill");
d3.select(this).style("background","yellow");
if (d.inGraph == true){
d.inGraph = false;
unloadInterest(d.text);
} else {
d.inGraph = true;
loadInterest(d.text, "#ff0000");
}
});
}
text elements don't have a background style or attribute. To highlight words you have to create a rect element with the same size as the text (getBBox() for dimensions).
Your code can be modified as follows.
function draw(words) {
var main_g = d3.select("#interests").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + [w >> 1, h >> 1] + ")")
main_g.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.style("cursor", "pointer")
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; })
.on("click", function(d) {
var bbox = this.getBBox();
main_g.insert("rect",":first-child")
.attr("x", bbox.x)
.attr("y", bbox.y)
.attr("width", bbox.width)
.attr("height", bbox.height)
.style("fill", "yellow");
if (d.inGraph == true){
d.inGraph = false;
unloadInterest(d.text);
} else {
d.inGraph = true;
loadInterest(d.text, "#ff0000");
}
});
}
I have a large number of legends, but I want to display only 5 legends at a time. I want to have a button that displays the next 5 legends. Can anyone help me in implementing this?
I have implemented the pagination but in the end I am having two repeating legends.
My working code is here js fiddle.
var data=[
{
"age":"<5",
"population":2704659
},
{
"age":"5-10",
"population":4499890
},
{
"age":"10-13",
"population":6736433
},
{
"age":"14-16",
"population":2159981
},
{
"age":"16-18",
"population":3853788
},
{
"age":"18-22",
"population":8848383
},
{
"age":"22-30",
"population":8384390
},
{
"age":"30-44",
"population":14106543
},
{
"age":"45-64",
"population":8819342
},
{
"age":"≥65",
"population":800000
}
]
var width = 1060,
height = 600,
radius = 175,
color = d3.scale.category10(),
legendNo=4, // number of legends to display at a time
legendCount=0; //To store number of legends
//creating svg element and appending to body
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); //transform it to center of body
//creating start and end angle for each arc
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
//creating the arcs based on pie layout
var arc = d3.svg.arc() //inner arc with color
.outerRadius(radius)
.innerRadius(radius-70);
//calculate the total to display in hole
var total=0;
data.forEach(function(d) {
d.population;
total +=parseInt(d.population);
legendCount++;
});
//creating svg element for center text
var center_group = svg.append("svg:g")
.attr("class", "center_group")
.attr("transform", "translate(" + (width/2) + "," + (height/2) + ")");
//selecting all inner arcs and appending data
var g = svg.selectAll("arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc")
//giving colour to each inner arc and execute onClick function
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); })
//display text in the inner arcs
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.style("font-size", "14px")
.text(function(d) { return d.data.age; });
count = 0;
var p=0;
var viewdata = data.slice(p,p+legendNo);
var hidedata;
var temp;
//selecting all legend elements
var legend = svg.selectAll(".legend")
.data(viewdata).enter()
.append("g").attr("class", "legend")
//.attr("width", )
.attr("id", function() {
return count++;
})
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
//appending coloured rectangles to legend
svg.selectAll("rect")
.data(viewdata)
.enter().append("rect")
.attr("x", width/2-150)
.attr("y",5)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
.attr("width", 23).attr("height", 23)
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
.style("fill", function(d) {
return color(d.age);
});
var prev=svg.append("svg:text")
.attr("id","prev")
.attr("x", width-1260)
.attr("y",height-385)
.attr("dy", "2.90em")
.attr("dx", "1.75em")
//.attr("stroke", "black")
//.style("fill","white")
// .style("text-anchor", "middle")
.style("font-size", "20px")
//.attr("width", 45).attr("height", 25)
.text("<|")
.on("click",onPrevClick)
var next=svg.append("svg:text")
.attr("id","next")
.attr("x", width-950)
.attr("y",height-385)
.attr("dy", "2.90em")
.attr("dx", "1.75em")
.style("font-size", "20px")
//.attr("stroke", "black")
//.style("fill","white")
//.attr("width", 45).attr("height", 25)
.text("|>")
.on("click",onNextClick)
function onNextClick()
{p+=legendNo;
if(p>=legendCount){
p-=legendNo;
viewdata = data.slice(p,legendCount);
//temp=legendNo-(legendCount-p);
//hidedata =data.slice(p-temp-2,p-2);
}
else{
viewdata = data.slice(p,p+legendNo);
//hidedata =data.slice(0,0);
}
svg.selectAll("rect")
.data(viewdata)
.attr("x", width/2-150)
.attr("y",5)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
.attr("width", 23).attr("height", 23)
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
.style("fill", function(d) {
return color(d.age);
});
legend.select("text").attr("x", width/2-150)
.data(viewdata)
.attr("y", 15)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
//.attr("transform", function(d, i) {return "rotate("+45*i+","+d.age+",200)";})
.attr("text-anchor", "middle").text(function(d) {
return d.age;
});
}
function onPrevClick(){
p-=legendNo;
if(p<=0){
p=0;
}
viewdata = data.slice(p,p+legendNo);
svg.selectAll("rect")
.data(viewdata)
.attr("x", width/2-150)
.attr("y",5)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
.attr("width", 23).attr("height", 23)
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
.style("fill", function(d) {
return color(d.age);
});
legend.select("text").attr("x", width/2-150)
.data(viewdata)
.attr("y", 15)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
//.attr("transform", function(d, i) {return "rotate("+45*i+","+d.age+",200)";})
.attr("text-anchor", "middle").text(function(d) {
return d.age;
});
}
// giving text to legends
legend.append("text").attr("x", width/2-150)
.data(viewdata)
.attr("y", 15)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
//.attr("transform", function(d, i) {return "rotate("+45*i+","+d.age+",200)";})
.attr("text-anchor", "middle").text(function(d) {
return d.age;
});
//displaying legend title
var legendTitle = svg.append("svg:text")
.attr("x", -(width/2-200))
.attr("y", height/2-25)
.style("font-size", "14px")
.style("text-decoration", "underline")
.text("Age Group");
I am unable to add a segment to a D3.js pie chart. I know I need to use .enter() and .append() to stage the new data -- but I am not sure how to apply that when I have the arcs grouped (which I need for the labels).
Here is my update function:
var updateChart = function(dataset) {
arcs.data(donut(dataset));
arcs.transition()
.duration(duration)
.attrTween("d", arcTween);
sliceLabel.data(donut(dataset));
sliceLabel.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + (arc.centroid(d)) + ")"; })
.style("fill-opacity", function(d) {
if (d.value === 0) { return 1e-6; }
else { return 1; }
});
};
How I setup the initial graph:
var arc = d3.svg.arc()
.innerRadius(radius * .4)
.outerRadius(radius);
var svg = d3.select("body")
.append("svg")
.append("svg")
.attr("width", width)
.attr("height", height);
var arc_grp = svg.append("g")
.attr("class", "arcGrp")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var label_group = svg.append("g")
.attr("class", "lblGroup")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var arcs = arc_grp.selectAll("path")
.data(donut(data));
arcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) { return this.current = d; });
var sliceLabel = label_group.selectAll("text")
.data(donut(data));
sliceLabel.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) { return "translate(" + (arc.centroid(d)) + ")"; })
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) { return 1e-6; }
else { return 1; }
})
.text(function(d) { return d.data.label; });
Complete jsfiddle: http://jsfiddle.net/kPM5L/
What is a clean way to add the new data to the chart?
To get the transition to work smoothly, you need to add the code that you're using initially to your update function as well. Working jsfiddle here.
And some code to make SO happy -- this is what needs to be in the update function as well:
.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) { return this.current = d; });
.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) { return "translate(" + (arc.centroid(d)) + ")"; })
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) { return 1e-6; }
else { return 1; }
})
.text(function(d) { return d.data.label; });