Adding dynamic tooltips to a streamgraph d3.js - javascript

Im working on a streamgraph at the moment, I want to add tooltips to each layer similar to this http://archive.stamen.com/mtvmovies-streamgraph/chart.html
The tooltips I have now dont really work at all. All I get is 'NaN' displayed in the tooltip box.
Any suggestions?? My code is below.
Thanks in advance.
var customPalette = [
"#ff7f0e", "#2ca02c", "#00FFFF", "#d62728", "#9467bd",
"#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"
];
var format = d3.time.format("%y");
//creating margins around the graph
var margin = {top: 20, right: 30, bottom: 30, left: 200},
width = 1200 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//OUTPUT RANGE
var x = d3.time.scale()
.range([0, width]);
//OUTPUT RANGE
var y = d3.scale.linear()
.range([height, 0]);
//assining custom colors to layers
var colours = d3.scale.ordinal().range(customPalette);
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
.ticks(d3.time.years);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//ctreate stack layout
var stack = d3.layout.stack()
.offset("wiggle")
.order("reverse")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.amount; });
//creates array of datya elements for stacked bar graph
var nest = d3.nest()
.key(function(d) { return d.age; });
//create area
var area = d3.svg.area()
//adds curviture
.interpolate("monotone")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select("body").append("svg")
//defines length of x-axis
.attr("width", width + margin.left + margin.right)
//defines height of y-axis
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("data6.csv", function(data) {
data.forEach(function(d) {
// Convert strings to numbers
d.date = format.parse(d.date);
d.amount = +d.amount;
});
//returns an array of objects with a key feild (0-20yrs....)
//and a value array which contains associated records
var layers = stack(nest.entries(data));
//.extent() returns min and max values of argument
x.domain(d3.extent(data, function(d) { return d.date; }));
//
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return colours(i); });
//CURRENT TOOLTIP CODE
var toolTip = svg.selectAll("path")
.append("svg:title")
.text(function(d) { return (d.date) + (d.amount) });;
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 0 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});

Are you hoping to concatenate the values of d.date and d.close, or are you interested in adding the values and returning the result?
If the latter:
I would bet that the types of d.date and d.close are not what you expect. I'd recommend, if you haven't already, putting some debug code in to check the types of those variables. Example:
//CURRENT TOOLTIP CODE
var toolTip = svg.selectAll("path")
.append("svg:title")
.text(function(d) {
console.log('d.date type:' + typeof d.date + 'd.close type:' + typeof d.close);
return (d.date) + (d.close);
}
);
Also, you have an extra semicolon at the end of that statement in your code snippet.
If the former:
One or both are of type number and Javascript will try to add them when you use the + operator instead of concatenating them. To return the strings:
.text(function(d) {
return d.date.toString() + ' ' + d.close.toString();
});

Related

Error: <rect> attribute width: Expected length, "NaN". Uncaught TypeError: Cannot read property 'length' of undefined

