D3 Bar Chart, bars not showing, axis on top - d3.js

I have a codepen here - https://codepen.io/ttmtsimon/pen/EbPgrw
I am trying to create a simple bar chart in D3.
The axis are showing but the x axis is on the top and it should be on the bottom and the bars are not showing at all.
I know this is a bad question but I can't see where the code is wrong, any help would be appreciated.
let margin = {top: 20, right: 20, bottom:100, left:60}
let width = 800 - margin.left - margin.right
let height = 500 - margin.top - margin.bottom
let x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.5);
let y = d3.scaleLinear().range([height, 0])
let xAxis = d3.axisBottom(x)
.tickFormat((d) => {
return d.x;
});
let yAxis = d3.axisLeft(y)
let svg = d3.select('.barGraph')
.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('https://api.myjson.com/bins/vkbjf', (data)=>{
x.domain(data.map((d)=>{
return d.name
}))
y.domain([0, d3.max(data, (d)=>{
return d.rank
})]);
svg.append('g')
.attr('class', 'x axis')
.attr('tranform', 'translate(0 " + height + ")')
.call(xAxis)
.selectAll('text')
.style('text-anchor', 'end')
.attr('dx', '-0.5em')
.attr('dy', '-55em')
.attr('y', 30)
.attr('transform', 'rotate(-45)');
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 5)
.attr('dy', '0.8em')
.attr('text-anchor', 'end')
.text('Member Rank')
svg.selectAll('bar')
.data(data)
.enter()
.append('rect')
.style('fill', 'orange')
.attr('x', (d)=>{
return y(d.rank)
})
.attr('height', (d)=>{
return height - y(d.rank)
})
})

There are a few issues, namely, you don't define width or y for your rectangles. Also, the rectangle height should be equal to y(value) rather than height-y(value) (this is the top of the rectangle, and therefore the rectangle's y property, as svg coordinates start from the top at y=0). Your rectangle attributes should probably look more like:
svg.selectAll('bar')
.data(data)
.enter()
.append('rect')
.style('fill', 'orange')
.attr('x', (d)=>{
return x(d.name)
})
.attr('height', (d)=>{
return y(d.rank)
})
.attr("width",x.bandwidth())
.attr("y",(d)=>height - y(d.rank))
})
Second, you have inconsistent quotations in your transforms (svg and x axis appends respectively below):
.attr('transform', 'translate(" + margin.left + "." + margin.top + ")');
.attr('tranform', 'translate(0 " + height + ")')
Which is why these aren't being appended right, also you need commas between the two values.
Updated pen.

Related

Draggable interactive graphic element using D3

I have a challenging idea to build and couldn't think about a solution yet. The design request to have interactive/draggable graphics, as the one I send by the link below.
However, those graphics elements will be distributed in specific places on the page, with other elements around (Text, images, etc). The idea is to let the user "to play" with the graphics circles, just doing something 'cool and fun'. The user must be able to drag the circles from the graphics and change its visual all along the page.
The problem is: If I place this element in an specific place (inside a div, for example), if we drag the circles outside the 'canvas' area, the elements is no longer visible.
How could I place this canvas-div element in specific place and at the same time to allow the elements inside it to go the outside limited zone?
I thought about putting it in position relative or absolute with 100% height and width of the page, but it will be out of its place in responsive I guess, or pretty complicate to place always at a good place by just using % position. Any suggestion?
I'm using d3.js
Thanks!!
Heres the link: https://codepen.io/A8-XPs/pen/ePWRxZ?editors=0010
HTML
<svg width="500" height="350"></svg>
JS
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
let points = d3.range(1, 10).map(function(i) {
return [i * width / 10, 50 + Math.random() * (height - 100)];
});
var x = d3.scaleLinear()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y);
var line = d3.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); })
.curve(d3.curveCatmullRom.alpha(0.5))
let drag = d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended);
svg.append('rect')
.attr('class', 'zoom')
.attr('cursor', 'move')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
var focus = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(points, function(d) { return d[0]; }));
y.domain(d3.extent(points, function(d) { return d[1]; }));
focus.append("path")
.datum(points)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
focus.selectAll('circle')
.data(points)
.enter()
.append('circle')
.attr('r', 5.0)
.attr('cx', function(d) { return x(d[0]); })
.attr('cy', function(d) { return y(d[1]); })
.style('cursor', 'pointer')
.style('fill', 'steelblue');
focus.selectAll('circle')
.call(drag);
focus.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
focus.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
function dragstarted(d) {
d3.select(this).raise().classed('active', true);
}
function dragged(d) {
d[0] = x.invert(d3.event.x);
d[1] = y.invert(d3.event.y);
d3.select(this)
.attr('cx', x(d[0]))
.attr('cy', y(d[1]))
focus.select('path').attr('d', line);
}
function dragended(d) {
d3.select(this).classed('active', false);
}
PS: I got to solve the problem by just applying simple CSS to the SVG:
Overflow: visible;
Hopefully it will work in a real page scenario as well.

