Updating a stacked bar chart in d3.js (includes JSFiddle) - javascript

jsfiddle example can be seen here
I'm expecting the chart to update correctly with the new dataset contained in the function "redraw", however this isn't happening.
Although the current rects are updated with the new dataset, the issue seems to be that the enter().append() part of the code isn't working:
var groups = svg.selectAll('g')
.data(dataset, retd);
groups.style('fill', function (d, i) {
return colours(i);
});
groups.enter()
.append('g')
.style('fill', function (d, i) {
return colours(i);
});
groups.exit().remove();
//update the Rects
var rects = groups.selectAll('rect')
.data(function (d) {
return d;
}, thisy);
rects.transition()
.attr('x', function (d) {
return xScale(d.x0);
})
.attr('y', function (d, i) {
return yScale(d.y);
})
.attr('height', function (d) {
return yScale.rangeBand();
})
.attr('width', function (d) {
return xScale(d.x);
});
rects.enter()
.append('rect')
.attr('x', function (d) {
return xScale(d.x0);
})
.attr('y', function (d, i) {
return yScale(d.y);
})
.attr('height', function (d) {
return yScale.rangeBand();
})
.attr('width', function (d) {
return xScale(d.x);
});
Any help/insight would be appreciated.

Since the first draw worked fine, I turned it into a function. Only had to remove the already written SVG to make it reusable:
JSFiddle
<button id="reset">Redraw</button>
<script>
document.getElementById('reset').onclick = function() {
var dataset = [
{"data":[
{"IssueMonth":"Apr","IS":"350","month":"Apr"},
{"IssueMonth":null,"IS":"100","month":"Aug"},
{"IssueMonth":null,"IS":"0","month":"Dec"}],
"name":"CHF"},
{"data":[
{"IssueMonth":null,"IS":"100","month":"Apr"},
{"IssueMonth":null,"IS":"200","month":"Aug"},
{"IssueMonth":null,"IS":"40","month":"Dec"}],
"name":"GBP"}
];
drawit(dataset);
}
var dataset = [
{"data":[
{"IssueMonth":"Apr","IS":"350","month":"Apr"},
{"IssueMonth":null,"IS":"0","month":"Aug"},
{"IssueMonth":null,"IS":"0","month":"Dec"}],
"name":"CHF"},
{"data":[
{"IssueMonth":null,"IS":"100","month":"Apr"},
{"IssueMonth":null,"IS":"0","month":"Aug"},
{"IssueMonth":null,"IS":"50","month":"Dec"}],
"name":"GBP"}
];
drawit(dataset);
function drawit(dataset) {
var margins = {
top: 40,
left: 40,
right: 40,
bottom: 40
};
var width = 400;
var height = 500 - margins.top - margins.bottom;
var series = dataset.map(function (d) {
return d.name;
});
var dataset = dataset.map(function (d) {
return d.data.map(function (o, i) {
// Structure it so that your numeric
// axis (the stacked amount) is y
return {
y: +o.IS,
x: o.month
};
});
});
stack = d3.layout.stack();
stack(dataset);
var dataset = dataset.map(function (group) {
return group.map(function (d) {
// Invert the x and y values, and y0 becomes x0
return {
x: d.y,
y: d.x,
x0: d.y0
};
});
});
d3.select("svg").remove();
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')')
var xMax = d3.max(dataset, function (group) {
return d3.max(group, function (d) {
return d.x + d.x0;
});
})
var xScale = d3.scale.linear()
.domain([0, xMax])
.range([0, width]);
var months = dataset[0].map(function (d) {
return d.y;
});
var yScale = d3.scale.ordinal()
.domain(months)
.rangeRoundBands([0, height], .1);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
var colours = d3.scale.category20();
var groups = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.style('fill', function (d, i) {
return colours(i);
});
var rects = groups.selectAll('rect')
.data(function (d) {
return d;
})
.enter()
.append('rect')
.attr('x', function (d) {
return xScale(d.x0);
})
.attr('y', function (d, i) {
return yScale(d.y);
})
.attr('height', function (d) {
return yScale.rangeBand();
})
.attr('width', function (d) {
return xScale(d.x);
});
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
}
</script>

Related

d3 v4 Stacked to Grouped Bar Chart from CSV

