Related
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>
I drawn a stackedbar Chart in which now I trying to place the Total value(I mean the yaxis value) on tip of the every rectangle. I have coded to fetch the details but here the problem is I am getting Every Layer value on tip of every layer but i need to show only the last layer value.
the problem is shown in below fig's.
My code is Shown below
var fData =
[{"orders":"A","Total_Orders":76,"A_Lines":123,"B_Lines":0,"C_Lines":0,"D_Lines":0,"Total_Lines":123,"Total_Units":3267},
{"orders":"B","Total_Orders":68,"A_Lines":0,"B_Lines":107,"C_Lines":0,"D_Lines":0,"Total_Lines":107,"Total_Units":3115},
{"orders":"C","Total_Orders":81,"A_Lines":0,"B_Lines":0,"C_Lines":123,"D_Lines":0,"Total_Lines":123,"Total_Units":3690},
{"orders":"D","Total_Orders":113,"A_Lines":0,"B_Lines":0,"C_Lines":0,"D_Lines":203,"Total_Lines":203,"Total_Units":7863},
{"orders":"AB","Total_Orders":62,"A_Lines":70,"B_Lines":76,"C_Lines":0,"D_Lines":0,"Total_Lines":146,"Total_Units":1739},
{"orders":"AC","Total_Orders":64,"A_Lines":77,"B_Lines":0,"C_Lines":79,"D_Lines":0,"Total_Lines":156,"Total_Units":2027},
{"orders":"AD","Total_Orders":100,"A_Lines":127,"B_Lines":0,"C_Lines":0,"D_Lines":144,"Total_Lines":271,"Total_Units":6467},
{"orders":"BC","Total_Orders":64,"A_Lines":0,"B_Lines":80,"C_Lines":84,"D_Lines":0,"Total_Lines":164,"Total_Units":1845},
{"orders":"BD","Total_Orders":91,"A_Lines":0,"B_Lines":108,"C_Lines":0,"D_Lines":135,"Total_Lines":243,"Total_Units":4061},
{"orders":"CD","Total_Orders":111,"A_Lines":0,"B_Lines":0,"C_Lines":132,"D_Lines":147,"Total_Lines":279,"Total_Units":5011},
{"orders":"ABC","Total_Orders":45,"A_Lines":58,"B_Lines":63,"C_Lines":55,"D_Lines":0,"Total_Lines":176,"Total_Units":1245},
{"orders":"ABD","Total_Orders":69,"A_Lines":105,"B_Lines":87,"C_Lines":0,"D_Lines":116,"Total_Lines":308,"Total_Units":4538},
{"orders":"ACD","Total_Orders":66,"A_Lines":91,"B_Lines":0,"C_Lines":88,"D_Lines":132,"Total_Lines":311,"Total_Units":4446},{
{"orders":"BCD","Total_Orders":68,"A_Lines":0,"B_Lines":84,"C_Lines":95,"D_Lines":111,"Total_Lines":290,"Total_Units":4187},
{"orders":"ABCD","Total_Orders":56,"A_Lines":96,"B_Lines":90,"C_Lines":93,"D_Lines":143,"Total_Lines":422,"Total_Units":6331}]
var headers = ["A_Lines", "B_Lines", "C_Lines", "D_Lines"];
var colors = ["#9999CC", "#F7A35C", "#99CC99", "#CCCC99"];
var colorScale = d3.scale.ordinal()
.domain(headers)
.range(colors);
var layers = d3.layout.stack()(headers.map(function (count) {
return fData.map(function (d, i) {
// alert(d);
return { x: d.ORDER_TYPE, y: +d[count], color: colorScale(count) };
});
}));
//StackedBar Rectangle Max
var yStackMax = d3.max(layers, function (layer) { return d3.max(layer, function (d) { return d.y0 + d.y; }); });
// Set x, y and colors
var xScale = d3.scale.ordinal()
.domain(layers[0].map(function (d) { return d.x; }))
.rangeRoundBands([25, width], .08);
var y = d3.scale.linear()
.domain([0, yStackMax])
.range([height, 0]);
// var color = d3.scale.ordinal()
//.domain(headers)
// .range(["#9999CC", "#F7A35C", "#99CC99", "#CCCC99"]);
// Define and draw axes
var xAxis = d3.svg.axis()
.scale(xScale)
.tickSize(1)
.tickPadding(6)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function (d, i) { return colorScale(i); });
var rect = layer.selectAll("rect")
.data(function (d) { return d; })
.enter().append("rect")
.attr("x", function (d) { return xScale(d.x); })
.attr("y", height)
.attr("width", xScale.rangeBand())
.attr("height", 0)
.attr("class", function (d) {
return "rect bordered " + "color-" + d.color.substring(1);
});
layer.selectAll("text.rect")
.data(function (layer) { return layer; })
.enter().append("text")
.attr("text-anchor", "middle")
.attr("x", function (d) { return xScale(d.x) + xScale.rangeBand() / 2; })
.attr("y", function (d) { return y(d.y + d.y0) - 3; })
.text(function (d) { return d.y + d.y0; })
.style("fill", "4682b4");
//********** AXES ************
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text").style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function (d) {
return "rotate(-45)"
});
svg.attr("class", "x axis")
.append("text")
.attr("text-anchor", "end") // this makes it easy to centre the text as the transform is applied to the anchor
.attr("transform", "translate(" + (width / 2) + "," + (height + 60) + ")") // centre below axis
.text("Order Velocity Group");
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(20,0)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr({ "x": -75, "y": -70 })
.attr("dy", ".75em")
.style("text-anchor", "end")
.text("No. Of Lines");
//********** LEGEND ************
var legend = svg.selectAll(".legend")
.data(headers.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) { return "translate(" + i * (-100) + "," + (height + 50) + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
//.style("fill", color);
.style("fill", function (d, i) { return colors[i]; })
.on("mouseover", function (d, i) {
svg.selectAll("rect.color-" + colors[i].substring(1)).style("stroke", "blue");
})
.on("mouseout", function (d, i) {
svg.selectAll("rect.color-" + colors[i].substring(1)).style("stroke", "white");
});
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
transitionStacked();
function transitionStacked() {
y.domain([0, yStackMax]);
rect.transition()
.duration(500)
.delay(function (d, i) { return i * 10; })
.attr("y", function (d) { return y(d.y0 + d.y); })
.attr("height", function (d) { return y(d.y0) - y(d.y0 + d.y); })
.transition()
.attr("x", function (d) { return xScale(d.x); })
.attr("width", xScale.rangeBand());
rect.on('mouseover', tip.show)
.on('mouseout', tip.hide)
};
can anyone help me.
Here is what you need: First, we'll set fData as the data for the texts:
layer.selectAll("text.rect")
.data(fData)
.enter()
.append("text")
But, as this dataset doesn't have the correct yScale value, we'll have to extract it using a filter:
.attr("y", function (d) {
var filtered = fData.filter(function(e){
return e.Total_Units == d.Total_Units
});
return y(filtered[0].Total_Lines) - 3;
})
All together:
layer.selectAll("text.rect")
.data(fData)
.enter().append("text")
.attr("text-anchor", "middle")
.attr("x", function(d) {
return xScale(d.orders) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
var filtered = fData.filter(function(e) {
return e.Total_Units == d.Total_Units
});
return y(filtered[0].Total_Lines) - 3;
})
.text(function(d) {
return d.Total_Units
})
.style("fill", "4682b4");
Here is your fiddle: https://jsfiddle.net/wnwb6meh/
I am trying to dynamically update my d3 treemap when the data updates. I have two functions, one that I call initially to build the treemap and another to redraw the treemap. When I redraw the treemap, I get a thin black bar on top of the treemap, and the first element in the array that I am graphing goes black. If I click on either of the two black elements. I get the error...
Uncaught TypeError: Cannot read property 'dx' of undefined
So the data being passed to the cell is undefined.
Additionally when I call regraph I have checked and the data has changed, but the graph is unchanged from when I initially built the treemap with the exception of the two black elements.
The code for building the treemap is below. Also the createObj function takes two arrays and creates a Json object.
function drawTreeMap(array1,array2){
console.log("got to drawing");
nestedJson=createObj(array1, array2);
w = 1880 - 80,
h = 900 - 180,
x = d3.scale.linear().range([0, w]),
y = d3.scale.linear().range([0, h]),
color = d3.scale.linear()
.range(['lightgreen', 'darkgreen']) // or use hex values
.domain([computeMin(array2), computeMaxNum(array2)]);
root,
node;
treemap = d3.layout.treemap()
.round(false)
.size([w, h])
.sticky(true)
.padding([10, 0, 0, 0])
.value(function(d) { return d.size; });
svg = d3.select("#body").append("div")
.attr("class", "chart")
.style("width", w + "px")
.style("height", h + "px")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(.5,.5)");
node = root = nestedJson;
var nodes = treemap.nodes(root)
.filter(function(d) { return !d.children; });
var cell = svg.selectAll("g")
.data(nodes)
.enter().append("svg:g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", function(d) { return zoom(node == d.parent ? root : d.parent); });
cell.append("svg:rect")
.attr("width", function(d) { return d.dx - 1; })
.attr("height", function(d) { return d.dy ; })
.style("fill", function(d) { return color(d.size)});
cell.append("svg:text")
.attr("x", function(d) { return d.dx / 2; })
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return (d.name+",-- "+d.size); })
.style("opacity", function(d) { d.w = this.getComputedTextLength(); return d.dx > d.w ? 1 : 0; });
d3.select(window).on("click", function() { zoom(root); });
d3.select("select").on("change", function() {
treemap.value(this.value == "size" ? size : count).nodes(root);
zoom(node);
});
}
The code for redrawing is below.
function redrawGraphFromJson(data) {
treemap = d3.layout.treemap()
.round(false)
.size([w,h])
.value(function(d) { return d.size; })
.sticky(true);
// Draw the graph
node = root = data;
var nodes = treemap.nodes(root)
.filter(function(d) { return !d.children; });
var cell = svg.selectAll("g")
.data(nodes)
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", function(d) { return zoom(node == d.parent ? root : d.parent); });
cell.append("svg:rect")
.attr("width", function(d) { return d.dx - 1; })
.attr("height", function(d) { return d.dy ; })
.style("fill", function(d) { return color(d.size);});
cell.append("svg:text")
.attr("x", function(d) { return d.dx / 2; })
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return (d.name+",-- "+d.size); })
.style("opacity", function(d) { d.w = this.getComputedTextLength(); return d.dx > d.w ? 1 : 0; });
}
Thank you.
In your redrawing of the graph, you are going to want to .exit() on the initial graph and then .enter() to update the groups with the new data. This will replace the old map with the new.
http://bost.ocks.org/mike/join/ explains it perfectly and has a really good example to look at.
I cannot figure out why during the chart transitions in the attached JSFiddle the bars do not refresh properly.
If you click on "CLICK ME" a few times you will see that when DATA1 is selected (for example) bars for x values of 13 to 19 do not appear. I have three data sets that the chart randomly picks to use in refreshing itself, each with a different number of x-values.
Can someone help me understand please. Thank you in advance!
Code:
var data;
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 838 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.05);
var yL = d3.scale.linear()
.range([height, 0]);
var yR = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxisL = d3.svg.axis()
.scale(yL)
.orient("left")
.ticks(10);
var yAxisR = d3.svg.axis()
.scale(yR)
.orient("right")
.ticks(10);
var EfficiencyLine = d3.svg.line()
.interpolate("basis")
.x(function (d) {
return x(d.xaxis);
})
.y(function (d) {
return yR(d.max_efficiency);
});
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>Energy:</strong> <span class='tt-text'>" + d.max_energy + "</span><br /><strong>Efficiency:</strong> <span class='tt-text'>" + d.max_efficiency + "</span>";
})
var svg = d3.select("#daychart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.call(tip);
var which_data = Math.floor(Math.random() * 3) + 1
switch (which_data) {
case 1:
data = data1;
break;
case 2:
data = data2;
break;
case 3:
data = data3;
break;
default:
};
//d3.json("http://10.0.0.13/sma/sma-php/inverterdata.php?var=CDAY&id=C1200031", function (error, data) {
data.forEach(function (d) {
d.max_energy = +d.max_energy;
d.max_efficiency = +d.max_efficiency;
});
x.domain(data.map(function (d) {
return d.xaxis;
}));
yL.domain([0, d3.max(data, function (d) {
return d.max_energy;
})]);
yR.domain([0, d3.max(data, function (d) {
return d.max_efficiency;
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("transform", "rotate(0)")
.attr("y", 23)
.attr("x", 340)
.attr("dy", ".71em")
.style("text-anchor", "bottom")
.text("Timeline");
svg.append("g")
.attr("class", "y-l axis")
.call(yAxisL)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -50)
.attr("x", -145)
.attr("dy", ".71em")
.style("text-anchor", "top")
.text("Energy - KWh");
svg.append("g")
.attr("class", "y-r axis")
.attr("transform", "translate(" + width + " ,0)")
.call(yAxisR)
.append("text")
.attr("y", 50)
.attr("x", -160)
.attr("transform", "translate(" + width + " ,0)")
.attr("transform", "rotate(-90)")
.attr("dy", ".71em")
.style("text-anchor", "top")
.text("Efficiency - KWh/KW");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.attr("class", "bar")
.attr("x", function (d) {
return x(d.xaxis);
})
.attr("width", x.rangeBand())
.attr("y", function (d) {
return yL(d.max_energy);
})
.transition().delay(function (d, i) {
return i * 10;
})
.duration(10)
.attr("height", function (d) {
return height - yL(d.max_energy);
});
svg.append("path")
.attr("d", EfficiencyLine(data))
.attr("class", "EfficiencyLine");
/* //Create labels
svg.selectAll("text.label")
.data(data)
.enter()
.append("text")
.attr("class", "label")
.text(function (d) {
if (d.max_energy == 0) {
return "";
} else {
return parseFloat(Math.round(d.max_energy * 100) / 100).toFixed(1);
};
})
.attr("x", function (d) {
return x(d.xaxis) + x.rangeBand() / 2;
})
.attr("y", function (d) {
return yL(d.max_energy) - 2;
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black");
*/
//});
//On click, update with new data
d3.select("p").on("click", function () {
var which_data = Math.floor(Math.random() * 3) + 1
switch (which_data) {
case 1:
data = data1;
break;
case 2:
data = data2;
break;
case 3:
data = data3;
break;
default:
};
// Get the data again
// d3.json("http://10.0.0.13/sma/sma-php/inverterdata.php?var=PDAY&id=P100023", function (error, data) {
data.forEach(function (d) {
d.max_energy = +d.max_energy;
d.max_efficiency = +d.max_efficiency;
});
// Scale the range of the data again
x.domain(data.map(function (d) {
return d.xaxis;
}));
yL.domain([0, d3.max(data, function (d) {
return d.max_energy;
})]);
yR.domain([0, d3.max(data, function (d) {
return d.max_efficiency;
})]);
svg.select("g.x").call(xAxis);
svg.select("g.y-l").call(yAxisL);
svg.select("g.y-r").call(yAxisR);
// Make the changes
svg.selectAll(".bar") // change the bar
.data(data) // Update the data within.
.transition().delay(function (d, i) {
return i / data.length * 1000;
})
.duration(500)
.attr("x", function (d) {
return x(d.xaxis);
})
.attr("y", function (d) {
return yL(d.max_energy);
})
.attr("width", x.rangeBand())
.attr("height", function (d) {
return height - yL(d.max_energy);
});
svg.selectAll("path.EfficiencyLine") // change the EfficiencyLine
.data(data) // Update the data within.
.transition().delay(function (d, i) {
return i / data.length * 1000;
})
.duration(500)
.attr("d", EfficiencyLine(data));
/* svg.selectAll("text.label")
.data(data)
.transition().delay(function (d, i) {
return i / data.length * 1000;
})
.duration(500)
.text(function (d) {
if (d.max_energy == 0) {
return "";
} else {
return parseFloat(Math.round(d.max_energy * 100) / 100).toFixed(1);
};
})
.attr("x", function (d) {
return x(d.xaxis) + x.rangeBand() / 2;
})
.attr("y", function (d) {
return yL(d.max_energy) - 2;
})
*/
//});
});
You need to handle the .enter() and .exit() selections in your update functions as well, as the data sets are of different size:
var sel = svg.selectAll(".bar") // change the bar
.data(data);
sel.exit().remove();
sel.enter().append("rect")
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.attr("class", "bar");
sel.transition().delay(function (d, i) {
return i / data.length * 1000;
})...
Complete example here.
I have an issue on my bar/line chart whereby when data is refreshed, the left hand side Y axis has its tick marks change orientation from left to right. I am sure I have muddled up something simple as I am 4 days into D3, but I cannot see the problem.
My code is in JFIDDLE here. I have also added it to this post. Thank you for any assistance!
var data;
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 838 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.05);
var yL = d3.scale.linear()
.range([height, 0]);
var yR = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxisL = d3.svg.axis()
.scale(yL)
.orient("left")
.ticks(10);
var yAxisR = d3.svg.axis()
.scale(yR)
.orient("right")
.ticks(10);
var EfficiencyLine = d3.svg.line()
.interpolate("basis")
.x(function (d) {
return x(d.xaxis);
})
.y(function (d) {
return yR(d.max_efficiency);
});
var svg = d3.select("#daychart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var which_data = Math.floor(Math.random() * 3) + 1
switch (which_data) {
case 1:
data = data1;
break;
case 2:
data = data2;
break;
case 3:
data = data3;
break;
default:
};
//d3.json("http://10.0.0.13/sma/sma-php/inverterdata.php?var=CDAY&id=C1200031", function (error, data) {
data.forEach(function (d) {
d.max_energy = +d.max_energy;
d.max_efficiency = +d.max_efficiency;
});
x.domain(data.map(function (d) {
return d.xaxis;
}));
yL.domain([0, d3.max(data, function (d) {
return d.max_energy;
})]);
yR.domain([0, d3.max(data, function (d) {
return d.max_efficiency;
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("transform", "rotate(0)")
.attr("y", 23)
.attr("x", 340)
.attr("dy", ".71em")
.style("text-anchor", "bottom")
.text("Timeline");
svg.append("g")
.attr("class", "y axis")
.call(yAxisL)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -50)
.attr("x", -145)
.attr("dy", ".71em")
.style("text-anchor", "top")
.text("Energy - KWh");
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + " ,0)")
.call(yAxisR)
.append("text")
.attr("y", 50)
.attr("x", -160)
.attr("transform", "translate(" + width + " ,0)")
.attr("transform", "rotate(-90)")
.attr("dy", ".71em")
.style("text-anchor", "top")
.text("Efficiency - KWh/KW");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) {
return x(d.xaxis);
})
.attr("width", x.rangeBand())
.attr("y", function (d) {
return yL(d.max_energy);
})
.transition().delay(function (d, i) {
return i * 10;
}).duration(10)
.attr("height", function (d) {
return height - yL(d.max_energy);
});
svg.append("path")
.attr("d", EfficiencyLine(data))
.attr("class", "EfficiencyLine");
//Create labels
svg.selectAll("text.label")
.data(data)
.enter()
.append("text")
.attr("class", "label")
.text(function (d) {
if (d.max_energy == 0) {
return "";
} else {
return parseFloat(Math.round(d.max_energy * 100) / 100).toFixed(1);
};
})
.attr("x", function (d) {
return x(d.xaxis) + x.rangeBand() / 2;
})
.attr("y", function (d) {
return yL(d.max_energy) - 2;
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black");
//});
//On click, update with new data
d3.select("p").on("click", function () {
var which_data = Math.floor(Math.random() * 3) + 1
switch (which_data) {
case 1:
data = data1;
break;
case 2:
data = data2;
break;
case 3:
data = data3;
break;
default:
};
// Get the data again
// d3.json("http://10.0.0.13/sma/sma-php/inverterdata.php?var=PDAY&id=P100023", function (error, data) {
data.forEach(function (d) {
d.max_energy = +d.max_energy;
d.max_efficiency = +d.max_efficiency;
});
// Scale the range of the data again
x.domain(data.map(function (d) {
return d.xaxis;
}));
yL.domain([0, d3.max(data, function (d) {
return d.max_energy;
})]);
yR.domain([0, d3.max(data, function (d) {
return d.max_efficiency;
})]);
svg.select("g.x").call(xAxis);
svg.select("g.y").call(yAxisL); <---- PROBLEM HERE IS SUSPECT?!
svg.select("g.y").call(yAxisR);
// Make the changes
svg.selectAll(".bar") // change the bar
.data(data) // Update the data within.
.transition().delay(function (d, i) {
return i / data.length * 1000;
})
.duration(500)
.attr("x", function (d) {
return x(d.xaxis);
})
.attr("y", function (d) {
return yL(d.max_energy);
})
.attr("width", x.rangeBand())
.attr("height", function (d) {
return height - yL(d.max_energy);
});
svg.selectAll("path.EfficiencyLine") // change the EfficiencyLine
.data(data) // Update the data within.
.transition().delay(function (d, i) {
return i / data.length * 1000;
})
.duration(500)
.attr("d", EfficiencyLine(data));
svg.selectAll("text.label")
.data(data)
.transition().delay(function (d, i) {
return i / data.length * 1000;
})
.duration(500)
.text(function (d) {
if (d.max_energy == 0) {
return "";
} else {
return parseFloat(Math.round(d.max_energy * 100) / 100).toFixed(1);
};
})
.attr("x", function (d) {
return x(d.xaxis) + x.rangeBand() / 2;
})
.attr("y", function (d) {
return yL(d.max_energy) - 2;
})
//});
});
Your problem is in following two lines: (line number 492-493)
svg.select("g.y").call(yAxisL);
svg.select("g.y").call(yAxisR);
Your solution is to have two y axes two different classes, and you will achieve that by changing above lines to:
svg.select("g.y-l").call(yAxisL);
svg.select("g.y-r").call(yAxisR);
and also change
line 386 to:
.attr("class", "y-l axis")
line 397 to:
.attr("class", "y-r axis")
You need class names that are consistent.
Updated working fiddle is here
Let me know if you need any additional clarification, have a dilemma etc.