D3.js axis label overlapping axis scale - javascript

I have the following example:
Please note that this code is just a combination of code I have found online.
Is there a way to prevent the Axis label/title from overlapping the axis scale/numbers? For example here the label is overlapping the numbers. In this example I could manually hardcode the label to be positioned more to the left which would work. However, what happens when we use changing data?
Say the data in the graph changes such that the y axis number may be in the 10s in one example but the next data set may have values in the 1,000,000's. Is it possible to make the graph/axis titles more dynamic/responsive to changing number lengths?
Is this something that can be done in the chart setup or is this as css issue. All suggestions or solutions are welcome.
I do not have much experience with D3.js so this may be a very simple issue to fix, I hope someone can help.
Thank you
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<script>
var width = 400,
height = 400;
var margin = {top: 50, bottom: 50, left: 50, right: 50}
var data = [0, 15, 20, 25, 30];
// Append SVG
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// Create scale
var scale = d3.scaleLinear()
.domain([ d3.max(data) , d3.min(data)])
.range([0, width - 100]);
var yAxis = d3.axisLeft()
.scale(scale);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + eval(margin.left-3) + ",0)")
.call(yAxis);
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("My testing title");
d3.selectAll(".tick text")
.attr("x", "-20");
</script>
</body>

I usually go with the title coming down the right side:
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + eval(margin.left-3) + ",0)")
.call(yAxis)
.append("text")
.text("Title")
.attr("transform","rotate(90)")
.attr("y",-2)
.style('fill','black')
.style("text-anchor","start")

Related

Axis-range-slider alignment

