Data passed to angular d3 directive not arriving in time - javascript

I am doing a mongodb query and passing results to an angular directive that contains d3 chart logic. I am using a controller to pass myData to scope, and can print it in HTML just fine, but the directive does not load - I think myData is not resolved in time because the query is not finished yet.
This has been discussed in other threads, but very specific to problems that I find difficult to apply in this context.
I am generally wondering how I should approach this problem. I am pretty new to Angular and d3, so I would appreciate any guidance.
The HTML. Here "lsk" can be accessed nicely, and "myData" can also be accessed directly in the html. However, the directive does not load.
<div ng-hide="editMode" class="container">
<div>
<p="lsk.value1">Verdi1: {{ lsk.value1 }} </p>
<p="lsk.value2">Verdi2: {{ lsk.value2 }} </p>
<p="lsk.value3">Verdi3: {{ lsk.value3 }} </p>
<p>
<button style="margin-top:20px" class="btn btn-default" ng-click="toggleEdit()">Edit</button>
<a style="margin-top:20px" class="btn btn-default" href="#/">Back</a>
</p>
</div>
</div>
<div>MyData: {{ myData }} Length: {{ myData.length }}</div>
<div>
<bars-chart chart-data="myData"></bars-chart>
</div>
The controller (extract):
.controller("EditLskController", function($scope, $routeParams, Lsks) {
Lsks.getLsk($routeParams.lskId).then(function(doc) {
//This works, lsk can be referenced in my form.
$scope.lsk = doc.data;
//This does not work for the d3 chart, but the
//values can be referenced in a div separately.
$scope.myData = [doc.data.value1,doc.data.value2,doc.data.value3];
}, function(response) {
alert(response);
});
//By uncommenting here I get static data that does render the d3 directive
// $scope.myData = [1,10,30,40,60, 80, 20,50];
})
The service (extract):
.service("Lsks", function($http) {
this.getLsks = function() {
return $http.get("/lsks").
then(function(response) {
return response;
}, function(response) {
alert("Error finding lsks.");
});
}
this.getLsk = function(lskId) {
var url = "/lsks/" + lskId;
return $http.get(url).
then(function(response) {
return response;
}, function(response) {
alert("Error finding this lsk.");
});
}
The directive is taken from this tutorial: http://odiseo.net/angularjs/proper-use-of-d3-js-with-angular-directives. It works fine.
//camel cased directive name
//in your HTML, this will be named as bars-chart
.directive('barsChart', function ($parse) {
//explicitly creating a directive definition variable
//this may look verbose but is good for clarification purposes
//in real life you'd want to simply return the object {...}
var directiveDefinitionObject = {
//We restrict its use to an element
//as usually <bars-chart> is semantically
//more understandable
restrict: 'E',
//this is important,
//we don't want to overwrite our directive declaration
//in the HTML mark-up
replace: false,
//our data source would be an array
//passed thru chart-data attribute
scope: {data: '=chartData'},
link: function (scope, element, attrs) {
//in D3, any selection[0] contains the group
//selection[0][0] is the DOM node
//but we won't need that this time
var chart = d3.select(element[0]);
//to our original directive markup bars-chart
//we add a div with out chart stling and bind each
//data entry to the chart
chart.append("div").attr("class", "chart")
.selectAll('div')
.data(scope.data).enter().append("div")
.transition().ease("elastic")
.style("width", function(d) { return d + "%"; })
.text(function(d) { return d + "%"; });
//a little of magic: setting it's width based
//on the data value (d)
//and text all with a smooth transition
}
};
return directiveDefinitionObject;
})

Indeed, $scope.myData will be undefined when the directive loads, since the service response isn't available yet, so the datareference in your directive scope will be undefined as well, so basically you're passing nothing to the d3.data() method.
There are two approaches I can think of, first would be setting a $watch on the data inside the directive and wait for the value to change.
scope.$watch('data', function(newVal, oldVal) {
if (newVal !== oldVal) {
// Render your chart here
}
});
That would render your chart every time the bound value of datachanges.
The other workaround is emitting and event once the Lsks service is resolved and pass the data in the event, but since you already have a binding in your $scope, better use that.

This is a continued exploration of errors, while the original problem was solved and the answer accepted.
The error message received
I get this initially when the directive is loaded. I am assuming it is because of the asyncronous call.
I will post my code below - it is most likely not very good. I am trying to understand how to use watchers and directives. The challenge is that I am trying to display a d3 graph before I get the data from the DB.
//camel cased directive name
//in your HTML, this will be named as bars-chart
.directive('bulletChart', function($parse) {
var directiveDefinitionObject = {
restrict: 'E',
replace: false,
//our data source will be
//passed thru bullet-data attribute
scope: {
bdata: '=bulletData'
},
link: function(scope, element, attrs) {
var margin = {
top: 5,
right: 40,
bottom: 20,
left: 120
},
width = 800 - margin.left - margin.right,
height = 50 - margin.top - margin.bottom;
var chart = d3.bullet()
.width(width)
.height(height);
//TESTING - I need some listeners on the bdata value
scope.$watch('bdata', function(newValue, oldValue) {
if ( newValue !== oldValue ) {
console.log('bdata has new value');
} else {
console.log('bdata did not change');
}
//TESTING - added this To avoid undefined-errors on first time rendering
if (!newValue) return;
var svg = d3.select(element[0]).selectAll("svg")
.data(scope.bdata)
.enter().append("svg")
.attr("class", "bullet")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart);
var title = svg.append("g")
.style("text-anchor", "end")
.attr("transform", "translate(-6," + height / 2 + ")");
title.append("text")
.attr("class", "title")
.text(function(d) {
return d.title;
});
}, true); //for deep dive something
//TESTING - I want a transition when I change a value, but
//I probably cannot add a second watcher like this. There
//must be some colission.
scope.$watch('bdata', function(newValue, oldValue) {
d3.select("body").selectAll("svg")
.datum(function(d, i) {
d.ranges = scope.data[i].ranges;
d.measures = scope[i].measures;
d.markers = scope[i].markers;
return d;
})
.call(chart.duration(1000));
}, true);
}
};
return directiveDefinitionObject;
})
If this is too little information, or too messy, I understand. I will probably in that case clean this up and prepare a new question.
I am generally finding it challenging to work with a d3 graph, getting data from a DB, and changing values in the chart based on the DB data. For example I want different ranges in these bullet charts to be dynamic, calculated based on some values in the DB.
I am basing the code on this tutorial for bullet charts: https://bl.ocks.org/mbostock/4061961
Thanks!

Related

angular directive d3 stuck when hidden on mobile

I have a simple directive that renders a d3 svg word cloud.
The directive should be visible only on desktop, so I remove it on mobile with bootstrap class: "xs-hidden"
the problem is the browser tab gets stuck as angular probably looks for the directive but cant find it.
I altered and added the render method of the directive scope,
the tab gets stuck when we call
layout.start();
only when the div is hidden with "hidden-xs"
** tab gets stuck means - it simply shows "loading" the page is loaded but the page is stuck, I cant refresh, I need to close the tab.
some code:
link: function(scope, element, attrs) {
//stuff....
scope.render(['word', 'word2']);
scope.render = function(words) {
if (!words || !words.length) return;
var maxSize = d3.max(words, function(d) { return d.count; });
var minSize = d3.min(words, function(d) { return d.count; });
var fontScale = d3.scale.linear()
.domain([minSize, maxSize])
.range([16, 40]);
var layout = d3.layout.cloud()
.size([svgWidth, 350])
.words(words.map(function(d) {
return {text: d.word, size: d.count};
}))
.padding(10)
.rotate(function() { return 0; })
.font("Arvo")
.fontSize(function(d) { return fontScale(d.size) })
.fontWeight(700)
.spiral('rectangular')
.on("end", draw);
//when this is called the tab get stuck and must be closed
layout.start();
function draw(words) {
//dont care
}
}
Have you checked if your element where word cloud needs to be rendered is present in DOM or not ?
The class you mentioned has:
.hidden-xs {
display: none !important;
}
So element may not be in DOM when you try to render the word cloud.

groups.exit(...).watchTransition is not a function

I have a multi-bar chart in which I've assigned a click event to the bars. This works fine until a user changes the chart type from grouped to stacked, at which point I've discovered that I need to reassign the onClick handler. This all seems to work correctly.
The problem is that after my click handler runs, whether or not the user has changed the chart type yet previously, attempting to change the chart type will result in a "groups.exit(...).watchTransition is not a function" JS error.
Chart definition:
nv.addGraph(function() {
// Defining the chart itself
var chart = nv.models.multiBarHorizontalChart()
.x(function(d) { return d.label })
.y(function(d) { return d.value })
.margin({top: 30, right: 20, bottom: 50, left: 275})
.showValues(true) //Show bar value next to each bar.
.tooltips(true) //Show tooltips on hover.
.valueFormat(d3.format('$,.2f'))
.groupSpacing(0.5)
.showControls(true); //Allow user to switch between "Grouped" and "Stacked" mode.
chart.yAxis
.tickFormat(d3.format('$,.2f'));
d3.select('#chart2 svg')
.datum(barData)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
},
function(){
// Set the click handler. This part works fine, but the onclick handler goes away after changing the chart type and thus needs redefined below.
// PROBLEM POINT: once this code is run, the user can no longer change the chart type. They just keep getting "groups.exit(...).watchTransition is not a function"
d3.selectAll(".nv-bar").on('click',
function(e){
var canName = e.label.split('(');
var canName = $.trim(canName[0]);
var searchTerm = canName + ' ' + e.key;
var detUrl = "/details.cfm?canName=" + encodeURIComponent(canName) + "&searchTerm=" + encodeURIComponent(searchTerm);
$("#detailsDiv").html("Loading...");
$("#detailsDiv").load(detUrl);
location.href = "#details";
});
// If I try to redefine the bar click handler in the radio button's "click" event it overwrites the built in JS used to change the chart type, so instead
// I handle it onMouseUp.
d3.selectAll(".nv-series").on('mouseup',
function(e){
setTimeout(function(){
// Just running this directly on mouseUp doesn't work. Apparently the chart needs time to load first. So we do it 100ms later, which works fine.
d3.selectAll(".nv-bar").on('click',
function(e){
var canName = e.label.split('(');
var canName = $.trim(canName[0]);
var searchTerm = canName + ' ' + e.key;
var detUrl = "/details.cfm?canName=" + encodeURIComponent(canName) + "&searchTerm=" + encodeURIComponent(searchTerm);
$("#detailsDiv").html("Loading...");
$("#detailsDiv").load(detUrl);
location.href = "#details";
});
}, 100);
});
});
watchTransition is defined by nvd3 on D3's selection prototype, if you have nv.d3.js loaded in the browser, you should be able to step with the debugger into the following code before, any chart is rendered:
d3.selection.prototype.watchTransition = function(renderWatch){
var args = [this].concat([].slice.call(arguments, 1));
return renderWatch.transition.apply(renderWatch, args);
};
I had the same issue. The reason was that I was using webpack, which bundled D3 inside my application, so the D3 that was used to draw the chart was not the D3 that NVD3 visits to add the function to the prototype. So if you are using webpack or browserify make sure to exclude D3 and add it only as reference script.
We solved this issue by downgrading to d3 3.4.4, as advised by this comment.

Append d3 svg to current ng-repeat element

I would like to append a d3.js pie chart to each li elements generated with ng-repeat.
<ol>
<li ng-repeat="h in hashtags | orderBy:predicate:reverse | limitTo: limit">
<div class="hashtag">
<a ng-click="showTweetsForHashtag(h)">#{{h.Hashtag}}</a>
</div>
<div class="frequency">
{{h.Frequency}} times
</div>
<div class="engagement">
{{h.Engagement}}
<pie-chart data="h" on-click="showTweetsForHashtag(item)"></pie-chart>
</div>
</li>
</ol>
My $scope.hashtag is an array of objects containing hashtags engagement properties :
[{
"Favorites": 0,
"Frequency": 1,
"Hashtag": "19h30",
"Replies": 0,
"Retweets": 1,
"Engagement":2,
"tweetId": 615850479952785400
}, {
"Favorites": 0,
"Frequency": 1,
"Hashtag": "80s",
"Replies": 0,
"Retweets": 2,
"Engagement":2,
"tweetId": [
616521677275533300,
617319253738393600
]
}{
"Favorites": 1,
"Frequency": 1,
"Hashtag": "AloeBlacc",
"Replies": 0,
"Retweets": 1,
"Engagement":2,
"tweetId": 617309488572420100
}, {
"Favorites": 2,
"Frequency": 1,
"Hashtag": "Alpes",
"Replies": 0,
"Retweets": 1,
"Engagement":3,
"tweetId": 615481266348146700
}]
Tanks to the ng-repeat, each time I call the pie-chart directive, I only pass one h object :
{
"Favorites": 2,
"Frequency": 1,
"Hashtag": "Alpes",
"Replies": 0,
"Retweets": 1,
"Engagement":3,
"tweetId": 615481266348146700
}
Which I then manually "map" into that format :
var mapped = [{
"label": "Retweets",
"value": data.Retweets
}, {
"label": "Favorites",
"value": data.Favorites
}, {
"label": "Replies",
"value": data.Replies
}];
In the end, I would like my directive to append the pie to the current <div class="pie_chart"></div> (which is generated in the directive template) with the mapped data of the current h object that has been passed. But as ocket-san mentionned d3.select(someElement) only matches the first element in the DOM.
Here is my directive :
.directive('pieChart', ['d3', function(d3) {
return {
restrict: 'E',
scope: {
data: '=',
onClick: '&'
},
template: '<div class="pie_chart"></div>',
link: function(scope, iElement, iAttrs) {
// watch for data changes and re-render
scope.$watch('data', function(newVals, oldVals) {
if (newVals) {
scope.render(newVals);
}
}, true);
scope.render = function(data) {
var w = 50, //width
h = 50, //height
r = data.Engagement / 3, // adapt radius to engagement value
color = d3.scale.ordinal().range(["#77b255", "#ffac33", "#07c"]); //custom range of colors
// map data to to be used by pie chart directive
var mapped = [{
"label": "Retweets",
"value": data.Retweets
}, {
"label": "Favorites",
"value": data.Favorites
}, {
"label": "Replies",
"value": data.Replies
}];
data = mapped;
// Courtesy of https://gist.github.com/enjalot/1203641
var vis = d3.select(".pie_chart")
.append("svg:svg") //create the SVG element inside the <body>
.data([data]) //associate our data with the document
.attr("width", w) //set the width and height of our visualization (these will be attributes of the <svg> tag
.attr("height", h)
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + r + "," + r + ")") //move the center of the pie chart from 0, 0 to radius, radius
var arc = d3.svg.arc() //this will create <path> elements for us using arc data
.outerRadius(r);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) {
return d.value;
}); //we must tell it out to access the value of each element in our data array
var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
.data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
.enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
.append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
.attr("class", "slice"); //allow us to style things in the slices (like text)
arcs.append("svg:path")
.attr("fill", function(d, i) {
return color(i);
}) //set the color for each slice to be chosen from the color function defined above
.attr("d", arc); //this creates the actual SVG path using the associated data (pie) with the arc drawing function
};
}
}
}]);
The problem is that the instruction
var vis = d3.select(".pie_chart")
.append("svg:svg")
Appends all the pie charts to the first div with the pie_chart class.
I tried changing it to d3.select(iElement) (…) but it didn't work.
Any suggestions ?
Thanks in advance !
Q.
You can see the current output there :
http://i61.tinypic.com/wqqc0z.png
The problem is that d3.select('.pie_chart') selects the first element matching such class in the body, not within your directive template. To achieve this, you should use the element object provided within the link function. In your case:
var vis = d3.select(element[0]).select(".pie_chart").append("svg")...
I have created a simplified fiddle trying to show this.
Hope it helps.
When we using Angularjs and d3js together we’ll need to make updating the d3.select('body') selection to be relative to the directive using
d3.select(element[0]) instead of the entire DOM. The reason we have to use element[0] instead of just element is because element “is” a jQuery
wrapped selection and not an ordinary DOM object. Doing element[0] gives us just the plain old DOM element. (I say “is” in quotes because it’s technically a jqlite wrapped DOM element. jqlite is essentially a slimmed down version of jQuery.)
So you need to update your Code to:
.directive('pieChart', ['d3', function(d3) {
return {
restrict: 'E',
scope: {
data: '=',
onClick: '&'
},
template: '<div class="pie_chart"></div>',
link: function(scope, iElement, iAttrs) {
// watch for data changes and re-render
scope.$watch('data', function(newVals, oldVals) {
if (newVals) {
scope.render(newVals);
}
}, true);
scope.render = function(data) {
var w = 50, //width
h = 50, //height
r = data.Engagement / 3, // adapt radius to engagement value
color = d3.scale.ordinal().range(["#77b255", "#ffac33", "#07c"]); //custom range of colors
// map data to to be used by pie chart directive
var mapped = [{
"label": "Retweets",
"value": data.Retweets
}, {
"label": "Favorites",
"value": data.Favorites
}, {
"label": "Replies",
"value": data.Replies
}];
data = mapped;
// Courtesy of https://gist.github.com/enjalot/1203641
//Part need Update
var vis = d3.select(iElement[0])
.append("svg:svg") //create the SVG element inside the <body>
.data([data]) //associate our data with the document
.attr("width", w) //set the width and height of our visualization (these will be attributes of the <svg> tag
.attr("height", h)
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + r + "," + r + ")") //move the center of the pie chart from 0, 0 to radius, radius
var arc = d3.svg.arc() //this will create <path> elements for us using arc data
.outerRadius(r);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) {
return d.value;
}); //we must tell it out to access the value of each element in our data array
var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
.data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
.enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
.append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
.attr("class", "slice"); //allow us to style things in the slices (like text)
arcs.append("svg:path")
.attr("fill", function(d, i) {
return color(i);
}) //set the color for each slice to be chosen from the color function defined above
.attr("d", arc); //this creates the actual SVG path using the associated data (pie) with the arc drawing function
};
}
}
}]);
When you update your code the directive('pieChart') function dynamically will select <pie-chart/> tag. if you have specific class, update your code to:
var vis = d3.select(iElement[0]).select(".pie_chart")
Update 1
You need to add $index to ng-repeat because:
What Angular is telling us is that every element in an ng-repeat needs to be unique. However,
we can tell Angular to use the elements index within the array instead to determine uniqueness by adding track by $index.
<ol>
<li ng-repeat="h in hashtags track by $index" | orderBy:predicate:reverse | limitTo: limit">
<div class="hashtag">
<a ng-click="showTweetsForHashtag(h)">#{{h.Hashtag}}</a>
</div>
<div class="frequency">
{{h.Frequency}} times
</div>
<div class="engagement">
{{h.Engagement}}
<pie-chart data="h" on-click="showTweetsForHashtag(item)"></pie-chart>
</div>
</li>
</ol>
I found the answers here to be incorrect in my case.
Jarandaf - was the closest however my solution was to remove the class selector.
and just use the below code:
d3.select(element[0]).append('svg')
d3.select("element") always selects the first element it finds. For example: suppose you have the following html structure:
<body>
<p></p>
<p></p>
<p></p>
</body>
and you would code: d3.select("p").append("svg"), the result is going to be
<body>
<p>
<svg></svg>
</p>
<p></p>
<p></p>
</body>
You need to use d3.selectAll(element), which will give you a d3 selection with all the items that fit the selector.
edit:
Ok, so i think your final html structure could look something like this:
<ol>
<li ng-repeat="h in hashtags | orderBy:predicate:reverse | limitTo: limit">
<div class="hashtag">
<a ng-click="showTweetsForHashtag(h)">#{{h.Hashtag}}</a>
</div>
<div class="frequency">
{{h.Frequency}} times
</div>
<div class="engagement">
{{h.Engagement}}
<div id="pie_chart">
<svg> your piechart goes here</svg>
</div>
</div>
</li>
<li ng-repeat="h in hashtags | orderBy:predicate:reverse | limitTo: limit">
<div class="hashtag">
<a ng-click="showTweetsForHashtag(h)">#{{h.Hashtag}}</a>
</div>
<div class="frequency">
{{h.Frequency}} times
</div>
<div class="engagement">
{{h.Engagement}}
<div id="pie_chart">
<svg> another piechart goes here</svg>
</div>
</div>
</li>
</ol>
so suppose that html structure already exists without the tag (its because i dont know anything about angular or directives :-) ) and you want to append the svg tag and append an tag to every div with class "pie_chart", you need to do it as following:
var piecharts = d3.selectAll(".pie_chart").append("svg");
The result will be an html structure like above.
If this is not what you want, then I am sorry, I think i totally misunderstood the question :-)
Thank you Gabriel for you answer !
In the meantime, I found a workaround (it may not be a the prettiest, but it works !)
Directive :
.directive('pieChart', ['d3', function(d3) {
return {
restrict: 'E',
scope: {
data: '=',
max: '#',
item: '#',
onClick: '&'
},
template: '<div class="pie_chart"></div>',
link: function(scope, iElement, iAttrs) {
// watch for data changes and re-render
scope.$watch('data', function(newVals, oldVals) {
if (newVals) {
scope.render(newVals);
}
}, true);
scope.render = function(data) {
// Courtesy of https://gist.github.com/enjalot/1203641
var vis = d3.selectAll(".pie_chart")
.each(function(d, i) {
if (scope.item == i) {
var w = 50, //width
h = 50, //height
normalized = 50 * (data.Engagement) / (scope.max),
r = normalized/2, // adapt radius to engagement value
color = d3.scale.ordinal().range(["#77b255", "#ffac33", "#07c"]); //custom range of colors
// map data to to be used by pie chart directive
var mapped = [{
"label": "Retweets",
"value": data.Retweets
}, {
"label": "Favorites",
"value": data.Favorites
}, {
"label": "Replies",
"value": data.Replies
}];
var vis = d3.select(this)
.append("svg:svg") //create the SVG element inside the template
.data([mapped]) //associate our data with the document
.attr("width", w) //set the width and height of our visualization (these will be attributes of the <svg> tag
.attr("height", h)
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + (w/2) + "," + (h/2) + ")") //move the center of the pie chart from 0, 0 to radius, radius
.on("click", function(d, i){
return scope.onClick({item: data});
});
var arc = d3.svg.arc() //this will create <path> elements for us using arc data
.outerRadius(r);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) {
return d.value;
}); //we must tell it out to access the value of each element in our data array
var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
.data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
.enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
.append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
.attr("class", "slice"); //allow us to style things in the slices (like text)
arcs.append("svg:path")
.attr("fill", function(d, i) {
return color(i);
}) //set the color for each slice to be chosen from the color function defined above
.attr("d", arc); //this creates the actual SVG path using the associated data (pie) with the arc drawing function
}
})
};
}
}
}])
HTML
<ol>
<li ng-repeat="h in hashtags | orderBy:predicate:reverse | limitTo: limit">
<div class="hashtag">
<a ng-click="showTweetsForHashtag(h)">
#{{h.Hashtag}}
</a>
</div>
<div class="frequency">
{{h.Frequency}} times
</div>
<div class="engagement">
<pie-chart data="h" max="{{hashtagMaxEngagement}}" item="{{$index}}" on-click="showTweetsForHashtag(item)">
</pie-chart>
</div>
</li>
</ol>
Thanks everyone for your help !
Q.