D3 triangle positioning to an exact point

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

move x axis to coordinate (0,0) on the chart with d3.js

You can see an example of what I am trying to achieve on this page.
I have negative and positive axis constructed with this code:
this.svg = d3.select(el).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 + ")");
this.xScale = d3.scale.linear()
.range([0, width]);
this.yScale = d3.scale.linear()
.range([height, 0]);
const xAxis = d3.svg.axis()
.scale(this.xScale);
const yAxis = d3.svg.axis()
.orient('left')
.scale(this.yScale);
const data = this.getDataFromProps(this.props.expression);
this.xScale.domain(d3.extent(data, function (d) {return d.x;}));
this.yScale.domain(d3.extent(data, function (d) {return d.y;}));
this.svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
this.svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(' + width/2 + ',0)')
.call(yAxis);
The only problem is that the x-axis is oriented to the bottom. How can I have the axis positioned at (0, 0) on the svg?
to make the x axis aligned to 0 mark of y-axis, use this :-
this.svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + (this.yScale(0)) + ')')
.call(xAxis);
Just make this line change:
this.svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + (height/2) + ')')
.call(xAxis);
Make the translate to half of the height of svg.

How to fix shifted columns on histogram?

I am building a histogram with number of days as the x axis, but the columns are shifted. I want to bin the data on months, or more exactly, 30-day intervals. For some reason, when generating this graph the columns are not the right size, and the bars end up shifting a lot, as shown in the picture:
The bins are hardcoded like this:
var data = d3.layout.histogram()
.bins(20)
(values);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.y; })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.tickValues([0,30,60,90,120,150,180,210,240,270,300,330,360,390,420,450,480,510,540,570,600])
.orient("bottom");
var svg = d3.select("#graph").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 = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", x(data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); });
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", x(data[0].dx) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
What is the correct way to solve this issue?

Label at the beginning of a D3 axis

How would I make d3 put "Tue 20" to the left of "Wed 21"?
http://jsfiddle.net/robdodson/KWRxW/
var data = [
{"date":"2012-03-20","total":3},
{"date":"2012-03-21","total":8},
{"date":"2012-03-22","total":2},
{"date":"2012-03-23","total":10},
{"date":"2012-03-24","total":3},
{"date":"2012-03-25","total":20},
{"date":"2012-03-26","total":12}
];
var margin = {top: 40, right: 40, bottom: 40, left:40},
width = 600,
height = 500;
var x = d3.time.scale()
.domain([
new Date(data[0].date),
d3.time.day.offset(new Date(data[data.length - 1].date), 1)
])
.rangeRound([0, width - margin.left - margin.right]);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.total; })])
.range([height - margin.top - margin.bottom, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.ticks(d3.time.days, 1)
.tickFormat(d3.time.format('%a %d'))
.tickSize(0)
.tickPadding(8);
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.tickPadding(8);
var svg = d3.select('body').append('svg')
.attr('class', 'chart')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
svg.selectAll('.chart')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', function(d) { return x(new Date(d.date)); })
.attr('y', function(d) {
return height - margin.top - margin.bottom -
(height - margin.top - margin.bottom - y(d.total))
})
.attr('width', 10)
.attr('height', function(d) {
return height - margin.top - margin.bottom - y(d.total)
});
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + (height - margin.top - margin.bottom) + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
It happened due to inconsistencies in date managing. By using UTC dates and the d3 date functions the issue will not occur.
https://github.com/mbostock/d3/issues/1234

Resources