X axis domain line overlapping with d3 bar stroke - javascript

I should have a white border when I select any bar in my d3 bar chart. So here the border is achieved using stroke, but the bottom border is getting hidden under the x domain line.
// container size
var margin = {top: 10, right: 10, bottom: 30, left: 30},
width = 400,
height = 300;
var data = [
{"month":"DEC","setup":{"count":26,"id":1,"label":"Set Up","year":"2016","graphType":"setup"}},
{"month":"JAN","setup":{"count":30,"id":1,"label":"Set Up","year":"2017","graphType":"setup"}},
{"month":"FEB","setup":{"count":30,"id":1,"label":"Set Up","year":"2017","graphType":"setup"}}];
var name = 'dashboard';
// x scale
var xScale = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.2);
// set x and y scales
xScale.domain(data.map(function(d) { return d.month; }));
// x axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.outerTickSize(0);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d.setup.count;
})])
.range([height, 0]);
var ticks = yScale.ticks(),
lastTick = ticks[ticks.length-1];
var newLastTick = lastTick + (ticks[1] - ticks[0]);
if (lastTick < yScale.domain()[1]){
ticks.push(lastTick + (ticks[1] - ticks[0]));
}
// adjust domain for further value
yScale.domain([yScale.domain()[0], newLastTick]);
// y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(-width, 0, 0)
.tickFormat(d3.format('d'))
.tickValues(ticks);
// create svg container
var svg = d3.select('#chart')
.append('svg')
.attr('class','d3-setup-barchart')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
//.on('mouseout', tip.hide);
// apply tooltip
//svg.call(tip);
// Horizontal grid (y axis gridline)
svg.append('g')
.attr('class', 'grid horizontal')
.call(d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(-width, 0, 0)
.tickFormat('')
.tickValues(ticks)
);
// create bars
var bars = svg.selectAll('.bar')
.data(data)
.enter()
.append('g');
bars.append('rect')
.attr('class', function(d,i) {
return 'bar';
})
.attr('id', function(d, i) {
return name+'-bar-'+i;
})
.attr('x', function(d) { return xScale(d.month); })
.attr('width', xScale.rangeBand())
.attr('y', function(d) { return yScale(d.setup.count); })
.attr('height', function(d) { return height - yScale(d.setup.count); })
.on('click', function(d, i) {
d3.select(this.nextSibling)
.classed('label-text selected', true);
d3.select(this)
.classed('bar selected', true);
d3.select('#'+name+'-axis-text-'+i)
.classed('axis-text selected', true);
});
//.on('mouseover', tip.show)
//.on('mouseout', tip.hide);
// apply text at the top
bars.append('text')
.attr('class',function(d,i) {
return 'label-text';
})
.attr('x', function(d) { return xScale(d.month) + (xScale.rangeBand()/2) - 10; })
.attr('y', function(d) { return yScale(d.setup.count) + 2 ; })
.attr('transform', function() { return 'translate(10, -10)'; })
.text(function(d) { return d.setup.count; });
// draw x axis
svg.append('g')
.attr('id', name+'-x-axis')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
// apply class & id to x-axis texts
d3.select('#'+name+'-x-axis')
.selectAll('text')
.attr('class', function(d,i) {
return 'axis-text';
})
.attr('id', function(d,i) { return name+'-axis-text-' + i; });
// draw y axis
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');
// remove 0 in y axis
svg.select('.y')
.selectAll('.tick')
.filter(function (d) {
return d === 0 || d % 1 !== 0;
}).remove();
svg
.select('.horizontal')
.selectAll('.tick')
.filter(function (d) {
return d === 0 || d % 1 !== 0;
}).remove();
JSFiddle

In a SVG, whoever is painted last stays on top.
That being said, simply append your x axis...
svg.append('g')
.attr('id', name + '-x-axis')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
... before the bars:
var bars = svg.selectAll('.bar')
.data(data)
.enter()
.append('g');
Here is your updated fiddle: https://jsfiddle.net/5bnzt6nb/

Related

Why the graph lines are off axis Y and X?

