assign new id attribute to each element created - d3.js

How do I assign id attribute to each append of circle so that I can later use the circles based on its id. For now I am able to clone the circle on drag with out any id.
Demo: https://jsbin.com/zuxetokigi/1/edit?html,output
Code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js"></script>
<script>
svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
circle1 = svg.append("circle")
.attr("id", "circleToClone")
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 20);
var drag = d3.behavior.drag()
.origin(function ()
{
var t = d3.select(this);
return {x: t.attr("cx"), y: t.attr("cy")};
})
.on('dragend', function (d) {
var mouseCoordinates = d3.mouse(this);
if (mouseCoordinates[0] > 120) {
//Append new element
var circle2 = d3.select("svg").append("circle")
.classed("drg", true)
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 20)
.attr("cx", mouseCoordinates[0])
.attr("cy", mouseCoordinates[1])
.style("fill", "green");
}
});
circle1.call(drag);
</script>
</body>
</html>

If you want to give every circle a unique id, you could use a function to generate a GUID/UUID ('Globally Unique Identifier') for every circle.
You could add the following function from Salvik Meltser's GUID/UUID function to your code (anywhere before the drag function):
function guid() {
function _random_letter() {
return String.fromCharCode(97+Math.floor(Math.random() * 26));
}
function _p8(s) {
var p = (Math.random().toString(16)+"000000000").substr(2,8);
return s ? "-" + p.substr(0,4) + "-" + p.substr(4,4) : _random_letter() + p.substr(0, 7);
}
return _p8() + _p8(true) + _p8(true) + _p8();
}
I modified Salvik's original function a little bit because HTML element IDs need to start with a letter in HTML 4 or contain at least one letter in HTML 5.
And then where you append the new circle, just use .attr("id", guid()) to generate a new id for the circle.
var circle2 = d3.select("svg").append("circle")
.attr("id", guid())
.classed("drg", true)
...

You can assign new id attribute to each element created using its index:
var circle2 = d3.select("svg").append("circle")
.classed("drg", true)
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 20)
.attr("cx", mouseCoordinates[0])
.attr("cy", mouseCoordinates[1])
.style("fill", "green");
circle2.selectAll("circle").attr("id", function(d,i) { return i; });

Related

Trouble loading CSV data in D3

I'm brand new to D3.js, so my apologies in advance if I'm overlooking something obvious, but I cannot for the life of me figure out what's not working here...
I've been inching my way through Scott Murray's Interactive Data Visualization for the Web and I've hit a block at Chapter 7. My goal is to load some simple data from a csv document, parse the first column as Date data, and then display. The head of the csv looks like this...
Date, Amount
9/15/17, 26
11/9/17, 31
11/30/17, 23
12/21/17, 26
2/7/18, 23
I run the code below (which comes directly from the book!) and...none of that seems to be happening. When I enter "dataset" into the console, it only seems to include the final row:
{Date: "01/31/17", Amount: "23"}
Amount: "23"
Date: "01/31/17"
I'm perplexed since, again, the code itself comes from the book. I've been working through previous chapters just fine until now. And yes, I'm running a local server. Any idea what I'm doing wrong here? Thanks very much in advance!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3: Time scale</title>
<script type="text/javascript" src="../d3.js"></script>
<style type="text/css">
/* No style rules here yet */
</style>
</head>
<body>
<script type="text/javascript">
//Width and height
var w = 500;
var h = 300;
var padding = 40;
var dataset, xScale, yScale;
//For converting strings to Dates
var parseTime = d3.timeParse("%m/%d/%y");
//Function for converting CSV values from strings to Dates and numbers
var rowConverter = function(d) {
return {
Date: parseTime(d.Date),
Amount: parseInt(d.Amount)
};
}
//Load in the data
d3.csv("time_scale_data.csv", rowConverter, function(data) {
//Copy data into global dataset
dataset = data;
//Create scale functions
xScale = d3.scaleTime()
.domain([
d3.min(dataset, function(d) { return d.Date; }),
d3.max(dataset, function(d) { return d.Date; })
])
.range([padding, w - padding]);
yScale = d3.scaleLinear()
.domain([
d3.min(dataset, function(d) { return d.Amount; }),
d3.max(dataset, function(d) { return d.Amount; })
])
.range([h - padding, padding]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Generate date labels first, so they are in back
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return formatTime(d.Date);
})
.attr("x", function(d) {
return xScale(d.Date) + 4;
})
.attr("y", function(d) {
return yScale(d.Amount) + 4;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "#bbb");
//Generate circles last, so they appear in front
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d.Date);
})
.attr("cy", function(d) {
return yScale(d.Amount);
})
.attr("r", 2);
});
</script>
</body>
</html>
Instead of :
d3.csv("time_scale_data.csv", rowConverter, function(data) {
--- your code ---
});
Try this :
d3.csv("time_scale_data.csv", rowConverter).then(data){
--- your code ---
});

