I'm trying to get a second bar into my graph. The elements are correctly getting appended but not in the correct location and not the correct height. What I want from the data to be at the 1 position in the x-axis to have 2 bars one with a height of 2 and the other height of 3 and so on.
http://jsfiddle.net/626uesbh/4/
var svg2 = d3.select("#histogram").append("svg2")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg2.selectAll(".rect")
.data(data)
.enter().append("g")
.attr("transform", "translate(0, 100)")
.append("rect")
.attr("class", "rect")
.attr("width", 10)
.attr("height", function(d) { return height - y2Map(d); })
.attr("x", xMap)
.attr("y", yMap)
.style("fill", "blue");
I suspect svg2 transform is the problem but after trying fiddling with it for an hour I can seem to get what I want. I looked at this question and tried to implement it into my problem. D3.js grouped bar chart
Since each element in your data contains the values for both bars, you have to add them as a group. That is, add a 'g' element to the chart for each element in the array, then add a bar for inner_array[1] and inner_array[2].
Hopefully this gets you on the right path, essentially all I changed was the stuff after your //bar comment.
http://jsfiddle.net/626uesbh/6/
// bar
var bar_groups = svg.selectAll('.bar-group')
.data(data)
.enter().append('g')
.attr('class', 'bar-group');
bar_groups.append('rect')
.attr("class", "rect")
.attr("width", 10)
.attr("height", function(d) { return height - yScale(d[1]); })
.attr("x", function(d) {
return xScale(d[0]) - 5;
})
.attr("y", function(d) {
return yScale(d[1]);
})
.style("fill", "green");
bar_groups.append('rect')
.attr("class", "rect")
.attr("width", 10)
.attr("height", function(d) { return height - yScale(d[2]); })
.attr("x", function(d) {
return xScale(d[0]) + 5;
})
.attr("y", function(d) {
return yScale(d[2]);
})
.style("fill", "blue");
Note: there are much more elegant ways to do this. I am only showing you how to add the bars to your existing code. Please take a look at http://bl.ocks.org/mbostock/3887051 for further guidance.
Related
I am trying to insert some text inside each bar of the given chart with it's values and I just can't figure it out. I'm still new to d3 and struggle with most of the things.
I also need to have different text inside each bar, dependent on the data.
I've looked up around and found that I need to use the following structure:
<g class="barGroup">
<rect />
<text />
</g>
This is how the chart looks:
This is how I want to achieve:
This is the function which creates the whole chart, the part where I attempt to insert the text is at the end:
function createBarChart(divChartId, receivedData, chartDimensions) {
// Set dimensions
var margin = chartDimensions["margin"];
var width = chartDimensions["width"];
var height = chartDimensions["height"];
// Create Svg and group
var svg = d3.select(divChartId)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Create and append X axis
var xAxis = d3.scaleBand()
.range([0, width])
.domain(receivedData.map(function (d) { return d.answerText; }))
.padding(0.2);
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xAxis))
.selectAll("text")
.attr("class", "font-weight-bold")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
.style("font-size", "1rem");
// Create and append Y axis
var yAxis = d3.scaleLinear()
.domain([0, Math.max.apply(Math, receivedData.map(function (d) { return d.answerCount; }))])
.range([height, 0]);
svg.append("g")
.attr("class", "y-axis")
.call(d3.axisLeft(yAxis)
.ticks(5)
.tickFormat(d3.format("d")));
// create a tooltip
var tooltip = d3.select("body")
.append("div")
.attr("class", "font-weight-bold text-dark px-2 py-2")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("background", "lavender")
.style("border", "1px solid gray")
.style("border-radius", "12px")
.style("text-align", "center")
.style("visibility", "hidden")
.style("visibility", "hidden")
.style("visibility", "hidden")
// Generate random color
var colorObject = generateRandomColorObject();
// Create and append bars, set values to 0 for animation start
svg.selectAll("rect").data(receivedData)
.enter()
.append("g")
.attr("class", "barGroup")
.append("rect")
.attr("x", function (d) { return xAxis(d.answerText); })
.attr("y", function (d) { return yAxis(0); })
.attr("width", xAxis.bandwidth())
.attr("height", function (d) { return height - yAxis(0); })
.attr("fill", colorObject.color)
.style("stroke", colorObject.border)
.style("stroke-width", 1)
.on("mouseover", function (d) { return tooltip.style("visibility", "visible").html(d.answerText + ": " + d.answerCount); })
.on("mousemove", function () { return tooltip.style("top", (event.pageY - 45) + "px").style("left", (event.pageX) + 5 + "px"); })
.on("mouseout", function (d) { return tooltip.style("visibility", "hidden").html(""); });
// Set correct values for animation end
svg.selectAll("rect")
.transition().duration(1500)
.attr("y", function (d) { return yAxis(d.answerCount); })
.attr("height", function (d) { return height - yAxis(d.answerCount); })
.delay(function (d, i) { return (i * 100) });
// Append text to each bar
svg.selectAll("barGroup")
.append("text")
.attr("font-size", "2em")
.attr("color", "black")
.text("Test");
}
In this line you want your selector to look for a class: svg.selectAll("barGroup") should be svg.selectAll(".barGroup"). Then, once you see your text show up, you'll need to position them.
Consider positioning your g.barGroups's first, using .attr('transform', function(d){ return 'translate(...)' }, before you .append('rect') and .append('text'). That way the entire group will be in approximately the right position, and you can make small adjustments to the rect and text.
Hi i have a heatmap here that im trying to give color to. Right now its all over red but I want to use the d3.interpolateRdYlBu, i want my values that are lower to be the blue and the higher be the red so i would like it to gel nicely. I know that its reading correctly since i get the red and no other errors in my console but Im not doing something right that it doesnt take my value into account and do accordingly. Any help would be appreciated!
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<!-- Load color palettes -->
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {top: 80, right: 25, bottom: 30, left: 40},
width = 1000 - margin.left - margin.right,
height = 1000 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/Nataliemcg18/Data/master/NASA_Surface_Temperature.csv", function(data) {
// Labels of row and columns -> unique identifier of the column called 'group' and 'variable'
var myGroups = d3.map(data, function(d){return d.group;}).keys()
var myVars = d3.map(data, function(d){return d.variable;}).keys()
// Build X scales and axis:
var x = d3.scaleBand()
.range([ 0, width ])
.domain(myGroups)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSize(0))
.select(".domain").remove()
// Build Y scales and axis:
var y = d3.scaleBand()
.range([ height, 0 ])
.domain(myVars)
.padding(0.05);
svg.append("g")
.style("font-size", 15)
.call(d3.axisLeft(y).tickSize(0))
.select(".domain").remove()
// Build color scale
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([1,100])
// create a tooltip
var tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
// Three function that change the tooltip when user hover / move / leave a cell
var mouseover = function(d) {
tooltip
.style("opacity", 1)
d3.select(this)
.style("stroke", "green")
.style("opacity", 1)
}
var mousemove = function(d) {
tooltip
.html("The exact value of this cell is: " + d.value, )
.style("left", (d3.mouse(this)[0]+70) + "px")
.style("top", (d3.mouse(this)[1]) + "px")
}
var mouseleave = function(d) {
tooltip
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
.style("opacity", 0.8)
}
// add the squares
svg.selectAll()
.data(data, function(d) {return d.group+':'+d.variable;})
.enter()
.append("rect")
.attr("x", function(d) { return x(d.group) })
.attr("y", function(d) { return y(d.variable) })
.attr("rx", 4)
.attr("ry", 4)
.attr("width", x.bandwidth() )
.attr("height", y.bandwidth() )
.style("fill", function(d) { return myColor(d.value)} )
.style("stroke-width", 4)
.style("stroke", "none")
.style("opacity", 0.8)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
})
// Add title to graph
svg.append("text")
.attr("x", 0)
.attr("y", -50)
.attr("text-anchor", "left")
.style("font-size", "22px")
.text("A d3.js heatmap");
// Add subtitle to graph
svg.append("text")
.attr("x", 0)
.attr("y", -20)
.attr("text-anchor", "left")
.style("font-size", "14px")
.style("fill", "grey")
.style("max-width", 400)
.text("A short description of the take-away message of this chart.");
</script>
In your colour scale, you've set the domain as [0,100]. Your values however are between 0 and 1.5, and you want them reversed
so, this should fix it:
// Build color scale
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([1.3,0])
To be even more thorough, you can use the d3.max and d3.min functions to work out the max in min for you:
var myColor = d3.scaleSequential()
.interpolator( d3.interpolateRdYlBu)
.domain([d3.max(data, d=>d.value),d3.min(data, d=>d.value)])
Heres a jsFiddle with this working: https://jsfiddle.net/x8zyud5t/
I'm trying to update the bar chart in d3 based on the input selected by the user. The updated data is being displayed but it is being displayed on the old SVG elements. I tried using exit().remove() but it did not work.
Can anyone edit the code attached below so that the old SVG elements are removed.
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<style>
.rect {
fill: steelblue;
}
.text {
fill: white;
font: 10px sans-serif;
text-anchor: middle;
}
</style>
</head>
<body>
<select id = "variable">
<option >select</option>
<option value="AZ">Arizona</option>
<option value="IL">Illinois</option>
<option value="NV">NV</option>
</select>
<script>
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 500 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var y = d3.scaleLinear()
.domain([0,5])
.range([height, 0]);
var yAxis = d3.axisLeft(y)
.ticks(10);
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var bar;
function update(state)
{
d3.csv("test3.csv", function(error, data)
{
data = data.filter(function(d, i)
{
if (d['b_state'] == state)
{
return d;
}
});
data = data.filter(function(d, i)
{
if (i<10)
{
return d;
}
});
var barWidth = width / data.length;
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Stars");
bar = svg.selectAll("bar")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(" + i * barWidth + ",0)"; });
bar.append("rect")
.attr("y", function(d) { return y(d.b_stars); })
.attr("height", function(d) { return height - y(d.b_stars); })
.attr("width", barWidth - 1)
.attr("fill", "steelblue");
bar.append("text")
.attr("x", function(d) { return height - y(d.b_stars); })
.attr("y", -40)
.attr("dy", ".75em")
.text(function(d) { return d.b_name; })
.attr("transform", "rotate(90)" );
});
svg.exit().remove();
bar.exit().remove();
}
d3.select("#variable")// selects the variable
.on("change", function() {// function that is called on changing
var variableName = document.getElementById("variable").value;// reads the variable value selected into another variable
update(variableName);});
</script>
</body>
Your problem here is about your bar selection. You can have a look to this part of the d3 documentation: Joining data.
By writing
bar = svg.selectAll("bar")
.data(data)
.enter()
You are selecting all bar elements, joining them data, and with this enter(), you are getting all the data items not linked to a bar element (enter() documentation).
But, your bar selector matches nothing. The parameter of the select()/selectAll() has to be a selector (element, class with ., id with #...). That is why the enter() your enter() selection is creating always new elements above the old ones instead of updating them.
So the first step is to rewrite this selection and creating the DOM elements that will match later this selection:
bar = svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr('class', 'bar')
.attr("transform", function(d, i) { return "translate(" + i * barWidth + ",0)"; });
Here, we are selecting all elements with the bar class. If there is no DOM element linked to an item from data, so we are creating it (in the enter() selection), as a new g with the bar class.
With the selection written like this, on the next call of your update, the selectAll('.bar') will match all the g previously created and not apply the enter() selection for existing elements.
To update or remove your existing bars, you can write your code like this:
var barData = svg.selectAll(".bar")
.data(data)
// Bars creation
var barEnter = barData.enter()
.append("g")
.attr('class', 'bar')
.attr("transform", function(d, i) { return "translate(" + i * barWidth + ",0)"; });
barEnter.append("rect")
.attr("y", function(d) { return y(d.b_stars); })
.attr("height", function(d) { return height - y(d.b_stars); })
.attr("width", barWidth - 1)
.attr("fill", "steelblue");
barEnter.append("text")
.attr("x", function(d) { return height - y(d.b_stars); })
.attr("y", -40)
.attr("dy", ".75em")
.text(function(d) { return d.b_name; })
.attr("transform", "rotate(90)" );
// Update the bar if the item in data is modified and already linked to a .bar element
barData.select('rect')
.attr("height", function(d) { return height - y(d.b_stars); })
// Remove the DOM elements linked to data items which are not anymore in the data array
barData.exit().remove()
Everything seems to be working as expected except for the text element. I can't seem to get text elements to append to my g element. Here is my code so far. I've inspected the DOM in a chrome browser, but I don't see any text elements and I'm not sure why. I was using this site as a sort of guide: https://www.dashingd3js.com/svg-text-element.
Also, I know the elements should stack on each other since they all share the same x and y position, I'm just trying to get the elements to appear first.
var svg = d3.select('svg'),
margin = {top: 60, right: 20, bottom: 45, left: 60},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(.1);
var y = d3.scaleLinear().range([height, 0]);
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(test.map(function(d) { return d.level; }));
y.domain([0, d3.max(test, function(d) { return d.time; })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.attr("writing-mode", "tb-rl")
.call(d3.axisBottom(x).tickSize(0).tickPadding(10));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(10))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 9)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Frequency")
g.selectAll(".bar")
.data(test)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.level); })
.attr("y", function(d) { return y(d.time); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.time); })
var text = g.selectAll("text")
.data(test).enter()
.append("text");
var textLabels = text.attr("x", 100)
.attr("y", 100)
.text("testing")
.attr("fill", "blue")
your selection g.selectAll("text") will include text from the axes you appended to the g element earlier in the code, so your "enter" won't have anything in it. D3 compares the incoming to data to the items in the selection, and if you don't specify a key, will do a simple comparison on the number of elements in each, and then add (enter) and remove (exit) accordingly.
If you change your selection to something that you know won't be on in DOM yet (ie an empty selection), for example g.selectAll(".label"), then when you append data, the enter selection will contain your new text labels.
I'm very new to d3js. I did a bar chart using one json file. It works very fine. But now I changed the JSON file format due to some unavoidable reasons.My previous json was:
[
{"name":"bike","value":98},
{"name":"car","value":52},
{"name":"bus","value":20},
{"name":"van","value":65}
]
Code is :
d3.json("sample.json", function(error, data) {
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
var chart = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = chart.selectAll(".bar")
.data(data)
.enter().append("g");
bar.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x.rangeBand())
.attr("height", function(d) { return height - y(d.value); });
});
This is my new json file:
[
{"category":"bike","bike":38,"car":0,"bus":0,"van":0},
{"category":"car","bike":0,"car": 50,"bus":0,"van":0,},
{"category":"bus","bike":0,"car": 0,"bus":14,"van":0},
{"category":"van","bike":0,"car": 0,"bus":0,"van":43}
]
I want to get a chart like same as previous one. Data with "0" (zero) shouldn't appear in chart.
Pls help me. Thanks in advance :)
You don't provide an explanation of your new format, and I'm not sure how your previous format was working (given the x attribute setting) but adjusting to the new format should be as simple as
.attr("x", function(d) { return x(d.category); })
.attr("y", function(d) { return y(d[d.category]); })
And similar changes elsewhere. (e.g. for the height)
Or maybe I'm missing something?