Showing Data in Rectangle on the click of circle using d3.js - d3.js

I Have few circles that contain people names,I need to show their information on the click of circle in rectangle using d3.js
Below is my script
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
d3.json("data.json", function (json) {
/* Define the data for the circles */
var elem = svg.selectAll("g myCircleText")
.data(json.nodes)
/*Create and place the "blocks" containing the circle and the text */
var elemEnter = elem.enter()
.append("g")
.attr("transform", function (d) { return "translate(" + d.x + ",80)" })
/*Create the circle for each block */
var circle = elemEnter.append("circle")
.attr("r", function (d) { return d.r })
.attr("stroke", "black")
.attr("fill", "white")
.on("click", function () {
var s = svg
.selectAll("circle");
s
.append("rect")
.attr("x", 100)
.attr("y", 200)
.attr("width", 200)
.attr("width", 200)
.style("fill", "red");
});
/* Create the text for each block */
elemEnter.append("text")
.attr("dx", function (d) { return -20 })
.text(function (d) { return d.label })
})
below is the json file:
{"nodes":[
{"x":80, "r":40, "label":"Sam","info":"Developer"},
{"x":200, "r":60, "label":"Pam","info":"Programmer"},
{"x":380, "r":80, "label":"Ram","info":"Architect"}
]}
Circles are being drawn with names but when I click on circles nothing is happening.
Please help.
Thanks

Two issues with your onclick function. First, width is set a second time instead of height:
.attr("width", 200)
.attr("width", 200)
Second, you're appending a rectangle to a circle:
var s = svg.selectAll("circle");
s.append("rect")
which isn't valid for a svg:
<circle r="60" stroke="black" fill="white">
<rect></rect>
</circle>
Instead, the rectangles should be appended to root of the svg or a g element.
Working code:
.on("click", function () {
svg.append("rect")
.attr("x", 100)
.attr("y", 200)
.attr("width", 200)
.attr("height", 200)
.style("fill", "red");
});

Related

How to do use getbbox to generate multiple rects

i can only generate one rect for the text i have. I dont know how to apply getBBox to multiple text elements so they can all have their own backgrounds. Does anyone know how i can accomplish this?
function jobsCreation() {
let enteringText = dataJobs
.enter()
.append("text")
.attr("x", function (d) { return d.posX })
.attr("y", function (d) { return d.posY })
.attr("id", "jobText")
.text(function (d) { return d.name });
let texts = d3.selectAll("#jobText");
let bbox = enteringText.node().getBBox();
console.log(bbox);
let rect = svg.append("rect")
.attr("x", bbox.x)
.attr("y", bbox.y)
.attr("width", bbox.width)
.attr("height", bbox.height)
.attr("id", "nodeBox")
.style("fill", "white")
.style("stroke", "black")
.style("stroke-width", "1.5px");
enteringText.raise();
}
svg.append() will create a single element and selection.node() will only return a single node.
While selection.nodes() will return an array all the nodes in a selection, we still need to append one rectangle for each node. To do this, we can use the array returned by selection.nodes() as a data array for a enter cycle:
let enteringRects = svg.selectAll("rect")
.data(enteringText.nodes())
.enter()
.append("rect")
.attr("x", function(node) { return node.getBBox().x })
.attr("y", function(node) { return node.getBBox().y })
.attr("width", function(node) { return node.getBBox().width })
.attr("height", function(node) { return node.getBBox().height })
.style("fill", "white")
.style("stroke", "black")
.style("stroke-width", "1.5px")
.lower();
Now we create one rectangle for every text element we create. As the bound datum in this selection is a DOM element we can access getBBox() easily as it is now a property of the datum itself.
let data = d3.range(15).map(function(d) { return Math.floor(Math.random() * 150) });
let width = 500;
let height = 300;
let svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
let enteringText = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", function (d) { return Math.random() * width })
.attr("y", function (d) { return Math.random() * height })
.text(function (d) { return d });
let enteringRects = svg.selectAll("rect")
.data(enteringText.nodes())
.enter()
.append("rect")
.attr("x", function(node) { return node.getBBox().x })
.attr("y", function(node) { return node.getBBox().y })
.attr("width", function(node) { return node.getBBox().width })
.attr("height", function(node) { return node.getBBox().height })
.style("fill", "white")
.style("stroke", "black")
.style("stroke-width", "1.5px")
.lower();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