svg bar chart fails to render on plnkr

Not sure if this is a plnkr issue or an issue with my code.. although the console is not showing any errors.
I am writting a little svg script and it does not render.
var w = 300;
var h = 100;
var padding = 2;
var dataset = [5, 10, 14, 20, 25];
var svg = d3.select("body").append("svg").attr("width", w).attr("height", h);
svg.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr('x', function(d, i) {
return i * (w/dataset.length);
})
.attr('y', function(d) {
return h - (d);
})
.attr("width", w/ dataset.length - padding)
.attr("height", function(d) {
return d;
});
Plnkr here:
http://plnkr.co/edit/X7KomlmOVS6C0zhNL19b
Run it inside a document.ready or wait till the window is loaded. http://plnkr.co/edit/TmFWndyCgLk7GYEng7XP
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#*" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<h1>Hello Plunker!</h1>
</body>
</html>
and in the JS
$(document).ready(function(){
var w = 300;
var h = 100;
var padding = 2;
var dataset = [5, 10, 14, 20, 25];
var svg = d3.select("body").append("svg").attr("width", w).attr("height", h);
console.log('running the plnkr');
svg.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr('x', function(d, i) {
return i * (w/dataset.length);
})
.attr('y', function(d) {
return h - (d * 4);
})
.attr("width", w/ dataset.length - padding)
.attr("height", function(d) {
return d * 4;
});
});

Drawing D3 Rectangles

I am a beginner with D3 and JS in general.
I am trying to do a simple rectangle visualisation with a small csv file as a source.
price, units
80.67, 100
80.87, 99
79.34, 47
File, csv are in the same folder.
I am using Python's SimpleHTTPServer to serve locally in this folder.
This is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test Data</title>
<script type="text/javascript" src="../d3/d3.v3.js"></script>
</head>
<body>
<script type="text/javascript">
// load csv from the same directory
d3.csv("test.csv", function (data){
return {
price: +data.price, // convert to number with +
units: +data.units, // convert to number with +
};
var canvas = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 500)
canvas.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", function (d) { return d.price; })
.attr("height", 48)
.attr("y", function (d) { return d.units; })
.attr("fill", "blue");
canvas.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("fill", "white")
.attr("y", function (d) { return d.units + 24; })
.text( function (d) { return d.units;})
});
</script>
</body>
I am getting no errors, just a blank page.
What is wrong with this code?
The first thing you do in your callback is to return. None of the code after that is being executed. I'm referring to
return {
price: +data.price, // convert to number with +
units: +data.units, // convert to number with +
};
which should probably be
data.forEach(function(d) {
d.price = +d.price;
d.units = +d.units;
});
The signature of the callback should also be function(error, data) instead of function(data).

D3 auto updates when array length change

I have simple code here from Interactive Data Visualization by Scott Murray with minor changes. What I changed is the initial data's length 5 is different from the dataset1's length 25 in the click function.
However, every time I click and update, it does generate random new numbers but the length only shows 5 bars.
What is the reason for this? And how could I modify to change it?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="text/javascript" src="../d3/d3.v3.js"></script>
<title>D3 index01 </title>
<style type="text/css">
div.bar{
display: inline-block;
width: 20px;
height: 75px;
background-color: teal;
margin-right: 2px;
}
</style>
</head>
<body>
<p> click it </p>
<script type = "text/javascript">
var w=600;
var h = 250;
var dataset = [5,10,13,14,15];
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length)).rangeRoundBands([0,w],0.05);
var yScale = d3.scale.linear()
.domain([0,d3.max(dataset)]).range([0,h]);
var svg = d3.select("body")
.append("svg")
.attr("width",w)
.attr("height",h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d,i){
return xScale(i);
})
.attr("y",function(d){return (h-yScale(d));})
.attr("width",xScale.rangeBand())
.attr("height",function(d){return yScale(d);});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i)+xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
d3.select("p")
.on("click",function(){
var dataset1 = [];
for(var i=0; i<25; i++){
var newNumber = Math.floor(Math.random()*25);
dataset1.push(newNumber);
}
svg.selectAll("rect").data(dataset1)
.transition()
.delay(function(d,i){
return i*100;
})
.duration(500)
.attr("x",function(d,i){
return xScale(i);
})
.attr("y",function(d){
return h-yScale(d);
})
.attr("x", function(d,i){
return xScale(i);
})
.attr("height",function(d){
return yScale(d);
})
.attr("fill",function(d){
return "rgb(0,0, " + (d*10) + ")";
});
svg.selectAll("text").data(dataset1).
text(function(d) {
return d;
})
.attr("x", function(d, i) {
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) + 14;
});
});
</script>
</body>
</html>
The issue, as I see it, is that when you update the rects with the new numbers using this statement
svg.selectAll("rect").data(dataset1)
the selectAll only selects the original 5 rects that were created based on dataset
var dataset = [5,10,13,14,15];
You could either create them all from the start with an equally sized starting dataset or append new rect svgs at this point.
Since you are doing a transition on the original rects I think it makes the most sense to just start with all 25. You could write a function to automatically generate this and put it where dataset is defined:
var dataset = [];
var numBars = 25; // This is the number of bars you want
var maxHeight = 25; // Seems like this is independent and needed as a ceiling
for(var i =0;i < N;i++){
var newNumber = Math.floor(Math.random()* maxHeight);
dataset.push(newNumber);
}
You could then replace 25 with numBars within your onclick function as well.