Reference Mike Bostick's Stacked to Grouped Bar Chart example, I am modifying it to work with a CSV file. I have been working on this for a couple weeks and have looked through countless examples on Stack Overflow and elsewhere and am stumped.
The stacked bar chart works.
Stacked Bar Chart:
When I transition to a grouped bar chart I am only having issues referencing the key or series that is stacked or grouped. Right now all the rectangles display on top of each other instead of next to each other.
Grouped Bar Chart:
In the function transitionStep2() I want to multiply by a number corresponding to the series or key. I am currently multiplying by the number 1 in this function as a placeholder .attr("x", function(d) { return x(d.data.Year) + x.bandwidth() / 7 * 1; }).
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<html><body>
<form>
<label><input type="radio" name="mode" style="margin-left: 10" value="step1" checked>1</label>
<label><input type="radio" name="mode" style="margin-left: 20" value="step2">2</label>
</form>
<svg id = "bar" width = "500" height = "300"></svg>
<script>
var svg = d3.select("#bar"),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.08);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal()
.range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);
d3.csv("data.csv", function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}, function(error, data) {
if (error) throw error;
var keys = data.columns.slice(1);
x.domain(data.map(function(d) { return d.Year; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
color.domain(keys);
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth());
rect = g.selectAll("rect");
});
d3.selectAll("input")
.on("change", changed);
function changed() {
if (this.value === "step1") transitionStep1();
else if (this.value === "step2") transitionStep2();
}
function transitionStep1() {
rect.transition()
.attr("y", function(d) { return y(d[1]); })
.attr("x", function(d) { return x(d.data.Year); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
}
function transitionStep2() {
rect.transition()
.attr("x", function(d) { return x(d.data.Year) + x.bandwidth() / 7 * 1; })
.attr("width", x.bandwidth() / 7)
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("stroke", "red");
}
</script></body></html>
And the csv file:
Year,A,B,C,D
1995,60,47,28,39
1996,29,56,99,0
1997,30,26,63,33
1998,37,16,48,0
1999,46,49,64,21
2000,78,88,81,57
2001,18,11,11,64
2002,91,76,79,64
2003,30,99,96,79
The example you're referring has linear values to group the chart into and so .attr("x", function(d, i) { return x(i) + x.bandwidth() / n * this.parentNode.__data__.key; }) works fine.
In your case, the columns/keys is not a linear scale but an ordinal value set that you have to set a scale for:
Refer simple d3 grouped bar chart
To do that i.e. set up a ordinal scale, here's what can be done:
var x1 = d3.scaleBand();
x1.domain(keys).rangeRound([0, x.bandwidth()]);
So this new scale would have a range of the x scale's bandwidth with the domain of ["A", "B", "C", "D"]. Using this scale to set the x attribute of the rects to group them:
.attr("x", function(d) {
return x(d.data.Year) + x1(d3.select(this.parentNode).datum().key);
})
where d3.select(this.parentNode).datum().key represent the column name.
Here's JSFIDDLE (I've used d3.csvParse to parse the data but I'm sure you'll get the point here. It's just the x attribute you need to reset.
Here's a Plunkr that uses a file.
Here's a code snippet as well:
var svg = d3.select("#bar"),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.08);
var x1 = d3.scaleBand();
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal()
.range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);
var csv = 'Year,A,B,C,D\n1995,60,47,28,39\n1996,29,56,99,0\n1997,30,26,63,33\n1998,37,16,48,0\n1999,46,49,64,21\n2000,78,88,81,57\n2001,18,11,11,64\n2002,91,76,79,64\n2003,30,99,96,79';
var data = d3.csvParse(csv), columns = ["A", "B", "C", "D"];
data.forEach(function(d) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
});
var keys = columns;
x.domain(data.map(function(d) { return d.Year; }));
x1.domain(keys).rangeRound([0, x.bandwidth()]);
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
color.domain(keys);
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth());
rect = g.selectAll("rect");
d3.selectAll("input")
.on("change", changed);
function changed() {
if (this.value === "step1") transitionStep1();
else if (this.value === "step2") transitionStep2();
}
function transitionStep1() {
rect.transition()
.attr("y", function(d) { return y(d[1]); })
.attr("x", function(d) { return x(d.data.Year); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
}
function transitionStep2() {
rect.transition()
.attr("x", function(d) {
return x(d.data.Year) + x1(d3.select(this.parentNode).datum().key);
})
.attr("width", x.bandwidth() / 7)
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("stroke", "red");
}
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<html><body>
<form>
<label><input type="radio" name="mode" style="margin-left: 10" value="step1" checked>1</label>
<label><input type="radio" name="mode" style="margin-left: 20" value="step2">2</label>
</form>
<svg id = "bar" width = "500" height = "300"></svg>
Hope something helps. :)
I updated your examples so that it works,
see https://jsfiddle.net/mc5wdL6s/84/
function transitionStep2() {
rect.transition()
.duration(5500)
.attr("x", function(d,i) {
console.log("d",d);
console.log("i",i);
return x(d.data.Year) + x.bandwidth() / m / n * i; })
.attr("width", x.bandwidth() / n)
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("height", function(d) { return y(0) - y(d[1] - d[0]); })
.attr("stroke", "red");
}
You have to pass the index of the key to the individual rectangles and use this index to multiply with the reduced bandwith. If you also use the length of the keys array (instead of 7) you are CSV column count independent. You need to put the declaration of keys variable outside the d3.csv handler.
You forgot to stroke the initial rects green.
<script>
var svg = d3.select("#bar"),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.08);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal()
.range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);
var keys;
d3.csv("/data.csv", function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}, function(error, data) {
if (error) throw error;
keys = data.columns.slice(1);
x.domain(data.map(function(d) { return d.Year; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
color.domain(keys);
var stackData = d3.stack().keys(keys)(data);
stackData.forEach(element => {
var keyIdx = keys.findIndex(e => e === element.key);
element.forEach(e2 => { e2.keyIdx = keyIdx; });
});
g.append("g")
.selectAll("g")
.data(stackData)
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
rect = g.selectAll("rect");
});
d3.selectAll("input")
.on("change", changed);
function changed() {
if (this.value === "step1") transitionStep1();
else if (this.value === "step2") transitionStep2();
}
function transitionStep1() {
rect.transition()
.attr("y", function(d) { return y(d[1]); })
.attr("x", function(d) { return x(d.data.Year); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
}
function transitionStep2() {
rect.transition()
.attr("x", function(d, i) { return x(d.data.Year) + x.bandwidth() / (keys.length+1) * d.keyIdx; })
.attr("width", x.bandwidth() / (keys.length+1))
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("stroke", "red");
}
</script>

Add labels to D3 line

I want to add labels that show the values on a line chart and can't figure out how to do this.
var svg = d3.select('svg')
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("transform", "translate(" + 0 + "," + 0 + ")");
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.date)})
.y(function(d) { return y(d.value)})
x.domain(d3.extent(data, function(d) { return d.date }));
y.domain(d3.extent(data, function(d) { return d.value }));
I don't know how to add labels, like here for example (though my case is simpler, because I have only one chart)
I've generated some random data here. Relevant code to add the text labels:
g.selectAll('.textLabels')
.data(data).enter()
.append('text')
.classed('textLabels', true)
.attr('x', function(d) { return x(d.date); })
.attr('y', function(d) { return y(d.value); })
.text(function(d) { return d.value; });
which are positioned based on x(date) and y(value) values in the data array. You can adjust the offsets using dx, dy attributes and use some styles to fill/stroke the text.
var data = [];
function randomDate(start, end) {
return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
}
for(var i=0; i<20; i++) {
var obj = {};
obj.date = randomDate(new Date(2018, 01,01), new Date());
obj.value = Math.floor(Math.random() * 50);
data.push(obj);
}
data.sort(function(a, b) {
return a.date > b.date ? -1 : 1;
})
var width = 800, height = 400;
var svg = d3.select('svg')
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("transform", "translate(" + 0 + "," + 0 + ")");
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var line = d3.line()
.x(function(d) {
return x(d.date)
})
.y(function(d) {
return y(d.value)
})
x.domain(d3.extent(data, function(d) {
return d.date
}));
y.domain(d3.extent(data, function(d) {
return d.value
}));
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
g.selectAll('.textLabels')
.data(data).enter()
.append('text')
.classed('textLabels', true)
.attr('x', function(d) { return x(d.date); })
.attr('y', function(d) { return y(d.value); })
.text(function(d) { return d.value; });
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Let me know if you have any questions. Hope this helps.:)

