Can't get d3 tooltips to display name, only number - json

I'm trying to set up d3 tooltips for my choropleth map. I have the tooltips working and am able to pull in the data from lotteryMapNum.json that I am using to assign the map colors, but I can't get any text to show up. I'm either getting "undefined" or NaN no matter what I do. I'm thinking that I need to call the data some other way, but I'm not sure how.
var newDict = {};
d3.json("data/myData/lotteryMapNum.json", function(data) {
data.forEach(function(d) { newDict[d.id] = +d.hopeDollars;})
data.forEach(function(d) { newDict[d.COUNTY] = +d.COUNTY;});
});
d3.json("data/myData/simpleGA.json", function(json) {
counties.selectAll("path")
.data(json.features)
.enter().append("svg:path")
.attr("class", function(d) { return quantize(newDict[d.id]);})
.attr("d", path)
.call(d3.helper.tooltip()
//.attr({class: function(d, i) { return d + ' ' + i + ' A'; }})
.text(function(d){ return 'value: '+newDict[d.id]+newDict[d.COUNTY]; })
)
.on('mouseover', function(d){ d3.select(this).style({fill: 'green', stroke: 'red'}); })
.on('mouseout', function(d){ d3.select(this).style({fill: '', stroke: ''}); });
});
lotteryMapNum.json
[
{"COUNTY":"APPLING",
"hopeDollars":12921240,
"id":"0"}
]
simpleGA.json
{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "id": 0, "properties": { "NAMELSAD10": "Appling County"}, "geometry": { "type": "Polygon", "coordinates": [ [ [ -83.04292, 30.947296 ], [ -83.05332, 30.94753 ],] ] } }
]
}

pretty simple fix i think. try getting rid of the + in front of d.COUNTY and then just making the value of each id key an array:
d3.json("data/myData/lotteryMapNum.json", function(data) {
data.forEach(function(d) { newDict[d.id] = [+d.hopeDollars, d.COUNTY];});
then just access the desires tooltip text in the same way:
.text(function(d){ return 'value: '+ newDict[d.id][0] + newDict[d.id][1]; })

after a quick look, I see two potential problems...
1) It doesn't look like there is any property 'd.COUNTY' in json.features... 'd' in the text() function is referencing the data on the dom element, which comes directly from simpleGA.json ('id' is present, but 'COUNTY' is not.)
Instead, you're probably wanting to reference:
d.properties['NAMELSAD10']
2) The two requests are being loaded in parallel, but one response depends on the other. It is possible that the completion code for simpleGA.json is executing before the lotteryMapNum.json request is complete... in which case the elements of 'newDict' may still be undefined.
A possible solution for this would be to chain the two requests based on the dependency:
var newDict = {};
d3.json("data/myData/lotteryMapNum.json", function(data) {
data.forEach(function(d) { newDict[d.id] = +d.hopeDollars;})
data.forEach(function(d) { newDict[d.COUNTY] = +d.COUNTY;});
d3.json("data/myData/simpleGA.json", function(json) {
counties.selectAll("path")
.data(json.features)
.enter().append("svg:path")
.attr("class", function(d) { return quantize(newDict[d.id]);})
.attr("d", path)
.call(d3.helper.tooltip()
//.attr({class: function(d, i) { return d + ' ' + i + ' A'; }})
.text(function(d){ return 'value: '+newDict[d.id]+newDict[d.COUNTY]; })
)
.on('mouseover', function(d){ d3.select(this).style({fill: 'green', stroke: 'red'}); })
.on('mouseout', function(d){ d3.select(this).style({fill: '', stroke: ''}); });
});
});
If you need more help, post a link to the two .json files and I can better troubleshoot with a jsfiddle.
EDIT: jsfiddle is giving me a lot of woes with large filesizes... but here's basically what I meant with a database-like join... also was working with a list instead of a geojson map:
// load json1
d3.json("lotterMapNum.json", function(lotterMapNum) {
// load json2
d3.json("simpleGA.json", function(simpleGA) {
for (var i = 0; i < simpleGA.features.length; i++) {
newDict[simpleGA.features[i].id] = simpleGA.features[i];
}
for (var j = 0; j < lotteryMapNum.length; j++) {
if (typeof newDict[lotteryMapNum[j].id] === 'undefined') {
newDict[lotteryMapNum[j].id] = {};
}
newDict[lotteryMapNum[j].id].hopeDollars = lotteryMapNum[j].hopeDollars;
newDict[lotteryMapNum[j].id].COUNTY = lotteryMapNum[j].COUNTY;
}
var counties = d3.select('#counties')
.data(newDict)
.enter()
.append('li')
.text(function(d){return d.id + ': ' + d.COUNTY + ', $' + d.hopeDollars;});
});
});

Related

How to transform code from a tsv import to direct data

I'm working with some D3 examples in php driven pages. I've found a great example I want to use of a line chart with inline lables (full code here: https://bl.ocks.org/mbostock/4b66c0d9be9a0d56484e), but I can't figure out how to transition the code from a tsv import to an array provided directly from the database.
I am obviously providing the data directly like this:
var data = [
{date:2009, Apples:130, Bananas:40},
{date:2010, Apples:137, Bananas:58},
{date:2011, Apples:166, Bananas:97},
{date:2012, Apples:154, Bananas:117},
{date:2013, Apples:179, Bananas:98},
{date:2014, Apples:187, Bananas:120},
{date:2015, Apples:189, Bananas:84}
]
And then I'm trying to replace this chunk of code that handles the import and the sorting into an array automatically.
d3.requestTsv("data.tsv", function(d) {
d.date = parseTime(d.date);
for (var k in d) if (k !== "date") d[k] = +d[k];
return d;
}, function(error, data) {
if (error) throw error;
var series = data.columns.slice(1).map(function(key) {
return data.map(function(d) {
return {
key: key,
date: d.date,
value: d[key]
};
});
});
But I think I'm having problems replicating the portion that creates the series.
I've tried several variations of this:
var series = data.map(function(key) {
return data.map(function(d) {
return {
key: key,
date: d.date,
value: d[key]
};
});
});
followed with a function to set the data type at the end of the code:
function type(d) {
d.date = parseTime(d.date);
for (var k in d) if (k !== "date") d[k] = +d[k];
return d;
}
But nothing seems to work. I'm sure there is something simple I'm missing, but what should I specifically be changing here to use code by providing the data directly in an array instead of a tsv import?
Your data array is correct (regarding the TSV in Bostock's code).
However, you have two problems:
The d3.tsv function creates an array property named columns. Since you're ditching d3.tsv and using a variable to store the data, you'll have to create that array yourself:
data.columns = ["date", "Apples", "Bananas"]
The d3.tsv accepts a row function. Again, since you're using a variable to store the data, you'll have to use a forEach to do what the row function does in Bostock's code:
data.forEach(d=>{
d.date = parseTime(d.date);
for (var k in d) if (k !== "date") d[k] = +d[k];
});
Here is the updated code using a variable to store the data: https://bl.ocks.org/anonymous/749f2c5bc6a42d68bca3ec579646ff1d
And here the same code in the Stack snippet:
<style>
text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke-width: 1.5px;
}
.label {
text-anchor: middle;
}
.label rect {
fill: white;
}
.label-key {
font-weight: bold;
}
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.0.0-alpha.9.min.js"></script>
<script>
var parseTime = d3.timeParse("%Y");
var svg = d3.select("svg");
var margin = {top: 30, right: 50, bottom: 30, left: 30},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
labelPadding = 3;
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = [
{date:2009, Apples:130, Bananas:40},
{date:2010, Apples:137, Bananas:58},
{date:2011, Apples:166, Bananas:97},
{date:2012, Apples:154, Bananas:117},
{date:2013, Apples:179, Bananas:98},
{date:2014, Apples:187, Bananas:120},
{date:2015, Apples:189, Bananas:84}
];
data.columns = ["date", "Apples", "Bananas"]
data.forEach(d=>{
d.date = parseTime(d.date);
for (var k in d) if (k !== "date") d[k] = +d[k];
});
var series = data.columns.slice(1).map(function(key) {
return data.map(function(d) {
return {
key: key,
date: d.date,
value: d[key]
};
});
});
var x = d3.scaleTime()
.domain([data[0].date, data[data.length - 1].date])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, d3.max(series, function(s) { return d3.max(s, function(d) { return d.value; }); })])
.range([height, 0]);
var z = d3.scaleCategory10();
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var serie = g.selectAll(".serie")
.data(series)
.enter().append("g")
.attr("class", "serie");
serie.append("path")
.attr("class", "line")
.style("stroke", function(d) { return z(d[0].key); })
.attr("d", d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); }));
var label = serie.selectAll(".label")
.data(function(d) { return d; })
.enter().append("g")
.attr("class", "label")
.attr("transform", function(d, i) { return "translate(" + x(d.date) + "," + y(d.value) + ")"; });
label.append("text")
.attr("dy", ".35em")
.text(function(d) { return d.value; })
.filter(function(d, i) { return i === data.length - 1; })
.append("tspan")
.attr("class", "label-key")
.text(function(d) { return " " + d.key; });
label.append("rect", "text")
.datum(function() { return this.nextSibling.getBBox(); })
.attr("x", function(d) { return d.x - labelPadding; })
.attr("y", function(d) { return d.y - labelPadding; })
.attr("width", function(d) { return d.width + 2 * labelPadding; })
.attr("height", function(d) { return d.height + 2 * labelPadding; });
</script>

Angularjs custom filter not working

I am trying to filter elements based on the range. I am using two controllers & $rootScope broadcast-on approach to retrieve the min-max range of a slider & sharing it with the other controller.
HTML-
<body ng-app="myApp">
<div ng-controller="RangeController as vm">
<rzslider rz-slider-model="vm.slider.minValue" rz-slider-high="vm.slider.maxValue" rz-slider-options="vm.slider.options"></rzslider>
</div>
<div ng-controller="SampleController">
<div ng-repeat="x in elements | inRange:min:max">
{{x}}
</div>
</div>
</body>
AngularJS-
var app = angular.module('myApp', ['rzModule']);
app.controller('SampleController', function($scope,$rootScope) {
$scope.min = 1500;
$scope.max = 5500;
$scope.elements = [1530,2100,2780,3323,3420,4680,5020,5300,5402];
$scope.$on('MIN_PRICE', function(response) {
$scope.min = minPrice;
})
$scope.$on('MAX_PRICE', function(response) {
$scope.max = maxPrice;
})
});
app.value('minPrice',1500);
app.value('maxPrice',5500);
app.controller('RangeController', RangeController);
function RangeController($scope,$rootScope) {
var vm = this;
vm.changeListener = function() {
minPrice = vm.slider.minValue;
maxPrice = vm.slider.maxValue;
console.log(minPrice + " " +maxPrice);
$rootScope.$broadcast('MIN_PRICE', minPrice);
$rootScope.$broadcast('MAX_PRICE', maxPrice);
};
vm.slider = {
minValue: 1500,
maxValue: 5500,
options: {
floor: 1500,
ceil: 5500,
step: 500,
translate: function(value) {
return '₹' + value;
},
onChange:vm.changeListener
}
}
}
app.filter('inRange', function() {
return function(array, min, max) {
array = array.filter(function(element) {
return (element >= min && element <= max);
});
console.log(array);
};
});
I tried debugging, the filter works fine but it won't reflect in the template.
The self-assignment to array inside your filter (array = array.filter(…);) seems slightly suspicious to me. Have you tried simply returning array.filter(…); directly?
app.filter('inRange', function() {
return function(array, min, max) {
return array.filter(function(element) {
return (element >= min && element <= max);
});
};
});

Using dynamic input(CSV) in d3js

