Can't display a single bar in D3 - javascript

I'm trying to display single bars in D3. I have a data of the type:
data = [
"value1": 1,
"value2": 2,
"value3": 3,
]
Because the y scale is not the same, I'm trying to display three different bar-charts, each one of them just with a bar. I don't need x-axis.
As you can see in this fiddle: http://jsfiddle.net/GPk7s/, The bar is not showing up, although if you inspect the source code, it has been added. I think it is because I'm not providing a x range, but I don't know how, because I don't really have one.
I just want a bar whose height is related to the range I provide (in the fiddle example this is [10, 30]).
I copy here the code just in case:
var margin = {
top: 50,
right: 0,
bottom: 100,
left: 30
},
width = 200 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var data = [{ "Value": 22.5 } ];
var yRange = d3.scale.linear().range([height, 0]);
yRange.domain([10, 30]);
var svg = d3.select("#chart").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 + ")");
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("y", function (d) {
return yRange(d.Value);
})
.attr("height", function (d) {
return height - yRange(d.Value);
})
.attr("y", function (d) {
return yRange(d.Value) + 3;
})
.attr("dy", ".75em")
.text(function (d) {
return d.Value;
});
Thanks for your help!

There are two problems with what you are doing:
1) Your rectangle doesn't have a width. I added this:
...
.attr("class", "bar")
.attr("width", 10)
.attr("y", function (d) {
...
2) Your data is not an array. D3 expects arrays of data to be provided with the .data() method, and you have data = {Value : 22.5} (effectively). I changed it to this:
...
var data = [{'Value' : 22.5}];
...
Updated fiddle is here.

Related

Find location of y-axis ticks using d3.js

I am building a SVG visualisation using d3.js (in PowerBI - so version 3 of d3), and I am struggling aligning my data points and fixed lines with the appropriate y-axis tick marker.
For example, with 8 axis points, the lines are almost right, just slightly above
But when there is only 1 or 2 points, it's way off
I am trying to dynamically calculate the offset as the number of y-axis ticks will depend on the PowerBI filter I have.
My current calculation is that I am taking the height of the svg, dividing it by the number of ticks, and then dividing that by two so it lands in the centre. The y-axis is ordinal.
Relevant code is:
var margin = {top: 20, right: 250, bottom: 50, left: 50},
width = pbi.width - margin.left - margin.right,
height = pbi.height - margin.top - margin.bottom,
legendleft = pbi.width - margin.right;
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], barPad, barOuterPad);
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom + legendleft)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
y.domain(Data.map(function(d) { return d.reportingyear; }));
var YearSize = new Set(yearArray).size // Gets the number of ticks on the y-axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0, 6)")
.call(d3.svg.axis().scale(y).orient("left"));
// Chart the rows
var publishedRow = svg.append("g").attr("id", "publishedgroup").selectAll(null)
.data(rowArray)
.enter();
publishedRow.append("line")
.style("stroke", "grey")
.style("stroke-width", 1)
.style("stroke-dasharray", ("2, 2"))
.attr("x1", function(d) { return 0; })
.attr("y1", function(d) { return y(d.entry)+((pbi.height-margin.top-margin.bottom) / (new Set(yearArray).size) / 2); })
.attr("x2", function(d) { return width; })
.attr("y2", function(d) { return y(d.entry)+((pbi.height-margin.top-margin.bottom) / (new Set(yearArray).size) / 2); });
publishedRow.append("circle")
.attr("cx", function(d) {return x(new Date(d.date))} )
.attr("cy", function(d) {return y(d.year)+((pbi.height-margin.top-margin.bottom) / (new Set(yearArray).size) / 2); })
.attr("r", 7)
.style("fill", function(d, i) { return milestoneMap[d.milestone]; })
});
It is the .attr lines that have the code that dynamically calculates the offset.
Is there an easier way to do this? Or can I get some advice as to why my calculation isn't working?
Thank you!
I should have used
.rangePoints rather than .rangeRoundBands
Then a static offset of 6 worked.
Problem solved

Can't see circles in d3.js

I'm trying to create a scatter plot and while my axes are rendered, I can't see any circles. I can't figure out what's wrong.
I realized that my data values are in string so I used '+' operator. Still doesn't work. There is no error message. I can see the elements in debug window being created but they are not displayed.
Here is my code:
function visualize(data) {
var margin = { top: 20, bottom: 80, right:50, left: 70 };
var width = 720 - margin.left + margin.right, height = 500 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.right + margin.left);
g=svg.append("g")
.attr("transform","translate("+margin.left+","+margin.top+")");
var xScale = d3.scaleLinear().domain(d3.extent(data,function(d) {
return +d.FTAG;
})).range([0, width]);
var yScale = d3.scaleLinear().domain([0,d3.max(data,function(d) { return +d["AF"]; })]).range([height, 0]);
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale);
g.append("g").attr("class", "x-axis").attr("transform", "translate(0," + height + ")").call(xAxis);
g.append("g").attr("class", "y-axis").attr("transform", "translate(0,0)").call(yAxis);
d3.selectAll("dot").data(data)
.enter()
.append("g")
.append("circle")
.attr("r", 20)
.attr("cx",
function(d) {
return xScale(+d.FTAG);
})
.attr("cy",
function(d) {
return yScale(+d.AF);
})
.style("fill",
function(d) {
if (+d.AF >= 20) {
return "red";
} else
return "steelblue";
})
.style("stroke","black")
.style("stroke-width",1.5);
}
In your code...
d3.selectAll("dot")
.data(data)
.enter()
.append("g")
... doesn't append groups anywhere.
It has to be:
svg.selectAll("dot")
.data(data)
.enter()
.append("g")
Since svg is the selection that appends an SVG to the <body>.

Adding labels to both ends of <rect> in a bar chart

