I have large dataset with the same detector having multiple occurrence. There are also many detectors. I want to fill a combobox with the unique detectors only.
I am using the following code:
d3.select("#detectors")
.selectAll("option")
.data(d3.map(data, function(d) {
return d.code;
})
.keys())
.enter()
.append("option")
.text(function(d) {
return d;
}).attr("value", function(d) {
return d;
});
But its not showing unique detector codes. Rather the combobox is being filled with number from 1 to ongoing.
How can I do my desired goal. My sample simple dataset is
var data=[{"code":5222,"date":3-4-2015},{"code":5222,"date":3-6-2015},{"code":5222,"date":3-7-2015},];
The data has in real a large number of detectors with unique code. I want to show these unique codes in the combobox. For the above case there will be only one 5222 in the options
I think you need to use nest().
var nest = d3.nest()
.key(function(d) { return d.code; })
.entries(data);
That will create a new array with each code only once and an object of all the objects that have that value.
Edit
var data=[{"code":5222,"date":3-4-2015},{"code":5222,"date":3-6-2015},{"code":5222,"date":3-7-2015}];
var nest = d3.nest()
.key(function(d) { return d.code;})
.entries(data);
d3.select("#detectors").selectAll("option")
.data(nest)
.enter()
.append("option")
.text(function(d){ return d.key;})
.attr("value",function(d){return d.key;});
This code works for me with no errors
You could solve this problem using d3.set() method: https://github.com/d3/d3-collection/blob/master/README.md#sets.
The code will be:
var data=[{"code":5222,"date":3-4-2015},{"code":5222,"date":3-6-2015},{"code":5222,"date":3-7-2015},];
var Unique = d3.set(data, function(d) {return d.code});
Unique returns only '5222', as you expected.
Please, note that d3 converts data to strings in this case, so it might be the case you would need to convert them back to numbers.
Related
I am looking to create a dropdown/select list from a .csv file. Is it possible to create a dropdown/select list from d3 nest? I'm working on a force directed graph that is being populated from a .csv file. I would like for the user to be able to choose from the dropdown list which node it wants to highlight. Previously I used a text search based and it worked, but I am actually looking for a dropdown list instead of text based search. Thank you in advance!
You can refer to this link
var nodesmap = d3.nest()
.key(function (d) { return d.name; })
.rollup(function (d) { return { "name": d[0].name, "group": d[0].group, "size": d[0].size }; })
.map(graph.nodes);
var output = document.createElement('block_container');
var select = d3.select("#searchName").append("select");
list.selectAll("option")
.data(nodesmap)
.enter()
.append("option")
.attr("value", function(d) {return d.key;})
.text(function(d) {
return d.key; });
return output;
I used the les_mis.csv
P.s: I am not even sure if I had setup the jsfiddle appropriately. Excuse my noobness.
First, I think you typed list when the variable name is select. Second, your nodesmap object does not have properties of .key.
This will work though:
var select = d3.select("#searchName")
.append("select")
.on('change', searchNode); //<-- fire your search function on change
select.selectAll("option")
.data(graph.nodes) //<-- use graph.nodes
.enter()
.append("option")
.attr("value", function(d) {return d.name;}) //<-- it has a name property
.text(function(d) {
return d.name;
});
function searchNode() {
//find the node
var selectedVal = this.options[this.selectedIndex].value; //<-- get value from dropdown
...
Full working code here.
I'm trying to get to grips with d3 by creating a bar chart of house prices based on whether the property address ends with 'Street', 'Road', 'Way' etc.
However, I'd also like the view of the data to change based on a column of data for local neighbourhoods.
It's the second query on this topic. Here's the previous query - How to extract nominal labels for d3 chart
You can see the structure of the data extracted through Pandas' to_json function here: http://plnkr.co/edit/He3yPUfuE8k7hvIkupjS?p=preview
I've used a nest function to key the data on the the local areas, but can't work out how to plug in a d3.filter method to restrict the data to a selected area.
I've got a function which creates a select button based on the keys:
var options = dropDown.selectAll("option")
.data(nested_data)
.enter()
.append("option");
options.text(function (d) { return d.key; })
.attr("value", function (d) { return d.key; });
But what I can't work out is how to plug the value from this selection into the plotting part of the d3 script.
d3.select("svg")
.selectAll("circle")
.data(nested_data)
.enter()
.append("circle")
d3.selectAll("circle")
.attr("cx", function(d) {
console.log(d["street_name"]);
return street_scale(d["street_name"]);
})
.attr("cy", function(d) {
return price_scale(d["value"]);
})
.attr("r", 5)
.attr("fill", "steelblue");
And while I know I need an update function to continue to change the chart as users select between, I've not found an example that I can adapt.
Thank you in advance for your patience - I'm very new to d3 and a Javascript noob.
Think about the data structure you want in the end. Since d3 likes arrays of objects and you want to filter by district, I'm picturing this:
var data = {
district1: [
{
street_split: 'a',
value: 1
},{
street_split: 'b',
value: 2
}
],
district2: [
{
street_split: 'a',
value: 1
},{
street_split: 'b',
value: 2
}
],
etc...
Your filter then simply becomes:
data[someDistrict]
So, how do we get your data in this format. I'd do everything in one loop, the data, the extents, the labels, etc...:
var orgData = {}, // our final data like above
street_labels = d3.set(), // set of streets
districts = d3.set(), // set of districts
minVal = 1e99, // min of values
maxVal = -1e99; //max of values
data.forEach(function(d){
d.value = +d.value; // convert to numeric
street_labels.add(d.street_split); // set of street_labels
districts.add(d.district); // set of districts
if (d.value < minVal) minVal = d.value; // new min?
if (d.value > maxVal) maxVal = d.value; //new max?
if (!orgData[d.district]){ // we want a associate array with keys of districts
orgData[d.district] = []; // and values that are arrays of object
}
orgData[d.district].push({ // those objects are street_split and value
street_split: d.street_split,
value: d.value
});
});
Now how do we update on a different select? That simply becomes:
dropDown.on("change", function() {
d3.selectAll("circle")
.data(orgData[this.value]) // new data
.attr("cx", function(d) { // update attributes
return street_scale(d.street_split);
})
.attr("cy", function(d) {
return price_scale(d.value);
});
});
Here's my working code.
I'm using a basic dc.js DataTable with my CrossFilter data and trying to sort it via my Value attribute which is a number but I am getting odd ordering of the data.
Here is a JSFiddle showing the issue - http://jsfiddle.net/DonalRafferty83/97mwyp0u/4/
I set up my CrossFilter dimensions as follows:
var ndx = crossfilter(data);
var parseDate = d3.time.format("%d/%m/%Y").parse;
data.forEach(function(d) {
d.date = parseDate(d.InDate);
d.Value = parseFloat(d.Value).toFixed(2);
});
var dateDim = ndx.dimension(function(d) {return d.date;});
var typeDim = ndx.dimension(function(d) {return d.Type;});
And then I create the DataTable as follows:
var datatable = dc.dataTable("#dc-data-table");
datatable
.dimension(dateDim)
.group(function(d) {return "";})
.size(data.length)
// dynamic columns creation using an array of closures
.columns([
function(d) { return d.Id; },
function(d) {return d.Indate;},
function(d) {return d.Type;},
function(d) {return d.Category;},
function(d) {return d.Value;}
]).sortBy(function(d) {
return d.Value;
})
.order(d3.descending);
Here is what the ordering comes out like, as you can 99 is ordered before 4000.46 which is incorrect:
Is this a known issue with CrossDilter/dc.js? Or is there something I am doing wrong? Maybe I need to manipulate my data to make it work as in the correct manner?
.toFixed(2) returns a string, and as such your sortBy function is sorting a string. Therefore it is correct that "99" is ordered before "4000.46". Switching your sortBy function to return +d.Value; (the + forces a conversion back to a number) should fix your problem.
A fixed version of the JSFiddle: https://jsfiddle.net/j9adz6bs/
As project to get to know d3.js, I’m displaying tweets on a map in real-time. Everything has worked this far, and I’m very, very pleased with the library.
On the page, I’m trying to list all languages. On hover, I want all tweets of that language to pop up. All of this is done, except some items in the list pops up the tweets of another language. A wrong one, I might add.
This is how I project the dots on the map:
points.selectAll('circle')
.data(tweets)
.enter()
.append('circle')
// Attach node to tweet, so I can use refer to the nodes later
.each(function(d) {
d.node = this;
})
.attr('r', 1);
This is how I create the list:
var data = d3.nest()
// Group by language code
.key(function(d) { return d.lang.code; })
.entries(tweets)
// Sort list by amount of tweets in that language
.sort(function(a, b) {
return b.values.length - a.values.length;
});
var items = languages_dom
// Add items
.selectAll('li')
.data(data)
.enter()
.append('li');
// Used for debugging
.attr('data-lang', function(d) {
return d.key; // Group key = language code
})
// Set text
.text(function(d) {
var dt = d.values[0];
return dt.lang.name;
})
// Mouseover handler
.on('mouseover', function(d) {
// Compare attribute with
// These values are actually different
var attr = d3.select(this).attr('data-lang');
console.log(attr, d.key);
// Pop up each node
d.values.forEach(function(d) {
d = d3.select(d.node);
d.transition()
.duration(200)
.attr('opacity', 0.5)
.attr('r', 8);
});
});
Note that the script above is run several times. d.key refers to another value later in the chain, while I’m not modifying data in that chain.
Edit 22:08
Things seems to work fine when I’m not sorting the data. At least it’s a lead.
As noted in the comments, you're overwriting d in your forEach function. Instead, you should try something like
d.values.forEach(function(p) {
d3.select(p.node)
.transition()
.duration(200)
.attr('opacity', 0.5)
.attr('r', 8);
});
Notice the forEach variable is named p instead of d.
As the data changed, the old data seems to be kept somehow.
Either way, I simply deleted the list before applying the new data:
languages_dom
.selectAll('li')
.remove();
Can’t say this is graceful, nor performant, but it gets the job done :)
I have a scatter graph built with d3.js. It plots circles in the graph for the spending habits of specific people.
I have a select menu that changes the specific user and updates the circles on the scatter graph.
The problem is the old circles are not removed on update.
Where are how should I use .remove() .update(), please see this plnkr for a working example
http://plnkr.co/edit/qtj1ulsVVCW2vGBvDLXO?p=info
First, Alan, I suggest you to adhere to some coding style convention to make your code readable. I know that D3 examples, and the library code per se, almost never promote code readability, but it's in your interest first, because it's much easier to maintain readable code.
Second, you need to understand how D3 works with enter, update and exit sets, when you change data. Mike Bostock's Thinking with Joins may be a good start. Unless you understand how the joins work, you won't be able to program dynamic D3 charts.
Third, here's a bug in updateScatter. name.length makes no sense because your first name variable is value. So it's not the case of deleting old data in the first place.
// Update circles for the selected user chosen in the select menu.
svg.selectAll(".markers")
.data(data.filter(function(d){ return d.FirstName.substring(0, name.length) === value;}))
.transition().duration(1000)
.attr("cx", function(d) { return xScale(d.timestamp); })
.attr("cy", function(d) { return yScale(d.price); });
Also what that weird equality comparison is d.FirstName.substring(0, name.length) === name. Your first name data is not even spaced in CSV file. Plain d.FirstName == name is fair enough. If you expect trailing spaces anyway, just trim your strings in the place where you coerce prices and dates.
This is how correct updateScatter may look look like:
function updateScatter()
{
var selectedFirstName = this.value;
var selectedData = data.filter(function(d)
{
return d.FirstName == selectedFirstName;
});
yScale.domain([
0,
d3.max(selectedData.map(function(d)
{
return d.price;
}))
]);
svg.select(".y.axis")
.transition().duration(750)
.call(yAxis);
// create *update* set
var markers = svg.selectAll(".markers").data(selectedData);
// create new circles, *enter* set
markers.enter()
.append('circle')
.attr("class", 'markers')
.attr("cx", function(d)
{
return xScale(d.timestamp);
})
.attr("cy", function(d)
{
return yScale(d.price);
})
.attr('r', 5)
.style('fill', function(d)
{
return colour(cValue(d));
});
// transition *update* set
markers.transition().duration(1000)
.attr("cx", function(d)
{
return xScale(d.timestamp);
})
.attr("cy", function(d)
{
return yScale(d.price);
});
// remove *exit* set
markers.exit().remove();
}