I am having trouble creating a chart with a simple line.
I'll put here my code and an image of how the line is getting off axis Y and X. I really have no idea why this is happening.
Chart:
HTML:
<div id="myChart"></div>
JavaScript:
Function to adjust my json only with the data I need:
var reduceVendedores = vendedores.reduce(function (allSales, sales) {
if (allSales.some(function (e) {
return e.vendnm === sales.vendnm;
})) {
allSales.filter(function (e) {
return e.vendnm === sales.vendnm
})[0].Vendas_Ano += sales.Vendas_Ano;
allSales.filter(function (e) {
return e.vendnm === sales.vendnm
})[0].Vendas_Ant += sales.Vendas_Ant
} else {
allSales.push({
vendnm: sales.vendnm,
Vendas_Ano: sales.Vendas_Ano,
Vendas_Ant: sales.Vendas_Ant
})
}
return allSales;
}, []);
Defining X and Y Axes of the Chart:
var margin = { top: 30, right: 30, bottom: 70, left: 60 },
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
function getMax() {
return reduceVendedores.map(d => d.Vendas_Ano)
}
var svg = d3.select("#myChart")
.append("svg")
.attr("width", width)
.attr("height", height);
var xscale = d3.scaleBand()
.range([0, width - 100])
.domain(reduceVendedores.map(function (d) { return d.vendnm; }))
var yscale = d3.scaleLinear()
.domain([0, d3.max(getMax()) + 30000])
.range([height / 2, 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 = height / 2 + 10;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate + ")")
.call(d3.axisBottom(xscale))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
Defining the chart line:
svg.append("path")
.datum(reduceVendedores)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function (d) { return xscale(d.vendnm); })
.y(function (d) { return yscale(d.Vendas_Ano) })
)
You're not adjusting for the margins you gave to your axis with:
.attr("transform", "translate(50, 10)")
Try:
svg.append('g')
.attr('transform','translate(50,0)')
.append("path")
.datum(reduceVendedores)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function (d) { return xscale(d.vendnm); })
.y(function (d) { return yscale(d.Vendas_Ano) })
)
Typically you'd set your margin in svg, like:
var svg = d3.select("#myChart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append('g')
.attr('transform','translate(' + margin.left + ',' + margin.top + ')')
But doing so know would ruin the alignment of your axis.

D3.js - Line Graph: Line path goes over x and y axis on zooming

I have a line graph with two linear paths. Everything works great, only when I select area on graph, the lines goes over x-axis and also y-axis:
Please help me finding the issue. Following is my code:
var data = JSON.parse("[{\"time\": 1496511413,\"correlation\": 0.1,\"offset\": 8104}, {\"time\": 1496511414,\"correlation\": 0.2,\"offset\": 8105},{\"time\": 1496511415,\"correlation\": 0.4,\"offset\": 8106},{\"time\": 1496511416,\"correlation\": 0.5,\"offset\": 8107},{\"time\": 1496511417,\"correlation\": 0.7,\"offset\": 8120},{\"time\": 1496511418,\"correlation\": 0.8,\"offset\": 8120},{\"time\": 1496511419,\"correlation\": 0.3,\"offset\": 8108},{\"time\": 1496511420,\"correlation\": 0.6,\"offset\": 8109},{\"time\": 1496511421,\"correlation\": 0.9,\"offset\": 8110}]");
console.log(data);
var margin = {top: 30, right: 40, bottom: 30, left: 50},
width = 700 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Scale axis
var timeScale = d3.scaleTime().range([0, width]);
var corrScale = d3.scaleLinear().range([height, 0]);
var offsetScale = d3.scaleLinear().range([height, 0]);
// Define Axes
var timeAxis = d3.axisBottom(timeScale).ticks(5);
var corrAxis = d3.axisLeft(corrScale).ticks(5);
var offsetAxis = d3.axisRight(offsetScale).ticks(5);
// Define Lines
var corrLine = d3.line()
.x(function(d) { return timeScale(d.time); })
.y(function(d) { return corrScale(d.correlation); });
var offsetLine = d3.line()
.x(function(d) { return timeScale(d.time); })
.y(function(d) { return offsetScale(d.offset); });
// plot graph
function plot_graph() {
data.forEach(function(d) {
d.time = new Date(d.time * 1000);
d.correlation = +d.correlation;
d.offset = +d.offset / 1000;
});
console.log(data);
// Define brush
var brush = d3.brush()
.extent([[0, 0], [width, height]])
.on('end', brushEnded),
idleTimeout,
idleDelay = 350;
//Add domain for scale x-y axis
timeScale.domain(d3.extent(data, function(d) { return d.time; }));
corrScale.domain(d3.extent(data, function(d) { return d.correlation; }));
offsetScale.domain(d3.extent(data, function(d) { return d.offset; }));
// create svg element
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', 'time axis')
.attr("transform", "translate(0," + height + ")")
.call(timeAxis);
svg.append('g')
.attr('class', 'corr axis')
.style('fill', 'steelblue')
.call(corrAxis);
svg.append('g')
.attr('class', 'offset axis')
.attr('transform', 'translate(' + width + ', 0)')
.style('fill', 'red')
.call(offsetAxis);
var focus = svg.append('g')
.attr("clip-path", "url(#clip)");
focus.append('svg:path')
.datum(data)
.attr('class', 'corr line')
.attr('d', corrLine);
focus.append('svg:path')
.datum(data)
.attr('class', 'offset line')
.style('stroke', 'red')
.attr('d', offsetLine);
focus.append('g')
.attr('class', 'brush')
.call(brush);
function idled() {
idleTimeout = null;
}
function brushEnded() {
var s = d3.brushSelection(d3.select('.brush').node());
if (!s) {
if (!idleTimeout) return idleTimeout = setTimeout(idled, idleDelay);
timeScale.domain(d3.extent(data, function(d) { return d.time; }));
corrScale.domain(d3.extent(data, function(d) { return d.correlation; }));
offsetScale.domain(d3.extent(data, function(d) { return d.offset; }));
} else {
console.log(s);
console.log([s[0][0], s[1][0]].map(timeScale.invert, timeScale));
console.log([s[1][1], s[0][1]].map(offsetScale.invert, offsetScale));
console.log([s[1][1], s[0][1]].map(corrScale.invert, corrScale));
timeScale.domain([s[0][0], s[1][0]].map(timeScale.invert, timeScale));
corrScale.domain([s[1][1], s[0][1]].map(corrScale.invert, corrScale));
offsetScale.domain([s[1][1], s[0][1]].map(offsetScale.invert, offsetScale));
svg.select(".brush").call(brush.move, null);
}
zoom();
}
function zoom() {
var t = svg.transition()
.duration(750);
svg.select('.time.axis').transition(t).call(timeAxis);
svg.select('.corr.axis').transition(t).call(corrAxis);
svg.select('.offset.axis').transition(t).call(offsetAxis);
console.log(timeScale(0));
console.log(corrScale(0));
console.log(corrLine(data));
svg.select('path.corr.line')
.transition(t)
.attr('d', corrLine);
svg.select('path.offset.line')
.transition(t)
.attr('d', offsetLine);
}
}

Draw wordcloud for each point in scatterplot

I create a scatterplot which is defined on the following data (note that only first two fields are currently using for plotting):
var data = [[5,3,"{'text':'word1',size:4},{'text':'word2','size':1}"],
[3,5,"{'text':'word3',size:5},{'text':'word4','size':4}"],
[1,4,"{'text':'word1',size:3},{'text':'word2','size':5},{'text':'word3','size':2}"],
[2,3,"{'text':'word2',size:1},{'text':'word3','size':5}"]];
Next, when we click on each particular point in the scatterplot the application should attach a wordcloud which is defined from words stored in the 3rd field of the data variable. I use Jason Davies's implementation of wordcloud. Currently (for demo purposes), the wordcloud is generating onlyfrom the static data stored in variable frequency_list. The current code is also stored on JSFiddle.
Any idea how to proceed?
var data = [[5,3,"{'text':'word1',size:4},{'text':'word2','size':1}"],
[3,5,"{'text':'word3',size:5},{'text':'word4','size':4}"],
[1,4,"{'text':'word1',size:3},{'text':'word2','size':5},{'text':'word3','size':2}"],
[2,3,"{'text':'word2',size:1},{'text':'word3','size':5}"]];
var margin = {top: 20, right: 15, bottom: 60, left: 60},
width = 500 - margin.left - margin.right,
height = 250 - 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 chart = d3.select('body')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// Draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis);
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(data)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 5)
.on("mouseover", function(){d3.select(this).style("fill", "red")})
.on("mouseout", function(){d3.select(this).style("fill", "black")});
// FUNCTION TO DISPLAY CIRCLE
g.on('mouseover', function(){
div.style("display", "block")
d3.select("krog").style("fill", "orange");
generate();
});
g.on('mouseout', function(){
//div.style("display", "none")
div.select("svg").remove();
});
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("display", "none");
// Functions to draw wordcloud
var frequency_list = [{"text":"study","size":40},{"text":"motion","size":15},{"text":"forces","size":10},{"text":"electricity","size":15},{"text":"movement","size":10},{"text":"relation","size":5},{"text":"things","size":10},{"text":"force","size":5},{"text":"ad","size":5}];
var color = d3.scale.linear()
.domain([0,1,2,3,4,5,6,10,15,20,100])
.range(["#ddd", "#ccc", "#bbb", "#aaa", "#999", "#888", "#777", "#666", "#555", "#444", "#333", "#222"]);
// Generates wordcloud
function generate(){
d3.layout.cloud().size([800, 300])
.words(frequency_list)
.rotate(0)
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
}
function draw(words) {
d3.select("div").append("svg")
.attr("width", 850)
.attr("height", 350)
.attr("class", "wordcloud")
.append("g")
// without the transform, words words would get cutoff to the left and top, they would
// appear outside of the SVG area
.attr("transform", "translate(320,200)")
.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("fill", function(d, i) { return color(i); })
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
You have a couple of problems here.
First, your data has strings for the words. I changed that for an array of objects:
var data = [[5,3,[{'text':'word1',size:4},{'text':'word2','size':1}]],
[3,5,[{'text':'word3',size:5},{'text':'word4','size':4}]],
[1,4,[{'text':'word1',size:3},{'text':'word2','size':5},{'text':'word3','size':2}]],
[2,3,[{'text':'word2',size:1},{'text':'word3','size':5}]]];
After that, I changed the function draw: instead of appending a new div every time you hover a circle, it just change the div content:
div.append("svg")
.attr("width", 300)
.attr("height", 300)
.attr("class", "wordcloud")
.append("g")
But now comes the most important change:
You are displaying the wordcloud every time the user hover a circle, but you're calling the mouseover for the group element. That way, we cannot access the data bound to each specific circle.
Instead of that, we'll set a variable for the circles:
var circle = g.selectAll("scatter-dots")
.data(data)
.enter()
.append("svg:circle");
Thus, we can get the data for each hovered circle, which is the third element in the array:
circle.on('mouseover', function(d){
div.style("display", "block")
d3.select("krog").style("fill", "orange");
generate(d[2]);//here, d[2] is the third element in the data array
});
And we pass this third element (d[2]) to the function generate as a parameter named thisWords:
function generate(thisWords){
d3.layout.cloud().size([800, 300])
.words(thisWords)
.rotate(0)
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
}
here is your fiddle: https://jsfiddle.net/jwrbps4j/
PS: you'll have to improve the translate for that words.

Show and hide additional element in d3.js

I have a simple script (also on JSFiddle) which draws scatterplot from given data. When I go over the data point on the scatter the script should display red circle below my chart; and vice versa, when the event "mouseout" is called the circle should disappear.
Now the red circle is shown when the "mouseover" event is called, but the circle appends to other circles. I wonder how to properly implement show/hide functionality in this case.
The code is pasted below.
var data = [[4,3], [3,3], [1,4], [2,3]];
var margin = {top: 20, right: 15, bottom: 60, left: 60},
width = 500 - margin.left - margin.right,
height = 250 - 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 chart = d3.select('body')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// Draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis);
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(data)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 5);
// FUNCTION TO DISPLAY CIRCLE BELO CHART
g.on('mouseover', function(){
div.style("display", "block")
div.append("svg")
.attr("width", 50)
.attr("height", 50)
.append("circle")
.attr("cx", 25)
.attr("cy", 25)
.attr("r", 25)
.style("fill", "red");
});
g.on('mouseout', function(){
div.style("display", "none")
});
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("display", "none");
You're appending a new SVG every time you hover the circle.
An easy and lazy solution is removing the SVG on "mouseout":
g.on('mouseout', function(){
div.select("svg").remove();
});
Here is your fiddle: https://jsfiddle.net/39pmwzzh/

