D3 the x axis positions in wrong way - javascript

I have run into problem of displaying the chart X axis which is translated in wrong position. Here is my example http://jsfiddle.net/staar2/Vww3h/1/.
As you can look from the html inspector the svg elements get translated wrong. What I think the xScale are set wrong.
var data = JSON.parse('[{"hour":0,"time":147},{"hour":1,"time":0},{"hour":2,"time":74},{"hour":3,"time":141},{"hour":4,"time":137},{"hour":5,"time":210},{"hour":6,"time":71},{"hour":7,"time":73},{"hour":8,"time":0},{"hour":9,"time":68},{"hour":10,"time":70},{"hour":11,"time":0},{"hour":12,"time":147},{"hour":13,"time":0},{"hour":14,"time":0},{"hour":15,"time":69},{"hour":16,"time":67},{"hour":17,"time":67},{"hour":18,"time":66},{"hour":19,"time":0},{"hour":20,"time":0},{"hour":21,"time":66},{"hour":22,"time":210},{"hour":23,"time":0}] ');
var w = 15,
h = 80;
var xScale = d3.scale.linear()
.domain([0, 1])
.range([0, w]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function (d) {
return d.time;
})])
.rangeRound([5, h]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var chart = d3.select("#viz")
.append("svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d, i) {
return xScale(i) - 0.5;
})
.attr("y", function(d) {
return h - yScale(d.time) - 0.5;
})
.attr("width", w)
.attr("height", function(d) {
return yScale(d.time);
});
chart.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
if (d.time > 10) {
return Math.round(d.time);
}
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "#FFF")
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + w / 2;
})
.attr("y", function(d) {
return h - yScale(d.time) - 0.5 + 10;
});
chart.append("g")
.attr("transform", "translate(0," + (h) + ")")
.call(xAxis);

You need to change your xScale domain and range as well accordingly, as
xScale
.domain([0, data.length-1])
.range([0, w*(data.length-1)])
while you call xScale(i) where i is the index of data element, as far as i understood, and so the domain should be between 0 - data.length.

Related

How to scale y-Axis in d3.js according to data input correctly

I created a small Barchart. My problem is that the y-axis doesn't scale according to my dataset. Here is a screenshot:
So as you can see the 313 is a little bit above the 800 scale. I would like the 300 to be at the 300. I tweaked with the scalings but I just end up messing it up completely. I am very new to D3.js so I hope someone can help.
Here is my code:
var svgWidth = 1000, svgHeight = 800;
var barWidth = svgWidth / month_description.length;
var barPadding = 5;
var svg = d3.select('svg')
.attr("width", svgWidth)
.attr("height", svgHeight);
var barChart = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("y", function(d) {
return svgHeight - d - 20
})
.attr("x", function(d, i) {
return i + 10;
})
.attr("height", function(d) {
return d;
})
.attr("width", barWidth - barPadding)
.attr("class", "bar")
.attr("transform", function (d, i) {
var translate = [barWidth * i, 0];
return "translate("+ translate +")";
});
var text = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("y", function(d, i) {
return svgHeight - 20;
})
.attr("x", function(d, i) {
return barWidth * i + 35;
})
.attr("fill", "white");
var xScale = d3.scaleLinear()
.domain([0, d3.max(month_description)])
.range([0, svgWidth]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([svgHeight, 0]);
var x_axis = d3.axisBottom()
.scale(xScale);
var y_axis = d3.axisLeft()
.scale(yScale);
svg.append("g")
.attr("transform", "translate(50, 10)")
.call(y_axis);
var xAxisTranslate = svgHeight - 20;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate +")")
.call(x_axis);
Help is very much appreciated. Thanks very much in advance!!

d3 bar chart with custom x axis and bar width