Change class of one element when hover over another element d3

I have a list of images and a list of image titles. I want to be able to show a hover state (change css) for the title when I mouse over its corresponding image, but I cannot figure out how to connect the two pieces of data. My code is below. I have it so that when you click on the top number the information appears beneath.
<!DOCTYPE html>
<html>
<head>
<script src="d3.v2.js"></script>
<title>Untitled</title>
</head>
<body>
<script type="text/javascript" >
function barStack(d) {
var l = d[0].length
while (l--) {
var posBase = 0, negBase = 0;
d.forEach(function(d) {
d=d[l]
d.size = Math.abs(d.y)
if (d.y<0) {
d.y0 = negBase
negBase-=d.size
} else
{
d.y0 = posBase = posBase + d.size
}
})
}
d.extent= d3.extent(d3.merge(d3.merge(d.map(function(e) { return e.map(function(f) { return [f.y0,f.y0-f.size]})}))))
return d
}
var ratiodata = [[{y:3.3}],[{y:-1.5}]]
var imageList = [
[3.3, 28, -1.5, 13, 857, 3, 4, 7, [{paintingfile:"676496.jpg", title:"Dessert1"}, {paintingfile:"676528.jpg", title: "Dessert2"}]
]]
var h=400
var w=1350
var margin=25
var color = d3.scale.category10()
var div = d3.select("body").append("div")
.attr("class", "imgtooltip")
.style("opacity", 0);
var x = d3.scale.ordinal()
.domain(['255'])
.rangeRoundBands([margin,w-margin], .1)
var y = d3.scale.linear()
.range([h-margin,0+margin])
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(6, 0)
var yAxis = d3.svg.axis().scale(y).orient("left")
barStack(ratiodata)
y.domain(ratiodata.extent)
var svg = d3.select("body")
.append("svg")
.attr("height",h)
.attr("width",w)
svg.selectAll(".series")
.data(ratiodata)
.enter()
.append("g")
.classed("series",true)
.style("fill","orange")
.selectAll("rect").data(Object)
.enter()
.append("rect")
.attr("x",function(d,i) { return x(x.domain()[i])})
.attr("y",function(d) { return y(d.y0)})
.attr("height",function(d) { return y(0)-y(d.size)})
.attr("width",x.rangeBand());
svg.selectAll("text")
.data(imageList)
.enter()
.append("text")
.text(function(d) {
return d[0];
})
.attr("x", function(d, i) {
return x(x.domain()[i]) + x.rangeBand() / 2;
})
.attr("y", function(d) {
return h - (32.1100917431*d[0] +150);
})
.attr("font-size", "16px")
.attr("fill", "#000")
.attr("text-anchor", "middle")
//.on("click", function(d) {console.log(d[1]);})
.on("click", function(d) {
//Update the tooltip position and value
d3.selectAll("ul")
.remove();
d3.selectAll("li")
.remove();
d3.select("#PaintingDetails")
.append("ul")
.selectAll("li")
.data(d[8])
.enter()
.append("li")
.text(function(d){
return (d.title);
});
d3.select("#imageBox")
.append("ul")
.selectAll("li")
.data(d[8])
.enter()
.append("li")
.classed("Myimageslist",true)
.append("img")
.classed("Myimages",true)
.attr("src", function(d){
return ("http://images.tastespotting.com/thumbnails/" + d.paintingfile);
})
.attr("align", "top");
d3.select(".Myimages")
.on("mouseover", function(){
d3.select("#PaintingDetails")
.selectAll("li")
.classed("selected", true)
});
});
svg.append("g").attr("class","axis x").attr("transform","translate (0 "+y(0)+")").call(xAxis);
// svg.append("g").attr("class","axis y").attr("transform","translate ("+x(margin)+" 0)").call(yAxis);
</script>
<div id="PaintingDetails"></div>
<div id="imageBox"></div>
</body>
</html>
The quick and dirty solution would be to simply use the index of the data element to figure out which title matches which image:
d3.selectAll(".Myimages")
.on("mouseover", function(d, i) {
d3.select("#PaintingDetails")
.selectAll("li")
.classed("selected", function(e, j) { return j == i; })
});

Resources