D3 dougnut chart customizing circle color

Im making a graph that fills the circle by the percentage of the number of a certain product by the total of products avaible im almost close to what i need only problem is i cant figure it out how to change the left-over part of the donut arc.
this is the code
http://jsfiddle.net/LBzx7/345/
I can change the color the circle of the % of the product on this line
.attr("fill", "#F1F1F1");
, but what is left is the same color of the page background, i need to be able to change the color of that. Any ideas?
Here's a code snippet with the requirement fulfilled.
var dataset = {
hddrives: [90,10],
};
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#2DA7E2", "red"]);
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 70);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
//Draw the Circle
svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 65)
.attr("fill", "#F1F1F1");
var path = svg.selectAll("path")
.data(pie(dataset.hddrives))
.enter().append("path")
.attr("class", "arc")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc);
svg.append("text")
.attr("dy", "0em")
.style("text-anchor", "middle")
.attr("class", "inside")
.text(function(d) { return '56%'; });
svg.append("text")
.attr("dy", "1.5em")
.style("text-anchor", "middle")
.attr("class", "data")
.text(function(d) { return '53GB / 123GB'; });
.inside {
font-family: 'Roboto Condensed', sans-serif;
font-size:30px;
}
.data {
font-size:12px;
color:grey;
}
.arc {
stroke: #fff;
}
.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.7/d3.min.js"></script>
Code changes:
Changed the color scale's range to .range(["#2DA7E2", "red"]);
With this, .attr("fill", function(d, i) { return color(i); }) will find appropriate color based on i. (as it was just one color before, the color was being repeated).
Got rid the opacity of the arcs i.e. removed the following line
(as this was causing the "left-over" part to have an opacity of 0)
.style("opacity", function(d, i) { return i == dataset.hddrives.length - 1 ? 0 : 1; })
Hope this helps. :)

d3 click to center content at position of element or click

I have been trying to get the basics of how I make a pannable zoomable, and click to center zoom on element d3 work. This example is what I want to do but I am having trouble translating it outside of the geo context: https://bl.ocks.org/mbostock/2206340
What I have accomplished is the first two parts pan and zoom, see a basic fiddle here https://jsfiddle.net/e9fbn2xp/
How can I accomplish centering the the circle in the center of the viewable window, so it looks like the circle is zoomed to? Note that although this is a fixed position circle I will eventually have dynamic data, so ideally I could reference the circles position dynamically.
Here is my code:
HTML (note that this is React JSX syntax but that should be irrelevant to question)
<div style={{width: 800}}>
<svg style={{border: '1px solid black'}} id="viz" width="800" height="800">
</svg>
</div>
JAVASCRIPT
var svg = d3.select("#viz")
var width = svg.attr("width");
var height = svg.attr("height");
var testLayer = svg.append('g');
var aRect = testLayer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", 800)
.attr("width", 800)
.attr("fill", 'green');
var aCircle = testLayer.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 200)
.attr("cy", 200)
.on("mousedown", zoomToMe);
function zoomToMe(){
console.log("do the zoom")
}
var zoom = d3.zoom()
.scaleExtent([.5, 40])
.translateExtent([[0, 0], [width, height]])
.on("zoom", zoomed);
svg.call(zoom);
function zoomed() {
testLayer.attr("transform", d3.event.transform);
}
svg.on("click", function() {
var coords = d3.mouse(this);
})
I got a working solution and thought I would share the code in case others find it useful. It is a fairly different approach then my original but accomplishes the three goals, pan, mouse zoom, zoom to element. While these are three simple static circles the same concept should work with a dynamic dataset.
fiddle: https://jsfiddle.net/uc7oprx3/5/
HTML
<svg id="viz" width="400" height="400" />
JAVASCRIPT
var zoom = d3.zoom()
.scaleExtent([0.3,2])
.on("zoom", zoomed);
var svg = d3.select("#viz")
var width = svg.attr("width");
var height = svg.attr("height");
var zoomer = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
var g = svg.append("g");
var aCircle = g.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 200)
.attr("cy", 200)
.on("mousedown", () => centerNode(200, 200));
var bCircle = g.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 400)
.attr("cy", 400)
.on("mousedown", () => centerNode(400, 400));
var cCircle = g.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", 40)
.attr("cx", 600)
.attr("cy", 600)
.on("mousedown", () => centerNode(600, 600));
function zoomed() {
g.attr("transform", d3.event.transform);
}
function centerNode(xx, yy){
g.transition()
.duration(500)
.attr("transform", "translate(" + (width/2 - xx) + "," + (height/2 - yy) + ")scale(" + 1 + ")")
.on("end", function(){ zoomer.call(zoom.transform, d3.zoomIdentity.translate((width/2 - xx),(height/2 - yy)).scale(1))});
}

