compare class of path elements to a dataset - d3.js

I am really new to d3 and I want to make a choropleth map with the d3 datamaps so if have to compare the classes of the pathways to my dataset. So far I did manage to get the path's
html
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.6.9/topojson.min.js"></script>
<script src="/d3map/datamaps.world.min.js"></script>
<div id="container" style="position: relative; width: 500px; height: 300px;"></div>
<script src="d3map.js"></script>
js code
var map = new Datamap({element: document.getElementById('container'),
})
var country = d3.selectAll("path")
Country is an array containing 177 elements
one element in country has this form:
<path d="bunch of numbers" class="datamaps-subunit AFG" style="etc"></path>
i want to see if the country code in the class (e.g. AFG) matches one in my dataset.
Country.class gives undefined
I am probably missing something really obvious

Here's an example fiddle: https://jsfiddle.net/g2v5sgmz/
You can filter specific classed elements in an array with the filter method like this:
var country = d3.selectAll("path");
var afg = country.filter(function(d,i){
if( d3.select(this).classed("AFG") ){
return d3.select(this);
}
})

Related

add legend to dc.js scatterplot

Somewhat related to this question.
I would like to add a legend to a scatterplot using dc.js, ideally with the possibility of highlighting points based on hover/click of the corresponding legend. The source code seems to indicate that color is "legendable" for scatterplots, but I cannot find any examples where this is done.
Running the code below in the browser triggers an error:
tmp.html:26 Uncaught TypeError: Cannot read property 'key' of undefined
at Object.myChart.dimension.group.colorAccessor.d (tmp.html:26)
at Object._chart.getColor (color-mixin.js:149)
at Object._chart.legendables (scatter-plot.js:360)
at Object._legend.render (legend.js:45)
at Object._chart.render (base-mixin.js:703)
at Object.dc.renderAll (core.js:230)
at tmp.html:33
<!DOCTYPE html><html><head><title></title>
<link rel="stylesheet" type="text/css" href="dc.css"/>
<script type="text/javascript" src="https://d3js.org/d3.v5.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.4.6/crossfilter.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/dc/3.0.9/dc.js"></script>
</head><body>
<div id="chart"></div>
<script type="text/javascript">
dc.config.defaultColors(d3.schemeCategory10);
var myChart = dc.scatterPlot("#chart");
let input = [{x:0,y:0,c:1}, {x:1,y:1,c:0},{x:0.5,y:0.5,c:2}];
(function(data) {
var ndx = crossfilter(input),
dims = ndx.dimension( d => [+d.x,+d.y,+d.c]),
grp = dims.group();
myChart
.dimension(dims)
.group(grp)
.colorAccessor(d=> +d.key[2])
.x(d3.scaleLinear().domain(d3.extent(d3.extent(input, d => +d.x))))
.valueAccessor(d => +d.key[1])
.brushOn(false)
.legend(dc.legend().x(750).y(10).itemHeight(13).gap(5));
})(input);
dc.renderAll();
</script></div></body></html>
Yes, unfortunately it is a bug that was reported a long time ago, but has never been fixed. The scatter plot is calling _chart.getColor() with no arguments.
https://github.com/dc-js/dc.js/issues/1138#issuecomment-217861014
Even if it's fixed, it's only returning one legendable, so I think it will only work for the scatter-series case. If you want to display legend items for each color in your chart, you'll have to generate that data yourself:
myChart.legendables = function () {
var byColor = {};
myChart.group().all().forEach(function(d) {
var color = myChart.colors()(myChart.colorAccessor()(d));
byColor[color] = {
chart: myChart,
name: 'color ' + myChart.colorAccessor()(d),
color: color
};
})
return Object.values(byColor);
};
Maybe the chart should do something this automatically. One complication is that there currently aren't any names defined for colors - here I just pasted 'color ' with the value returned by the color accessor, but you presumably have better names in your actual data set.
Fork of your fiddle.

Leaflet and D3js: Feature.properties are not showing in Leaflet popup

