Handling class attribute assignments in Polymaps - css
I'm a social science person increasingly getting into web programming for data vis work so apologies if this question is dumb. I'm working on a polymaps implementation to visualize country level data over time. I reads in json temporal data and a geojson world map and spits out a quantile chloropleth map that iterates over monthly entries. The heart of this is a country formating function that binds a colorbrewer class to the country geojson objects (see below). This works find for the animation portion. The problem is that I am using a custom d3 layer that displays the date of the data currently displayed and acts as a mouseover control to stop the animation and choose a date or to choose a date once the animation is through. It does this by creating an blank svg element that uses the d3.scale() function to round mouse input to an integer that matches the index of the month desired. I've front loaded all the other calculations on load so that the only thing that happens at mouse over is the change of svg class (this is basically the same as Tom Carden's wealth of nations implementation on Bostock's d3 page here). Unfortunately, this still overloads the browser pretty quickly. Is there another way to do this that I'm totally missing? I admit im new to geojson so maybe some way to construct an array of classes with in the class attribute of the geojson object? Thanks a ton of any help.
function foo(local, geojson){
for(var x=0;x<geojson.length;x++){
var n = geojson[x].data.properties.name;
n$(geojson[x].element)
.attr("class", geojson[x].data.formats[local])
.add("svg:title");
}
}
EDIT: I'm adding the full script below.
<meta charset="utf-8">
<script src="scripts/d3.v3.js"></script>
<script src="scripts/polymaps.js"></script>
<script src="scripts/nns.js"></script>
<script>
//Polymaps namespace
var po = org.polymaps;
//Chart dimensions
var margin = {top: 20, right: 20, bottom: 20, left: 20};
var w = 960 - margin.right;
var h = 500 - margin.top - margin.bottom;
// Create the map object, add it to #map div
var map = po.map()
.container(d3.select("#map").append("svg:svg").attr("width", w + margin.left + margin.right).attr("height",h +margin.top + margin.bottom).node())
.center({lat: 28, lon: 0})
.zoom(1.85)
.zoomRange([1.5, 4.5])
.add(po.interact());
// Add the CloudMade image tiles as a base layer…
map.add(po.image()
.url(po.url("http://{S}tile.cloudmade.com"
+ "/1a1b06b230af4efdbb989ea99e9841af" // http://cloudmade.com/register
+ "/20760/256/{Z}/{X}/{Y}.png")
.hosts(["a.", "b.", "c.", ""])));
//Import contribution data
d3.json("assets/contributionsTCC1990-1991.json", function(data){
//find length of json data object and loop over it at interval
var dataLength = Object.keys(data).length;
//Create date key/value array using construtor
function date_array_constructor() {
var dateArray = {};
for(var i = 0; i < dataLength; i++) {
var d = i + 1;
dateArray[d] = data[i].date;
}
return dateArray;
}
var dateArray = date_array_constructor();
// Insert date label/control layer and add SVG elements that take on attributes determined by load function
var labelLayer = d3.select("#map svg").insert("svg:g");
map.add(po.geoJson()
.url("assets/world.json")
.tile(false)
.zoom(3)
.on("load", load));
map.container().setAttribute("class", "Blues");
map.add(po.compass()
.pan("none"));
function find_max(data, dataLength) {
var max = 0;
for(var i in data) {
if(data[i] > max) {
max = data[i] + 1;
}
}
return max;
}
function max_array_constructor(data, dataLength) {
var maxArray = {};
for(var i=0;i<dataLength;i++) {
var d = i+1;
maxArray[d] = find_max(data[i].contributions);
}
return maxArray;
}
var maxArray = max_array_constructor(data, dataLength);
function contribution_array_constructor(data, dataLength, tccName, feature) {
var contributions = {};
//iterate over date entries
for(var i=0;i<dataLength;i++) {
//contribution iterator
contributions[i+1] = 0;
for(x in data[i].contributions){
if(x == tccName) {
contributions[i+1] = data[i].contributions[x];
}
}
}
return contributions;
}
function format_array_constructor(data, dataLength, maxArray, feature) {
var formats = {};
// console.log(feature.data.contributions);
//iterate over date entries
for(var i=0;i<dataLength;i++) {
var percentile = feature.data.contributions[i+1] / maxArray[i+1];
if(percentile != 0){
var v = "q" + ((~~(percentile*7)) + 2) + "-" + 9;
}else{
var v = "countries";
}
formats[i+1] = v;
}
return formats;
}
///////////////////////////////
//load function
///////////////////////////////
function load(e) {
//Bind geojson and json
var geojson = e.features;
console.log(geojson);
geojson.dates = dateArray;
for(var x = 0; x < geojson.length; x++) {
// var tccID = geojson[x].data.id;
var tccName = geojson[x].data.properties.name;
geojson[x].data.contributions = contribution_array_constructor(data, dataLength, tccName, geojson[x]);
geojson[x].data.formats = format_array_constructor(data, dataLength, maxArray, geojson[x]);
}
//Insert date label
var dateLabel = labelLayer.append("text")
.attr("class", "date label")
.attr("text-anchor", "end")
.attr("x", w-670)
.attr("y", h )
.text(dateArray[1]);
//Add interactive overlay for date label
var box = dateLabel.node().getBBox();
var overlay = labelLayer.append("rect")
.attr("class", "overlay")
.attr("x", box.x)
.attr("y", box.y)
.attr("opacity",0)
.attr("width", box.width)
.attr("height", box.height)
.on("mouseover",enable_interaction);
function country_class_constructor(local, geojson){
for(var x=0;x<geojson.length;x++){
var n = geojson[x].data.properties.name;
n$(geojson[x].element)
.attr("class", geojson[x].data.formats[local])
.add("svg:title");
}
}
function foo(local, geojson){
for(var x=0;x<geojson.length;x++){
var n = geojson[x].data.properties.name;
n$(geojson[x].element)
.attr("class", geojson[x].data.formats[local])
.add("svg:title");
}
}
//incrementor function
function incrementor(local, geojson, dateArray) {
setTimeout(function() {
//set date label to current iteration
d3.transition(dateLabel).text(dateArray[local]);
//construct country classes
country_class_constructor(local, geojson);
// console.log(geojson);
}, 500*local);
}
///////////////////////////////
//Increment on load
///////////////////////////////
country_class_constructor(1, geojson)
for(var i=1; i< dataLength; i++) {
//Set incrementer as local variable
var local = i+1;
var timer = incrementor(local, geojson, dateArray);
}
///////////////////////////////
//interaction element
///////////////////////////////
function enable_interaction(){
var dateScale = d3.scale.linear()
.domain([1,Object.keys(dateArray).length])
.range([box.x + 10, box.x + box.width - 10])
.clamp(true);
timer = null;
overlay
.on("mouseover", mouse_over)
.on("mouseout",mouse_out)
.on("mousemove",mouse_move)
.on("touchmove",mouse_move);
function mouse_over() {
dateLabel.classed("active", true);
}
function mouse_out() {
dateLabel.classed("active", false);
}
function mouse_move() {
update_map(dateScale.invert(d3.mouse(this)[0]),data);
// displayYear(dateScale.invert(d3.mouse(this)[0]));
}
function update_map(userInput) {
var date = Math.floor(userInput);
d3.transition(dateLabel).text(dateArray[date]);
// console.log(date);
// country_class_constructor(date, geojson);
foo(date, geojson);
}
}
}
});
</script>
Edit 2: I forgot to add the JSON format. See below for two months of data:
[
{"date":"11/90",
"contributions":{
"Algeria":7,
"Argentina":39,
"Australia":41,
"Austria":967,
"Bangladesh":5,
"Belgium":4,
"Brazil":27,
"Canada":1002,
"Chile":7,
"China":5,
"Colombia":12,
"Czech Republic":6,
"Denmark":374,
"Ecuador":21,
"Fiji":719,
"Finland":992,
"France":525,
"Germany":13,
"Ghana":892,
"Hungary":15,
"India":40,
"Indonesia":5,
"Ireland":814,
"Italy":79,
"Jordan":6,
"Kenya":7,
"Malaysia":15,
"Nepal":851,
"Netherlands":15,
"New Zealand":22,
"Nigeria":2,
"Norway":924,
"Poland":165,
"Republic of the Congo":6,
"Russia":35,
"Senegal":4,
"Serbia":17,
"Spain":63,
"Sweden":738,
"Switzerland":5,
"Turkey":2,
"United Kingdom":769,
"United States":33,
"Uruguay":10,
"Venezuela":23,
"Zambia":6
}
},
{"date":"12/90",
"contributions":{
"Algeria":7,
"Argentina":39,
"Australia":41,
"Austria":967,
"Bangladesh":5,
"Belgium":4,
"Brazil":27,
"Canada":1002,
"Chile":7,
"China":5,
"Colombia":12,
"Czech Republic":6,
"Denmark":374,
"Ecuador":21,
"Fiji":719,
"Finland":992,
"France":525,
"Germany":13,
"Ghana":892,
"Hungary":15,
"India":40,
"Indonesia":5,
"Ireland":814,
"Italy":79,
"Jordan":6,
"Kenya":7,
"Malaysia":15,
"Nepal":851,
"Netherlands":15,
"New Zealand":22,
"Nigeria":2,
"Norway":924,
"Poland":165,
"Republic of the Congo":6,
"Russia":35,
"Senegal":4,
"Serbia":17,
"Spain":63,
"Sweden":738,
"Switzerland":5,
"Turkey":2,
"United Kingdom":769,
"United States":33,
"Uruguay":10,
"Venezuela":23,
"Zambia":6
}
}
]
After hours of debugging, it turns out that the nns.js library was causing me problems. Each iteration of the animation was creating a new set of DOM objects which started to max out the browser at 25,000. The solution was to use nss to create the initial state and then using the following function to change the class of each svg element.
function country_class_constructor(local, geojson){
for(var x=0;x<geojson.length;x++){
var n = geojson[x].data.properties.name;
element = document.getElementById(n);
element.className["animVal"] = geojson[x].data.formats[local];
element.className["baseVal"] = geojson[x].data.formats[local];
}
Related
Is it possible to reset an entire class without looping through the elements
I have a 12 month calendar. When a user clicks on the month I am calling my function toggleZoom $monthNode.onclick = function(){toggleZoom(this)}; at the moment I cam controlling the zoom using this JS: function toggleZoom(month) { var zoomed = window.getComputedStyle(month).zIndex; var m = document.getElementsByClassName("month"); for(var i = 0; i < m.length; i++) { m[i].style ='' ; } if (zoomed != 2) { month.style = 'transform:scale(1.1,1.1); z-index:2'; } } Is there a cleaner way (one line of code, maybe) to reset all of my month classes to un-zoomed without looping through all 12? Something like document.getElementsByClassName("month").style=""
You can use the map() function to loop through your array in a single line without creating a for loop, like so: m.map(function(mo){ mo.style = ''; }); function toggleZoom(month) { var zoomed = window.getComputedStyle(month).zIndex; var m = document.getElementsByClassName("month"); m.map(function(mo){ mo.style = ''; }); if (zoomed != 2) { month.style = 'transform:scale(1.1,1.1); z-index:2'; } } Or, using ES6's arrow function: function toggleZoom(month) { var zoomed = window.getComputedStyle(month).zIndex; var m = document.getElementsByClassName("month"); m.map(mo => mo.style = ''); if (zoomed != 2) { month.style = 'transform:scale(1.1,1.1); z-index:2'; } }
Why can't I increment global variable
I created a variable q outside of any function. From within my function I am attempting to simply increment it with a ++. Will this increment the global q or is this simply appending the value to a local variable? As you can see in the code sample below I am attempting to use the value of the global variable (which I intend to be updated during each execution of this script) to set a variable which should trigger this function via .change. The function is initially trigger (when q = 1) however it is not trigger when a selection is made from the dropdown box with id = "selectedId2" which is leading me to believe that q has retained a value of 1 though I successfully incremented it when the function was ran prior. Any advise of how I can increment the variable "q" for each iteration of this script would be greatly appreciated. if (q === 1) { selectedDiv = '#selectId1'; selectedDiv2 = '#selectId2'; } if (q === 2) { selectedDiv = '#selectedId2'; selectedDiv2 = '#selectedId3'; } if (q === 3) { selectedDiv = '#selectedId3'; selectedDiv2 = '#selectedId4'; } if (q === 4) { selectedDiv = '#selectedId4'; selectedDiv2 = '#selectedId5'; } if (q === 5) { selectedDiv = '#selectedId5'; selectedDiv2 = '#selectedId6'; } $(selectedDiv).change(function () { if (q == 1) { var pullDownDivs = '#2'; } if (q == 2) { var pullDownDivs = '#3'; } if (q == 3) { var pullDownDivs = '#4'; } if (dropDownSelectJoined != null) { var dropDownSelectJoined = dropDownSelectJoined + ", " + $(selectedDiv).val(); } else { var dropDownSelectJoined = $(selectedDiv).val(); } var SelArea = $(selectedDiv).val(); if (SelArea != 0) { var url = '#Url.Action("NetworkSubForm")'; q++; $.post(url, { RemovedAreaId: $('#RemovedAreaId').val(), selectedNetworkId: $('#SelectedNetworkId').val(), dropDownSelectJoined: dropDownSelectJoined }, function (data) { var productDropdown = $(selectedDiv2); productDropdown.empty(); productDropdown.append("<option>-- Select Area --</option>"); for (var i = 0; i < data.length; i++) { productDropdown.append($('<option></option>').val(data[i].Value).html(data[i].Text)); } }); $(pullDownDivs).show(); $(pullDownDivs).html(); } else { $(pullDownDivs).hide(); $(pullDownDivs).html(); } });
I don't know what the rest of your code looks like, but you can see this kind of behavior due to "shadowing": var q = 0; //global "q" function handler() { var q = 0; //local "q" that shadows the global "q"; ... ... q++; console.log(q); } Repeatedly calling handler will output 1 each time since you are redefining a local q within handler. However, the outer q remains unchanged. But if you did this: var q = 0; //global "q" function handler() { var q = 0; //local "q" that shadows the global "q"; ... ... window.q++; console.log(window.q); } The global q will be updated since you are explicitly referencing it by doing window.q.
Google Earth Plugin multilpe placemarks
I'm using Google Earth plugin in order to show multiple polygons(circles) at the same time, how can I do this?, I have a listbox with lat or geoDataSplit[0] and lng or geoDataSplit[1], Want to go through this listbox latlngs, pass them to polygonplacemark, store all the circles may be in an array or what would you suggest and show all of them, the code below prints all the circles but one by one and not all at the same time: var setOfPlacemarks = []; function createCircle(centerLat, centerLng, radius) { function make2Circle(centerLat, centerLng, radius) { var ring = ge.createLinearRing(''); var steps = 25; var pi2 = Math.PI * 2; for (var i = 0; i < steps; i++) { var lat = parseFloat(centerLat) + radius * Math.cos(i / steps * pi2); var lng = centerLng + radius * Math.sin(i / steps * pi2); ring.getCoordinates().pushLatLngAlt(lat, lng, 0); } return ring; } var polygonPlacemark = ge.createPlacemark(''); polygonPlacemark.setGeometry(ge.createPolygon('')); var outer = ge.createLinearRing(''); var dlist = document.getElementById('salesList'); for (var i = 0; i < dlist.options.length; i++) { var geoData = dlist.options[i].text; geoDataSplit = geoData.split(","); polygonPlacemark.getGeometry().setOuterBoundary(make2Circle(parseFloat(geoDataSplit[0]), parseFloat(geoDataSplit[1]), .00001*parseInt(geoDataSplit[2])/5)); polygonPlacemark.setName(geoDataSplit[2]); ge.getFeatures().appendChild(polygonPlacemark); setOfPlacemarks.push(polygonPlacemark); } printAllPlacemarks(); } function printAllPlacemarks() { var kmlObjectList = ge.getFeatures().getChildNodes(); alert(kmlObjectList); for (var i = 0; i < setOfPlacemarks.length; i++) { alert(setOfPlacemarks[i]); ge.getFeatures().appendChild(setOfPlacemarks[i]); } }
How to put a infowindow on polyline in Google Maps v3?
I want to know how to show infowindow on polyline in using Google Maps Api V3? and to appear in the middle of the polyline ?!
Firstly you will need to calculate the middle/center of the polyline. This has been discussed and answered here; https://stackoverflow.com/a/9090409/787921 Then you will have to open the infowindow; var infowindow = new google.maps.InfoWindow({ content: "infowindow text content"}); infowindow.setPosition(midLatLng); infowindow.open(map);
find the middle point and set your custom view . func showPath(polyStr :String){ polyline?.map = nil mapView1.reloadInputViews() pathDraw = GMSPath(fromEncodedPath: polyStr)! polyline = GMSPolyline(path: pathDraw) polyline?.strokeWidth = 4.0 polyline?.strokeColor = UIColor.init(red: 247/255.0, green: 55/255.0, blue: 76/255.0, alpha: 1.0) polyline?.map = mapView1 let poinsCount = pathDraw.count() let midpoint = pathDraw.coordinate(at: poinsCount) DispatchQueue.main.async { self.addMarkerPin(corrdinate: midCordinate, distance: "10 min") } } func addMarkerPin(corrdinate:CLLocationCoordinate2D, distance: String) { let marker = GMSMarker() marker.position = corrdinate PathTimeView = PathInfoView.loadFromNib() //here i am load Xib file, you can use your custom view let DynamicView=PathTimeView DynamicView.timelbl.text = distance UIGraphicsBeginImageContextWithOptions(DynamicView.frame.size, false, UIScreen.main.scale) DynamicView.layer.render(in: UIGraphicsGetCurrentContext()!) let imageConverted: UIImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() marker.icon = imageConverted marker.map = self.mapView1 marker.infoWindowAnchor = CGPoint(x: -1900 , y: -2000) }
First you should got center/middle of polyline and this what works for me private fun centerPos(points: MutableList<LatLng>): LatLng { val middleDistance = SphericalUtil.computeLength(points).div(2) return extrapolate(points, points.first(), middleDistance.toFloat()) ?: points[0] } private fun extrapolate(path: List<LatLng>, origin: LatLng, distance: Float): LatLng? { var extrapolated: LatLng? = null if (!PolyUtil.isLocationOnPath( origin, path, false, 1.0 ) ) { // If the location is not on path non geodesic, 1 meter tolerance return null } var accDistance = 0f var foundStart = false val segment: MutableList<LatLng> = ArrayList() for (i in 0 until path.size - 1) { val segmentStart = path[i] val segmentEnd = path[i + 1] segment.clear() segment.add(segmentStart) segment.add(segmentEnd) var currentDistance = 0.0 if (!foundStart) { if (PolyUtil.isLocationOnPath(origin, segment, false, 1.0)) { foundStart = true currentDistance = SphericalUtil.computeDistanceBetween(origin, segmentEnd) if (currentDistance > distance) { val heading = SphericalUtil.computeHeading(origin, segmentEnd) extrapolated = SphericalUtil.computeOffset( origin, (distance - accDistance).toDouble(), heading ) break } } } else { currentDistance = SphericalUtil.computeDistanceBetween(segmentStart, segmentEnd) if (currentDistance + accDistance > distance) { val heading = SphericalUtil.computeHeading(segmentStart, segmentEnd) extrapolated = SphericalUtil.computeOffset( segmentStart, (distance - accDistance).toDouble(), heading ) break } } accDistance += currentDistance.toFloat() } return extrapolated } then You can add infoWindow with normal way with your platform at it is differ from each platform
ActionScript 2 character selection
I haven't been able to make a character selection in ActionScript 2 so what is an example that, if I click on this button, a movieclip comes out in this frame?
Frame 1: movieClip1.alpha = 0; movieClip1.stop(); movieClip2.alpha = 0; movieClip2.stop(); movieClip3.alpha = 0; movieClip3.stop(); button1.onPress = function() { movieClip1.alpha = 100; movieClip1.play(); } button2.onPress = function() { movieClip2.alpha = 100; movieClip2.play(); } button3.onPress = function() { movieClip3.alpha = 100; movieClip3.play(); }
try something like the below. I haven;t tested this so it prob won;t compile but it'll be very close. Basically put this on a single empty frame on the main timeline. make sure you have button and character movieclips all with export settings and linkage identifiers set. Modify code below and see what happens. var numButtons:Number = 10; //number of buttons you want var buttonMovieClipName:String = "button"; //linkage identifier of button var startX:Number = 10; //start x position var startY:Number = 500; //start y position var dist:Number = 10; //distance between buttons var characters:Array = {"A","B","C","D"}; //linkage names of your characters var currentChar:MovieClip = null; for(var i:Number = 0; i < numButtons; i++) { this.attachMovie("button", "button"+i, this.getNextHighestDepth()); this["button"+i]._x = startX + (i*(dist+this["button"+i]._width])); this["button"+i]._y = startY; this["button"+i].character = characters[i]; this["button"+i].onPress = displayCharacter; } function displayCharacter():void { var par = this._parent; //remove previous character on stage if(currentChar != null) { removeMovieClip(par[currentChar]); } par.attachMovie(this.character, this.character, par.getNextHighestDepth()); //atach character par[this.character]._x = 400; //set to whatever par[this.character]._y = 300; //set to whatever currentChar = this.character; //set current character to this }