I am trying to use dynamic input to a draw function in d3js. So when the user changes the csv it would remove the current selection and draw the visualization for the new input. So my question is would I be using a onChange function with the select and then within this function parse the csv and call for the draw function.The current working code is here in plunker:
https://plnkr.co/edit/AjVBK3rTOF5aI4eDDbV5?p=preview
<svg width="1250" height="1080"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width");
var format = d3.format(",d");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var pack = d3.pack()
.size([width, width])
.padding(1.5);
var inputs = {};
function selectCity(){
//storing the drop-dsown selection in the ddSelection var
var ddSelection = document.getElementById("city").value;
//feeding that to create the csv filename you want
var str1 = ddSelection;
var str2 = ".csv";
var csvFile = str1.concat(str2);
str1.concat(str2);
console.log(csvFile);
d3.csv(csvFile, function(d) {
d.sno = +d.sno;
return d;
}, function(error, data) {
if (error) throw error;
d3.selectAll("input").on("change", function(){
inputs[this.id] = +this.value;
console.log(inputs.myValue + "-" + inputs.myRating)
if(inputs.myValue && inputs.myRating){
var classes = data.filter(d => d.value < inputs.myValue && d.rating >= inputs.myRating);
draw(classes);
}
})
function draw(classes) {
console.log(classes.length);
var root = d3.hierarchy({
children: classes
})
.sum(function(d) {
return d.value;
})
.each(function(d) {
if (id = d.data.id) {
var id, i = id.lastIndexOf(".");
d.id = id;
d.package = id.slice(0, i);
d.class = id.slice(i + 1);
}
});
var node = svg.selectAll(".node")
.data(pack(root).leaves())
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("circle")
.attr("id", function(d) {
return d.id;
})
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.package);
});
node.append("clipPath")
.attr("id", function(d) {
return "clip-" + d.id;
})
.append("use")
.attr("xlink:href", function(d) {
return "#" + d.id;
});
node.append("text")
.attr("clip-path", function(d) {
return "url(#clip-" + d.id + ")";
})
.selectAll("tspan")
.data(function(d) {
return d.class.split(/(?=[A-Z][^A-Z])/g);
})
.enter().append("tspan")
.attr("x", 0)
.attr("y", function(d, i, nodes) {
return 13 + (i - nodes.length / 2 - 0.5) * 10;
})
.text(function(d) {
return d;
});
node.append("title")
.text(function(d) {
return d.data.id + "\n" + format(d.value);
});
}
});
}
</script>
</div>
Here is one example how to do it: http://www.d3noob.org/2014/04/using-html-inputs-with-d3js.html
You don't have to redraw everything but update certain elements.
I don't understand your part about changing the CSV. The user does not change the CSV but your visual output is depending on some user data. So yes, within the callback function of d3.csv(), you write your code that calls some kind of a draw function. But the draw function does not have to be defined there. You can write the function outside and just call it there. This increased the readable of your code dramatically. ;)

how to replace comma with dot

I am having comma values inside my json and i want when i get those values i get them in dot so basically I want to convert my comma values into dot values..my json looks like and its always fixed that i will get comma values at val003.
I know something to do like var new = new.replace(/,/g, '.') . but how can i specify my val003 here for the conversion. Thank you in advance
My html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta content="utf-8" http-equiv="encoding">
<div id="below">
<div id="chart"></div>
</div>
<script>
var jsonURL = 'avb.json';
var myData = [];
var fliterdata = [];
var tempdata = [];
var selectop = "";
var selectDate = false;
var chartType = chartType || 'bar';
function filterJSON(json, key, value) {
var result = [];
for (var foo in json) {
var extractstr = json[foo][key] ;
extractstr=String(extractstr);
if (extractstr.slice(3)== value) {
result.push(json[foo]);
}
}
return result;
}
function selectValue(d) {
switch (selectop) { //d object select particular value for Y axis
case "01":
return d.val001;
break;
case "02":
return d.val002;
break;
case "03":
return d.val003;
break;
case "04":
return d.val004;
break;
default:
//console.log("default");
return d.val001;
}
}
var line = d3.svg.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(selectValue(d));
})
.interpolate("monotone")
.tension(0.9);
yScale.domain([0, d3.max(tempData, function(d) {
return +selectValue(d);
})]);
var svg = d3.select('#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 + ")");
if (chartType == 'bar') {
svg
.selectAll(".bar") //makes bar
.data(tempData)
.enter().append("rect")
.attr("class", "bar")
.style("fill", "teal")
.attr("x", function(d) {
return xScale(d.date);
}).attr("width", xScale.rangeBand())
.attr("y", function(d) {
return yScale(selectValue(d));
}).attr("height", function(d) {
console.log("as", d.value);
return height - yScale(selectValue(d));
})
}
if (chartType == 'line') {
svg.append("path") // Add the line path.
.data(tempData)
.attr("class", "line")
.attr("d", line(tempData));
}
}
d3.json(jsonURL, function(data) {
myData = data; //data from json in mydata
d.val003.replace(",",".")
myData.forEach(function(d) {
d.date = new Date(d.date);
d.date = new Date(d.date + " UTC");
});
$("#listbox").on("click", function() {
var key = $(this).val();
console.log("key:", key);
var value = $('#listbox option:selected').text();
console.log("vaue:", value);
selectop = String(key);
selectop = selectop.slice(-2);
console.log("mydata: ", myData);
console.log("selectops:", selectop);
fliterdata = filterJSON(myData, key, value); //selected value from user and picks the whole element that contains that attribute
console.log("fliterdata: ", fliterdata);
tempData = fliterdata; //graph made by temp data
if (selectDate)
render(true);
});
});
function selectChartType(type) {
chartType = type;
render(true);
}
</script>
</body>
</div>
</body>
</html>
Try this,
return d.val003.toString().replace(",",".");
Yo can simply request a value in a JSon object -- it pretty much serves as an object in JavaScript.
So if you have your JSon object, lets call it json you can simply do:
var url = *your url*, json;
// retrieve the json object from a URL
$.getJSON(url, function (response) {$
json = response;
});
// reassing val003 with the corrected string
json.val003 = json.val003.replace(",", ".")
That should work, I believe.
If it is always the comma in a decimal number you want to replace, than you can do a search replace in the whole json string for the sequence "number" "comma" "number" like:
([0-9]),([0-9])
and replace it with:
$1.$2
$1 and $2 are the placeholders for the found numbers before and after the comma.
You can use this site for online testing:
http://www.regexe.com/