I've been fiddling with this for a few days now and I either get the error in the title, or I get "key.call is not a function", and I don't know why... basically the point of the script is to load different csv files based on the selection made in the dropdown. Without all the dropdown stuff, the script works fine, but once I introduce the dropdown script it starts bugging out and I can't figure out why. Below is the script, and I'll attach a sample of the kind of csv I'm working with if that helps at all.
Thanks a ton in advance!
CSV example: https://www.dropbox.com/s/24zopp43r3y1ft6/PCC-edit.csv?dl=0 (QC = QuantityCharged in this example)
<svg class="chart"></svg>
<select onchange="loadData()" id="metric">
<option>A-codes</option>
<option>Anesthesia</option>
<option>C-codes</option>
<option>Clinical Components (NRV)</option>
<option>E-Codes</option>
<option>Emerging Technology</option>
<option>Evaluation & Management</option>
<option>G-codes</option>
<option>J-codes</option>
<option>L-codes</option>
<option>Medicine</option>
<option>Pathology & Laboratory</option>
<option>P-codes</option>
<option>Q-codes</option>
<option>Radiology</option>
<option>Surgery</option>
<option>V-codes</option>
</select>
<script src="http://d3js.org/d3.v3.js" charset="utf-8"></script>
<script>
var svg = d3.select("svg"),
margin = {top:10, right: 720, bottom: 5, left: 720},
width = +svg.attr("width") + margin.left + margin.right,
height = +svg.attr("height") + margin.top + margin.bottom;
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.linear()
.range([3, width]);
var y = d3.scale.ordinal()
.range([height, 1]);
var chart = d3.select(".chart")
.attr("width", width);
var parseRow = function(row) {
return {
subcategory: row.Subcategory,
quantityCharged: parseFloat(row.QuantityCharged)
}
};
//d3
var loadData = function(data) {
var metric = document.getElementById('metric').selectedOptions[0].text;
var dataFile = 'data/' + metric + '.csv'
d3.csv(dataFile, parseRow, function(error, data) {
x.domain([1, d3.max(data, function(d) { return d.QuantityCharged; })]);
y.domain([1, d3.max(data, function(d) { return d.Charge; })])
});
chart.attr("height", height);
var bar = chart.selectAll("g")
.data("width", data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * height + ")"; });
bar.append("rect")
.attr("width", function(d) { return x(d.QuantityCharged); })
.attr("height", height - 1);
bar.append("text")
.attr("x", function(d) { return x(d.QuantityCharged) + 10; })
.attr("y", height / 2)
.attr("dy", ".35em")
.text(function(d) { return d.QuantityCharged + " -- " + d.Charge; });
var xAxis = d3.svg.axis()
.scale(x)
.ticks(20)
.orient("top")
var yAxis = d3.svg.axis()
.scale(y)
.ticks(24)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.QuantityCharged); })
.y(function(d) { return y(d.QuantityCharged); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("path")
.attr("class", "line")
.attr("data", line);
function type(d) {
d.QuantityCharged = +d.QuantityCharged; // coerce to number
d.Subcategory = d.Subcategory;
return d;
}};
function Subcategory(csv) {
d3.text(dataFile, parseRow, function(text) {
var Subcategory = d3.csv.parseRows(text),
Charge = d3.csv.parseRows(text);
})}
loadData();
</script>

zoomable d3 line chart has disappearing data

I'm trying to add a zoom ability to a historical line chart I've built using a custom data object. I've been using http://codepen.io/brantwills/pen/igsoc/ as a template. The chart is rendered but when I try zooming there are two errors:
Error: Invalid value for path attribute d=""
Uncaught TypeError: undefined is not a function (in the last transform, translate of the last part of the zoomed function)
JSFiddle: http://jsfiddle.net/dshamis317/sFp6Q/
This is what my code looks like:
function renderHistoricalData(data) {
var parseDate = d3.time.format("%Y%m%d").parse;
data.forEach(function(d) { d.date = parseDate(d.date); });
// data.sort(function(a,b) { return a.date - b.date; });
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 1200 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 10])
.on("zoom", zoomed);
var line = d3.svg.line()
.interpolate("basis")
// .defined(function(d) { return d.y!=0; })
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.sentiment); });
var svg = d3.select("#historical_chart").append("svg")
.call(zoom)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
var sites = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, sentiment: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(sites, function(c) { return d3.min(c.values, function(v) { return v.sentiment; }); }),
d3.max(sites, function(c) { return d3.max(c.values, function(v) { return v.sentiment; }); })
]);
var site = svg.selectAll(".site")
.data(sites)
.enter().append("g")
.attr("class", "site");
site.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
site.append("text")
.attr("transform", function(d) {
var val = d.values[d.values.length-1];
return "translate(" + x(val.date) + "," + y(val.sentiment) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d) { return d.name; });
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("Sentiment (%)");
function zoomed() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.selectAll('path.line').attr('d', line);
sites.selectAll('.site').attr("transform", function(d) {
return "translate(" + x(d.date) + "," + y(d.sentiment) + ")"; }
);
}
}
Thank you!
Alright, let's walk through each thing.
To start with, in zoomed, the last transform doesn't need to be there. In the original, it's there to move the circles, which you don't have.
Also important, your edit on path.line sets d to the wrong function. If you look at what you're setting d to when you first make it, it should be the same, as a general rule of thumb, so it should be function(d) { return line(d.values); }, not just line.
Now, for the actual reason it's disappearing.
Your scale extent is calculated based off the original domain. However, you don't set the domain until AFTER you call scaleExtent, which means your scaling is all based on the default. It's not actually disappearing, it's being compressed to the left hand side of the graph. If you remove your x axis, you'll see the colored smear of all your data flattened against the side.
Move all your domain calculations to above where you build your scale, and it'll be fine.
To make things a bit more concrete:
function renderHistoricalData(data) {
var parseDate = d3.time.format("%Y%m%d").parse;
data.forEach(function(d) { d.date = parseDate(d.date); });
// data.sort(function(a,b) { return a.date - b.date; });
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 1200 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
var sites = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, sentiment: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(sites, function(c) { return d3.min(c.values, function(v) { return v.sentiment; }); }),
d3.max(sites, function(c) { return d3.max(c.values, function(v) { return v.sentiment; }); })
]);
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 10])
.on("zoom", zoomed);
var line = d3.svg.line()
.interpolate("basis")
// .defined(function(d) { return d.y!=0; })
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.sentiment); });
var svg = d3.select("#historical_chart").append("svg")
.call(zoom)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var site = svg.selectAll(".site")
.data(sites)
.enter().append("g")
.attr("class", "site");
site.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
site.append("text")
.attr("transform", function(d) {
var val = d.values[d.values.length-1];
return "translate(" + x(val.date) + "," + y(val.sentiment) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d) { return d.name; });
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("Sentiment (%)");
function zoomed() {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.selectAll('path.line').attr('d', function(d) { return line(d.values); });
}
}
If you want to text to move, you can give it an easily identifiable class, and then update it in zoomed.
Giving it a class:
site.append("text")
.attr("class", "lineLabel")
Updating it in zoomed:
svg.selectAll(".lineLabel")
.attr("transform", function(d) {
var val = d.values[d.values.length-1];
return "translate(" + x(val.date) + "," + y(val.sentiment) + ")";
});
This will just make it follow the ends of the lines, but you can modify whatever attributes you like to get the wanted effects.

