I've got a page where I build a bunch of pie charts. On each one, I want to add an href to another location on the page.
Currently my code works, but it only applies the href to the individual pieces of the pie chart, as well as the text. So for example, if you click on a ring of the pie chart, it will work like it should, but if you click on the space between the rings, it will not.
The SVG itself is much larger and easier to click, but even though I append the anchor tag to the svg, it only applies to the elements within the SVG. How do I correct this behavior?
function pieChartBuilder(teamName, values) {
var dataset = values;
var trimTitle = teamName.replace(' ', '');
trimTitle = trimTitle.toLowerCase();
var width = 175,
height = 175,
cwidth = 8;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select("#pieChart").append("svg")
.attr("width", width)
.attr("height", height)
.append("a") // here is where I append the anchor tag to the SVG, but it only applies to the individual elements within.
.attr("href", ("#" + trimTitle))
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function (d) { return pie(d); })
.enter().append("path")
.attr("fill", function (d, i) { return color(i); })
.attr("d", function (d, i, j) { return arc.innerRadius(5 + cwidth * j).outerRadius(3 + cwidth * (j + 1))(d); });
svg.append("text")
.attr("class", "title")
.attr("x", 0)
.attr("y", (0 - (height / 2.5)))
.attr("text-anchor", "middle")
.style("fill", "#808080")
.text(teamName);
}
I think you just have the selectors the wrong way round. You want to have the svg inside an a tag right?:
d3.select("#pieChart")
.append("a")
.append("svg");
You (1) select the pieChart, then (2) append an a tag to it, then you (3) append the svg to that.
Related
I'm using d3.js
Hi, I'm having an issue finding how to append two elements (path and image) to the same g (inside my svg) from the same data. I know how to do this, but the tricky thing is I need to get the BBox values of the "path" elements in order to place the "image" elements in the middle... My goal is actually to place little clouds in the center of cities on a map like this : this is the map I am trying to reproduce
On the map it's not centered but I have to do so. So this is my current code:
// Draw the map
svg.append("g")
.selectAll("path")
.data(mapEPCI.features)
.enter()
.append("path")
.attr("fill", d => d.properties.color)
.attr("d", d3.geoPath().projection(projection))
.style("stroke", "white")
.append("image")
.attr("xlink:href", function(d) {
if (d.properties.plan_air == 1)
return ("data/page8_territoires/cloud.png")
else if (d.properties.plan_air == 2)
return ("data/page8_territoires/cloudgray.png")
})
.attr("width", "20")
.attr("height", "15")
.attr("x", function (d) {
let bbox = d3.select(this.parentNode).node().getBBox();
return bbox.x + 30})
.attr("y", function (d) {
return d3.select(this.parentNode).node().getBBox().y + 30})
This gets the right coordinates for my images but it's because the parent node is actually the path... If I append the image to the g element, is there a way to get the "BrotherNode", or maybe the last child of the "g" element ? I don't know if I'm clear enough but I hope you get my point.
I'm kinda new to js so maybe I'm missing something simple I just don't know yet
Thanks for your help
I would handle your data at the g level and create a group for every map feature (country) which contains the path and a sibling image:
<!doctype html>
<html>
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<svg width="600" height="600"></svg>
<script>
let svg = d3.select('svg'),
mapEPCI = {
features: [100, 200, 300, 400]
};
let g = svg.selectAll('g')
.data(mapEPCI.features)
// enter selection is collection of g
let ge = g.enter().append("g");
// append a path to each g according to data
ge.append('path')
.attr("d", (d) => "M" + d + ",10L" + d + ",100")
.style("stroke", "black");
// append a sibling image
ge.append("image")
.attr("xlink:href", "https://placeimg.com/20/15/animals")
.attr("width", "20")
.attr("height", "15")
.attr("transform", function(d) {
// find my sibling path to get bbox
let sibling = this.parentNode.firstChild;
let bbox = sibling.getBBox();
return "translate(" + (bbox.x - 20 / 2) + "," + (bbox.y + bbox.height / 2 - 15 / 2) + ")"
});
</script>
</body>
</html>
I am trying to make a d3 javascript that creates a rectangle whose color depends on a data set. All of the rectangles are adjacent to each other like:
[][][][][][]
[][][][][][]
I got my script to work to create rectangles for all of my data, but it overflows like:
[][][][][][][][][][][][][][][][][][][][][][][][][][][]
How can I create width and height properties for my d3 script so it looks more like
[][][][]
[][][][]
[][][][]
Here is my script:
<script>
//for whatever data set
var data = [];
//Make an SVG Container
var svgContainer = d3.select("body").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("width", 38)
.attr("height", 25);
//Draw the rectangle
var rectangle = svgContainer.append("rect")
.attr("x", 0)
.attr("y", 5)
.attr("width", 38)
.attr("height", 25);
</script>
You have to change the x and y properties.
.attr("x", function(d, i) {
return 5 + (i%itemPerLine) * widthRect;
})
.attr("y", function(d, i) {
return 5 + Math.floor(i/itemPerLine) * heightRect;
})
(itemPerLine is the number of rect per line)
See this fiddle as example
This is the code I use for calling same js function in two blocks (in two div tag). The answer for the second tag (div id="frame4") also prints inside the first one (div id="frame3"). I want to print them separately. How can I do that?
<div id="frame3">
<! ----pieChart ----- !>
<h5><i>Code Coverage</i></h5>
<div id="pieChart"></div>
<script type="text/javascript">
dsPieChart(<%=coverage %>);
</script>
</div>
<!test_density !>
<div id="frame3">
<div id="pieChart"></div>
<script type="text/javascript">
dsPieChart(<%=density %>);
</script>
</div>
code for the function
function dsPieChart(x){
var formatAsPercentage = d3.format("%") ;
var dataset = [
{category: "", measure:x },
{category: "", measure:(100-x)},
]
;
var width = 100,
height = 100,
outerRadius = Math.min(width, height) / 2,
innerRadius = outerRadius * .9,
// for animation
innerRadiusFinal = outerRadius * .8,
innerRadiusFinal3 = outerRadius* .7,
color = d3.scale.category20b() //builtin range of colors
;
var vis = d3.select("#pieChart")
.append("svg:svg") //create the SVG element inside the <body>
.data([dataset]) //associate our data with the document
.attr("width", width) //set the width and height of our visualization (these will be attributes of the <svg> tag
.attr("height", height)
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")") //move the center of the pie chart from 0, 0 to radius, radius
;
var arc = d3.svg.arc() //this will create <path> elements for us using arc data
.outerRadius(outerRadius).innerRadius(innerRadius);
// for animation
var arcFinal = d3.svg.arc().innerRadius(innerRadiusFinal).outerRadius(outerRadius);
var arcFinal3 = d3.svg.arc().innerRadius(innerRadiusFinal3).outerRadius(outerRadius);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.measure; }); //we must tell it out to access the value of each element in our data array
var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
.data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
.enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
.append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
.attr("class", "slice") //allow us to style things in the slices (like text)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", up)
;
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } ) //set the color for each slice to be chosen from the color function defined above
.attr("d", arc) //this creates the actual SVG path using the associated data (pie) with the arc drawing function
.append("svg:title") //mouseover title showing the figures
// .text(function(d) { return d.data.category + ": " + d.data.measure ; });
.text(function(d) { return d.data.measure ; });
d3.selectAll("g.slice").selectAll("path").transition()
.duration(750)
.delay(10)
.attr("d", arcFinal )
;
// Add a label to the larger arcs, translated to the arc centroid and rotated.
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; })
.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "translate(" + arcFinal.centroid(d) + ")rotate(" + angle(d) + ")"; })
.text(function(d) { return d.data.category; })
;
// Computes the label angle of an arc, converting from radians to degrees.
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
// Pie chart title
vis.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(x +"%")
.attr("class","title")
;
function mouseover() {
d3.select(this).select("path").transition()
.duration(750)
.attr("d", arcFinal3)
;
}
function mouseout() {
d3.select(this).select("path").transition()
.duration(750)
//.attr("stroke","blue")
//.attr("stroke-width", 1.5)
.attr("d", arcFinal)
;
}
function up(d, i) {
/* update bar chart when user selects piece of the pie chart */
//updateBarChart(dataset[i].category);
updateBarChart(d.data.category, color(i));
updateLineChart(d.data.category, color(i));
}
}
Change function to pass second parameter for element ID.
function dsPieChart(x, selectorId){
Change the hard code selector:
var vis = d3.select("#pieChart");
To
var vis = d3.select("#" + selectorId);
Then when you call the function also identify the id selector in second paramater. Note that element ID's must be unique in a page by definition:
<div id="pieChart-1"></div>
<script type="text/javascript">
dsPieChart(<%=coverage %>, 'pieChart-1');
</script>
</div>
<div id="pieChart-2"></div>
<script type="text/javascript">
dsPieChart(<%=density %>, 'pieChart-2');
</script>
</div>
I am trying to create an interactive sunburst diagram using D3, where the user can select a data source from a dropdown menu. Once the data source is selected, any existing sunburst would be erased and redraw using the new data. This is based off the D3 example called "Sequences Sunburst" http://bl.ocks.org/kerryrodden/7090426
Having done a bit of research, it looks like you need to follow the add/append/transition/exit pattern.
Here is a link to a semi-functioning example on JSFiddle: http://jsfiddle.net/DanGinMD/dhpsxm64/14/
When you select the first data source, the sunburst diagram is created. When you select the second data source, a second sunburst is added. Each one appears to be connected to its unique data source. How do I erase the first sunburst before drawing the second sunburst?
Here is the code for listener event for the dropdown box:
// an event listener that (re)draws the breadcrumb trail and chart
d3.select('#optionsList')
.on('change', function() {
var newData = eval(d3.select(this).property('value'));
createVisualization(newData);
});
Here is the code that draws the sunburst diagram:
function createVisualization(json) {
sysName = json.sysName;
var titletext = sysName + " - Impact to Organization";
d3.select("#title2").text(titletext);
initializeBreadcrumbTrail();
var vis = d3.select("#chart").append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("id", "container")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx; })
.innerRadius(function(d) { return Math.sqrt(d.y); })
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
// Bounding circle underneath the sunburst, to make it
// easier to detect when the mouse leaves the parent g.
vis.append("svg:circle")
.attr("r", radius)
.style("opacity", 0);
// For efficiency, filter nodes to keep only those large enough to see.
var nodes = partition.nodes(json)
.filter(function(d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
});
var path = vis.data([json]).selectAll("path")
.data(nodes)
.enter().append("svg:path")
.attr("display", function(d) { return d.depth ? null : "none"; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", function(d) { return colors[d.category]; })
.style("opacity", 1)
.on("mouseover", mouseover);
// Add the mouseleave handler to the bounding circle.
d3.select("#container").on("mouseleave", mouseleave);
// Get total size of the tree = value of root node from partition.
totalSize = path.node().__data__.value;
path.exit().remove();
nodes.exit().remove();
arc.exit().remove();
partition.exit().remove();
vis.exit().remove();
}
Note the following call that appends a new svg at visualization initialization:
var vis = d3.select("#chart").append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("id", "container")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
You just need to remove any old svg before this statement:
d3.select("#chart svg").remove();
var vis = d3.select("#chart").append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("id", "container")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
fiddle
I am currently trying to place a svg:image in the centre of my arc:
var arcs = svg.selectAll("path");
arcs.append("svg:image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg ")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("width", "150px")
.attr("height", "200px");
I would appreciate it if someone could give me any advice on why it isn't appearing
thanks : http://jsfiddle.net/xwZjN/17/
Looking at the jsfiddle, you are creating the path elements after you try to append the svg:image elements to the them. It should be the other way around. You should first create the arcs and then append the images.
Second, as far as I know, the svg:path element should not contain any svg:image tags. It doesn't seem to display them if you place some inside. Instead what you should do is create svg:g tags with class arc and then use those to place the svg:images
Slightly modifying your jsfiddle could look something like this:
var colours = ['#909090','#A8A8A8','#B8B8B8','#D0D0D0','#E8E8E8'];
var arcs = svg.selectAll("path");
for (var z=0; z<30; z++){
arcs.data(donut(data1))
.enter()
//append the groups
.append("svg:g")
.attr("class", "arc")
.append("svg:path")
.attr("fill", function(d, i) { return colours[(Math.floor(z/6))]; })
.attr("d", arc[z])
.attr("stroke","black")
}
//here we append images into arc groups
var pics = svg.selectAll(".arc").append("svg:image")
.attr("xlink:href", "http://www.e-pint.com/epint.jpg ")
.attr("transform", function(d,i) {
//since you have an array of arc generators I used i to find the arc
return "translate(" + arc[i].centroid(d) + ")"; })
.attr("x",-5)
.attr("y",-10)
.attr("width", "10px")
.attr("height", "20px");
Where I also decreased the size of the images and offset them so that they fit into the arc.