How do you add feature properties under a D3 chart in a Leafletjs popup?
I got the popup and chart working, but I can't seem to add the feature.properties below the chart.
Here's a sample of my geoJSON data:
var myData = [{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"Name":"Gulran","Province":"Hirat","Ethnic1":0.19,"Ethnic2":0.32,"Ethnic3":"0.10","Ethnic4":"0.00","Ethnic5":"0.10","Ethnic6":"0.00"},"geometry":{"type":"Polygon","coordinates":[[[60.941162109375,29.897805610155874],[61.92993164062499,31.034108344903512],[63.34716796874999,31.3348710339506],[64.05029296875,30.401306519203583],[64.412841796875,29.735762444449076],[64.09423828125,29.36302703778376],[62.29248046875,29.36302703778376],[60.941162109375,29.897805610155874]]]}},{"type":"Feature","properties":{"Name":"Chahar Burjak","Province":"Nimroz","Ethnic1":0.25,"Ethnic2":0.12,"Ethnic3":0.03,"Ethnic4":0.01,"Ethnic5":"0.00","Ethnic6":"0.00"},"geometry":{"type":"Polygon","coordinates":[[[63.38012695312499,31.3348710339506],[65.06103515625,31.80289258670676],[65.6982421875,31.156408414557],[66.016845703125,30.467614102257855],[65.291748046875,30.164126343161097],[64.22607421875,30.0405664305846],[63.38012695312499,31.3348710339506]]]}}]}];
Here's my popup code:
var popup = L.popup({minWidth: 600}).setContent(div);
layer.bindPopup(popup + '<br>' + feature.properties.NAME);
Here's my jsfiddle code to test. As you can see, the popups are working, but can't get the feature properties to display under the chart.
Thanks for any help...
I'm seeing multiple possible issues.
You are opening a <svg> element, but you are not closing it. If you add text like this it will get "swallowed" and end up within the rendered <svg>here</svg>, but the chart drawn by d3 will hide it:
// won't show the text
var div = $('<div id="chart" style="width: 600px; height: 400px;"><svg>here is some text</div>')[0];
You can fix that by using a self-closing <svg/>:
var div = $('<div id="chart" style="width: 600px; height: 400px;"><svg/><br/>here is some text</div>')[0];
Next issue is that you are trying to combine a L.popup object with strings:
layer.bindPopup(popup + 'here is some text');
Because this L.popup is an object you can not simply concatenate text to it. The result would look like this: "[object Object]here is some text".
And then you are using feature.properties.NAME which will not work because your GeoJSON properties' keys are named Name (notice the capitalization) - use instead: feature.properties.Name
To conclude, to solve your problem change the one line var div = ... in your onEachFeature_LMA function to this:
var div = $('<div id="chart" style="width: 600px; height: 400px;"><svg/><br/>'+feature.properties.Name+'</div>')[0];
Since you are using jQuery, if you want to add more graphs, other HTML elements later you can also build the popup's HTML like this:
var div = $('<div id="chart" style="width: 600px; height: 400px;"><svg/></div>')[0];
var div2 = $('<div></div>').html(feature.properties.Name);
$(div).append($(div2));

Polymer 1.0 / leaflet-map 1.0: lat/long not updating on moveend event

I have a map element in a section of an HTML page using Polymer 1.0 and leaflet-map 1.0:
<section data-route="Page">
<div flex>
<my-maps id="mymap" flex></my-maps>
</div>
</section>
I am trying to add an event to the element that will pass the lat/long of the center coordinates to the console window anytime the map is panned, similar to the 'event' example given here: Polymer Leaflet demo
The problem is that the Lat/Long coordinates do not update; I only see the center coordinates of the starting center point. The map should open initially centered on a geolocated point, with a marker at that point.
I have tried adding the event script to the main index page, and to the element registration as so:
<script>
Polymer({
is: "my-maps",
ready: function () {
L.Icon.Default.imagePath="../../bower_components/leaflet/dist/images";
},
listeners:{
'moveend': 'testmove'
},
testmove: function(e){
var text = "Center: " + this.latitude.toFixed(9)
+ ", " + this.longitude.toFixed(9);
console.log(text);
}
});
</script>
... But I get the same result in both cases; the event fires, but only the initial center point coordinates are passed to the console.
Here is the HTML for my-maps element:
<dom-module id="my-maps">
<style>
:host {
height: 100%;
width:100%;
overflow:hidden;
}
leaflet-map {
position:absolute;
height: 900px;
width:100%;
overflow:hidden;
}
</style>
<template>
<leaflet-map latitude="{{latitude}}" longitude="{{longitude}}" zoom="14">
<leaflet-geolocation enable-high-accuracy latitude="{{latitude}}" longitude="{{longitude}}">
</leaflet-geolocation>
<template is="dom-if" if="{{latitude}}">
<leaflet-marker latitude="{{latitude}}" longitude="{{longitude}}">
</leaflet-marker>
</template>
</leaflet-map>
</template>
(Polymer registration here)
</dom-module>
I tried adding the listener to the leaflet-map tag as well (on-moveend), and I tried moving the registration inside and outside of dom-module element, but same result. I added an id to the map and assigned that lat and long to that instance as Ricky suggests, but now the geolocation point moves to the center on each map pan (where it should remain on the geolocated point.)
To make the marker stay on the geo-located point, but update the center co-ords, simply add an id to the leaflet-map and grab the latitude and longitude attributes:
<leaflet-map id="map">
...
testmove: function(e) {
var text = 'Center: ' + this.$.map.latitude.toFixed(9)
+ ', ' + this.$.map.longitude.toFixed(9);
console.log(text);
}
I essentially answered my extended question here. My solution involved editing the leaflet-map element itself, by adding a one-time listener for the initial latitude and longitude if given by the geolocator. If the geolocator fails, the map pans to a default location. I removed the latitude and longitude property from the leaflet-map element tag in my custom map element.