I need to design a d3 component like the one shown in the figure below.
I referred to an existing code sample from this link, and modified it to create something like this.
Left was changing the width of the axis, which I tried by changing the stroke-width property of the domain class. However, I ended with something like this.
Problems:
The slider handle isn't aligning with the axis.
The axis color imprints on the slider.
The ends of the axis are not perfectly round.
Questions:
I can't figure out what do I translate/transform to align the sliders and the axis.
I tried fiddling around with the opacity values, but didn't help.
I set stroke-linecap to round, but it's still not completely round.
I am using d3 v4 for this. And the jsfiddle for my final code is here.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
.tick{
visibility:hidden;
}
.domain {
stroke: grey;
stroke-width:10px;
stroke-linecap: round;
}
.selection {
fill:red
}
</style>
</head>
<body>
<div style="margin-left: 20px;margin-top: 20px;">
<span></span> to <span></span>
</div>
<script>
var margin = 20,
width = 400 - margin * 2,
height = 15;
// v3 = var x = d3.scale.linear()
var x = d3.scaleLinear()
.domain([0,100])
.range([0, width]);
/*
var brush = d3.svg.brush()
.x(x)
.extent([20, 50]);
*/
var brush = d3.brushX()
.extent([[0,0], [width,height]])
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin * 2)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
.call(d3.axisBottom()
.scale(x)
.tickSize(0));
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush)
// left circle
var left_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
var right_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
/*
Height of the brush's rect is now
generated by brush.extent():
brushg.selectAll("rect")
.attr("height", height);
*/
function brushed() {
/*
The brush attributes are no longer stored
in the brush itself, but rather in the
element it is brushing. That's where much of
the confusion around v4's brushes seems to be.
The new method is a little difficult to adapt
to, but seems more efficient. I think much of
this confusion comes from the fact that
brush.extent() still exists, but means
something completely different.
Instead of calling brush.extent() to get the
range of the brush, call
d3.brushSelection(node) on what is being
brushed.
d3.select('#start-number')
.text(Math.round(brush.extent()[0]));
d3.select('#end-number')
.text(Math.round(brush.extent()[1]));
*/
var range = d3.brushSelection(this)
.map(x.invert);
console.log('range->'+range)
d3.selectAll("span")
.text(function(d, i) {
console.log(Math.round(range[i]))
return Math.round(range[i])
})
left_text.attr("x", x(range[0]));
left_text.text(Math.round(range[0]));
right_text.attr("x", x(range[1]));
right_text.text(Math.round(range[1]));
d3.selectAll("rect").attr("dy", "-5em")
}
// v3: brushed();
brush.move(brushg, [20, 40].map(x));
</script>
</body>
</html>
The axis and the brush are actually perfectly aligned!
You can see this if you set the stroke-width to 1px:
.as-console-wrapper { max-height: 30% !important;}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
.tick{
visibility:hidden;
}
.domain {
stroke: grey;
stroke-width:1px;
stroke-linecap: round;
}
.selection {
fill:red
}
</style>
</head>
<body>
<div style="margin-left: 20px;margin-top: 20px;">
<span></span> to <span></span>
</div>
<script>
var margin = 20,
width = 400 - margin * 2,
height = 15;
// v3 = var x = d3.scale.linear()
var x = d3.scaleLinear()
.domain([0,100])
.range([0, width]);
/*
var brush = d3.svg.brush()
.x(x)
.extent([20, 50]);
*/
var brush = d3.brushX()
.extent([[0,0], [width,height]])
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin * 2)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
.call(d3.axisBottom()
.scale(x)
.tickSize(0));
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush)
// left circle
var left_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
var right_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
/*
Height of the brush's rect is now
generated by brush.extent():
brushg.selectAll("rect")
.attr("height", height);
*/
function brushed() {
/*
The brush attributes are no longer stored
in the brush itself, but rather in the
element it is brushing. That's where much of
the confusion around v4's brushes seems to be.
The new method is a little difficult to adapt
to, but seems more efficient. I think much of
this confusion comes from the fact that
brush.extent() still exists, but means
something completely different.
Instead of calling brush.extent() to get the
range of the brush, call
d3.brushSelection(node) on what is being
brushed.
d3.select('#start-number')
.text(Math.round(brush.extent()[0]));
d3.select('#end-number')
.text(Math.round(brush.extent()[1]));
*/
var range = d3.brushSelection(this)
.map(x.invert);
console.log('range->'+range)
d3.selectAll("span")
.text(function(d, i) {
console.log(Math.round(range[i]))
return Math.round(range[i])
})
left_text.attr("x", x(range[0]));
left_text.text(Math.round(range[0]));
right_text.attr("x", x(range[1]));
right_text.text(Math.round(range[1]));
d3.selectAll("rect").attr("dy", "-5em")
}
// v3: brushed();
brush.move(brushg, [20, 40].map(x));
</script>
</body>
</html>
So, what's happening here? The issue is that when you tell the browser to take a line (in this case it's a path, but it doesn't matter) and increase its stroke to, let's say, 100 pixels, it will increase 50 pixels to one side and 50 pixels to the other side. So, the middle of that thick axis is right on the top of the brush's rectangle.
There are several solutions here, like drawing an rectangle. If, however, you want to keep your approach of increasing the .domain stroke-width, let's break the selections and move the axis half its stroke-width down (here I'm increasing the width to 20 pixels, so it's easier to see the alignment):
.as-console-wrapper { max-height: 30% !important;}
<!DOCTYPE html>
<meta charset="utf-8">
<script src="//d3js.org/d3.v4.min.js"></script>
<!--
axes and brushes are styled out of the box,
so this is no longer needed
<style>
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.brush .extent {
fill-opacity: .125;
shape-rendering: crispEdges;
}
</style>
-->
<style>
.tick {
visibility: hidden;
}
.domain {
stroke: grey;
stroke-width: 20px;
stroke-linecap: round;
}
.selection {
fill: red
}
</style>
<body>
<div style="margin-left: 20px;margin-top: 20px;">
<span></span> to <span></span>
</div>
</body>
<script>
var margin = 20,
width = 400 - margin * 2,
height = 20;
// v3 = var x = d3.scale.linear()
var x = d3.scaleLinear()
.domain([0, 100])
.range([0, width]);
/*
var brush = d3.svg.brush()
.x(x)
.extent([20, 50]);
*/
var brush = d3.brushX()
.extent([
[0, 0],
[width, height]
])
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin * 2)
.attr("height", 100);
svg.append("g")
.attr("transform", "translate(" + margin + "," + (margin + 10) + ")")
.call(d3.axisBottom()
.scale(x)
.tickSize(0));
var brushg = svg.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
.attr("class", "brush")
.call(brush)
// left circle
var left_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
var right_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
/*
Height of the brush's rect is now
generated by brush.extent():
brushg.selectAll("rect")
.attr("height", height);
*/
function brushed() {
/*
The brush attributes are no longer stored
in the brush itself, but rather in the
element it is brushing. That's where much of
the confusion around v4's brushes seems to be.
The new method is a little difficult to adapt
to, but seems more efficient. I think much of
this confusion comes from the fact that
brush.extent() still exists, but means
something completely different.
Instead of calling brush.extent() to get the
range of the brush, call
d3.brushSelection(node) on what is being
brushed.
d3.select('#start-number')
.text(Math.round(brush.extent()[0]));
d3.select('#end-number')
.text(Math.round(brush.extent()[1]));
*/
var range = d3.brushSelection(this)
.map(x.invert);
console.log('range->' + range)
d3.selectAll("span")
.text(function(d, i) {
console.log(Math.round(range[i]))
return Math.round(range[i])
})
left_text.attr("x", x(range[0]));
left_text.text(Math.round(range[0]));
right_text.attr("x", x(range[1]));
right_text.text(Math.round(range[1]));
d3.selectAll("rect").attr("dy", "-5em")
}
// v3: brushed();
brush.move(brushg, [20, 40].map(x));
</script>
The path in the axis is a closed shape and stroking that gives problems. Also you don't want ticks so why not draw the "axis" yourself. Then the round edge will be drawn correct.
var svg = d3.select("body").append("svg")
.attr("width", width + margin * 2)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
// .call(d3.axisBottom()
// .scale(x)
// .tickSize(0))
;
svg.append("path")
.attr("class", "domain")
.attr("d", `M${x(0)},0 ${x(100)},0`);
You have to match the brush extent to the stroked path surface
var margin = 20,
width = 400 - margin * 2,
height = 10; // same as stroke width
var brush = d3.brushX()
.extent([[0,-height*0.5], [width,height*0.5]])
.on("brush", brushed);
The dy attribute has no purpose
//d3.selectAll("rect").attr("dy", "-5em")
Set the fill-opacity of the selection
.selection {
fill:red;
fill-opacity: 1;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
.tick{
visibility:hidden;
}
.domain {
stroke: grey;
stroke-width:10;
stroke-linecap: round;
}
.selection {
fill:red;
fill-opacity: 1;
}
</style>
</head>
<body>
<div style="margin-left: 20px;margin-top: 20px;">
<span></span> to <span></span>
</div>
<script>
var margin = 20,
width = 400 - margin * 2,
height = 10; // same as stroke width
// v3 = var x = d3.scale.linear()
var x = d3.scaleLinear()
.domain([0,100])
.range([0, width]);
/*
var brush = d3.svg.brush()
.x(x)
.extent([20, 50]);
*/
var brush = d3.brushX()
.extent([[0,-height*0.5], [width,height*0.5]])
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin * 2)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")")
// .call(d3.axisBottom()
// .scale(x)
// .tickSize(0))
;
svg.append("path")
.attr("class", "domain")
.attr("d", `M${x(0)},0 ${x(100)},0`);
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush)
// left circle
var left_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
var right_text = brushg.append("text")
.attr("class", "label")
.attr("fill", "black")
.attr("text-anchor", "middle")
.text("hello world")
.attr("transform", "translate(0," + (35) + ")")
/*
Height of the brush's rect is now
generated by brush.extent():
brushg.selectAll("rect")
.attr("height", height);
*/
function brushed() {
/*
The brush attributes are no longer stored
in the brush itself, but rather in the
element it is brushing. That's where much of
the confusion around v4's brushes seems to be.
The new method is a little difficult to adapt
to, but seems more efficient. I think much of
this confusion comes from the fact that
brush.extent() still exists, but means
something completely different.
Instead of calling brush.extent() to get the
range of the brush, call
d3.brushSelection(node) on what is being
brushed.
d3.select('#start-number')
.text(Math.round(brush.extent()[0]));
d3.select('#end-number')
.text(Math.round(brush.extent()[1]));
*/
var range = d3.brushSelection(this)
.map(x.invert);
//console.log('range->'+range)
d3.selectAll("span")
.text(function(d, i) {
//console.log(Math.round(range[i]))
return Math.round(range[i])
})
left_text.attr("x", x(range[0]));
left_text.text(Math.round(range[0]));
right_text.attr("x", x(range[1]));
right_text.text(Math.round(range[1]));
//d3.selectAll("rect").attr("dy", "-5em")
}
// v3: brushed();
brush.move(brushg, [20, 40].map(x));
</script>
</body>
</html>

