I am trying to draw a circle using d3.js with text inside it.
Here is my code:
<script src="//d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.min.js"></script>
<div class="circles"></div>
<script>
$(document).ready(function () {
circles();
$(".circles").show();
function circles() {
var svg = d3.select(".circles");
var groups = svg.selectAll("div")
.data("DEMO")
.enter()
.append("div");
groups.attr("transform","translate(" +100+ "," +100+ ")");
var circles = groups.append("circle")
.attr("cx", "100")
.attr("cy","100")
.attr("r", "100")
.attr("fill", "red")
.attr("stroke-width","2.4192")
.attr("stroke","#00ffff");
var label = groups.append("text")
.text(function(d){
return d;
})
.attr({
"alignment-baseline": "middle",
"text-anchor": "middle",
"font-family":"Arial",
"font-size":"30",
"fill":"white"
});
}
});
</script>
But it displays only the text and the circle is not being displayed.
I am unable to fix it.
Can anyone please point out my mistake and help me fix it.
You're appending a <circle> element in HTML, not SVG. It doesn't have any meaning there, so it doesn't get rendered. You need to append an svg element, then append your circle and text nodes to that.
Fill, stroke-width, and stroke should also be applied with .style instead of .attr.
Some relevant code changes are:
var svg = d3.select('.circles').append('svg') // added .append('svg')
.attr('height', /* set the height */)
.attr('width', /* set the width */);
var groups = svg.selectAll('g') // 'g' instead of 'div'
.data('DEMO')
.enter().append('g');
Related
I am not succeding in appending a path on a simple svg container.
It considers my "path" element as a text instead.
Here is my code:
// Create the SVG
var tracer = {};
tracer.$container = document.getElementById('container');
tracer.$svg = $('<svg class="svg-d3-table" style="width:100%; height:100%">');
tracer.$container.append(tracer.$svg);
// Specify the path points
var pathInfo = [{x:0, y:60},
{x:50, y:110},
{x:90, y:70},
{x:140, y:100}];
// Specify the function for generating path data
var pathLine = d3.line()
.x(function(d){return d.x;})
.y(function(d){return d.y;})
.curve(d3.curveBasis);
// Append path
tracer.$svg.append("path")
.attr("d", pathLine(pathInfo))
.attr("stroke", "white")
.attr("stroke-width", 8)
.attr("fill", "none");
Instead of having a new path element as
<path d="M314,352L314,352C314,352,314,352,..."></path>
It comes with the following:
<svg class="svg-d3-table" style="..." d="M0,60L8.3,68.3.7,...">path</svg>
What am I missing?
PS: Sorry, I come from c++ and can be struggled with some very basic js operations.
Thank you very much.
Kind Regards.
As others have pointed out in the comments, avoid mixing jquery and d3.
The root of your problems though is that your tracer.$svg is a jquery selector but you are treating it as a d3 selector. While both of them have an append method, they are two wholly different beasts (and as #altocumulus points out, jquery don't play so nice manipulatively SVG).
Here's your code as I would write it with just d3:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="container" style="background-color: steelblue"></div>
<script>
// Create the SVG
var tracer = {};
tracer.$container = d3.select('#container');
tracer.$svg = tracer.$container.append("svg")
.attr("class", "svg-d3-table")
.style("width", "100%")
.style("height", "100%");
// Specify the path points
var pathInfo = [{
x: 0,
y: 60
}, {
x: 50,
y: 110
}, {
x: 90,
y: 70
}, {
x: 140,
y: 100
}];
// Specify the function for generating path data
var pathLine = d3.line()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
})
.curve(d3.curveBasis);
// Append path
tracer.$svg.append("path")
.attr("d", pathLine(pathInfo))
.attr("stroke", "white") //<-- need a color?
.attr("stroke-width", 8)
.attr("fill", "none");
</script>
</body>
</html>
Thanks to altocumulus:
jquery's append not working with svg element?
Is exactly the kind of information I was looking for better understanding of manipulating DOM (but still shows me how much front-end development can be akward).
Thank to Gerardo Furtado:
Indeed the need of different lib leads easily to confusing behavior and makes JS hard to handle when working on an existing project (how to determine namespace by reading code, avoid collision...)
I handled my problem by only managing my svg using D3 as you adviced:
tracer.$svg = tracer.capsule.$svg = d3.select("#" + tracer.defaultName)
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("id", tracer.defaultName + "-svg")
.attr("class", "svg-d3-table")
.attr("xmlns", "http://www.w3.org/2000/svg");
Thank you for your comments.
I am trying to display a legend for a heat map I have created, but am unable to do that. Here is my HTML
<html>
<head>
<title>Heat Map Data Visualization</title>
</head>
<body>
<div class='chart-area'>
<svg class='chart'></svg>
<svg class='legend'></svg>
</div>
</body>
</html>
Here is the code for the legend I am trying to create
var legend = d3.select('.legend')
.data([0].concat(colorScale.quantiles()), function(d){
return d;
});
legend.enter()
.append('g')
.attr('class', 'legend-element');
legend.append("rect")
.attr("x", function(d, i) {
console.log(i);
return legendElementWidth * i + (width - legendElementWidth * buckets);
})
.attr("y", height)
.attr("width", legendElementWidth)
.attr("height", gridHeight / 2)
.style("fill", function(d, i) {
return colors[i];
});
When I use Chrome Developer Tools to inspect the element, I see that the required g elements have been created but they all have dimensions of 0x0.
I read somewhere that rect elements can only be appended to an svg element, which is why I changed my HTML code to include an svg element with a class of legend, however I am still not getting any result.
Here is a link to the codepen for this program
http://codepen.io/redixhumayun/pen/eBqamb?editors=0010
I have modified your pen like this:
// Save the legend svg in a variable
// also changed the translate in order to keep the legend within the svg
// and place it on the right side just for the example's sake
var legendSvg = svg.append('g')
.attr('class', 'legend')
.attr("transform","translate("+ (width - 40) + ",20)")
// Define the legend as you did
var legend = d3.legendColor()
.useClass(true)
.shape('rect')
.orient('vertical')
.title('Temperature Variance')
.shapeWidth(legendElementWidth)
.scale(colorScale);
// And then call legend on the legendSvg not on svg itself
legendSvg.call(legend);
Hope this helps, good luck!
I have the following D3.js project that is available here:
http://bl.ocks.org/diggetybo/raw/e75dcb649ae3b26e2312a63434fc970c/
The latitude and longitude inputs are below the map.
It's supposed to take user input numbers of latitude and longitude and "project" svg circles at the given coordinate. The issue is I'm either getting ____ is not a function error or dev tools throws no errors at all, but the circles are never projected.
It's a short file, can someone explain why it's not working the way I thought?
Your update function doesn't make any sense.
It accepts two inputs, but you only ever call it with one.
.selectAll("circle").enter() is not valid d3 syntax.
You need to call projection with both the latitude and longitude, you pass 0 which will result in it returning null since it's outside of the projection.
After you fix all this, you'll still be off because you've moved your paths by your margin and would have been better off putting them in a g moved by the margins.
All that said, a simple rewrite would be:
var lat = d3.select("#latValue").on("input", function() {
update();
}).node();
var long = d3.select("#lonValue").on("input", function() {
update();
}).node();
function update() {
// lat/long to pixel
var coors = projection([long.value, lat.value]);
// if outside projection don't add circle
if (coors === null) return;
// add circle
container
.append("circle")
.attr("cx", coors[0])
.attr("cy", coors[1])
.attr("r", Math.sqrt(5) * 4)
.style("fill", "black")
.style("opacity", 0.85);
}
Running code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<style type="text/css">
/* On mouse hover, lighten state color */
path:hover {
fill-opacity: .7;
}
</style>
</head>
<body>
<script type="text/javascript">
//Width and height of map
var width = 960;
var height = 500;
var margins = { left: 0, top: 100, right: 0, bottom: 0 };
// D3 Projection
var projection = d3.geo.albersUsa()
.translate([width/2, height/2]) // translate to center of screen
.scale([1000]); // scale things down so see entire US
// Define path generator
var path = d3.geo.path() // path generator that will convert GeoJSON to SVG paths
.projection(projection); // tell path generator to use albersUsa projection
// Define linear scale for output
var color = d3.scale.linear()
.range(["#c3e2ff","#15198e"]);
//Create SVG element and append map to the SVG
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height+margins.top);
svg.append('text')
.text('Coordinate Query')
.attr('font-size','24px')
.attr('transform', 'translate(' + 30 + ',' +70 + ')')
.attr('font-family','Calibri');
svg.append('text')
.text('Data as of 12/2016')
.attr('font-size','12px')
.attr('transform', 'translate(' + 35 + ',' +100 + ')')
.attr('font-family','Calibri');
// Load in my states data!
color.domain([0,100]); // setting the range of the input data
// Load GeoJSON data and merge with states data
d3.json("https://jsonblob.com/api/573228c3-d068-11e6-b16a-b501dc8d2b08", function(json) {
//var coordinates = d3.mouse(this);
// Bind the data to the SVG and create one path per GeoJSON feature
var container = svg.append("g")
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
container.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke", "#fff")
.style("stroke-linejoin","round")
.style("stroke-width", "1.5")
.style("fill", 'steelblue');
// Modified Legend Code from Mike Bostock: http://bl.ocks.org/mbostock/3888852
var lat = d3.select("#latValue").on("input", function() {
update();
}).node();
var long = d3.select("#lonValue").on("input", function() {
update();
}).node();
function update() {
// lat/long to pixel
var coors = projection([long.value, lat.value]);
// if outside projection don't add circle
if (coors === null) return;
// add circle
container
.append("circle")
.attr("cx", coors[0])
.attr("cy", coors[1])
.attr("r", Math.sqrt(5) * 4)
.style("fill", "black")
.style("opacity", 0.85);
}
});
</script>
<p>
<label for="latValue"
style="display: inline-block;width:240px;text-align:right;font-size:18px;font-family:Play">
Lattitude:<span id="latValue-value"></span>
</label>
<input type="number"min="-360"max="360"step="1"value="0" id="latValue">
<label for="lonValue"
style="display: inline-block;width:240px;text-align:right;font-size:18px;font-family:Play">
Longitude:<span id="lonValue-value"></span>
</label>
<input type="number"min="-360"max="360"step="1"value="0" id="lonValue">
</p>
</body>
</html>
I made a chart bar, in c3.js adding modifications d3.js.
I want the text of the bars this at the beginning of each bar, then do it with the following code in d3.js.
d3.selectAll('.c3-text')
.each(function(d){
var self = d3.select(this);
self.attr('x', '5');
});
the problem is when I move the window. It unsettles the text.the text appears by default to the right, as initially.
What I can do to fix it?
thank you very much.
https://jsfiddle.net/ezd1ggwa/
First move all the d3 activities to move text into a resize function like this:
function resize() {
d3.selectAll('.c3-axis-x .tick')
.each(function(d, i) {
// clear tick contents and replace with image
var self = d3.select(this);
// self.selectAll("*").remove();
self.append('image')
.attr("xlink:href", arrayOfPics[i])
.attr("x", -40)
.attr("y", -20)
.attr("width", 25)
.attr("height", 25);
});
d3.selectAll('.c3-axis-x .tick')
.each(function(d, i) {
// clear tick contents and replace with image
var self = d3.select(this);
// self.selectAll("*").remove();
self.append('text')
.attr("x", 50)
.attr("y", -110)
.style("text-anchor", "middle")
.text("this text is a indicator");
});
d3.select('.c3-axis-x').attr("clip-path", null);
d3.selectAll('.c3-text')
.each(function(d) {
var self = d3.select(this);
self.attr('x', '5');
});
}
Now on chart resize call this function like this:
var chart = c3.generate({
bindto: '#chart',
size: {
height: 500,
}
....
onresized: function() {//callback called on resize of a chart
resize()
}
});
working code here
I'm looking to create a pie chart with floating labels using D3. I'm new to D3 and I'm not even sure this is possible? Can you use the labels of one graph in another somehow? If you can, can you point me to an example?
Shorter Explanation:
I want labels from:
http://bl.ocks.org/1691430
...to be on a pie chart.
Here's the code I was running below:
Or in a JSBIN: http://jsbin.com/awilak/1/edit
If I understand his code correctly, this is the section that adds the labels. I don't understand what the labelForce.update does. From there, I don't care about transition, so that line isn't needed. Then the rest is just drawing the circles and adds a link / line? If someone could integrate that would be amazing but if you can help me understand what's going on and what I'm missing I'd be more than grateful.
// Now for the labels
// This is the only function call needed, the rest is just drawing the labels
anchors.call(labelForce.update)
labels = svg.selectAll(".labels")
.data(data, function(d,i) {return i;})
labels.exit()
.attr("class","exit")
.transition()
.delay(0)
.duration(500)
.style("opacity",0)
.remove();
// Draw the labelbox, caption and the link
newLabels = labels.enter().append("g").attr("class","labels")
newLabelBox = newLabels.append("g").attr("class","labelbox")
newLabelBox.append("circle").attr("r",11)
newLabelBox.append("text").attr("class","labeltext").attr("y",6)
newLabels.append("line").attr("class","link")
labelBox = svg.selectAll(".labels").selectAll(".labelbox")
links = svg.selectAll(".link")
labelBox.selectAll("text").text(function(d) { return d.num})
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Testing Pie Chart</title>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.1.3"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.geom.js?2.1.3"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.layout.js?2.1.3"></script>
<style type="text/css">
.slice text {
font-size: 16pt;
font-family: Arial;
}
</style>
</head>
<body>
<button id="button"> Test </button>
<br>
<form id="controls">
<div>
<h2>Y axis</h2>
<ul id="y-axis">
<li><label><input checked="checked" type="radio" name="y-axis" value="Component">Component</label></li>
<li><label><input type="radio" name="y-axis" value="Browser">Browser</label></li>
<li><label><input type="radio" name="y-axis" value="Version">Version</label></li>
</ul>
</div>
</form>
<script type="text/javascript">
// return a list of types which are currently selected
function plottableTypes () {
var types = [].map.call (document.querySelectorAll ("#coaster-types input:checked"), function (checkbox) { return checkbox.value;} );
return types;
}
var w = 600, //width
h = 600, //height
r = 100,
r2 = 200, //radius
axis = getAxis (), //axes
color = d3.scale.category20c(); //builtin range of colors
data = [
{"Browser":"Internet Explorer ","Version":"8.0","Toatl":2000,"Component":"6077447412293130422"},
{"Browser":"Internet Explorer ","Version":"9.0 ","Toatl":1852,"Component":"6077447412293130422"},
{"Browser":"Internet Explorer ","Version":"6.0 ","Toatl":1754,"Component":"6077447412293130422"},
{"Browser":"Firefox ","Version":"16.0 ","Toatl":1020,"Component":"6077447412293130422"},
{"Browser":"Chrome ","Version":"23.0 ","Toatl":972,"Component":"6077447412293130422"},
{"Browser":"Internet Explorer ","Version":"7.0 ","Toatl":700,"Component":"6077447412293130422"},
{"Browser":"Mobile Safari ","Version":"6.0 ","Toatl":632,"Component":"6077447412293130422"},
{"Browser":"BOT ","Version":"BOT ","Toatl":356,"Component":"6077447412293130422"},
{"Browser":"Firefox ","Version":"8.0 ","Toatl":196,"Component":"6077447412293130422"},
{"Browser":"Mobile Safari ","Version":"5.1 ","Toatl":184,"Component":"6077447412293130422"}
];
var vis = d3.select("body")
.append("svg:svg") //create the SVG element inside the <body>
.data([data]) //associate our data with the document
.attr("width", w) //set the width and height of our visualization (these will be attributes of the <svg> tag
.attr("height", h)
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + r2 + "," + r2 + ")") //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(r);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.Toatl; }); //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)
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
arcs.append("svg:text") //add a label to each slice
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.innerRadius = r2;
d.outerRadius = r;
return "translate(" + arc.centroid(d) + ")"; //this gives us a pair of coordinates like [50, 50]
})
.attr("text-anchor", "middle") //center the text on it's origin
.text(function(d, i) {
if(axis.yAxis == "Component"){
return data[i].Component;
}
return data[i].Browser; //get the label from our original data array
});
d3.select('#button').on('click', reColor);
var arcOver = d3.svg.arc()
.outerRadius(r + 30)
.innerRadius(0);
var arc = d3.svg.arc()
.outerRadius(r)
.innerRadius(0);
var arcs = vis.selectAll("g.slice")
.attr("class", "slice")
.on("mouseover", function(d) {
getAxis();
d3.select(this)
.select("path")
.transition()
.duration(500)
.attr("d", arcOver);
d3.select(this).select("text")
.text(function(d, i) {
if(axis.yAxis == "Component"){
return data[i].Component;
}
return data[i].Browser; //get the label from our original data array
});
})
.on("mouseout", function(d) {
getAxis();
d3.select(this)
.select("path")
.transition()
.duration(500)
.attr("d", arc);
d3.select(this)
.select("text")
.text(function(d, i) {
if(axis.yAxis == "Component"){
return data[i].Component;
}
return data[i].Browser; //get the label from our original data array
});
});
function reColor(){
var slices = d3.select('body').selectAll('path');
slices.transition()
.duration(2000)
.attr("fill", function(d, i) { return color(i+2); } );
slices.transition()
.delay(2000)
.duration(2000)
.attr("fill", function(d, i) { return color(i+10); } )
}
function makeData(){
}
// return an object containing the currently selected axis choices
function getAxis () {
var y = document.querySelector("#y-axis input:checked").value;
return {
yAxis: y,
};
}
function update() {
axis = getAxis()
arcs.selectAll("text") //add a label to each slice
.text(function(d, i) {
if(axis.yAxis == "Component"){
return data[i].Component;
}
return data[i].Browser; //get the label from our original data array
});
}
document.getElementById("controls").addEventListener ("click", update, false);
document.getElementById("controls").addEventListener ("keyup", update, false);
</script>
</body>
</html>
As others mentioned in the comments to your introduction-post it's possible to achieve a solution like you described it and it's possible using your code plus parts of the "moving-labels"-example. If I understand you correctly, you want to achieve non-overlapping labels using the force-layout, which is a pretty nice idea that I didn't stumble upon yet.
The code-part you pasted from the example just draws the labels and the lines as you already explained correctly. The next step is to rearrange the labels in a force-like layout around your pie chart.
The part that rearranges the labels (and links) in the example is the following:
function redrawLabels() {
labelBox
.attr("transform",function(d) { return "translate("+d.labelPos.x+" "+d.labelPos.y+")"})
links
.attr("x1",function(d) { return d.anchorPos.x})
.attr("y1",function(d) { return d.anchorPos.y})
.attr("x2",function(d) { return d.labelPos.x})
.attr("y2",function(d) { return d.labelPos.y})
}
// Initialize the label-forces
labelForce = d3.force_labels()
.linkDistance(0.0)
.gravity(0)
.nodes([]).links([])
.charge(-60)
.on("tick",redrawLabels)
The function is the one that changes the positions of the labels and lines. The force is calculated by D3 and started with the d3.force_labels().... As you can see, the function is assigned as an event-handler for the tick-event. In other words: After every step of calculating the force, D3 calls the 'drawer' for every label and updates the positions.
Unfortunately I'm not very familiar with the force_labels() method of D3, but I would assume it works pretty much like the regular force().
An anchor, in your case, is placed somewhere in each pie-piece for each label. The more centered within each pie-piece (NOT the pie itself), the better. Unfortunately you have to calculate this anchor-position somehow (sin and cos stuff) and set the line-ends to this fixed position within redrawLabels().
After you've done this you will see the first result. You may have to play around with gravity, linkDistance etc values of the force to achieve good results. (That's what the silders in the example do.)
See d3 docs for more info: https://github.com/mbostock/d3/wiki/Force-Layout
Then you will maybe stumble upon the problem that the labels are ordered around the pie without overlapping but in some strange order. You could solve this by initially placing the labels in correct order on a larger circle around your pie instead of positioning them randomly around the panel, which is the cause for the problem. This way you will experience way less jitter and misplacements.
The idea also is described in another blocks example: http://bl.ocks.org/mbostock/7881887
In this example, the nodes are initially placed on a virtual circle. The positioning is calculated by the following functions:
x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
They represent a circle with a radius of 200, place in the center of the drawing-panel. The circle is divided into m equally large pieces. i/m just calculates the 'piece-positions' where i ranges from 0 to m-1.
Hope I could help!
Yes, you can definitely combine force-labels with a pie chart! There is nothing particularly special about the pie chart labels you started with, they're just text elements that can be positioned like anything else using transform or x/y. It looks like you were initially positioning these labels according to the centroids of the arcs they were labelling, but you can just as easily use another criteria (like the output of a force layout).
D3's force layout calculates positions for things based on a set of constraints about what is fixed, what is movable, and which are connected to which. The labelForce.update method from Mike's bl.ocks example is being used to inform the force layout about how many objects need to be positioned, and where the fixed "anchor" points are. It then saves the computed positions for the labels into the diagram's data model, and they are used later on in the redrawLabels function.
You need to create two arcs. One for the pie chart drawing, and one which is large for the labels to sit on.
// first arc used for drawing the pie chart
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
// label attached to first arc
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
// second arc for labels
var arc2 = d3.svg.arc()
.outerRadius(radius + 20)
.innerRadius(radius + 20);
// label attached to second arc
g.append("text")
.attr("transform", function(d) { return "translate(" + arc2.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });