I am trying to create a bar chart that has 'attendees' and 'coins'. The data is being read from an external file and I'd like to update the chart as the data changes (or check the file every couple seconds and update the data). I have been trying to follow along mbostock's tutorial on the general update pattern but have had a heck of a time even starting to adapt for my own chart. I didn't find any other questions/answers that dealt with transitioning data from external files, but if I missed something, please let me know. So, with that, I turn you all!
Here is my current JS code:
var margin = {top: 40, right: 20, bottom: 30, left: 40},
width = 950 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatNumber = d3.format(".1f");
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")
.tickFormat(formatNumber);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Coins:</strong> <span style='color:red'>" + d.coins + "</span>";
})
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.call(tip);
d3.tsv("data.tsv", type, function(error, data) {
x.domain(data.map(function(d) { return d.attendee; }));
y.domain([0, d3.max(data, function(d) { return d.coins; })]);
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", ".5em")
.style("text-anchor", "end")
.text("Coins");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.attendee); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.coins); })
.attr("height", function(d) { return height - y(d.coins); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
});
function type(d) {
d.coins = +d.coins;
return d;
}
var inter = setInterval(function() {
update();
}, 1000);
function update() {
}
I should also mention that this is the first time really trying to dig deeper with D3. I hope I am not missing something too obvious! Thank you in advance for any help, suggestions, or pushes in the right direction!
** Edit to note the bar chart is an attempt to add functionality upon the sample found here.
Edit 2: Adding .tsv here for better formatting:
attendee coins
George 35
Charlie 50
Harrison 50
Billy 45
Wally 30
Harley 40
Steven 120
Paul 30
First of all you can call the update function like this:
var inter = setInterval(updateChart, 5000);
The logic which would simulate the fetch is the following:
function fetchData() {
console.log('fetching');
return new Promise(function(resolve, reject) {
var data = [{
attendee: "Paul",
coins: Math.floor(Math.random() * 40) + 1
}, {
attendee: "Bessy the Cow",
coins: Math.floor(Math.random() * 40) + 1
}, {
attendee: "Zeke",
coins: Math.floor(Math.random() * 40) + 1
}];
setTimeout(function() { // Adding timeout to simulate latency
resolve(data);
}, 4000)
})
}
Then we create an update function which will use the newly retrieved data:
function updateChart() {
fetchData()
.then(function(data) {
// Update our y domain with new coin values
y.domain([0, d3.max(data, function(d) {
return d.coins;
})]);
// Update our axis because our y domain just changed
svg.select('g.y')
.transition()
.duration(300)
.ease("linear")
.call(yAxis);
// Create a new data join with the simuldated data
var bars = svg.selectAll('.bar').data(data);
// Remove extra elements (say new data just has 2 bars, this would remove third one)
bars.exit().remove();
// Update existing elements
bars.transition()
.duration(300)
.ease("linear")
.call(renderBar);
// Add new elements (say new data has 5 bars, this would add the additional 2)
bars.enter().append('rect')
.transition()
.duration(300)
.ease("linear")
.call(renderBar);
})
}
I created the renderBar function since we are basically repeating the same routine at adding and updating.
function renderBar(rect) {
rect.attr("class", "bar")
.attr("x", function(d) {
return x(d.attendee);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.coins);
})
.attr("height", function(d) {
return height - y(d.coins);
});
}
This plunkr shows the working code, I removed the d3.tip part:
http://plnkr.co/edit/X3vZp5sReOWBsuZrxf8D?p=preview
Related
This question already has answers here:
Why does click event handler fire immediately upon page load?
(4 answers)
Closed 4 years ago.
I'm getting introduced to javascript and I'm trying to use .on("mouseovert", ...) in order to get the x-value of my graph when the cursor is upon the graph.
My code look like this:
// do something as mouseover the graph
svg.select("svg")
.on("mouseover", alert("mouse on graph"));
The result is: an alert appears when I open the html file (and loading my js script), but nothing happen as is hover the graph.
Everything else in the script works fine.
Do you know why?
Thank you very much for the time you take!
Here is the full script:
function draw_co2(url) {
d3.select("svg").remove() //remove the old graph
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%d");
// Get the data
d3.json(url, function (error, data) {
if (error)
throw ('There was an error while getting geoData: ' + error);
data.forEach(function (d) {
d.Date = parseTime(d.Date);
d.Trend = +d.Trend;
});
// set the ranges // Scale the range of the data
var x = d3.scaleTime().domain([new Date("1960"), new Date("2015")]).range([0, width]);
var y = d3.scaleLinear()
.domain([d3.min(data, function (d) {
return d.Trend;
}) - 1 / 100 * d3.min(data, function (d) {
return d.Trend;
}), d3.max(data, function (d) {
return d.Trend;
}) + 1 / 100 * d3.min(data, function (d) {
return d.Trend;
})])
.range([height, 0]);
// define the line
var valueline = d3.line()
.x(function (d) {
return x(d.Date);
})
.y(function (d) {
return y(d.Trend);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#graph_draw").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 + ")");
//Y Axis label
svg.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Carbon dioxide (ppm)");
// Add the valueline path.
svg.append("path")
.data([data])
.style("opacity", 0)
.transition()
.duration(1000)
.style("opacity", 1)
.attr("class", "line")
.attr("d", valueline);
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
// gridlines in x axis function
function make_x_gridlines() {
return d3.axisBottom(x)
.ticks(10);
};
// add the X gridlines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_gridlines()
.tickSize(-height)
.tickFormat(""));
// do something as mouseover the graph
svg.select("svg")
.on("mouseover", alert("mouse on graph"));
})
}
Use mouser over as an inline function
svg.select("svg")
.on("mouseover", function () {
alert("mouse on graph")
});
Looking at this Histogram chart using d3 example I plugged in my data but it had some strange side effects e.g. after refreshing to a new dataset, some information from the previous dataset i.e. x-axis scale was retained. I tried deleting and appending a new x-axis etc but nothing worked.
This happened due to the fact that my datasets had completely different x-axis ranges and scales. The only way I found to make it work was to select the whole svg element, remove it and re-append everything anew. However, this doesn't make a pleasant transition for the user so I was wondering how can this be improved to make it refreshable using transitions as in the original example even when having datasets with different x-scales and ranges.
This was my last approach which is a bit harsh to the eye:
// delete old
d3.select("#" + divId).select("svg").remove();
// then recreate all new
And this was my refresh attempt (integrated with AngularJS). Note how it has some common initialization and then if the SVG doesn't exist appends everything new otherwise tries to update it. I went bit by bit but can't see why the refresh doesn't remove all the previous dataset information of the x-axis scale:
var divId = $scope.histogramData.divId;
var color = $scope.histogramData.color;
var values = $scope.histogramData.data[$scope.histogramData.selected];
var svg = $scope.histogramData.svg;
// plot common initialization
var margin = {top: 40, right: 20, bottom: 20, left: 20},
width = 450 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
var max = d3.max(values);
var min = d3.min(values);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(10))
(values);
var yMax = d3.max(data, function(d){ return d.length });
var yMin = d3.min(data, function(d){ return d.length });
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// ===================================================================
// If the SVG doesn't exist then adds everything new
// ===================================================================
if (svg === undefined) {
var svg = d3.select("#" + divId)
.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 + ")");
$scope.histogramData.svg = svg;
var bar = svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
var gTitle = svg.append("text")
.attr("x", 0)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "left")
.classed("label", true)
.text($scope.histogramData.spec[selected]);
$scope.histogramData.gTitle = gTitle;
var gAxis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
$scope.histogramData.gAxis = gAxis;
} else {
// ===================================================================
// If the SVG does exist then tries refreshing
// ===================================================================
var bar = svg.selectAll(".bar").data(data);
// remove object with data
bar.exit().remove();
bar.transition()
.duration(1000)
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.select("rect")
.transition()
.duration(1000)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.select("text")
.transition()
.duration(1000)
.text(function(d) { return formatCount(d.y); });
var gTitle = $scope.histogramData.gTitle;
gTitle.transition()
.duration(1000)
.text($scope.histogramData.spec[selected]);
var gAxis = $scope.histogramData.gAxis;
gAxis.transition()
.duration(1000)
.call(xAxis);
}
I would suggest to keep this d3 code inside one angularJS directive and keep a watch on the json which you are using to plot that graph. As soon as values are changing the directive will be called again and the graph will be plotted. Hope it helps.
I'm having a bar chart created with d3 that sorts itself whenever there's an input. I found it in the internet on http://bl.ocks.org/mbostock/3885705.
It looks like that:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 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")
.tickFormat(formatPercent);
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 + ")");
d3.tsv("data.tsv", function(error, data) {
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", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.frequency - a.frequency; }
: function(a, b) { return d3.ascending(a.letter, b.letter); })
.map(function(d) { return d.letter; }))
.copy();
svg.selectAll(".bar")
.sort(function(a, b) { return x0(a.letter) - x0(b.letter); });
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.letter); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
});
I left out the CSS part as I don't think it matters. The problem is, that I also have multiple radio buttons in html like:
<div>
<input type="radio" name="radio" id="radio1" class="radio" onclick="radios('finance.csv')" />
<label for="radio1">Financial Situation</label>
</div>
Whenever the bars sort themselves, the radio buttons reset themselves. So I click on a radio button. It is marked for a few seconds, and then it jumps back to the first radio button. I guess its because of the "input" parts in:
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
How can I prevent the radios to reset themselves but still have my countries be sorted on input change?
Thanks in advance!
.property can be used as a getter or a setter. Here, you're using it as a setter, so when you do:
d3.select("input").property("checked", true)
You're setting all inputs to be checked. And since only one radio button is allowed to be checked, it checks the first.
I don't have the time to set up a fiddle and test, but I believe just using it as a getter would fix it:
d3.select("input").property("checked").each(change);
You could also be more precise with your selector:
d3.select("input[type=radio]:checked").each(change);
This has the benefit of not firing unpredictably if you add another input somewhere else on the page.
Fiddle Example
I have been following these two examples (1)(2) to create small multiple grouped bar charts on the same page. Here's a JSON data example:
var data = [
{"name":"AA","sales_price":20,"retail_price":25},
{"name":"BB","sales_price":30,"retail_price":45},
{"name":"CC","sales_price":10,"retail_price":55},
{"name":"DD","sales_price":10,"retail_price":25},
{"name":"EE","sales_price":13,"retail_price":20},
{"name":"GG","sales_price":13,"retail_price":15},
];
I've managed to get the bar values to show up correctly in each chart, but the X domain and Y domain values aren't right. I couldn't figure out how to bind each data row's sales_price and retail_price to the axises instead of the entire JSON data. I guess there's a problem with this block of code:
data.forEach(function(d) {
d.compare = field_name.map(function(name) {
return {name: name, value: +d[name]};
});
});
x0.domain(data.map(function(d) { console.log(d); return d.name; }));
x1.domain(field_name).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) {
return d3.max(d.compare, function(d) {
return d.value; });
})]);
How can I make the domains return each row's values for each grouped bar charts?
Full Code:
function multi_bars(el){
var margin = {top: 45, right:20, bottom: 20, left: 50},
width = 350 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var field_name = ['retail_price','sales_price'];
data.forEach(function(d) {
d.compare = field_name.map(function(name) {
return {name: name, value: +d[name]};
});
});
x0.domain(data.map(function(d) { console.log(d); return d.name; }));
x1.domain(field_name).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) {
return d3.max(d.compare, function(d) {
return d.value; });
})]);
var svg = d3.select(el).selectAll("svg")
.data(data)
.enter().append("svg: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);
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("Price");
// Accessing nested data: https://groups.google.com/forum/#!topic/d3-js/kummm9mS4EA
// data(function(d) {return d.values;})
// this will dereference the values for nested data for each group
svg.selectAll(".bar")
.data(function(d) {return d.compare;})
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x1(d.name); })
.attr("width", x1.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", color)
var legend = svg.selectAll(".legend")
.data(field_name.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
function type(d) {
d.percent = +d.percent;
return d;
}
}
multi_bars(".container");
Your setting up of x0, x1 and y is fine.
Later when you manipulate the DOM is where your references to the data don't work.
I have done two things: First I change your first block, so you create just one svg instead of
var svg = d3.select(el).selectAll("svg")
.data(data)
.enter().append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Later I just followed the example of http://bl.ocks.org/mbostock/3887051 and made the changes accordingly.
The result is here:
http://jsfiddle.net/ee2todev/g61f93gx/
If you want to have separate charts for each group as in your original fiddle you just have to translate each bar with the x0 scale. Just two adjustments have to be made:
a) you have to add the group name to the d.compare so it is accessible from the corresponding data in the bar selection
data.forEach(function(d) {
d.compare = field_name.map(function(name) {
return {group: d.name, name: name, value: +d[name]};
});
});
b) In the bar selection you have to translate each group accordingly:
svg.selectAll(".bar")
.data(function(d) {return d.compare;})
.enter()
.append("rect")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x0(d.group) + ",0)"; })
.attr("x", function(d) { console.log("x: "+d.value); return x1(d.name); })
.attr("width", x1.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", color);
The complete fiddle is here: http://jsfiddle.net/ee2todev/en8sr5m4/
Two more notes:
1) I just slightly changed your code. I highly recommend using meaningful and intuitive variable/object names. This is to me the most effective way to minimize errors. This might have been the reason you got confused. So I would rename the d.compare properties, e.g. {groupName: d.name, priceType: name, value: +d[name]}. As of now, you switched the meaning of name since name suddenly refers to the price type not the grouping name as in the original data!
2) This is a nice example of selection of selections. See also http://bost.ocks.org/mike/nest/
The first selectAll selection (the svg variable) contains an Array[6] with the objects. The second selection:
svg.selectAll(".bar").data(function(d) {return d.compare;})
iterates for each element of the svg data over an Array[2] containing an object with the price type and the value. There I added the group name.
I'm just getting into using d3, and relatively novice in js still. I'm trying to set up a page of log file visualizations for monitoring some servers. Right now I'm focusing on getting a line chart of CPU utilization, where I can focus on specific time periods (So an x-zoom only). I am able to do a static charts easily, but when it comes to the zooming the examples are going over my head and I can't seem to make them work.
This is the static example I followed to get things up and running, and this is the zoom example I've been trying to follow.
My input csv is from a rolling set of log files (which will not be labled on the first row), each row looks like this:
datetime,server,cpu,memory,disk,network_in,network_out
So far what I've been able to get on the zoom looks like this:
var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
// define dimensions of graph
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
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")
.tickSize(-height, 0)
.tickPadding(6);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(-width)
.tickPadding(6);
// Define how we will access the information needed from each row
var line = d3.svg.line()
.interpolate("step-after")
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[2]); });
// Insert an svg element into the document for each chart
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 + ")");
// Declare zoom handler
var zoom = d3.behavior.zoom().on("zoom", draw);
// Open the log and extract the information
d3.text("log.csv", function(text) {
var data = d3.csv.parseRows(text).map(function(row) {
return row.map(function(value, index) {
if (index == 0) {
return parseDate(value);
}
else if (index > 1) {
return +value;
}
else {
return value;
}
});
});
// Set the global minimum and maximums
x.domain(d3.extent(data, function(d) { return d[0]; }));
y.domain(d3.extent(data, function(d) { return d[2]; }));
zoom.x(x);
// Finally, we have the data parsed, and the parameters of the charts set, so now we
// fill in the charts with the lines and the labels
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")
.attr("text-anchor", "end")
.text("Percent (%)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.append("text")
.attr("x", margin.left)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.text('all');
svg.append("rect")
.attr("class", "pane")
.attr("width", width)
.attr("height", height)
.call(zoom);
svg.select("path.line").data([data]);
draw();
});
function draw() {
svg.select("g.x.axis").call(xAxis);
svg.select("g.y.axis").call(yAxis);
svg.select("path.line").attr("d", line);
}
What this gives me is a very sluggish chart that can be zoomed and panned, but it does not clip off the line at the ends of the chart. I've tried adding in the clipping elements described in the example, but that ends up fully erasing my line every time.
Thanks for any help or direction