Unable to change x position of legend text inside a group element using D3

I am trying to create a horizontal graph legend in D3.js. I am using a group element (g) as a container for all the legends and the individual legends (text) are also each wrapped inside a "g" element. The result is that the individual legends are stacked on top of each other rather than spaced out.
I have tried changing the x attribute on the legends and also transform/translate. Whilst the DOM shows that the x values are applied the legends don't move. So if the DOM shows the legend / g element is positioned at x = 200 it is still positioned at 0.
I have spent two days trying to solve this and probably looked at over 50 examples including anything I could find on StackExchange.
Below code is my latest attempt. It doesn't through any error and the x values are reflected in the DOM but the elements just won't move.
I have included the code covering the relevant bits (but not all code).
The legend container is added here:
/*<note>Add container to hold legends. */
var LegendCanvas = d3.select("svg")
.append("g")
.attr("class", "legend canvas")
.attr("x", 0)
.attr("y", 0)
.attr("width", Width)
.style("fill", "#ffcccc")
.attr("transform", "translate(0,15)");
There is then a loop through a json array of objects:
var PrevElemLength = 0;
/*<note>Loop through each data series, call the Valueline variable and plot the line. */
Data.forEach(function(Key, i) {
/*<note>Add the metric line(s). */
Svg.append("path")
.attr("class", "line")
.attr("data-legend",function() { return Key.OriginId })
/*<note>Iterates through the data series objects and applies a different color to each line. */
.style("stroke", function () {
return Key.color = Color(Key.UniqueName); })
.attr("d", Valueline(Key.DataValues));
/*<note>Add a g element to the legend container. */
var Legend = LegendCanvas.append("g")
.attr("class", "legend container")
.attr("transform", function (d, i) {
if (i === 0) {
return "translate(0,0)"
} else {
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength) + ",0)"
}
});
/*<note>Adds a rectangle to pre-fix each legend. */
Legend.append("rect")
.attr("width", 5)
.attr("height", 5)
.style("fill", function () {
return Key.color = Color(Key.UniqueName); });
/*<note>Adds the legend text. */
Legend.append("text")
.attr("x", function() {
return this.parentNode.getBBox().width + 5;
})
/*.attr("y", NetHeight + (Margin.bottom/2)+ 10) */
.attr("class", "legend text")
.style("fill", function () {
return Key.color = Color(Key.UniqueName); })
.text(Key.UniqueName);
Here is a screen shot of what the output looks like:
enter image description here
Any help on how to create a horizontal legend (without over lapping legends) would be much appreciated. Chris
The problem is you are using local variables d and i as function parameters while setting the transform attribute. Parameter i in local scope overrides the actual variable. The value of local variable i would be always zero as there is no data bind to that element.
var Legend = LegendCanvas.append("g")
.attr("class", "legend container")
.attr("transform", function (d, i) { //Remove i
if (i === 0) {
return "translate(0,0)"
} else {
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength) + ",0)"
}
});
I have also made slight updates to the code for improvements.
var LegendCanvas = d3.select("svg")
.append("g")
.attr("class", "legend canvas")
.attr("x", 0)
.attr("y", 0)
.attr("width", 500)
.style("fill", "#ffcccc")
.attr("transform", "translate(0,15)");
var PrevElemLength = 0;
var Data = [{
OriginId: 1,
UniqueName: "Some Long Text 1"
}, {
OriginId: 2,
UniqueName: "Some Long Text 2"
}];
/*<note>Loop through each data series, call the Valueline variable and plot the line. */
var Color = d3.scale.category10();
Data.forEach(function(Key, i) {
/*<note>Add a g element to the legend container. */
var Legend = LegendCanvas.append("g")
.attr("class", "legend container")
.attr("transform", function() {
if (i === 0) {
return "translate(0,0)"
} else {
var marginLeft = 5;
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength + marginLeft) + ",0)"
}
});
/*<note>Adds a rectangle to pre-fix each legend. */
Legend.append("rect")
.attr("width", 5)
.attr("height", 5)
.style("fill", function() {
return Key.color = Color(Key.UniqueName);
});
/*<note>Adds the legend text. */
Legend.append("text")
.attr("x", function() {
return this.parentNode.getBBox().width + 5;
})
.attr("dy", "0.4em")
.attr("class", "legend text")
.style("fill", function() {
return Key.color = Color(Key.UniqueName);
})
.text(Key.UniqueName);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg height=500 width=500></svg>
The d3 way of implementation(Using data binding) would be as follows
var LegendCanvas = d3.select("svg")
.append("g")
.attr("class", "legend canvas")
.attr("x", 0)
.attr("y", 0)
.attr("width", 500)
.style("fill", "#ffcccc")
.attr("transform", "translate(0,15)");
var Data = [{
OriginId: 1,
UniqueName: "Some Long Text 1"
}, {
OriginId: 2,
UniqueName: "Some Long Text 2"
}];
var Color = d3.scale.category10();
var Legend = LegendCanvas.selectAll(".legend")
.data(Data)
.enter()
.append("g")
.attr("class", "legend container");
Legend.append("rect")
.attr("width", 5)
.attr("height", 5)
.style("fill", function(Key) {
return Key.color = Color(Key.UniqueName);
});
Legend.append("text")
.attr("x", function() {
return this.parentNode.getBBox().width + 5;
})
.attr("dy", "0.4em")
.attr("class", "legend text")
.style("fill", function(Key) {
return Key.color = Color(Key.UniqueName);
})
.text(function(Key){ return Key.UniqueName });
var PrevElemLength = 0;
Legend.attr("transform", function(d, i) {
if (i === 0) {
return "translate(0,0)"
} else {
var marginLeft = 5;
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength + marginLeft) + ",0)"
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width=500 height=500></svg>
Try this :
//Legend
var legend = vis.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("image")
.attr("x", 890)
.attr("y", 70)
.attr("width", 20)
.attr("height", 18)
.attr("xlink:href",function (d) {
return "../assets/images/dev/"+d+".png";
})
legend.append("text")
.attr("x", 910)
.attr("y", 78)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d) {
return d;
});

Backfill a D3 bar chart with gray to show 100% vs. actual

I'm trying to get a gray fill in the background to show 100% with the actual percentage fill in the foreground, please see the image:
Image of what I'm trying to do
JS Fiddle Example
//load the status'
var theStatus = svg.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", function (d) { return (d.status); })
.attr("width", 0)
.attr("height", 20);
//animate the status
theStatus.transition()
.attr("width", function (d) { return xBarScale(d.percent); })
.attr("y", function (d) { return yBarScale(d.id)-10; })
.duration(animationDur2);
});
Any help is appreciated!!
You can do this by appending another set of rectangles with grey fill and constant width:
svg.selectAll(".background")
.data(data)
.enter()
.append("rect")
.attr("class", "background")
.attr("y", function (d) { return yBarScale(d.id)-10; })
.attr("width", width)
.attr("height", 20);
Complete jsfiddle here.

Resources