How to add a line graph to a scatterplot using d3?

I currently have a working scatter plot that I make using this
var data = (an array of arrays with two integers in each array)
var margin = {top: 20, right: 15, bottom: 60, left: 60}
, width = 300 - margin.left - margin.right
, height = 250 - 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 chart = d3.select('.scatterGraph')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.ticks(5);
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis);
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(data)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 2);
I was wondering how I could add a line graph (or alternatively another scatter plot) to this graph. I'm very new to d3 so I'm currently lost on how to do it. For example I would just want to add a line described by a function y = 2t where t is the x axis of the scatterplot. Thanks!
If it is as simple as a line described by a function y=2t you can just append a line element to your chart in this case main like this, assuming that your width is at least greater than twice your height
main.append("line").attr("x1", 0).attr("x2", height/2)
.attr("y1", height).attr("y2", 0);
But if you have a line that connected through multiple points, you will need to add a path element to your svg, and use d3.svg.line() function to generate its d attribute. So something like this,
var lineFunction = d3.svg.line().x(function (d) { x(d[0])}; )
.y(function (d) { y(d[1])}; );
var gLine = main.append("g");
gLine.append("path").attr("d", lineFunction(data));
For another scatter plot, you can reuse
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(data2)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 2);
but with a different set of data, and different accessor functions or scales if needed.

Categories