D3 - Update chart based on data keys - javascript

I am new to D3 and I would like to train myself on some scatterplot chart (based on the one from the NYT : Student debt.
I managed to recreate a graph like this one : Oklahoma Colleges.
Now, I have much more entries than the Oklahoma chart, so my chart is not very readable and I would like to filter the data based on a button with which I can select to display only the "public" colleges or the "private" ones.
I have read many tutorials about the ENTER-UPDATE-EXIT methods but I still have some trouble in applying it practically on my case.
Assuming the following JSON file :
[ {
"Id": 1,
"Name": "College A",
"Type": "Public" }, {
"Id": 2,
"Name": "College B",
"Type": "Private" }, {
"Id": 3,
"Name": "College C",
"Type": "Public" }, {
"Id": 4,
"Name": "College D",
"Type": "Public" }, {
"Id": 5,
"Name": "College E",
"Type": "Private" }, {
"Id": 6,
"Name": "College F",
"Type": "Private" }, ]
I would like to achieve the following algorithm :
button.on("change"){
If value == "public" :
display data where data.type == "public"
Else
display data where data.type == "private"
}
My first solution was to create a SVG each time I push the button (and erase the previous SVG) with the new dataset. But I think there is a much nicer way to do this :)
Can you help me ?
Thank you !
EDIT : following #sapote warrior answer -
Here what I do when I load the data :
d3.json("data.json", function(data) {
//Coerce string to numbers
...
dataset = data;
...
//Add axis and legend
}
And when I click to one of the two button :
function update(input){
var data = [];
for(i in dataset) {
if(dataset[i]["Type"] == input)
data.push(dataset[i]);
}
test = data; //Global scope variable, to check if new data is here on the console
circles = svg.selectAll(".circle")
.data(data);
circles.exit().remove();
circles.enter().append("circle")
.attr("class","circle")
...
}
But this doesn't work perfectly. Circles appear correctly when first click to any button, then they not all disappear when I click to the second button, and the new data doesn't seem to be correctly appended.
Hum, still have some issue understanding the enter-update-exit process ^^
EDIT : Ok problem solved ! I have just made some mistakes when implementing the enter-update-exit methods. Did it with a reduced dataset to understand the issue and now it's clear in my mind :)

I think I may be able to help you. Assuming that your circles are already displayed on the SVG, one way to do it is build a new array of values when your button is clicked that are of type "Public" or "Private". Something like this:
publicButton.on("click", function() {
newData = [];
for(i in existingDataArray) {
if(existingDataArray[i]["Type"] == "Public")
newData.push(existingDataArray[i]);
}
Now you can use this new data with the .data().enter().exit().remove() methods that you mentioned to append new data to your circle objects. After that you can remove those circles that aren't in the new data, and keep those that are. Those that you keep you can then doing something to them like update their color or nothing at all if you like. Sort of like this:
var circles = svg.selectAll("circle").data(newData);
circles.exit().remove();
circles.enter().attr("fill", ...);
}
Hopefully this helps some, let me know if you have any questions.

Related

How to access an array of objects with tooltip.format() from anychart.js

I am having trouble trying to present an array of objects on the tooltip of an Anychart.js map. I understand that we can access the dataset by doing something like: %[name of property in data set]. My data set has the following form:
{
"country": "Austria",
"id": "AT",
"continent": "Europe",
"songs": [
{
"rank": 33,
"title": "Stuck with U (with Justin Bieber)",
"artists": "Ariana Grande, Justin Bieber",
"album": "Stuck with U",
"explicit": 0,
"duration": "3:48"},
{
"rank": 34,
"title": "Late Night",
"artists": "Luciano",
"album": "Late Night",
"explicit": 0,
"duration": "3:20"
},
... more objects
]
}
}
If I wanted to access the Country property I would simply add it to the tooltip by doing:
tooltip.format("Country: " + {%country});
The issue is when trying to access an array of objects, I have tried different variations and none of them worked. Trying to show the title of every song:
tooltip.format({%songs}.{%title});
tooltip.format({%songs.%title});
tooltip.format({%songs}[{%title}]);
I also saw in the documentation that we can send a function as argument so I tried the following where I would concatenate every title of the collection but did not succeed either:
tooltip.format(function() {
let concatenated = '';
this.songs.forEach(song => {
concatenated += song + ' ';
});
return concatenated;
});
I would really appreciate your help guys.
String tokens do not support nested objects/properties. But you can use the callback function of the formatted to get access to songs. The context prototype includes getData() method provides that. Like this:
series.tooltip().format(function() {
console.log(this.getData('songs'));
return 'tooltip';
});
For details, check the live sample we prepared.
In case any one else is looking for a solution to this answer. I figured out how to loop through an embed array, and call on specific information.
chart.edges().tooltip().format(function () {
var format = ''
var songs = this.getData('songs');
songs.forEach(function (data, builtin, dom) {
format = '<p>'+data['title']+' by '+data['artists']+' </span></p>' + format
});
console.log(format)
return format
});

How to get another context path in factory method?

Let's imagine that we have sap.m.UploadCollection and we bind the data to this collection which is done like this:
bind: function () {
this._oUploadCollection.bindAggregation("items", {
path: "/attachments",
factory: jQuery.proxy(this._bindUploadCollectionItem, this)
});
},
The example of the binding data is here:
{
"attachments": [
{
"size": 123,
"filename": "pdf.pdf",
"id": "pdfId"
},
{
"size": 440,
"filename": "text.txt",
"id": "textId"
}
],
"source":"personWhoAddedAttachments"
}
So, in _bindUploadCollectionItem I successfully can get size, filename and id by oContext.getProperty("nameOfParameter"), but cannot get source:
_bindUploadCollectionItem: function (sID, oContext) {
return new sap.m.UploadCollectionItem({
"id": oContext.getProperty("id"),
"fileName": oContext.getProperty("filename"),
"attributes": [
{
"title": "author",
"text": oContext.getProperty("../source") // <- problem
}]
});
},
So, because I bind attachments it is kind of clear that I could not get source, but how to reach it if I need it?
It depends a little on what property of the model you want to get to. If it is really like you described it and the target property is in the /source absolute model path, then the easiest way of getting it inside the factory function is by using: oContext.getModel().getProperty("/source").
If you need something which is inside a collection (and somehow depends on the current context), you can achieve an effect similar to the .. path construct that you tried by using something along the lines:
var sPath = oContext.getPath(),
sParent = sPath.substring(0, sPath.lastIndexOf("/")),
sText = oContext.getModel().getProperty(sParent + "/source");
return new sap.m.UploadCollectionItem({
"id": oContext.getProperty("id"),
"fileName": oContext.getProperty("filename"),
"attributes": [{
"title": "author",
"text": sText
}]
});
You basically obtain the parent object path by searching for the last / inside the path. You can apply this repeatedly (or use a split, pop some elements, followed by a join) to get to the ancestors (e.g. parent of parent).

How can I use jQuery to push JSON data into an array?

I'm new to jQuery, and I'm trying out the getJSON function. What I want to do is pull the "id" section of a JSON file and push it into an array called planes in jQuery. From there, the array is used in an autocomplete function to fill in the searchable IDs.
var planes = [];
$.getJSON('planes.json', function(data) {
console.log('Filling array...');
//This is where I think the issue is occurring.
//Is using the name of the section you want to use the correct syntax here?
$.each(data.id, function (index, val) {
planes.push(val.id);
console.log('Pushed ' + index);
});
});
// After getJSON, array should look something like this:
// var planes = [
// 'Alara',
// 'Fiora',
// 'Innistrad',
// 'Kamigawa',
// 'Lorwyn',
// 'Mirrodin',
// 'Ravnica',
// 'Shandalar',
// 'Zendikar'
// ];
The JSON file is arranged like so:
[
{"id": "Ravnica"},
{"id": "Lorwyn"},
{"id": "Innistrad"},
{"id": "Zendikar"},
{"id": "Kamigawa"},
{"id": "Mirrodin"},
{"id": "Shandalar"},
{"id": "Alara"},
{"id": "Fiora"}
]
Plunker
Any help is much appreciated.
You almost have it, although you are looping through data.id which is not what you want to be doing. You should just loop through data, and push val.id.
If you wanted to loop through data.id, then you're json would have to be structured like so:
{
"id": [
"things",
"to",
"loop",
"through"
]
}
..but it's not, so just loop through data.
Please check following solution. I have hard coded plane data instead of getting from file but solution is same. You just need update your $.each line by iterating over data instead of data.id (this is you'r bug rest of code is fine).
var data = [{
"id": "Ravnica"
}, {
"id": "Lorwyn"
}, {
"id": "Innistrad"
}, {
"id": "Zendikar"
}, {
"id": "Kamigawa"
}, {
"id": "Mirrodin"
}, {
"id": "Shandalar"
}, {
"id": "Alara"
}, {
"id": "Fiora"
}];
var planes = [];
//surround this each with your $.getJSON. I have just hardcoded json data instead of getting it from file
$.each(data, function(index, val) {
planes.push(val.id);
});
console.log(planes);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Your update plunker link Plunker
You might also look into the native array map method, which saves you having to create an array and then push things onto it. It just returns a new array given the original array by applying the mapping function on each item.
$.getJSON("planes.json",function(data){
console.log(data.map(function(plane){return plane.id;}))
}
However, this is not available in IE<=8 if I recall correctly.

Nested JSON structure to build side-by-side d3.js charts

I'm currently stuck on how to traverse a JSON structure (I created) in order to create side-by-side donut charts. I think I've created a bad structure, but would appreciate any advice.
I'm working from Mike Bostock's example here: http://bl.ocks.org/mbostock/1305337 which uses a .csv file for source data.
In that example he uses d3.nest() to create a nested data structure with an array of flight origins.
var airports = d3.nest()
.key(function(d) { return d.origin; })
.entries(flights);
He is then able to bind that new data structure to a new div selector:
var svg = d3.select("body").selectAll("div")
.data(airports)
.enter().append("div")
Which allows him to create a donut chart for each flight origin.
My JSON data looks like the following:
{"years": [
{
"year": 2015,
"type": "bestSellers",
"chartOne": [
{
"label": "smarties",
"value": 11
},
{
"label": "nerds",
"value": 13
},
{
"label": "dots",
"value": 16
},
{
"label": "sour patch kids",
"value": 61
}
],
"chartTwo": [
{
"label": "Pepsi",
"value": 36
},
{
"label": "sunkist",
"value": 13
},
{
"label": "coke",
"value": 34
}
]}
I'm a CS student, with little experience of data structure best practices and d3.js. The structure I created doesn't look "flat" to me so I'm not sure if I need to use d3.nest(). However, I'm not clear how to traverse chartOne and chartTwo using the structure as is.
I can get to the arrays within the charts:
var chartOne = years[0].chartOne;
var cartTwo = years[0].chartTwo;
But I would like to be able to have one object to access chart1 and chart2. I'm tempted to create another array block in my JSON, but not clear if there isn't a more simple approach.
No, you don't need to use .nest here. The easiest way to build the required data structure is as you suggest (d3 always wants an array to iterate over):
var nestedData = [ years[0].chartOne, years[0].chartTwo ];
After that, it's as simple as cleaning up the accessor functions for your data and Bostock's example works well.
Example here.

Why does the country with no data always go on top?

I have a GeoJSON map and I am trying to hook it up in Highmaps. This map consists of 4 small squares ("properties": {"name":"shape1"}) that will have data and 1 big square that will never have data ("properties": {"name":"base"}) The purpose of this big square is to be a background shape for the others.
The problem is that the big shape will always be on top of the small ones. I have tried both of these arrangements in the GeoJSON file:
placing the big shape before the small ones
placing the big shape after the small ones
Please see this example
Is there any way to make the shapes with data stay on top of the ones with no data?
Simply for that base element set null value, like this: http://jsfiddle.net/91ut26vz/1 - note it need to be first element in data array.
// Prepare random data
var data = [
{
"name": "base",
"value": null
},{
"name": "shape1",
"value": 728
},
{
"name": "shape2",
"value": 710
},
{
"name": "shape3",
"value": 963
},
{
"name": "shape4",
"value": 541
}
];

Categories