I'm using D3 to present some data as a horizontal bar chart. Values will typically range between -10 and +10 on 8 different scales. I have the bars rendering as I want, but I can't work out how to add lables for each of the extreems of the axes.
so far I have:
but I want to achieve something like:
In other words a label for each extreme of each scale.
I have found lots of examples that add data labels to the bars them selves (e.g. the value), but I want to some how force the array of strings to be rendered at the extremes of the container.
At the moment, I am rendering the data from an array, and I have the labels stored in 2 other arrays e.g.
var data = [10, 5, -5, -10, 2, -2, 8, -8];
var leftLabels = ["label 1","label 2", ...];
var rightLabels = ["label 1", "label 2", ...];
Any ideas or links to examples most welcome.
I am not an expert in d3.js, but I think this can be easily done. There are different ways to go about it. I have created a pen for your use case.
I will paste the important part of the code below. In your chart, you will have to certainly make some adjustments to suit your needs. Feel free to play around with the values until you feel they are stable.
// Your array containing labels for left and right values
var leftSideData = ["left1", "left2", "left3", "left4", "left5", "left6", "left7", "left8"];
var rightSideData = ["right1", "right2", "right3", "right4", "right5", "right6", "right7", "right8"];
var left = svg.selectAll(".leftData")
.data(leftSideData)
.enter().append("g")
.attr("class", "leftVal")
.attr("transform", function(d, i) {
return "translate(0," + i * 57 + ")";
});
left.append("text")
.attr("x", 0)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
var right = svg.selectAll(".rightData")
.data(rightSideData)
.enter().append("g")
.attr("class", "rightVal")
.attr("transform", function(d, i) {
return "translate(0," + i * 57 + ")";
});
right.append("text")
.attr("x", width + 30)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
I won't say this is perfect, but I hope you get an idea about how to approach it. All the best!!
It's funny, just by asking the q on SE I find it helps me reformulate the problem.. and then some time later a new try yields a result. Anyone else find that?
I managed to make it work by changing the way the SVG was created. So I now have the following structure:
<SVG>
><g> (one for each bar)
>><text>
>><rect>
>><text>
><other stuff like axies>
It turns out that <text> elements cannot be added to <rect> elements (well they can, be added but they won't render).
the code is:
var data = [10,2,4,-10,...etc...];
var leftLabels = ["left 1","left 1", ...etc...];
var rightLabels = ["right 1","right 2", ...etc...];
//chart dimentions
var margin = { top: 20, right: 30, bottom: 40, left: 30 },
width = 600 - margin.left - margin.right,
barHeight = 30,
height = barHeight * data.length;
//chart bar scaling
var x = d3.scale.linear()
.range([100, width-100]);
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], 0.1);
var chart = d3.select(".chartsvg")
.attr("width", width + margin.left + margin.right)
.attr("height", barHeight * data.length + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain([d3.min(data), d3.max(data)]);
//append a g for each data item
var bar = chart.selectAll(".bar")
.data(data)
.enter()
.append("g");
//in each bar add a rect for the bar chart bar
bar.append("rect")
.attr("class", function (d) { return "bar--" + (d < 0 ? "negative" : "positive"); })
.attr("x", function (d) { return x(Math.min(0, d)); })
.attr("y", function (d, i) { return i* barHeight; })
.attr("width", function (d) { return Math.abs(x(d) - x(0)); })
.attr("height", barHeight-1);
//append the labels to each g using the label data
bar.append("text")
.data(rightLabels)
.attr("x", width)
.attr("y", function (d, i) { return (i * barHeight)+barHeight/2; })
.attr("dy", ".5em")
.attr("fill","steelblue")
.attr("text-anchor","end")
.text(function (d) { return d; });
bar.append("text")
.data(leftLabels)
.attr("x", 0)
.attr("y", function (d, i) { return (i * barHeight) + barHeight / 2; })
.attr("dy", ".5em")
.attr("fill","darkorange")
.attr("text-anchor", "start")
.text(function (d) { return d; });
//then append axis etc...
Formatting: something else to note. It turns out that to color the text in the label you need to use "stroke" and "fill" attributes. These are broadly equiv to the HTML "color" attribute on text.

D3: How to show only top 3 of 6 items in bar chart?

I'm an extreme newbie to D3. I am working with a web map built in LeafletJS and have put a D3 bar chart into a popup that shows the various ethnic groups for a district in Afghanistan. In my real project, I have over 26 ethnic groups in the area and I only want to show the top 3 largest ethnic groups in the bar chart.
I've set up a demo in jsfiddle with only 6 ethnic groups for now.
How do I only show the top 3 largest ethnic groups in my bar chart and hide the rest of the ethnic groups?
Example of my JSON data:
var myData = [{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"Name":"Gulran","Province":"Hirat","Ethnic1":0.19,"Ethnic2":0.32,"Ethnic3":"0.10","Ethnic4":"0.00","Ethnic5":"0.10","Ethnic6":"0.00"},"geometry":{"type":"Polygon","coordinates":[[[60.941162109375,29.897805610155874],[61.92993164062499,31.034108344903512],[63.34716796874999,31.3348710339506],[64.05029296875,30.401306519203583],[64.412841796875,29.735762444449076],[64.09423828125,29.36302703778376],[62.29248046875,29.36302703778376],[60.941162109375,29.897805610155874]]]}},{"type":"Feature","properties":{"Name":"Chahar Burjak","Province":"Nimroz","Ethnic1":0.25,"Ethnic2":0.12,"Ethnic3":0.03,"Ethnic4":0.01,"Ethnic5":"0.00","Ethnic6":"0.00"},"geometry":{"type":"Polygon","coordinates":[[[63.38012695312499,31.3348710339506],[65.06103515625,31.80289258670676],[65.6982421875,31.156408414557],[66.016845703125,30.467614102257855],[65.291748046875,30.164126343161097],[64.22607421875,30.0405664305846],[63.38012695312499,31.3348710339506]]]}}]}];
My D3 code:
var div = $('<div id="chart" style="width: 600px; height: 400px;"><svg></div>')[0];
var popup = L.popup({minWidth: 600}).setContent(div);
layer.bindPopup(popup);
var values = feature.properties;
var data = [
{name:"Ethnic1",value:values["Ethnic1"]},
{name:"Ethnic2",value:values["Ethnic2"]},
{name:"Ethnic3",value:values["Ethnic3"]},
{name:"Ethnic4",value:values["Ethnic4"]},
{name:"Ethnic5",value:values["Ethnic5"]},
{name:"Ethnic6",value:values["Ethnic6"]}
];
var margin = {top: 20, right: 30, bottom: 40, left: 40},
width = 600 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom,
barHeight = height / data.length;
var formatPercent = d3.format(".0%");
var x = d3.scale.linear()
.domain([0, d3.max(data, function(d){return d.value;})])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(formatPercent);
var svg = d3.select(div).select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.classed("chart", true);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var bar = svg.selectAll("g.bar")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect")
.attr("width", function(d){return x(d.value);})
.attr("height", barHeight - 1);
bar.append("text")
.attr("x", function(d) { return x(d.value) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
The way you do it is to first sort the data. Once you have sorted, you can filter the data to use only the top 3 values for creating the bar chart.
I have forked your fiddle. Here is the link https://jsfiddle.net/ankit89/x8u31jdo/
The important piece of code that I added/modifed are
var sortedData = data.sort(function(a,b){
return b.value - a.value;
});
//if you want to just keep top three
sortedData = sortedData.filter(function(d,i){
return i < 3;
});
var bar = svg.selectAll("g.bar")
.data(sortedData)
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
Here is the Working Fiddle for you.
And the code used is as below
function GetTopThreeEthnic(arrayData){ //sorting to top 3 function
arrayData.sort(function(a, b) {
return parseFloat(b.value) - parseFloat(a.value);
});
return arrayData.slice(0, 3);
}
And just make a call to this function after you initialize your data variable.
var data = [
{name:"Ethnic1",value:values["Ethnic1"]},
{name:"Ethnic2",value:values["Ethnic2"]},
{name:"Ethnic3",value:values["Ethnic3"]},
{name:"Ethnic4",value:values["Ethnic4"]},
{name:"Ethnic5",value:values["Ethnic5"]},
{name:"Ethnic6",value:values["Ethnic6"]}
];
data = GetTopThreeEthnic(data); // this is the code added

Getting key and value of object

While I'm trying to access the key and value of an object, it's giving undefined. Below is my code
<script type="text/javascript">
var data;
var xAxisName;
var yAxisName;
var jso;
function getX(d) {
return d[xAxisName];
}
function getY(d) {
return d[yAxisName];
}
d3.json("response.json", function (json) {
console.log("hi");
console.log(json); //getting the values
console.log("this " +json.users); //getting the values
xAxisName = json.attribute1.;
console.log("xAxisName=" + xAxisName); //Not getting the values
yAxisName = json.attribute2;
console.log("yAxisName=" + yAxisName); //Not getting the values
data = json.users;
alert(data);
data.map(function(d) { console.log(getX(d));});
data.map(function(i) {console.log(i);});
visualize(data); //then start the visualization
});
function visualize (data) {
var padding = 40;
var margin = {top:30, right: 30, bottom: 30, left:100};
var w = 700 - margin.left - margin.right;
var h = 400 - margin.top - margin.bottom;
//the svg
var svg = d3.select("#container")
.append("svg")
.attr("class", "chart")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//the scales
var xScale = d3.scale.ordinal()
.domain(d3.range(data.length))
.rangeRoundBands([0, w], 0.04);
var yScale = d3.scale.linear()
.domain([d3.max(data, getY), 0])
.range([0, h]);
//the axes
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
var yAxis = d3.svg.axis().scale(yScale).orient("left");
//add the data and bars
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d, i) { return xScale(i);})
.attr("y", function(d) { return yScale(getY(d));})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return h - yScale(getY(d));})
.attr("class", "bar");
//create axes
svg.append("g").attr("class", "x axis")
.attr("transform", "translate(0," + h + ")").call(xAxis);
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(yAxisName);
}
alert("done");
</script>
It's giving undefined for the xAxisName and yAxisName. In svg.selectAll("rect") y and height giving NaN.
My JSON is
{
"users": [
{
"name": "warchicken",
"score": 30
},
{
"name": "daydreamt",
"score": 100
},
{
"name": "Anas2001",
"score": 30
},
{
"name": "ocjojo",
"score": 30
},
{
"name": "joklawitter",
"score": 30
}
]
}
It looks likes you want to extract property names from the user objects. To do that, you can either use Object.keys() or iterate over the object with for...in (related question: How do I enumerate the properties of a JavaScript object?).
var keys = Object.keys(json.users[0]);
xAxisName = keys[0];
yAxisName = keys[1];
Beware though that object properties are not ordered. You might end up with xAxisName being "score" and vice versa.
If you need xAxisName to be a certain value, you either have to hardcode it, or add the information to the JSON you return from the server. For example:
{
"axes": ["name", "score"],
"users": [...]
}
Then you get it with
xAxisName = json.axes[0];
// ...
Side note: Choosing json as variables name for an object is not optimal because it suggests that the variables holds a string containing JSON, while it actually holds an object. How about chartData instead?

Categories

Resources