D3.js -- How do I update dataset via Javascript?

I have a dataset with 11 different variables (csv file with 12 columns). I want to be able to select a certain column for my scatterplot, but I'm having some difficulties. Please bear with me, as JavaScript is not my strong suit (obviously). Here's what I attempted:
<div class="variables" id="fixedacidity" onclick="drawPlot('fixedacidity');">
<h1>fixed acidity</h1>
</div>
<div class="variables" id="volatileacidity" onclick="drawPlot('volatileacidity');">
<h1>volatile acidity</h1>
</div>
<div class="variables" id="citricacid" onclick="drawPlot('citricacid');">
<h1>citric acid</h1>
</div>
<div class="variables" id="residualsugar" onclick="drawPlot('residualsugar');">
<h1>residual sugar</h1>
</div>
etc ...
I made a simple menu that calls on the drawPlot function, but I'm having trouble trying to get the variable to pass on correctly.
Relevant d3/javascript:
function drawPlot(selectedVar){
$(".visarea").html("");
var wineVar = selectedVar;
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 860 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0,10]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left").ticks(10);
var chart1 = d3.select(".visarea").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.csv("red.csv", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.wineVar = +d.wineVar;
d.quality = +d.quality;
});
x.domain(d3.extent(data, function(d) { return d.wineVar; })).nice();
y.domain([0,10]).nice();
chart1.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text(wineVar);
chart1.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Rated Quality")
chart1.selectAll(".red.dot")
.data(data)
.enter().append("circle")
.attr("class", "red dot")
.attr("r", 3)
.attr("cx", function(d) { return x(d.wineVar); })
.attr("cy", function(d) { return y(d.quality); })
.style("fill", "red");
});
}
Although the variable gets passed on to the function, d.wineVar, as expected, does not return the desired value, and thus the chart does not draw the correct values. Can anyone recommend a workaround for this? It seems so simple, yet I've spent hours failing trying to figure this out.
Sample of red.csv:
fixedacidity,volatileacidity,citricacid,residualsugar,chlorides,freesulfurdioxide,totalsulfurdioxide,density,pH,sulphates,alcohol,quality
7.4,0.7,0,1.9,0.076,11,34,0.9978,3.51,0.56,9.4,5
7.8,0.88,0,2.6,0.098,25,67,0.9968,3.2,0.68,9.8,5
7.8,0.76,0.04,2.3,0.092,15,54,0.997,3.26,0.65,9.8,5
Image of what I'm trying to accomplish. The first dataset, fixedacidity, gets drawn up fine. I'm having difficulties trying to get the menu to correctly show its respective dataset. "Rated Quality" will always be the data for the Y-axis.
You has wrong variable reference, here:
data.forEach(function(d) {
d.wineVar = +d.wineVar; // <---------Here
d.quality = +d.quality;
});
change by:
data.forEach(function(d) {
d.wineVar = +d[wineVar]; // <----------Here
d.quality = +d.quality;
});
There is the obvious issue pointed out by klaujesi about data extraction. But there are more issues with your code.
I would say you need to adapt your approach to the way d3.js works. Currently you will add a new svg on each call to the function, caused by this line in your code: d3.select(".visarea").append("svg")
I usually have some init code wrapped in one function, which creates the svg and sets ups everything static. Then there is an update function which will handle input changes to show different data, use different scales etc.
The nice thing about d3.js is that you can control very easily what's to happen with newly introduced data via .enter() and removed data via .exit().

