I've tried to make a simple line chart.
The x axis will show 10,100,1000 etc' values
For some reason I get all the values stacked on the left side of the x axis instead of spreading them equally on the axis.
var data = [
{views:10, odds: 56},
{views:100, odds: 64},
{views:1000, odds: 81},
{views:10000, odds: 95},
{views:100000, odds: 99},
{views:1000000, odds: 99},
{views:10000000, odds: 100},
];
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Set the ranges
var x = d3.scale.linear()
.domain([
d3.min(data, function(d) { return d.views; }),
d3.max(data, function(d) { return d.views; })
])
.range([0, width]);
var y = d3.scale.linear()
.domain([
d3.min(data, function(d) { return d.odds; }),
d3.max(data, function(d) { return d.odds; })
])
.range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(7)
.tickValues(data.map((d)=>{ return d.views; }));
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(7);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.views); })
.y(function(d) { return y(d.odds); });
// Adds the svg canvas
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 + ")");
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
https://jsfiddle.net/guy_l/77agq0hz/
This is the expected behaviour. They are not "stacked" on the left side, it's just a math problem: each value of x is just 10% of the next value! Keep in mind that your domain goes from 10 to 10 million, so the points would never be equally spread: 90% of your domain is just the space between the 6th and the 7th point.
You can change the scale for an ordinal one or, if you want to keep it quantitative, you need a logarithmic scale here:
d3.scale.log();
Check your updated fiddle: https://jsfiddle.net/gerardofurtado/v17cpqdk/
Related
On a horizontal bar graph with positive and negative values, where the y axis would sit somewhere mid page, how would we move the y axis labels to the far left side. Taking this jsfiddle as an example, can we move foo, bar, etc. away from the y axis to the far left side of the graph?
https://jsfiddle.net/Kavitha_2817/fmr1x1gu/18/
var margin = {top: 30, right: 10, bottom: 50, left: 50},
width = 500,
height = 300;
var data = [{value: -10, dataset:"barbaz"},
{value: 40, dataset:"barbar"},
{value: -10, dataset:"foobaz"},
{value: -50, dataset:"foobar"},
{value: 30, dataset:"baz"},
{value: -20, dataset:"bar"},
{value: -70, dataset:"foo"}];
// Add svg to
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 + ')');
// set the ranges
var y = d3.scaleBand()
.range([height, 0])
.padding(0.1);
var x = d3.scaleLinear()
.range([0, width]);
// Scale the range of the data in the domains
x.domain(d3.extent(data, function (d) {
return d.value;
}));
y.domain(data.map(function (d) {
return d.dataset;
}));
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function (d) {
return "bar bar--" + (d.value < 0 ? "negative" : "positive");
})
.attr("x", function (d) {
return x(Math.min(0, d.value));
})
.attr("y", function (d) {
return y(d.dataset);
})
.attr("width", function (d) {
return Math.abs(x(d.value) - x(0));
})
.attr("height", y.bandwidth());
// add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the y Axis
svg.append("g")
.attr("class", "y axis")
You can achieve that by moving the ticks manually:
// add the y Axis
let yAxisGroup = svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + x(0) + ",0)")
.call(d3.axisRight(y));
yAxisGroup.selectAll('.tick')
.data(data)
.select('text')
.attr('x', function(d,i){return d.value<0?9:-9})
.style('text-anchor', function(d,i){return d.value<0?'start':'end'})
Updated jsfiddle
i have some values in my csv file and i show a graph with values on y axis and dates on x axis.
For first graph i have following values
date,close
13-Jul-16,0.8736701869033555
15-Jul-16,0.3631761567983922
17-Jul-16,0.4795564555162078
19-Jul-16,0.3754827857186281
21-Jul-16,0.4355941951068847
23-Jul-16,0.34393804366457353
25-Jul-16,0.40967947088135176
27-Jul-16,0.2707818657230363
29-Jul-16,0.34430251610420176
31-Jul-16,0.28089496856221585
For second graph i have following values
date,close
11-Jul-16,0.766705419439816
15-Jul-16,0.7353651170975812
17-Jul-16,0.41531502169603063
19-Jul-16,0.5927871032351933
21-Jul-16,0.7986419920511857
23-Jul-16,0.7904979990272231
25-Jul-16,0.817690401573838
27-Jul-16,0.8433545168648027
29-Jul-16,0.8612307965742473
31-Jul-16,0.806498303188971
But in second graph x axis does not contain all dates.. As an example i put a printscreen of my output graphs myoutput to here.
This is my code which takes datas from csv file and visualize it.
var selectedMonth=document.getElementById('selectedMonth').value;
var selectedTopic=document.getElementById('selectedTopic').value;
var userFileDirectory="../documents/";
userFileDirectory=userFileDirectory+selectedMonth+"/"+selectedTopic+"/"+"dataCs.csv";
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// Adds the svg canvas
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 + ")");
// Get the data
d3.csv(userFileDirectory, function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("text")
.text("("+selectedMonth+" "+selectedTopic+")");
});
I would try setting the tick values explicitly, using tickValues:
https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickValues
ticks(5) will suggest 5 ticks, but will be adapted based on the scale's domain. Alternative to tickValues(), you could try ticks(d3.time.day, 2) to have a tick every 2 days.
I try to make a bar chart on the basis of 2D data array (I din`t want to use 2D array initially, so there is a function "mergingAr", which merges them) using d3.js. Here is the code:
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
var arr1 = [399200,100000, 352108, 600150, 39000, 17005, 4278];
var arr2 = [839, 149, 146, 200, 200, 121, 63];
function mergingAr (array1, array2)
{
var i, out = [];//literal new array
for(i=0;i<array1.length;i++)
{
out.push([array1[i],array2[i]]);
}
return out;
}
var data = mergingAr(arr1, arr2);
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d[0]; })])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 + ")")
.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")
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d[0]); })
//.attr("width", x.rangeBand())
.attr("width", width/a1.length)
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return height - y(d[1]); });
Te problem is - the bars cover each other, there are no distance between them, even if I used rangeRoundBands.
There are 2 issues in your code.
The first one is that the data array is not sorted. In order to sort it you can do:
out = out.sort(function(a,b) { return d3.ascending(a[0],b[0]) })
before returning out in your mergeAt function. Sorting the array makes sure that you process bars in the right order.
The second issue is that your intervals are not equal. To remediate to this, I made the width of a block equal to the distance to the next one (but you might want to do something different):
.attr("width", function(d,i){
if(i!=(data.length-1)) {
return x(data[i+1][0])-x(data[i][0])
} else {
return 10; // the last block is of width 10. a cleaner way is to add a
// marker at the end of the array to know where to finish
// the axis
}
})
JsFiddle: http://jsfiddle.net/chrisJamesC/6WJPA/
Edit
In order to have the same interval between each bar and the same width, you have to change the scale to an ordinal one:
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1)
.domain(data.map(function(d){return d[0]}))
Then, you need to change the way you compute the width to:
.attr("width", x.rangeBand())
JsFiddle: http://jsfiddle.net/chrisJamesC/6WJPA/2/
I am trying to use the hexbin layout with data that is normally distributed around 0 - all the examples use data centered around the center of the screen, so the scales are the same as the screen scales (except for y inversion)
I've tried to modify the scale functions to account for possible negative values. It works for the y-scale, but the x-scale gives NaNs, and the hexagons are plotted off the screen upper left. That is not the only problem - I would like to programmatically determine the bin size for the hexbin function - in my data series, all of the values are 'binned' into only one to three hexagons, and I need them spread out over the available domain.. here is my code
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.hexbin.v0.min.js?5c6e4f0"></script>
<script>
minMultArray =function(arr,index){
var min = arr[0][index];
for (i=0;i<arr.length;i++) {
min = (min>arr[i][index]?arr[i][index]:min);
}
return min;
};
maxMultArray =function(arr,index){
var max = arr[0][index];
for (i=0;i<arr.length;i++) {
max = (max< arr[i][index]?arr[i][index]:max);
}
return max;
};
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var randomX = d3.random.normal(0, 5),
randomY = d3.random.normal(0, 6),
points = d3.range(2000).map(function() { return [randomX(), randomY()]; });
var minX = minMultArray(points,0);
var minY = minMultArray(points,1);
//var minZ = minMultArray(points,2);
var maxX = maxMultArray(points,0);
var maxY = maxMultArray(points,1);
//var maxZ = maxMultArray(points,2);
var color = d3.scale.linear()
.domain([0, 20])
.range(["white", "steelblue"])
.interpolate(d3.interpolateLab);
var hexbin = d3.hexbin()
.size([width, height])
.radius(20);
alert('minX='+minX +' maxX='+maxX);
var x = d3.scale.linear()
.domain([minX, maxX])
.range(0,width);
alert('xScale(3)='+x(3));
var y = d3.scale.linear()
.domain([minY, maxY])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(6, -height);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(6, -width);
console.log('hex = ' +hexbin(points));
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("clipPath")
.attr("id", "clip")
.append("rect")
.attr("class", "mesh")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("clip-path", "url(#clip)")
.selectAll(".hexagon")
.data(hexbin(points))
.enter().append("path")
.attr("class", "hexagon")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) { return "translate(" + (d.x) + "," + (d.y) + ")"; })
.style("fill", function(d) { return color(d.length); });
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
</script>
After more debugging the hexbin functions, they are not compatible with negative and/or fractional domains- so I solved this by mapping my original data by linear scales up to the height and width of the hexagon plots. Then bin size is controlled by radius. I also modified the hexbin binning function to handle three element arrays, and can compute stats on the third element, using color or size to show mean/median/stddev/max/min. If interested, I can post the code on github...
I am trying to use the margin conventions described in http://bl.ocks.org/mbostock/3019563
when plotting bar charts. However, the bars do not align with the x-axis as you can see
in this basic example: http://bl.ocks.org/kyrre/bbd29f225173825797e3. What am I doing wrong?
var data = [
{x: "Differential Geometry", y: 10},
{x: "Statistical Physics", y: 5},
{x: "Music", y: 3}
]
var margin = {top: 20, right: 10, bottom: 20, left: 50};
var width = 500 - margin.left - margin.right;
var height = 320 - margin.top - margin.bottom;
var y = d3.scale.linear()
.domain([0, d3.max(data, function(x) {
return x.y;
})])
.range([0, height]);
var x = d3.scale.ordinal()
.domain(_.map(data, function(d) { return d.x;}))
.rangeRoundBands([0, width], 0.10);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 rect = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", 0)
.attr("height", function(d) {
return y(d.y);
})
.attr("fill", function(d) { return "blue";})
.attr("width", 20);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0,0)")
.call(yAxis);
The y coordinates are counted from the top (i.e. 0 is at the top of the image). It should work if you set y to the total minus height.
.attr("y", function(d) { return (height - y(d.y)); })