How to pass data from Vue js method scope to external JS file?

This my component code of vue js
<template>
<div class="grapha">
<div id="chart2"></div>
</div>
</template>
<script>
// var apiURL = 'https://jsonplaceholder.typicode.com/users';
var apiURL = 'http://localhost:3000/db';
export default {
name: 'grapha',
data () {
return {
users:[]
}
},
methods: {
fetchData: function () {
var self = this;
$.get( apiURL, function( data ) {
self.users = data;
});
}
},
created: function (){
this.fetchData();
}
}
</script>
Here is my d3 combo chart code , here is a variable named Data takes values and shows it on graph.
Now, I want to get data from self.users (vue js component) to pass it on data variable ( external function ). Could you suggest me what would be the possible way to do that ?
function getTextWidth(text, fontSize, fontName) {
c = document.createElement("canvas");
ctx = c.getContext("2d");
ctx.font = fontSize + ' ' + fontName;
return ctx.measureText(text).width;
}
function DataSegregator(array, on) {
var SegData;
OrdinalPositionHolder = {
valueOf: function () {
thisObject = this;
keys = Object.keys(thisObject);
keys.splice(keys.indexOf("valueOf"), 1);
keys.splice(keys.indexOf("keys"), 1);
return keys.length == 0 ? -1 : d3.max(keys, function (d) { return thisObject[d] })
}
, keys: function () {
keys = Object.keys(thisObject);
keys.splice(keys.indexOf("valueOf"), 1);
keys.splice(keys.indexOf("keys"), 1);
return keys;
}
}
array[0].map(function (d) { return d[on] }).forEach(function (b) {
value = OrdinalPositionHolder.valueOf();
OrdinalPositionHolder[b] = OrdinalPositionHolder > -1 ? ++value : 0;
})
SegData = OrdinalPositionHolder.keys().map(function () {
return [];
});
array.forEach(function (d) {
d.forEach(function (b) {
SegData[OrdinalPositionHolder[b[on]]].push(b);
})
});
return SegData;
}
Data = self.users.comboChart;
var margin = { top: 20, right: 30, bottom: 60, left: 40 },
width = 800 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var textWidthHolder = 0;
/// Adding Date in LineCategory
Data.forEach(function (d) {
d.LineCategory.forEach(function (b) {
b.Date = d.Date;
})
});
var Categories = new Array();
// Extension method declaration
// Categories.pro;
var Data;
var ageNames;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var XLine = d3.scale.ordinal()
.rangeRoundPoints([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var YLine = d3.scale.linear().range([height, 0])
.domain([0, d3.max(Data, function (d) { return d3.max(d.LineCategory, function (b) { return b.Value }) })]);
var color = d3.scale.ordinal()
.range(["#6aae6a", "#639ce4", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var line = d3.svg.line().x(function (d) {
return x0(d.Date) + x0.rangeBand() / 2;
}).y(function (d) { return YLine(d.Value) });
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
// var yAxis = d3.svg.axis()
// .scale(y)
// .orient("left")
// .tickFormat(d3.format(".2s"));
// var YLeftAxis = d3.svg.axis().scale(YLine).orient("right").tickFormat(d3.format(".2s"));
var svg = d3.select("#chart2").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 + ")");
// Bar Data categories
Data.forEach(function (d) {
d.Categories.forEach(function (b) {
if (Categories.findIndex(function (c) { return c.Name===b.Name}) == -1) {
b.Type = "bar";
// console.log(JSON.stringify(b))
Categories.push(b)
}
})
});
// Line Data categories
Data.forEach(function (d) {
d.LineCategory.forEach(function (b) {
if (Categories.findIndex(function (c) { return c.Name === b.Name }) == -1) {
b.Type = "line";
// console.log(JSON.stringify(b))
Categories.push(b)
}
})
});
// Processing Line data
lineData = DataSegregator(Data.map(function (d) { return d.LineCategory }), "Name");
// Line Coloring
LineColor = d3.scale.ordinal();
LineColor.domain(Categories.filter(function (d) { return d.Type == "line" }).map(function (d) { return d.Name }));
LineColor.range(["#333471", "#386a07", "#7f8ed4", "#671919", "#0b172b"])
x0.domain(Data.map(function (d) { return d.Date; }));
XLine.domain(Data.map(function (d) { return d.Date; }));
x1.domain(Categories.filter(function (d) { return d.Type == "bar" }).map(function (d) { return d.Name})).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(Data, function (d) { return d3.max(d.Categories, function (d) { return d.Value; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var state = svg.selectAll(".state")
.data(Data)
.enter().append("g")
.attr("class", "state")
.attr("transform", function (d) { return "translate(" + x0(d.Date) + ",0)"; });
state.selectAll("rect")
.data(function (d) { return d.Categories; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function (d) { return x1(d.Name); })
.attr("y", function (d) { return y(d.Value); })
//.attr("height", function (d) { return height - y(d.Value); })
.style("fill", function (d) { return color(d.Name); })
.transition().delay(500).attrTween("height", function (d) {
var i = d3.interpolate(0, height - y(d.Value));
return function (t)
{
return i(t);
}
});
// drawaing lines
svg.selectAll(".lines").data(lineData).enter().append("g").attr("class", "line")
.each(function (d) {
Name=d[0].Name
d3.select(this).append("path").attr("d", function (b) { return line(b) }).style({ "stroke-width": "3px", "fill": "none" }).style("stroke", LineColor(Name)).transition().duration(1500);
})
// Legends
var LegendHolder = svg.append("g").attr("class", "legendHolder");
var legend = LegendHolder.selectAll(".legend")
.data(Categories.map(function (d) { return {"Name":d.Name,"Type":d.Type}}))
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) { return "translate(0," +( height+ margin.bottom/2 )+ ")"; })
.each(function (d,i) {
// Legend Symbols
d3.select(this).append("rect")
.attr("width", function () { return 18 })
.attr("x", function (b) {
left = (i+1) * 15 + i * 18 + i * 5 + textWidthHolder;
return left;
})
.attr("y", function (b) { return b.Type == 'bar'?0:7})
.attr("height", function (b) { return b.Type== 'bar'? 18:5 })
.style("fill", function (b) { return b.Type == 'bar' ? color(d.Name) : LineColor(d.Name) });
// Legend Text
d3.select(this).append("text")
.attr("x", function (b) {
left = (i+1) * 15 + (i+1) * 18 + (i + 1) * 5 + textWidthHolder;
return left;
})
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(d.Name);
textWidthHolder += getTextWidth(d.Name, "10px", "calibri");
});
// Legend Placing (center)
d3.select(".legendHolder").attr("transform", function (d) {
thisWidth = d3.select(this).node().getBBox().width;
return "translate(" + ((width) / 2 - thisWidth / 2) + ",0)";
})
// round the rectangle corners
svg.selectAll('rect').attr('rx', 5).attr('ry', 5);
You can achieve by using Using Javascript's custom event feature
We need to register an event, globally with document as follows
Registering Event
document.addEventListener("updateUser", function(e) {
console.info("Event is: ", e);
console.info("Custom data is: ", e.detail);
// Place your logic to operate with new data
})
The above snippet needs to be placed in your d3 script.
Emitting the custom event
var myEvent = new CustomEvent("updateUser", {
detail: data // Your data from vuejs part
});
// Trigger it!
document.dispatchEvent(myEvent);

D3 multi line chart mouseover issue

I found a reference to the issue I am facing in this link
'nemesv' has provided with a solution to the actual issue.
var myApp = angular.module('app', []);
myApp.directive("lineChart", function() {
return {
restrict: 'E',
scope: {
data: '=',
id: '#'
},
link: function (scope, element, attrs) {
scope.$watch( 'data', function ( data ) {
d3.select("#"+attrs.id).select("svg").remove();
if (data) {
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = element[ 0 ].parentElement.offsetWidth - margin.left - margin.right,
height = element[ 0 ].parentElement.offsetHeight - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var bisectDate = d3.bisector(function(d) { return d[0]; }).left;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(-height)
.ticks(4)
.outerTickSize(0)
.tickPadding(5)
.tickFormat(function(d) { return d3.time.format('%d/%m %H:%M')(new Date(d)); });
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
var line = d3.svg.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
var svg = d3.select(element[0]).append("svg")
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox','0 0 '+ element[ 0 ].parentElement.offsetWidth +' '+ element[ 0 ].parentElement.offsetHeight )
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var minX = d3.min(data, function (item) { return d3.min(item.values, function (d) { return d[0]; }); });
var maxX = d3.max(data, function (item) { return d3.max(item.values, function (d) { return d[0]; }); });
var minY = d3.min(data, function (item) { return d3.min(item.values, function (d) { return d[1]; }); });
var maxY = d3.max(data, function (item) { return d3.max(item.values, function (d) { return d[1]; }); });
x.domain([minX, maxX]);
y.domain([0, maxY]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var domaine = svg.selectAll(".domaine")
.data(data)
.enter().append("g")
.attr("class", "domaine");
domaine.append("path")
.attr("class", "line")
.attr("d", function (d) {
return line(d.values);
})
.style("stroke", function (d) {
return d.color;
});
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
for(var i=0;i<data.length;i++){
focus.append("g")
.attr("class", "focus"+i)
.append("circle")
.attr("r", 4.5);
svg.select(".focus"+i)
.append("text")
.attr("x", 9)
.attr("dy", ".35em");
}
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]);
var series = data.map(function(e) {
var i = bisectDate(e.values, x0, 1),
d0 = e.values[i - 1],
d1 = e.values[i];
return x0 - d0[0] > d1[0] - x0 ? d1 : d0;
});
for(var i=0; i<series.length;i++){
var selectedFocus = svg.selectAll(".focus"+i);
selectedFocus.attr("transform", "translate(" + x(series[i][0]) + "," + y(series[i][1]) + ")");
selectedFocus.select("text").text(series[i][1]);
}
}
}
});
}
};
});
function MainCtrl($scope) {
$scope.lineData = [{"key": "users","color": "#16a085","values": [[1413814800000,4034.418],[1413815400000,5604.155000000001],[1413816000000,6343.079],[1413816600000,7308.226],[1413817200000,9841.185],[1413817800000,6571.891],[1413818400000,4660.6005000000005],[1413819000000,4555.4795],[1413819600000,5963.723],[1413820200000,9179.9595]]},{"key": "users 2","color": "#d95600","values": [[1413814800000,3168.183],[1413815400000,1530.8435],[1413816000000,2416.071],[1413816600000,1274.309],[1413817200000,1105.0445],[1413817800000,2086.0299999999997],[1413818400000,712.642],[1413819000000,1676.725],[1413819600000,3721.46],[1413820200000,2887.7975]]}];
}
Presently, if I hover over one line, I can see another dot hovering over the other line as well - i.e. mouseover is working simultaneously on both the lines. Could this mouseover/tooltip be done separately on each line i.e. when I hover over one line, focus circle will appear for only that line?

d3 stacked bar mouse over whole bar

I have created a stacked bar graph using a tutorial. The stack is created with the following:
var layers = d3.layout.stack()(["passed", "failed", "skipped"].map(function(cause){
return data.map(function(d){
return {x: d.UP, y: +d[cause]};
});
}));
When I hover my mouse over a stack, I want the whole stack to be marked (e.g. stronger border line).
I am currently using:
var barMouseOverFunction = function(d, i) {
var bar = d3.select(this);
bar.attr("stroke", "black" );
However this only changes for the current stack. Any ideas of how to get all the bars in the stack?
EDIT FULLCODE:
d3.json('db/data.php', function(data) {
var layers = d3.layout.stack()(["passed", "failed", "skipped"].map(function(cause){
return data.map(function(d){
return {x: d.UP, y: +d[cause]};
});
}));
var n = 3, // number of layers
stack = d3.layout.stack(),
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var x = d3.scale.ordinal()
.domain(data.map(function(d){return d.UP;}))
.rangeRoundBands([0, width], .4);
var y = d3.scale.linear()
.domain([0, yStackMax])
.range([height, 0]);
var color = d3.scale.ordinal()
.range(["#6ad46a", "#ed2828", "#fae650"]);
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(0)
.tickPadding(6)
.orient("bottom");
// Grid lines
var gridcontainer = svg.append('g');
gridcontainer.selectAll("line").data(y.ticks(10)).enter().append("line")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#87cbff")
.style("stroke-width", .3);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); })
.style("stroke", function(d, i){return d3.rgb(color(i)).darker();});
var timeformatter = d3.time.format("%b %e %Y %x%p");
// On mouseover display test case information
var barMouseOverFunction = function(d, i) {
var bar = d3.select(this);
bar.attr("stroke", "black" );
var dataItem = data[i];
}
var barMouseOutFunction = function() {
var bar = d3.select(this);
bar.attr("stroke", function(d){
return StatusColor(d.result);
});
d3.select("p").text("");
}
var rect = layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", height)
.attr("width", x.rangeBand())
.attr("height", 0)
.attr("rx", 0.5)
.attr("ry", 0.5)
.on("mouseover", barMouseOverFunction)
.on("mouseout", barMouseOutFunction);
The easiest way to do this is probably to assign a separate class to the bars for a particular stack and then select based on that, i.e. something like
var rect = layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("class", function(d, i, j) { return "stack" + j; })
...
var barMouseOverFunction = function(d, i, j) {
d3.selectAll("rect.stack" + j).attr("stroke", "black");
var dataItem = data[i];
}

Categories