How do you move number labels further away from the x-axis in d3.js?

Newbie question: How can one move the x-axis tick labels further from the x-axis? The snippet of code below produces this:
Whereas what I want is more like this:
From the question here:
d3.js: Align text labels between ticks on the axis
it seems that I need to select the text that contains these labels using select and then apply something like translate(0,-10) to them, but I can't figure out where this text "lives"/the syntax for performing such a selection. Sorry if this is simple; I am very new to D3 (and javascript). Can anyone help? Thanks in advance! Code follows.
<script>
var superscript = "⁰¹²³⁴⁵⁶⁷⁸⁹",
formatPower = function(d) { return (d + "").split("").map(function(c) { return superscript[c]; }).join("");\
};
var margin = {top: 20, right: 20, bottom: 100, left: 100},
width = 400,
height = 400;
var x = d3.scale.log()
.domain([1e1, 1e4])
.range([0, width])
.nice();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickValues([1e1, 1e2, 1e3, 1e4])
.ticks(0, function(d) { return 10 + formatPower(Math.round(Math.log(d) / Math.LN10)); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", (width+margin.left)/2)
.attr("y", 60)
.style("text-anchor", "end")
.text("x-axis label");
</script>
There's a simpler way to do this using tickPadding. Where you define xAxis, just add .tickPadding(15) (change 15 to the number of pixels you want).
Example here: http://jsfiddle.net/henbox/5q4k2r0p/1/

Axis not displaying and alignment

Below code draws a circle and an x axis. There are a couple of problems. The y axis is not displayed but it is being added :
svgContainer.append('g')
.attr("class", "axis")
.call(yAxis);
The x axis is not being aligned to bottom but I have set the orientation to bottom :
orient("bottom").ticks(5);
here is complete code :
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
var svgContainer = d3.select("body").append("svg")
.attr("width", 1000)
.attr("height", 1000);
var circle = svgContainer.append("circle")
.attr("cx", 150)
.attr("cy", 150)
.attr("r", 100)
.attr("fill", "none")
.attr("stroke" , "red")
.attr("stroke-width" , 2);
var x = d3.scale.linear().range([0, 1000]);
var y = d3.scale.linear().range([0, 1000]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
svgContainer.append('g')
.attr("class", "axis")
.call(xAxis);
svgContainer.append('g')
.attr("class", "axis")
.call(yAxis);
</script>
</body>
</html>
fiddle : http://jsfiddle.net/zzz8svuq/1/
Why are these issues occurring ?
Setting the orientation only affects the placement of the labels with respect to the axis line, D3 won't automatically position the axis on the canvas for you. The way this is usually done is to set a margin for the axes and offset the containers accordingly:
svgContainer.append('g')
.attr("class", "axis")
.attr("transform", "translate(" + margin + "," + (1000 - margin) + ")")
.call(xAxis);
svgContainer.append('g')
.attr("class", "axis")
.attr("transform", "translate(" + margin + ",0)")
.call(yAxis);
Complete demo here.

How do I update this data without redrawing datapoints that are already there?

New to D3. I am trying to modify the simple bar chart example shown here. I'm trying to update the data but am missing something fundamental. I'm trying to follow along here, where Mike talks about object constancy. Specifically, I am trying to achieve the following in my code:
Key functions can be useful for improving performance independent of transitions. For example, if you filter a large table, you can use a key function to reduce the number of DOM modifications: reorder DOM elements in the update selection rather than regenerating them. We used this technique at Square to improve the performance of merchant analytics, and it’s one of the reasons that D3 is faster than most template frameworks.
(In my case, my key function is simply ".data(data)" (which is ok, according to this post)
My code below works, but I suspect isn't the most performance friendly. For instance, frequency "70" is in both sets of data, but by "removing" the data, I am effectively redrawing it. (If I don't "remove" the data first, then another chart is drawn, rather than the old chart just getting updated data). How do I modify the code below to adhere to the key function and so that data that exists in both datasets doesn't get redrawn?
My code for the bar chart:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<button id="change" name="change">Update</button>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js" type="text/javascript"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
$(document).ready(function () {
var old_data = [{"letter": "A","frequency": "50"},
{"letter": "B","frequency": "60"},
{"letter": "C","frequency": "70"}, // this also appears in new_data
{"letter": "D","frequency": "80"},
];
draw_chart(old_data);
$("#change").click(function(){
var new_data = [{"letter": "A","frequency": "10"},
{"letter": "B","frequency": "20"},
{"letter": "C","frequency": "70"}, // this appears in old_data
{"letter": "D","frequency": "30"},
];
var bar = d3.select('body').selectAll('svg').remove();// delete this line and you'll get multiple charts rather than just updating the data in the original chart
draw_chart(new_data);
});
});
</script>
<script>
function draw_chart(data){
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
data.forEach(function(d) {
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", height)
.attr("height","0")
.transition()
.delay(function(d, i) { return i*300 })
.duration(1000)
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
}
</script>
First, the reason why you have the line,
var bar d3.select('body')...remove() // delete this line and you'll get double...
Is beacuse in your draw_chart your always appending to the page when it's called. You need to change this line,
var svg = d3.select("body").append("svg")
to something that doesn't continually append a new svg
If I have more time i'll take a look at the main question.

Categories

Resources