I'm learning d3js and I need help in creating line charts. I'm fairly successful plotting simple objects and bar charts. Line charts seems to be a steep hill to climb.
const data = [{
"LINE1": [
10,
11,
12,
15
]
},
{
"LINE2": [
21,
22,
23,
32
]
},
{
"LINE3": [
11,
12,
13,
15
]
}
]
// set the dimensions and margins of the graph
var margin = {
top: 50,
right: 100,
bottom: 130,
left: 120
},
width = 900 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#ca")
.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 X axis
var x = d3.scaleLinear()
.domain(d3.extent(data, (d) => d.length))
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
// Add Y axis
// I need help in this area, how can I get the min and max values set in the domain?
var y = d3.scaleLinear()
.domain([0, d3.max(data, (d) => d.values)])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Draw the line
// I need help in this area, how can I get the lines plotted, js gives error in this!
svg.selectAll(".line")
.data(data)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.attr("d", (d) => {
console.log(d)
var k = d3.line()
.x((d) => x(d))
.y((d) => y(d))
(d.values);
console.log(k);
return k;
});
<div id="ca">
</div>
<script src="https://d3js.org/d3.v6.min.js"></script>
How can I get the line charts plotted with data format I have?
I've made your example work, but there are several things you need to know here:
Your data structure was very chaotic. If you want to have an array of objects as data, make sure all objects have the same keys. It's fine if you use [{y: [...]}, {y: [...]}], but [{LINE1: [...]}, {LINE2: [...]}] is very difficult to work with. I changed your data to be more like [[...], [...], [...]] as a structure.
Don't create a separate d3.line for every line, just create it once and call it. It's a line factory, which means it is a function that, when called, returns a line. If it's not shared, it might use different domains and ranges, making the chart difficult or even useless.
If the first argument in a function is d, the second is i, the index of the node in the array. In this case, you use that to start at x=0, and go to x=3. You used d to try to get that value.
Keep in mind the structure of your data. You kept wanting to access d.values, but that never existed!
const data = Object.values({
"LINE1": [
10,
11,
12,
15
],
"LINE2": [
21,
22,
23,
32
],
"LINE3": [
11,
12,
13,
15
]
});
var line = d3.line()
.x((d, i) => x(i))
.y((d) => y(d));
// set the dimensions and margins of the graph
var margin = {
top: 50,
right: 100,
bottom: 130,
left: 120
},
width = 900 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#ca")
.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 X axis
var x = d3.scaleLinear()
.domain([0, d3.max(data, (d) => d.length)])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
// Add Y axis
// I need help in this area, how can I get the min and max values set in the domain?
var y = d3.scaleLinear()
.domain([0, d3.max(data, (d) => Math.max(...d))])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Draw the line
// I need help in this area, how can I get the lines plotted, js gives error in this!
svg.selectAll(".line")
.data(data)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.attr("d", (d) => line(d));
<div id="ca">
</div>
<script src="https://d3js.org/d3.v6.min.js"></script>
Related
I am working on this project with D3js and I have come across a problem now.
When I have more data, the bars of my barchart will append correctly in the same line as the name but when I fetch less data from my database, the bars will "loose control" and append higher than their name and causing a bad view of my chart.
Here's a picture of what I'll have if I load more data do it.
And here's my second picture of the chart if I load less data.
I don't really understand what I am missing here but I believe is something with the height of the Y-axis and the bars y-position. Can you please help me sort this out?
Here is my code:
var margin = { top: 20, right: 30, bottom: 40, left: 90 },
width = 360 - margin.left - margin.right,
height = 300 - 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 + ")");
// Parse the Data
var data2 = d3.json("/Events/BarChart/4").then(function (data) {
console.log(data);
// Add X axis
var x = d3.scaleLinear()
.domain([0, 5])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
;
// Y axis
var y = d3.scaleBand()
.range([0, height])
.domain(data.map(function (d) { return d.name; }))
svg.append("g")
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", 4)
.attr("y", function (d) { return y(d.name) + 10; })
.attr("width", function (d) { return x(d.value); })
.attr("height", 20)
.attr("fill", function (d) {
if (d.value > 1) {
return "rgb(51, 80, 92)";
}
else if (d.value > 1 && d.value < 4) {
return "rgb(118, 161, 179)"
}
else {
return "rgb(171, 209, 224)";
}
})
})
The issue arises because you manually assign each rectangle a height of 20 pixels, but you give the scale a range of 0 - 240 (the value of height). The scale will divide the range into equal segments (bands), one for each value in its domain. When you have only two values in the domain they will have bands of 120 px each (reduced if there is padding). Nowhere does the scale "know" you have assigned a height of just 20 px for each bar; afterall, you told it to spread values evenly over a range of 0 - 240. These conflicting instructions are why your bars aren't aligned with your axis.
When using d3 scales you will find it much easier if you use the scale for both axis and drawing the data itself (rects/circles/etc): this way they will always be aligned.
The d3 band scale offers a convenient method: scale.bandwidth(), this returns the length/width/height of a band in the scale: at its simplest (without padding) it is the size of the range divided by how many distinct values are in the domain. We can use this value to set bar height:
var margin = { top: 20, right: 30, bottom: 40, left: 90 },
width = 360 - margin.left - margin.right,
height = 300 - 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 + ")");
var data = [
{name: "a", value: 1},
{name: "b", value: 2}
]
// Add X axis
var x = d3.scaleLinear()
.domain([0, 5])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
;
// Y axis
var y = d3.scaleBand()
.range([0, height])
.domain(data.map(function (d) { return d.name; }))
svg.append("g")
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", 4)
.attr("y", function (d) { return y(d.name); })
.attr("width", function (d) { return x(d.value); })
.attr("height", y.bandwidth())
.attr("fill", function (d) {
if (d.value > 1) {
return "rgb(51, 80, 92)";
}
else if (d.value > 1 && d.value < 4) {
return "rgb(118, 161, 179)"
}
else {
return "rgb(171, 209, 224)";
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="my_dataviz"></div>
I also noticed that you add 10 pixels to the y value of each bar: this was probably to manually align the bars better with multiple data entries. Generally this will cause problems (unless manually correcting for them): scale(value) and scale.bandwidth() for y/x and height/width respectively produces bars centered on axis ticks. If you want padding (space between the bars), it is simplest to set that using the scale: scale.padding(number) where number is a value between 0 and 1 representing the portion of each segment that is empty:
var margin = { top: 20, right: 30, bottom: 40, left: 90 },
width = 360 - margin.left - margin.right,
height = 300 - 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 + ")");
var data = [
{name: "a", value: 1},
{name: "b", value: 2}
]
// Add X axis
var x = d3.scaleLinear()
.domain([0, 5])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
;
// Y axis
var y = d3.scaleBand()
.range([0, height])
.padding(0.1)
.domain(data.map(function (d) { return d.name; }))
svg.append("g")
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", 4)
.attr("y", function (d) { return y(d.name); })
.attr("width", function (d) { return x(d.value); })
.attr("height", y.bandwidth())
.attr("fill", function (d) {
if (d.value > 1) {
return "rgb(51, 80, 92)";
}
else if (d.value > 1 && d.value < 4) {
return "rgb(118, 161, 179)"
}
else {
return "rgb(171, 209, 224)";
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="my_dataviz"></div>
But what if you don't want 120 px wide segments? You want your bars to be always 20-ish pixels, regardless of how many bars you have. Well we can modify the range of the scale to reflect the length of the domain:
var margin = { top: 20, right: 30, bottom: 40, left: 90 },
width = 360 - margin.left - margin.right,
height = 300 - 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 + ")");
var data = [
{name: "a", value: 1},
{name: "b", value: 2}
]
// Add X axis
var x = d3.scaleLinear()
.domain([0, 5])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + data.length*20 + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
;
// Y axis
var y = d3.scaleBand()
.domain(data.map(function (d) { return d.name; }))
.range([0, data.length*20])
.padding(0.1);
svg.append("g")
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", 4)
.attr("y", function (d) { return y(d.name); })
.attr("width", function (d) { return x(d.value); })
.attr("height", y.bandwidth())
.attr("fill", function (d) {
if (d.value > 1) {
return "rgb(51, 80, 92)";
}
else if (d.value > 1 && d.value < 4) {
return "rgb(118, 161, 179)"
}
else {
return "rgb(171, 209, 224)";
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="my_dataviz"></div>
I also updated the transform for the x axis, you could go further an adjust svg height to be better sized as well
This question already has answers here:
Why my D3 line graphs shows black areas for each entity?
(1 answer)
Unclosed SVG path appears to be closed
(2 answers)
D3 line acting as a closed path
(2 answers)
Closed 2 years ago.
I would like to know what causes this, and I also want to insert another line to the chart, what is the right way to do that? I know how to update data, but don't know how to make multiple lines,
any help is appreciated, thank you!
D3.js is a JavaScript library for producing dynamic, interactive data visualizations in web browsers. It makes use of Scalable Vector Graphics, HTML5, and Cascading Style Sheets standards. It is the successor to the earlier Protovis framework.
const data = [{
name: "A",
x: 10,
},
{
name: "B",
x: 22,
},
{
name: "C",
x: 33,
},
{
name: "D",
x: 20,
},
{
name: "E",
x: 21,
},
];
//No.1 define the svg
let graphWidth = 600,
graphHeight = 450;
let margin = {
top: 30,
right: 10,
bottom: 30,
left: 85
};
let totalWidth = graphWidth + margin.left + margin.right,
totalHeight = graphHeight + margin.top + margin.bottom;
let svg = d3
.select("body")
.append("svg")
.attr("width", totalWidth)
.attr("height", totalHeight);
//No.2 define mainGraph
let mainGraph = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//No.3 define axises
let categoriesNames = data.map((d) => d.name);
let xScale = d3
.scalePoint()
.domain(categoriesNames)
.range([0, graphWidth]); // scalepoint make the axis starts with value compared with scaleBand
var yScale = d3
.scaleLinear()
.range([graphHeight, 0])
.domain([0, d3.max(data, (data) => data.x)]); //* If an arrow function is simply returning a single line of code, you can omit the statement brackets and the return keyword
//No.4 set axises
mainGraph
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + graphHeight + ")")
.call(d3.axisBottom(xScale));
mainGraph.append("g").attr("class", "y axis").call(d3.axisLeft(yScale));
//No.5 make lines
var line = d3
.line()
.x(function(d) {
return xScale(d.name);
}) // set the x values for the line generator
.y(function(d) {
return yScale(d.x);
}) // set the y values for the line generator
.curve(d3.curveMonotoneX); // apply smoothing to the line
mainGraph
.append("path")
.datum(data) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
<script src="https://d3js.org/d3.v6.min.js"></script>
You need to set fill: none; and stroke: <your line colour here> for the path. Otherwise, it thinks it's a closed shape and tries to fill it in.
That is because normally, paths are used to draw two-dimensional shapes. Only lines are assumed not to have two dimensions. See also the MDN docs
const data = [{
name: "A",
x: 10,
},
{
name: "B",
x: 22,
},
{
name: "C",
x: 33,
},
{
name: "D",
x: 20,
},
{
name: "E",
x: 21,
},
];
//No.1 define the svg
let graphWidth = 600,
graphHeight = 450;
let margin = {
top: 30,
right: 10,
bottom: 30,
left: 85
};
let totalWidth = graphWidth + margin.left + margin.right,
totalHeight = graphHeight + margin.top + margin.bottom;
let svg = d3
.select("body")
.append("svg")
.attr("width", totalWidth)
.attr("height", totalHeight);
//No.2 define mainGraph
let mainGraph = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//No.3 define axises
let categoriesNames = data.map((d) => d.name);
let xScale = d3
.scalePoint()
.domain(categoriesNames)
.range([0, graphWidth]); // scalepoint make the axis starts with value compared with scaleBand
var yScale = d3
.scaleLinear()
.range([graphHeight, 0])
.domain([0, d3.max(data, (data) => data.x)]); //* If an arrow function is simply returning a single line of code, you can omit the statement brackets and the return keyword
//No.4 set axises
mainGraph
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + graphHeight + ")")
.call(d3.axisBottom(xScale));
mainGraph.append("g").attr("class", "y axis").call(d3.axisLeft(yScale));
//No.5 make lines
var line = d3
.line()
.x(function(d) {
return xScale(d.name);
}) // set the x values for the line generator
.y(function(d) {
return yScale(d.x);
}) // set the y values for the line generator
.curve(d3.curveMonotoneX); // apply smoothing to the line
mainGraph
.append("path")
.datum(data) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
.line {
stroke: blue;
fill: none;
}
<script src="https://d3js.org/d3.v6.min.js"></script>
Good Image
Bad Image
I am trying to create a D3 graph which looks like the Illustrator created design (Good Image 1), but the closest in terms of positioning I have been able to get is the second (Bad Image 2).
I'm really new to D3 and creating SVGs in general, so I may be going about this all wrong. The code below is what I've been able to figure out / find online. It looks like I can't directly adjust positioning of the elements themselves using css positioning? I tried adding classes via the html and also in JQuery with $(.myClass).css..., but everything I do has exactly zero effect. The only thing that seems to work is transform, but it's ugly, as can be seen in the second pic.
var margin = { left:10, right:10, top:10, bottom:10 };
var width = 400 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
var g = d3.select("#pyramid-chart-area")
.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 + ")");
d3.json("../data/pyramid_hp.json").then(function(data){
data.forEach(function(d){
d.hp = +d.hp;
});
var x = d3.scaleBand()
.domain(data.map(function(d){ return d.hp; }))
.range([0, width])
.paddingInner(0.3)
.paddingOuter(0.3);
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d){
return d.hp;
})])
.range([height, 0]);
var xAxisCall = d3.axisBottom(x);
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + height + ")")
.call(xAxisCall)
.selectAll("text")
.attr("y", "10")
.attr("x", "-5")
.attr("text-anchor", "end")
.attr("transform", "rotate(-40)");
var yAxisCall = d3.axisLeft(y)
.ticks(3)
.tickFormat(function(d){
return d;
});
g.append("g")
.attr("class", "y-axis")
.call(yAxisCall);
var arc = d3.symbol().type(d3.symbolTriangle)
.size(function(d){ return scale(d.hp); });
var scale = d3.scaleLinear()
.domain([0, 5])
.range([0, width]);
var colors = d3.scaleLinear()
.domain(d3.extent(data, function(d) {return d.hp}))
.range([
'#ffffff',
'#303030'
]);
var group = g.append('g')
.attr('transform','translate('+ 192 +','+ 320 +')')
.attr('class', 'triangle-container');
var line = group.selectAll('path')
.data(data)
.enter()
.append('path')
.attr('d', arc)
// .attr('fill',function(d){ return colorscale(d.hp); })
.attr('fill', d => colors(d.hp))
.attr('stroke','#000')
.attr('stroke-width', 1)
.attr('class', 'triangle')
.attr('transform',function(d,i){ return "translate("+ (i * 20) +","+(i * 10)+")"; });
You can position the symbols, but its tricky - symbol size represents area and as rioV8 notes symbols are positioned by their center. But if you can figure out the properties of the triangle we can place it relatively easily.
In the case of a equilateral triangle, you'll want to know the length of a given side as well as the height of that centroid (which is triangle height/3). So these functions will likely be useful:
// get length of a side/width of upright equilateral triangle from area:
function getWidth(a) {
return Math.sqrt(4 * a / Math.sqrt(3));
}
// get height of the triangle from length of a side
function getHeight(l) {
return Math.sqrt(3)*l/2;
}
Using the height of the centroid we can position the circle where we want with something like:
y = SVGheight - SymbolHeight/3 - marginBottom;
No need for scaling here.
The x values of each symbol do need some scaling to arrange them to your liking. Below I use a linear scale with a range of [width/10,0] arbitrarily, the denominator will change the horizontal skew in this case, there are probably better ways to fine tune this.
With this you can achieve the desired result:
For simplicity's sake, below I'm using data (since you don't show any) that represents pixel area - scaling must be factored into the height and width calculations if scaling areas. I've also included circles on the top of each triangle for possible label use, since we know the dimensions of the triangle this is trivial now
var margin = { left:10, right:10, top:10, bottom:10 };
var width = 400 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var g = 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 data = [
{a: 40000},
{a: 30000},
{a: 20000},
{a: 10000}
];
function getWidth(a) {
return Math.sqrt(4 * a / Math.sqrt(3));
}
function getHeight(l) {
return Math.sqrt(3)*l/2;
}
data.forEach(function(d) {
d.w = getWidth(d.a);
d.h = getHeight(d.w);
})
var x = d3.scaleLinear()
.domain(d3.extent(data, function(d){ return d.w; }) )
.range([width/10,0]);
var arc = d3.symbol().type(d3.symbolTriangle)
.size(function(d){ return d.a; });
var colors = d3.scaleLinear()
.domain(d3.extent(data, function(d) {return d.a}))
.range(['#ffffff','#303030']);
var group = g.append('g')
.attr('transform','translate('+ width/2 +',0)')
.attr('class', 'triangle-container');
var line = group.selectAll('path')
.data(data)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', d => colors(d.a))
.attr('class', 'triangle')
.attr('transform',function(d,i){ return "translate("+ x(d.w) +","+ (height - d.h/3 - margin.bottom ) +")"; });
var circles = group.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return x(d.w); })
.attr("cy", function(d) { return height - d.h - margin.bottom; })
.attr("r", 3);
<script src='https://d3js.org/d3.v5.min.js' type='text/javascript'></script>
axes could present a bit of a challenge
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/
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/