I want to create a bar chart with custom bar width I tried following code but not aware if its the right way to do.
Also I want to update the bar chart with new data how can I do it?
TO update I tried - https://jsfiddle.net/eqr8deef/
var margin = {
top: 25,
right: 40,
bottom: 35,
left: 85
},
w = 500 - margin.left - margin.right,
h = 350 - margin.top - margin.bottom;
var padding = 10;
var colors = {
0: ["Local", "#377EB8"],
1: ["Global", "#4DAF4A"]
};
var dataset = [{
"global": 1468604556084,
"local": 100,
}, {
"local": 11500,
"global": 1313048950629
}, {
"local": 11500,
"global": 1213048950629
}, {
"local": 11500,
"global": 1113048950629
}, {
"local": 11500,
"global": 1123048950629
}, {
"local": 11500,
"global": 1013048950629
}];
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.01);
// ternary operator to determine if global or local has a larger scale
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d.local;
})])
.range([h, 0]);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
var commaFormat = d3.format(',');
//SVG element
var svg = d3.select("#searchVolume")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Graph Bars
var sets = svg.selectAll(".set")
.data(dataset)
.enter()
.append("g")
.attr("class", "set")
.attr("transform", function(d, i) {
return "translate(" + xScale(i) + ",0)";
});
sets.append("rect")
.attr("class", "global")
.attr("width", 20)
.attr("y", function(d) {
return yScale(d.local);
})
.attr("height", function(d) {
return h - yScale(d.local);
})
.attr("fill", colors[1][1])
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "red");
// yAxis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0 ,0)")
.call(yAxis);
var yTextPadding = 20;
svg.selectAll(".bartext")
.data(dataset)
.enter()
.append("text")
.attr("class", "bartext")
.attr("text-anchor", "middle")
.attr("fill", "black")
.attr("x", function(d,i) {
console.log(i, xScale(i))
return xScale(i) + 10;
})
.attr("y", function(d,i) {
return h + 15;
})
.text(function(d){
return new Date(d.global).getFullYear();
});
// xAxis label
http://jsfiddle.net/pq0xrard/
To answer your question step by step -
rangeRoundBands is used to evenly space your bars. But if you want to have custom width then you can not use it like the way you are using it.
to update the data you can simply use enter-update-exit methods as shown below.
var update_sel = svg.selectAll("circle").data(data)
update_sel.attr(/* operate on old elements only */)
update_sel.enter().append("circle").attr(/* operate on new elements
only */)
update_sel.attr(/* operate on old and new elements */)
update_sel.exit().remove() /* complete the enter-update-exit pattern
*/
Here is a complete example - https://jsfiddle.net/seej4dfd/
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
function draw(data) {
x.domain(data.map(function(d) {
return d.letter;
}));
y.domain([0, d3.max(data, function(d) {
return d.frequency;
})]);
var labels = svg
.selectAll(".topLabel")
.data(data, function(d) {
return d.letter;
});
labels
.exit()
.remove();
labels
.enter()
.append("text")
.attr("class", "topLabel")
.attr("text-anchor", "middle")
.attr("fill", "black")
labels
.attr("x", function(d, i) {
return x(d.letter) + 7.5;
})
.attr("y", function(d, i) {
return y(d.frequency);
})
.text(function(d, i) {
return d.letter;
});
var labels = svg
.selectAll(".bartext")
.data(data, function(d) {
return d.letter;
});
labels
.exit()
.remove();
labels
.enter()
.append("text")
.attr("class", "bartext")
.attr("text-anchor", "middle")
.attr("fill", "black");
labels
.attr("x", function(d, i) {
return x(d.letter) + 7.5;
})
.attr("y", function(d, i) {
return height + 15;
})
.text(function(d, i) {
return d.letter;
});
svg.select(".y.axis").transition().duration(300).call(yAxis)
var bars = svg.selectAll(".bar").data(data, function(d) {
return d.letter;
})
bars.exit()
.transition()
.duration(300)
.remove();
bars.enter().append("rect")
.attr("class", "bar");
bars.transition().duration(300).attr("x", function(d) {
return x(d.letter);
})
.attr("width", 15)
.attr("y", function(d) {
return y(d.frequency);
})
.attr("height", function(d) {
return height - y(d.frequency);
});
}
To change the width, use the xScale.rangeBand() for setting the width of your rect on line 73.
http://jsfiddle.net/073u0ump/3/