nested html list to json

[
{
"tree_id": 6,
"fields" : ["id","lft", "rgt"], // tree_id is stripped if requested via fields because redundant
"values" :
[1,1,4,[
[2,2,3,[]]
]]
}
// more could follow ...
]
above is the json code that Bobab uses to export/import nested sets.
Baobab nested set json export/import format
How can i parse a nested html list to yield json like above?
I am trying to manipulate nested lists using drag and drop
Nestable list
It has 2 functions that kind of do what i want to achieve, but my head keeps twisting around it.
toHierarchy: function(options) {
var o = $.extend({}, this.options, options),
sDepth = o.startDepthCount || 0,
ret = [];
$(this.element).children(o.items).each(function () {
var level = _recursiveItems(this);
ret.push(level);
});
//console.log(JSON.stringify(ret));
return ret;
function _recursiveItems(item) {
var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
if (id) {
var currentItem = {"id" : id[2]};
if ($(item).children(o.listType).children(o.items).length > 0) {
currentItem.children = [];
$(item).children(o.listType).children(o.items).each(function() {
var level = _recursiveItems(this);
currentItem.children.push(level);
});
}
return currentItem;
}
}
},
toArray: function(options) {
var o = $.extend({}, this.options, options),
sDepth = o.startDepthCount || 0,
ret = [],
left = 2;
ret.push({
"item_id": o.rootID,
"parent_id": 'none',
"depth": sDepth,
"left": '1',
"right": ($(o.items, this.element).length + 1) * 2
});
$(this.element).children(o.items).each(function () {
left = _recursiveArray(this, sDepth + 1, left);
});
ret = ret.sort(function(a,b){ return (a.left - b.left); });
//console.log(JSON.stringify(ret));
return ret;
function _recursiveArray(item, depth, left) {
var right = left + 1,
id,
pid;
if ($(item).children(o.listType).children(o.items).length > 0) {
depth ++;
$(item).children(o.listType).children(o.items).each(function () {
right = _recursiveArray($(this), depth, right);
});
depth --;
}
id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/));
if (depth === sDepth + 1) {
pid = o.rootID;
} else {
var parentItem = ($(item).parent(o.listType)
.parent(o.items)
.attr(o.attribute || 'id'))
.match(o.expression || (/(.+)[-=_](.+)/));
pid = parentItem[2];
}
if (id) {
ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right});
}
left = right + 1;
return left;
}
},
If your goal is to insert that data on the database using the Baobab library then you do not need to create the JSON code with the left/right indexes yourself, which can be fairly complicated to do.
Just send to the server tree structured data and on server side iterate over it adding the objects to the database.
You could create a generic tree structure with something like this (using jQuery to have a shorter example):
function genTree(domNode){
var parentObj = {
data : { /* filled with data found in domNode, e.g. the baobab node id */ },
children: []
};
$(domNode).find('> li, > ul > li').each(function(){
parentObj.children.push(genTree(this));
});
return parentObj;
}
Then when you'll travel the structure you will use the Baobab API to add the nodes to your database (at that point you can export it to JSON if you really need it)