Creating a rectangle chart with D3.js - javascript

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

Related

How would I apply an href to the ENTIRE svg?

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.

Translating a whole array continuously in d3

var bardata = []; //array that holds the current value for the candlestick chart
var pastRectangles = [50,12,14,15,35,64] //holds the data for the historical rectangles to be drawn
var data;
setInterval(function () {
var x = Math.floor(Math.random() * 100) //generate a random whole number between 0-99
bardata.push(x); // push that value into the bardata array
// console.log(bardata)
data = x; //set the value of x to data, will be used to update the pastRectangles array every 10 seconds
}, 1000);
var height = 900
, width = 900
, barWidth = 50
, barOffset = 55;
var offset = pastRectangles.length * (barOffset + barWidth);
var scale = d3.scale.linear()
.range([0, pastRectangles])
.domain([0, height]);
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
.style('background', 'black')
.append("g")
.attr("class", "rectangles")
update(pastRectangles[pastRectangles.length-1]);
pastDraw(); // call post draw to draw the dummy data first before the update function starts running
function pastDraw()
{
var pastRect = svg.selectAll('rect').data(pastRectangles); //This function will loop through the pastRectangles array and will
pastRect.enter() //draw a rectange for every index in the array
.append("rect") //The reason for not using bardata is that it only holds one value
.attr("g", "rectangles")
.attr("x", function(d,i){return i * (barWidth+barOffset)}) //every second and therefore a second array is needed to hold the
.attr("y", function(d){return height - d}) //historical data
.attr("height", function(d){return d})
.attr("width", 60)
.attr("id", "history")
.attr("fill", "steelblue")
pastRect.transition()
.duration(1000)
.ease('linear')
.attr("height", function (d) {
return d
})
pastRect.exit()
}
function update(bardata) {
var rect = svg.selectAll('rect').data([bardata]); //This function essentially draws a new rectangle for every
rect.enter() //value of bardata, however, because bardata is constantly
.append("rect") //removing the current value for a new value every second
.attr("x",offset) //it gives the illusion of one rectangle updating regularly
.attr("y", function(d){return height - d})
.attr("id", "updateBar")
.attr("height", bardata)
.attr("width", 60)
.attr("fill", "steelblue")
rect.transition()
.duration(1000)
.ease('linear')
.attr("height", function (d) {
return d
})
// rect.exit().transition()
// .duration(1000)
// .attr("transform", "translate(-80,0)")
// .remove();
//
//console.log(bardata);
}
function moveBar()
{
svg.selectAll("#history")
.transition()
.duration(1000)
.attr("transform", "translate(-80,0)")
svg.select("#updateRect")
.transition()
.duration(1000)
.attr("transform", "translate(80,0)")
}
setInterval(function () {
update(bardata); //call the update function with the current value of bardata
bardata.shift(); // remove the the last index of the array from bardata
}, 1000)
setInterval(function () {
pastRectangles.push(data) //update pastrectangles array with the most current value of x every 10 seconds
pastDraw(); // call the pastDraw function to draw the latest recatngle
moveBar();
}, 10000)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Messing around with d3 and trying to make a live bar chart, I want it to behave like a candle stick chart
reference image for people who dont know what that is:
(source: chart-formations.com)
I want a live updating bar and after a set amount of time I want to draw a rectangle to the left of it and I've succeeded in that so far by using two arrays, one that only has one value in it every second to draw the updating bar, and another that will hold data for all the past rectangles.
The rectangles in pastRectangles however are being drawn in the wrong order, I can only assume this is because when d3 goes through the dataset it goes from the beginning of the array to the end of the array, I've tried reversing the array to try prevent that but still no help.
I've fixed it up and got it working somewhat how I would like it to be, however Im unable to translate it how I want it, it seems to be only translating the newest rectangle to the array and not the whole array each time, is there anyway to do that? or alternatively, move the updating bar forward each time too.
Instead of shifting the whole array of numbers to the left, I finally got the updatebar to move to the right by writing a simpe moveBars function
function moveBar()
{
var bar = svg.selectAll("#updateBar")
.transition()
.duration(50)
.attr("transform", "translate("+move+",0)")
move += (barWidth+barOffset);
console.log(move)
}
And then I just called that in my pastDraw() function that gets called every ten seconds
var bardata = []; //array that holds the current value for the candlestick chart
var pastRectangles = [50,12,14,15,35,64] //holds the data for the historical rectangles to be drawn
var data
, height = 250
, width = 800
, barWidth = 50
, barOffset= 55;
var move = 0;
setInterval(function () {
var x = Math.floor(Math.random() * 100) //generate a random whole number between 0-99
bardata.push(x); // push that value into the bardata array
data = x; //set the value of x to data, will be used to update the pastRectangles array every 10 seconds
}, 1000);
data = (barOffset+barOffset)
var offset = pastRectangles.length * (barOffset + barWidth);
var scale = d3.scale.linear()
.range([0, pastRectangles])
.domain([0, height]);
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
.style('background', 'red')
.append("g")
.attr("class", "rectangles")
update(pastRectangles[pastRectangles.length-1]);
pastDraw()
function pastDraw()
{
var pastRect = svg.selectAll('rect').data(pastRectangles); //This function will loop through the pastRectangles array and will
pastRect.enter() //draw a rectange for every index in the array
.append("rect") //The reason for not using bardata is that it only holds one value
.attr("g", "rectangles")
.attr("x", function(d,i){return i * (barWidth+barOffset)}) //every second and therefore a second array is needed to hold the
.attr("y", function(d){return height - d}) //historical data
.attr("height", function(d){return d})
.attr("width", 60)
.attr("id", "history")
.attr("fill", "steelblue")
pastRect.transition()
.duration(1000)
.ease('linear')
.attr("height", function (d) {
return d
})
pastRect.exit()
var bar = svg.selectAll("#updateBar")
.transition()
.duration(1000)
.attr("transform", "translate("+move+",0)")
move += (barWidth+barOffset);
setTimeout(function()
{
pastRectangles.push(data)
pastDraw();
},30000)
}
function update(bardata) {
var rect = svg.selectAll('rect').data([bardata]); //This function essentially draws a new rectangle for every
rect.enter() //value of bardata, however, because bardata is constantly
.append("rect") //removing the current value for a new value every second
.attr("x",offset) //it gives the illusion of one rectangle updating regularly
.attr("y", function(d){return height - d})
.attr("id", "updateBar")
.attr("height", bardata)
.attr("width", 60)
.attr("fill", "white")
.attr("stroke", "black")
rect.transition()
.duration(1000)
.ease('linear')
.attr("height", function (d) {
return d
})
rect.exit();
//console.log(bardata);
}
setInterval(function () {
update(bardata); //call the update function with the current value of bardata
bardata.shift(); // remove the the last index of the array from bardata
}, 1000)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
running this code on chrome can tend to stall because of how Chrome handles SetInterval() so instead I opted to use recursive setTimeouts() instead