d3 text all over the place and not in the right places

I'm trying to build a linechart based off of the example at http://bl.ocks.org/mbostock/3884955. I'm not using temperatures but rather value levels from 0-1 that I call sentiments.
I'm not using a tsv file either, but rather rendering JSON in real time through an AJAX call where my data object is an array with objects in it:
[{date: '20140716', ESPN.com: 0.4, SI.com: 0.5})]
There are typically multiple dates in an array and at least 6-7 websites (they're included in every object).
The code looks like this:
function renderHistoricalData(array) {
var data = array;
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 1200 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.defined(function(d) { return d.y!=0; })
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.sentiment); });
var svg = d3.select("#historical_chart").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) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
d.date = parseDate(d.date);
var sites = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, sentiment: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(sites, function(c) { return d3.min(c.values, function(v) { return v.sentiment; }); }),
d3.max(sites, function(c) { return d3.max(c.values, function(v) { return v.sentiment; }); })
]);
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("Sentiment (%)");
var site = svg.selectAll(".site")
.data(sites)
.enter().append("g")
.attr("class", "site");
site.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
site.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.sentiment) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
// .style("text-anchor", "inherit")
.text(function(d) { return d.name; });
});
}
This results in something that looks like:
http://imgur.com/hFTItGp
Any ideas? I'd greatly appreciate your help. Thanks!
The root cause of the problem is that in the code
{name: d.name, value: d.values[d.values.length - 1]}
you're assuming that values are sorted by date, latest date last. In your data, the opposite is the case -- the latest date is first. Therefore, your text appears at the beginning of the x axis. To fix, simply sort your data.
I've fixed this and a few other things here.

Updating nested Area chart in D3.js

In my application I have multiple checkboxes which contain the name of the product. User can select one or multiple products. When the user selects a particular product an area graph is drawn based on the number of products sold on certain days.
If multiple products are selected a stacked area graph is drawn.
If no product is selected no graph is shown just a message to select atleast one product is shown.
Code:
This is called once when the graph is initialized.
function initialiseChart(element){
var bRect = element.getBoundingClientRect();
var margin = {top: 0, right: 0, bottom: 20, left: 20};
var height = 250 - margin.top - margin.bottom;
var width = bRect.width - margin.left - margin.right;
var localChart = d3.select(element).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 + ")");
}
This is called when a user selects or de-selects a checkbox, selectedIds contain all the product ids which are selected. If all are deselected then selectedIds array is an empty array.
function updateChart(selectedIds,localChart,height,width){
var z = d3.scale.category20c();
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").ticks(12);
var yAxis = d3.svg.axis().scale(y).orient("left");
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) {
return d.values;
})
.x(function(d,i) { return d.date; })
.y(function(d,i) { return d.counts; });
var nest = d3.nest()
.key(function(d) { return d.pid; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) {
return x(d.date);
})
.y0(function(d) {
return y(d.y0);
})
.y1(function(d) {
return y(d.y0 + d.y);
});
var data = assignDefaultValues(selectedIds); //returns the data for selectedIds
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
localChart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
localChart.append("g")
.attr("class", "y axis")
.call(yAxis);
localChart.selectAll(".layer")
.data(layers)
.exit().remove();
localChart.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layers")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
}
Problem:
Whenever I select or deselect the checkbox the selectedIds array has content as expected, as in empty if all are selected or ids of one or more products which are selected.
But the axes are redrawn, also the first graph that is plotted doesnt go away even if its deselected.
What is it that I am doing wrong.

Error: parsing d="MNaN,260L on D3.js multiline graph with JSON data

I'm trying to show at least one line of a graph with this, hopefully to be adding a second later. I just can't get anything to show up though, when this hits the line return line(d.values); I get Error: parsing d="MNaN,260L... for a bunch of lines. I started out with this example, my data structure is identical to the one in that example, and I kept all the variable names and when I console.log(cities), my structure is the same. Ideas?
cities var looks like :
[
{name:"count", values: [{date:1, temperature: 10}, {date:1, temperature: 10}]}
]
JS code looks like:
var margin = {top: 20, right: 0, bottom: 20, left: 20},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) {
console.log(d)
return x(d.date);
})
.y(function(d) {
return y(d.temperature);
});
var svg = d3.select(".heartbeat").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.json("/static/data/test.json", function(error, data) {
color.domain(["one"]);
function get_vs(){
a = []
$.each(data, function(k, v){
if(v.hasOwnProperty("CN")){
a.push({"date": new Date(), "temperature": v.CN.count})
}else{
a.push({"date": new Date(), "temperature": 0})
}
})
return a
}
var cities = [
{
name: "count",
values: get_vs()
}
]
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.temperature; }); }),
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.temperature; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) { return color(d.name); });
});
The data just needed a bit of adapting - the original code was expecting an array of objects.
data = []
d3.json("/static/data/test.json", function(error, json_data) {
$.each(json_data, function(k, v){
a = {count: count, avg: avg, date: date}
data.push(a)
})
})

Categories

Resources