D3.js Problems Filtering topojson data

I've setup an example to demonstrate the issue's I've encountered:
To be brief, I'm using d3 to render a map of the united states. I'm appending relevant data attributes to handle click events and identify which state was clicked.
On click events the following is preformed:
I'm grabbing the US County topojson file (which contains ALL US
Counties).
Removing irrelevant counties from the data and rendering them on the
map of the state that was clicked.
I can't seem to figure out what is going on behind the scenes that is causing some of the counties to be drawn while others are ignored.
When I log the data that is returned from the filtered list, I'm displaying the accurate number of counties, but they are only partially drawn on the map. Some states don't return any. Pennsylvania and Texas partially work.
I've checked the data and the comparison operations, but I'm thinking this may have to do with arcs properties being mismatched.
If I utilize the Counties JSON file to render the entire map of the united states they are all present.
If anyone can help shed some light on what might be happening that would be great.
svg {
fill:#cccccc;
height:100%;
width:100%;
}
.subunit{
outline:#000000;
stroke:#FFFFFF;
stroke-width: 1px;
}
.subunit:hover{
fill:#ffffff;
stroke:#FFFFFF;
stroke-width="10";
}
<body>
<script src="http://www.cleanandgreenfuels.org/jquery-1.11.3.min.js"></script>
<script src="http://www.cleanandgreenfuels.org/d3.v3.min.js"></script>
<script src="http://www.cleanandgreenfuels.org/topojson.v1.min.js"></script>
<script>
var width = window.innerWidth;
height = window.innerHeight;
var projection = d3.geo.albers()
.scale(1500)
.translate([width / 2, height / 2]);
//d3.geo.transverseMercator()
//.rotate([77 + 45 / 60, -39 - 20 / 60]);
//.rotate([77+45/60,-40-10/60])
//.scale(500)
//.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width+"px")
.attr("height", height+"px");
d3.json("http://www.cleanandgreenfuels.org/usstates2.json.php", function(error, us, init) {
//svg.append("path")
// .datum(topojson.feature(us, us.objects.counties))
//.attr("d", path);
function init(){
$( document ).ready(function() {
$('.subunit').on('click',function(){
var stateid = $(this).attr("data-stateid");
function clearStates(stateid){
d3.json("http://www.cleanandgreenfuels.org/uscounties2.json.php", function(error, us) {
console.log(us);
console.log("DATA CLICKED ID "+stateid);
test = jQuery.grep(us.objects.counties.geometries, function(n){
return (n.properties.stateid == stateid);
});
us.objects.counties.geometries = test;
console.log(test.length);
console.log(us.objects.counties.geometries.length);
var test = topojson.feature(us, us.objects.counties).features;
console.log(test);
console.log(test.length);
svg.selectAll(".subunit")
.data(test)
.enter().append("path")
.attr("class", function(d) { return "subunit"; })
.attr("d", path)
.attr("data-countyid", function(r){ return r.id; });
});
}
clearStates(stateid);
});
});
}
svg.selectAll(".subunit")
.data(topojson.feature(us, us.objects.us).features)
.enter().append("path")
.attr("class", function(d) { return "subunit"; })
.attr("d", path)
.attr("data-stateid", function(r){ return r.id; });
init();
});
</script>
</body>
It appears as if I was attempting to utilize some outdated features, using topojson.mesh and .datum() to add the new data has resolved this issue, but has introduced a new error.
Now it appears as if the polygons that are rendered must be in sequence to be drawn properly this way.
I think the data going in should be cleaned up to optimize the way d3 is designed to function, but I'd still like to know more about how it is rendering this information that is obtained from the dataset.
function clearStates(stateid){
d3.json("http://www.cleanandgreenfuels.org/uscounties2.json.php", function(error, us) {
console.log(us);
console.log("DATA CLICKED ID "+stateid);
test = jQuery.grep(us.objects.counties.geometries, function(n){
return (n.properties.stateid == stateid);
});
us.objects.counties.geometries = test;
console.log(test.length);
console.log(us.objects.counties.geometries.length);
**var test = topojson.mesh(us, us.objects.counties);**
console.log(test);
console.log(test.length);
**svg.append("path")
.datum(test)
.attr("class", function(d) { return "subunit"; })
.attr("d", path)
.attr("data-countyid", function(r){ return r.id; });**
});
}

How to use D3 inside a ui-view state

So I have an angular application that is using ui-router. Say I want to display a bar graph inside div.wtflike so:
index.html
<div ui-view ></div>
state1.html
<div class="wtf"></div>
js:
var dataset = [5,3,2,2,10];
d3.select('.wtf').selectAll('div')
.data(dataset)
.enter()
.append('div')
.attr('class', 'bar')
.style('height', function (d) {
return d * 5 + 'px';
});
Does the js have to be in a controller? if so how to I make sure the document is ready?
You could go with custom directives. It gives access to the DOM without junking up your controller.
app.directive('bindDeeThreeStuff', function($window){
var d3 = $window.d3;
return function(scope, elem, attrs) {
var d3Elem = d3.select(elem[0]);
scope.$watch(attrs.bindDeeThreeStuff, function(val) {
d3Elem.selectAll('.bar').data(val)
.enter()
.append('div')
.attr('class', 'bar')
.style('height', function(d) {
return d * 5
});
});
};
});
And in your view:
<div bind-dee-three-stuff="myData"></div>
and in your controller:
$scope.myData = [10,20,30,40,50];
Edit: Example
Here's a quick example of a simple directive that binds data with d3.

Categories

Resources