Difference between dynamic rectangles and circles in d3?

I am confused about why I am able to create an svg and even have circles defined in the DOM of the page, however, the circles are not showing up on the page as they would with rectangles.
var svgContainer = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200);
var populations = svgContainer.selectAll("circle")
.data(jsonCircle)
.enter()
.append("circle");
var populationAttributes = populations
.attr("x", function (d) { return d.x_axis; })
.attr("y", function (d) { return d.y_axis; })
.attr("radius", function (d) {return d.radius;})
.style("fill", function (d) {return d.color;});
I am trying to follow the example in dashing d3 exactly except with a circle instead of a rectangle (https://www.dashingd3js.com/dynamic-svg-coordinate-space)
The attributes for a circle are not x, y and radius they are cx, cy and r.

Adding points of map as one layer

I'm adding points from a json file using the following approach:
svg.selectAll("circle")
.data(places.features)
.enter()
.append("circle")
.attr('cx', function(d) { d.geometry.coordinates)[0]})
.attr('cy', function(d) { return proj(d.geometry.coordinates)[1]})
.attr("r", function(d) {
if (d.properties.category == 'a'){
return 2
}else if (d.properties.category == 'b'){
return 4
}else if (d.properties.category == 'c'){
return 6
}
});
I have been tweaking the map in Adobe Illustrator, and I realize that I am not adding the points as a group to the map; instead each point is an individual layer due to how I constructed this. How can I add the points as one layer group instead?
I constructed the svg and proj in the following way:
var width = 1000,
height = 900,
radius = 340;
var proj = d3.geo.naturalEarth()
.scale(200)
.translate([width / 2 , height/2])
var path = d3.geo.path()
.projection(proj);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
Place them in a group:
svg.append('g').selectAll("circle")
.data(places.features)
.enter()
You can use an SVG <g> element for grouping graphics elements together. The "g" doesn't have any visual representation of it's own. It's only purpose is to group other elements.