refactor d3 bar chart to be horizontal bar chart

I was trying to refactor this bar chart in d3 to be a horizontal bar chart. I think I've isolated the three areas that need to be changed:
the original scale declarations:
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
The domain definitions:
x0.domain(data.map(function(d) { return d.deviceType; }));
x1.domain(deviceClass).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) { return d3.max(d.whenPurchased, function(d) { return d.value; }); })]);
the rect appending and positioning:
device.selectAll("rect")
.data(function(d) { return d.whenPurchased; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.device); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.device); });
I can't seem to get the refactoring right and I think that is because I don't exactly get how domain works for x0 and basically not getting that right has a waterfall effect of everything else not working.
I put the full example here: JSFIDDLE
Here's a quick refactor:
var margin = {top: 20, right: 20, bottom: 30, left: 270},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var y0 = d3.scale.ordinal()
.rangeRoundBands([0, height], 0.1);
var y1 = d3.scale.ordinal();
var x = d3.scale.linear()
.range([0, width]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y0)
.orient("left");
// .tickFormat(d3.format(".2s"));
var svg = d3.select("#deviceown-barchart").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 data = d3.csv.parse( d3.select("pre#data").text() );
var deviceClass = d3.keys(data[0]).filter(function(key) { return key !== "deviceType"; });
data.forEach(function(d) {
d.whenPurchased = deviceClass.map(function(device) { return {device: device, value: +d[device]}; });
});
y0.domain(data.map(function(d) { return d.deviceType; }));
y1.domain(deviceClass).rangeRoundBands([0, y0.rangeBand()]);
x.domain([0, d3.max(data, function(d) { return d3.max(d.whenPurchased, function(d) { return d.value; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.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");
var device = svg.selectAll(".device")
.data(data)
.enter().append("g")
.attr("class", "device")
.attr("transform", function(d) { return "translate(0," + y0(d.deviceType) + ")"; });
device.selectAll("rect")
.data(function(d) { return d.whenPurchased; })
.enter().append("rect")
.attr("height", y1.rangeBand())
.attr("y", function(d) { return y1(d.device); })
.attr("x", 0)
.style("fill", function(d) { return color(d.device); })
.attr("width", 0)
.transition()
.duration(1500)
.attr("width", function(d) { return x(d.value); });
var legend = svg.selectAll(".legend")
.data(deviceClass.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
// });
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.y.axis path {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="deviceown-barchart"></div>
<pre id="data">
deviceType,Within the last 6 months,More than 6 months ago
Netatmo Weather Station,0.14,0.09
Nest Learning Thermostat,0.13,0.14
Philips Hue Connected Bulb,0.13,0.08
Nest Cam,0.12,0.12
Belkin WeMo Switch + Motion,0.09,0.08
Nest Protect,0.08,0.12
Canary,0.08,0.03
Other smart home security device,0.08,0.12
August Smart Lock,0.06,0.07
Other connected home appliance,0.05,0.08
GE/Quirky Aros Smart AC,0.04,0.04
Other smart energy monitor,0,0.03
</pre>
Updated fiddle.

How to improve this data-visualisation?

I have a d3.js plot that I want to improve it but I can't figure out how to do it!
This is my plot:
Mainly I am trying to change axis and add a little legend so I can get something like this (with x and y zeros centered in the plot ):
This is how I define x and y axis in my d3.js/JavaScript code
var xScale = d3.scale.linear().domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })]).range([padding, w - padding]);
var yScale = d3.scale.linear().domain([d3.min(dataset, function(d) { return d[1]; }), d3.max(dataset, function(d) { return d[1]; })]).range([h - padding, padding]);
// Create axis
var xAxis = d3.svg.axis().scale(xScale).orient("bottom").ticks(5);
//Define Y axis
var yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(5);
//Create SVG element
var svg = d3.select("#mydiv").append("svg").attr("width", w).attr("height", h);
svg.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
svg.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
Thanks in advance:
If you provide your actual dataset I can give you more exact code for your case, without it the best we can really do is give you examples from the docs.
How to create a legend:
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
How to hardcode max/min on your axes:
var yScale = d3.scale.linear().domain([0,6]).range([h - padding, padding]);
How to add gridlines:
var yAxisGrid = yAxis
.tickSize(width, 0)
.tickFormat("")
.orient("right")
var xAxisGrid = xAxis
.tickSize(-height, 0)
.tickFormat("")
.orient("top")
svg.append("g")
.classed('y', true)
.classed('axis', true)
.call(yAxisGrid)
svg.append("g")
.classed('x', true)
.classed('axis', true)
.call(xAxisGrid)

D3 spaces between groups

How do I add some padding between groups in a grouped bar chart?
I have 2 groups and 5 categories on the x axis. Im trying to get some padding between the pairs ([group1, group2,] space, [group1, group2], etc.) I tried with rangeBand(), with the scale, etc. but its not working.
Any suggestions?
jsfiddle
Relevant samples of the code:
var values = feature.properties;
var data = [
{name:"NoDipOL",value:values["NoDipOL"]},
{name:"NoDipNOL",value:values["NoDipNOL"]},
{name:"HSOL",value:values["HSOL"]},
{name:"HSNOL",value:values["HSNOL"]},
{name:"ColOL",value:values["ColOL"]},
{name:"ColNOL",value:values["ColNOL"]},
{name:"UnOL",value:values["UnOL"]},
{name:"UnNOL",value:values["UnNOL"]}
];
var Colors = ["#a6cee3", "#1f78b4"];
var ColorNames = ["Group1", "Group2"];
[...]
// Scale for x and y axis
var x = d3.scale.ordinal()
.domain(["No diploma", "High school", "College", "University"])
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d){return d.value;})])
.range([height, 0]);
//Ordinal x axis.
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.outerTickSize(0);
[...]
bar.append("rect")
.attr("x", function(d, i) { return i * barWidth;})
.attr("width", barWidth - 1 )
.attr("y", function(d) {return y(d.value); })
.attr("height", function(d) {return height - y(d.value);})
.attr("fill", function(d, i) { return Colors[i % 2]; }); //Alternate colors
bar.append("text")
.attr("class", "text")
.text(function(d) { return d.value; })
.attr("y", function(d) {return y(d.value) - 5; })
.attr("x", function(d, i) { return i * barWidth;})
.attr("dx", barWidth / 2)
.attr("fill", "black")
.attr("font-family", "sans-serif")
.attr("font-size", "14px")
.attr("text-anchor", "middle");
You use the rangeBand to place your ticks but you don't use it to size and place your rects. First fix the data so it can use the rangeBand:
var data = [
{name:"No diploma",value:values["NoDipOL"]},
{name:"No diploma",value:values["NoDipNOL"]},
{name:"High school",value:values["HSOL"]},
{name:"High school",value:values["HSNOL"]},
{name:"College",value:values["ColOL"]},
{name:"College",value:values["ColNOL"]},
{name:"University",value:values["UnOL"]},
{name:"University",value:values["UnNOL"]}
];
Then place your rects:
bar.append("rect")
.attr("x", function(d, i) {
return (i % 2 === 0) ? // every other bar
x(d.name) : // if even place at start of band
x(d.name) + x.rangeBand()/2; // if odd (second bar) move it over
})
.attr("width", x.rangeBand()/2 ) // and width is half the rangeband
...
Updated example.

Categories