MouseOver or hover() or css() for changing positions

I have an image on my page and want that it change the position on mouseover or on click, so I tried several things but I cant find the right start.
So I started to change the top-position but that's not working, I would like that on mouseover the image it jumps to position x/y (maybe random x/y) if I mouseover it again it will jump to another position.
$('#image').one('click', function () {
$(this).css( 'top' : '+=200' );
});
But that's not working so please someone could give me some input that I can figure out what to do?
Note that while setting values for top, jQuery will not automatically get the value of top set previously and add 200 to it(like a normal programming language). You could have seen this as an error in executing if you try to see in a console. You can get the value of top first and then add 200px to it and then set it again.
This should work:
$('#image').one('click', function () {
var top = parseInt($(this).css("top"));
$(this).css( 'top' , top+ 200);
});
I had to parse the string to an integer returned by css, you could also substitute that extra operation by some method that returns only the value and not the value with "px" added to it, dunno if there is one already.
Try something like this:
<html>
<head>
<script type="text/javascript">
function switchPos(obj)
{
var divobj = document.getElementById('div1')
var x = divobj.offsetHeight - obj.height - 5;
var y = divobj.offsetWidth - obj.width - 5;
var randomx = Math.floor(Math.random()*x+1);
var randomy = Math.floor(Math.random()*y+1);
obj.style.top = randomx + 'px';
obj.style.left = randomy + 'px';
}
</script>
</head>
<body>
<div id="div1" style="width: 800px; background: red;">
<img src="image.jpg" style="position:relative;" onmouseover="switchPos(this);" onmouseout="switchPos(this);"/>
</div>
</body>
</html>

Highlight multiple path elements within SVG <g> on event action

I have an SVG which has multiple path elements contained within a group (it's a map with multiple islands). I want all the islands (each represented by a path within the group) to be highlighted when the user initiates an action such as onclick.
I can get each element to highlight by doing this:
<script type="text/ecmascript">
<![CDATA[
var _undo_render_hash = new Object;
function onAreaClick(evt) {
// Undo previous highlight ...
for(var i in _undo_render_hash) {
document.getElementById(i).setAttribute("style", _undo_render_hash[i]);
}
// Reset undo stack ...
_undo_render_hash = new Object;
// Highlight this ...
var change = evt.target;
_undo_render_hash[change.id] = change.getAttribute("style")
change.setAttribute("style", "opacity:1; stroke:green; stroke-width:3");
}
]]>
</script>
And then in the document having something like this:
<g id="Comhairle_nan_Eilean_Siar" onclick="onAreaClick(evt)">
<path d="M218.641,49.68c-7.79,6.53-14.651,13.989-19.681,23.28c5.158-0.089,5.851,5.187,10.561,5.52
c7.229,0.514,8.971-7.178,15.359-8.88c0.016,1.914-0.065,5.869-0.96,7.92c-3.403,7.813-18.518,2.504-25.199,1.2
c-2.494,4.745,2.057,11.877-2.16,14.4c-3.206,1.918-8.535-0.242-10.08,3.6c-0.606,1.507-0.103,3.258,0,4.8
c1.748,1.852,4.421,0.283,6.72,0c1.417,2.63,0.715,6.368-2.88,6c0.886,2.395,3.484,3.076,3.6,6.24
c-4.017,1.442-10.129-0.689-14.159,0.72c0.559,1.287,3.194,1.784,3.6,3.601c0.75,3.359-3.252,6.585-3.12,10.08
c-4.764-1.083-9.61,3.419-14.16,2.88c-2.724-0.322-3.863-2.384-5.76-4.08c-0.331,1.269-0.527,2.673-1.44,3.359
c-2.143,0.743-4.523-0.247-5.76-1.199c-1.449,1.431-3.391,2.368-4.8,3.84c1.44,3.359,4.801,4.798,5.76,8.64
c-3.942-0.331-10.234-3.217-14.88-2.88c-6.333,0.46-3.063,7.522-0.479,10.32c-2.172,2.07-5.325,0.009-7.681,0.239
c-4.061,0.398-8.587,6.609-11.279,9.36c-3.73,3.812-6.437,7.813-10.561,9.84c-1.912-4.06-3.364-6.849-6.479-9.6
c-3.927-3.466-10.139-4.854-10.08-11.04c3.815,0.022,4.96,2.719,6.239,5.28c6.865-1.135,5.284-10.717,14.16-9.841
c0.157-1.276-1.402-3.928-0.479-6c4.959-2.283,13.127,3.235,17.52-0.72c-0.652-0.853-0.797-2.692-1.2-4.32
c-0.391-1.582-1.24-3.078-0.96-3.84c-6.283-5.527-17.934-6.946-26.159-11.04c-2.425-1.206-6.217-2.982-5.04-6.72
c4.851,0.01,9.509-0.931,9.119-5.76c-0.326-4.061-5.008-4.486-7.68-6.72c-0.909-2.646-1.201-5.376-1.68-8.16
c-0.46-2.676-1.58-5.595-1.2-8.16c0.578-3.899,5.171-6.548,3.36-10.8c1.446-0.008,2.471,0.408,4.08,0.239
c0.734-4.944,1.731-9.628,4.56-12.479c7.463,4.137,13.87,9.33,17.76,17.04c2.904-1.336,4.214-4.267,5.76-6.96
c3.43,1.182,9.073,2.812,9.841-0.96c1.174-5.771-7.64-13.473-3.841-19.2c1.145-1.725,4.916-3.022,7.44-4.32
c2.276-1.17,5.278-3.83,7.68-3.84c0.969-0.004,2.588,1.384,3.841,1.44c3.428,0.153,8.889-3.59,11.76-6.24
c6.317-5.831,14.279-9.8,22.319-13.68c4.311-2.081,8.819-3.566,12-6c2.371-1.814,3.864-4.521,5.521-6
c0.204-0.044,0.227,0.094,0.24,0.239c2.891,1.724,5.448,4.414,8.16,7.681c2.588,3.118,5.115,5.628,5.279,9.359
C221.368,34.817,211.062,43.433,218.641,49.68z"/>
<path d="M44.16,169.2c1.14,1.858-0.598,3.167-0.24,5.52c0.411,2.7,3.443,4.906,6.24,5.04
c8.161,0.392,14.983-9.597,23.04-9.12c1.205,1.26,2.406,2.029,2.4,3.36c-0.01,1.919-2.489,2.896-2.4,4.56
c0.055,1.041,1.147,2.174,1.92,2.641c4.312,2.598,13.878-0.731,17.28,2.88c0.164,4.52-5.563,9.653-5.76,2.159
c-1.912,0.602-2.41,1.726-4.32,1.681c-4.39-0.104-6.364-7.368-10.561-7.44c-1.626-0.027-3.817,0.905-4.079,3.12
c-0.598,5.049,5.754,6.326,7.68,9.601c-2.523,7.254,7.267,4.608,11.76,4.8c0.29,4.37-3.093,5.066-5.52,6.72
c-4.387-5.06-17.802-5.835-23.04-0.96c5.834,3.937,12.206,8.58,22.08,7.68c0.258,1.184,1.034,1.846,1.199,3.12
c-2.392,2.849-7.622,1.498-11.76,1.68c-2.038,0.091-4.323,0.584-6.24,0.24c-2.577-0.462-4.418-2.99-6.479-3.359
c-4.983-0.896-7.753,2.42-12.24,1.92c-3.857-2.997-3.507-8.639-5.76-12.721c-3.031-5.492-10.032-7.792-18.24-6.479
c-2.548-2.172-4.34-5.101-7.2-6.96c4.087-2.713,2.958-10.643,5.28-15.12c5.915-2.263,6.876,9.052,13.68,7.92
c4.908-0.816,6.09-10.913,11.04-12.479C44,169.2,44.08,169.2,44.16,169.2z"/>
<path d="M60.48,224.88c-1.123,3.356-5.712,3.248-7.44,6c-0.189,4.43,5.952,2.528,8.16,4.56
c-5.562,1.186-12.328-0.868-18-1.439c-0.145-6.089-5.911-1.854-9.12-2.641c-2.348-0.574-4.384-6.269-2.64-8.88
c2.262-3.386,14.088-4.188,18-1.2c1.309,1,0.646,2.567,2.64,3.601C54.909,226.023,58.178,223.486,60.48,224.88z"/>
<path d="M38.88,238.561c1.151,5.955,11.284,5.267,9.36,12.72c0.696,1.465,2.814,1.506,4.56,1.921
c-0.191,1.327-2.382,0.657-2.64,1.92c0.414,4.786,3.711,6.688,3.84,11.76c-1.044,1.436-2.94,2.019-5.279,2.16
c-0.516,5.726-1.513,10.968-7.2,11.52c0.217,3.38,3.585,5.16,4.08,8.88c0.644,4.846-0.931,7.977-4.32,9.84
c-0.208,4.385,8.039,3.485,7.44,7.44c-0.272,1.796-5.073,2.562-7.921,2.64c-5.482,0.151-10.604-1.434-12.479-4.8
c-4.678-8.392-3.109-23.416-3.12-35.279c6.781-5.746-2.917-23.356,2.64-29.761C29.915,237.129,35.103,237.705,38.88,238.561z"/>
<path d="M15.84,322.801c1.478,2.442,2.316,5.523,3.601,8.16c-4.067,0.868-7.218,5.468-11.76,5.279
c-2.323-0.096-2.966-1.522-5.521-1.68C5.635,329.556,9.217,324.657,15.84,322.801z"/>
</g>
However, only each one of the islands lights up. I'm just getting to grips with SVG - please can someone advise how to structure the document so that each element within the group will have the action applied to it?
Here is a simple example showing how you can use CSS classes on the parent group to easily style the individual elements:
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head>
<meta http-equiv="content-type"
content="application/xhtml+xml; charset=utf-8" />
<title>SVG Styling via CSS Classes</title>
<style type="text/css" media="screen">
body { background:#eee; margin:2em }
svg { display:block; border:1px solid #ccc; background:#fff; margin:auto }
.islands * { stroke-width:2px; stroke:#700; fill:#620; fill-opacity:0.5 }
.over * { stroke-width:4px; stroke:#930; fill:#ff0; fill-opacity:0.9 }
</style>
</head><body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full"
viewBox="-350 -250 700 500" width="800px" height="600px">
<g class="islands"><circle r="70" /><circle cx="150" cy="20" r="50" /></g>
</svg>
<script type="text/javascript"><![CDATA[
var svg = document.getElementsByTagName('svg')[0];
var svgNS = svg.getAttribute('xmlns');
svg.addEventListener('mouseover',function(e){
var g = e.target.parentNode;
g.setAttribute('class',g.getAttribute('class')+' over');
},false);
svg.addEventListener('mouseout',function(e){
var g = e.target.parentNode;
g.setAttribute('class',g.getAttribute('class').replace(' over',''));
},false);
]]></script>
</body></html>
You can see this as a working example on my site:
http://phrogz.net/SVG/css-driven-styles.xhtml
To answer your original question, you would do something like the following:
function onAreaClick(evt){
var island = evt.target;
var parent = island.parentNode;
var others = parent.getElementsByTagName('path');
for (var i=0,len=others.length;i<len;++i){
others[i].setAttribute(...);
}
Variations might include using parent.childNodes, or using direct presentational attributes (e.g. others[i].stroke='#0f0';) instead of setting the style attribute as a string.
This may not directly answer your question, but it is related, and hopefully of some use.
You can use CSS :hover rules to do mouseover highlighting without even using scripting.
<style>
*:hover { fill: red !important; }
</style>
Here's an example showing that, the style is at the top of the file, and there's a short script snippet at the end to reuse the same map data for the zoomed-in view.
Create a polyline for your path, using as many as points you want.
<polyline points="fill points of your paths here" style="">
<set attributeName="stroke" from="original colur" to="red" begin="mouseover" end="mouseout"/>##
</polyline>
Note: attributename="stroke" is important!

Resources