plots on multiple instances of graph

I'm using the d3 library to plot a bar graph with JSON objects recieved from the server through websockets. What is happening though is that each time the graph is plotted it draws a new instance of a graph. So I end up with multiple graphs.
But I want the JSON data to be all plotted onto the same one graph.
Here's my code:
ws = new WebSocket("ws://localhost:8888/dh");
var useData = []
//var chart;
var chart = d3.select("body")
.append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 200);
ws.onmessage = function(evt)
{
var distances = JSON.parse(evt.data);
data = distances.miles;
console.log(data);
if(useData.length <= 10){
useData.push(data)
}
else
{
var draw = function(data){
// Set the width relative to max data value
var x = d3.scale.linear()
.domain([0, d3.max(useData)])
.range([0, 420]);
var y = d3.scale.ordinal()
.domain(useData)
.rangeBands([0, 120]);
var rect = chart.selectAll("rect")
.data(useData)
// enter rect
rect.enter().append("svg:rect")
.attr("y", y)
.attr("width", x)
.attr("height", y.rangeBand());
// update rect
rect
.attr("y", y)
.attr("width", x)
.attr("height", y.rangeBand());
var text = chart.selectAll("text")
.data(useData)
// enter text
text.enter().append("svg:text")
.attr("x", x)
.attr("y", function (d) { return y(d) + y.rangeBand() / 2; })
.attr("dx", -3) // padding-right
.attr("dy", ".35em") // vertical-align: middle
.attr("text-anchor", "end") // text-align: right
.text(String);
// update text
text
.data(useData)
.attr("x", x)
.text(String);
}
useData.length = 0;
}
}
How can I plot all points onto on graph which is being constantly updated?
It's a shame that d3 cannot handle data in real-time and update charts accordingly, or if it can that there's no clear tutorial/ explanation of how to.
Thanks
My guess is because you're creating a chart every time with:
var chart = d3.select("body")
.append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 20 * useData.length);
Rather you need to check if chart exists, and if so, don't call that line.
// outside of .onmessage
var chart;
// inside of .onmessage
if (!chart) {
chart = d3.select("body")
.append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 20 * useData.length);
}
Like Brian said you keep creating a new svg element at every onmessage event. However, in D3 you don't need to use an if statement to check for element existence; after you do a data join, the enter() selection will only contain the elements that did not exist yet:
// data join
var chart = d3.select("body").selectAll(".chart")
.data([useData]); // if you wanted multiple charts: .data([useData1, useDate2, useData3])
// enter
chart.enter().append("svg") // this will only execute if the .chart did not exist yet
.attr("class", "chart")
.attr("width", 420);
// update (both new and existing charts)
chart
.attr("height", function(d) { return 20 * d.length; });
The concepts of the data join, enter(), update(), and exit() selections are explained in the Thing with Joins article. See also the 3 General Update Pattern articles.
You have to use a similar approach when you update or add new rect elements in your chart. Assuming for the moment that useData contains all accumulated data (although the useData.length = 0 might mean that is not the case):
// data join
var rects = chart.selectAll("rect")
.data(function(d) { return d; }); // use the data bound to each chart
// enter
rects.enter().append("rect");
// update
rects
.attr("y", function(d) { return y(d.yValue); }) // not sure what your data structure looks like
.attr("width", function(d) { return x(d.xValue); })
.attr("height", y.rangeBand());
// exit
rects.exit().remove();
Some suggestions how to update a path with real time data are given in Path Transitions.

Categories