Based on the response and example made by Andrew Reid, I produced this
pen code here points_in_subdivisons: on clicking on areas(Germany) on the screen
We want to offer a smooth animation from one close-up on the map to another
by using ZOOM OUT, PAN, ZOOM IN.
I have many divisions(countries) on Country level and then many sub-divisions(regions) inside each country .
Many points scattered across all divisions (countries) on my example mainly above Germany.
when I have to click on a targeted division(country) I must get only the points which correspond to this targeted division(country) that I have just clicked on
That means when the zoom of the subdivision(regions) is triggered(when the click is
made),
the code should take all the points that exist already only inside the
contours of the targeted divison(country) (that have just been clicked on) and points
enclosed-in should scatter in their corresponding subdivisions(regions).
To achieve this functionality and
based on Michael Rovinsky comment:
in the function manipulate(), the code is able to filter and extract only points that are embedded inside the targeted and triggered subdivisions(regions) and exclude markers those that are outside.
Inside function redraw() the enter exit pattern works well .
var svg = d3.select("svg");
width = 960;
height = 500;
var dataArray = [];
var mydataArray= [];
var projection = d3.geoMercator();
var baseProjection = d3.geoMercator();
var path = d3.geoPath().projection(projection);
var gBackground = svg.append("g"); // appended first
var gProvince = svg.append("g");
var gDataPoints = svg.append("g"); // appended second
var ttooltip = d3.select("body").append("div")
.attr("class", "ttooltip");
var csvPath="https://dl.dropbox.com/s/rb9trt4zy87ezi3/lonlat.csv?dl=0";
d3.csv(csvPath, function(error, data) {
if (error) throw error;
d3.json("https://gist.githubusercontent.com/rveciana/5919944/raw/2fef6be25d39ebeb3bead3933b2c9380497ddff4/nuts0.json", function(error, nuts0) {
if (error) throw error;
d3.json("https://gist.githubusercontent.com/rveciana/5919944/raw/2fef6be25d39ebeb3bead3933b2c9380497ddff4/nuts2.json", function(error, nuts2) {
if (error) throw error;
// convert topojson back to geojson
var countries = topojson.feature(nuts0, nuts0.objects.nuts0);
var regions = topojson.feature(nuts2, nuts2.objects.nuts2);
baseProjection.fitSize([width,height],regions);
projection.fitSize([width,height],regions);
var color = d3.scaleLinear().range(["steelblue","darkblue"]).domain([0,countries.features.length]);
var regionColor = d3.scaleLinear().range(["orange","red"]);
baseProjection.fitSize([width,height],countries);
projection.fitSize([width,height],countries);
var featureCollectionCountries = { "type":"FeatureCollection", "features": countries.features };
gBackground
.attr("class", "country")
.selectAll("path")
.data(countries.features)
.enter()
.append("path")
.attr("fill",function(d,i) { return color(i); })
.attr("opacity",0.7)
.attr("d", path)
.style("stroke","black")
.style("stroke-width",0)
.on("mouseover", function() {
d3.select(this)
.style("stroke-width",1)
.raise();
})
.on("mouseout", function(d,i) {
d3.select(this)
.style("stroke-width", 0 );
})
///// now zoom in when clicked and show subdivisions:
.on("click", function(d) {
// remove all other subdivisions:
d3.selectAll(".region")
.remove();
// add new features:
var features = regions.features.filter(function(feature) { return feature.properties.nuts_id.substring(0,2) == d.properties.nuts_id; });
regionColor.domain([0,features.length])
gProvince.selectAll(null)
.data(features)
.enter()
.append("path")
.attr("class","region")
.attr("fill", function(d,i) { return regionColor(i) })
.attr("d", path)
.style("stroke","black")
.style("stroke-width",0)
.on("click", function() {
zoom(projection,baseProjection);
d3.selectAll(".subdivision")
.remove();
})
.on("mouseover", function() {
d3.select(this)
.style("stroke-width",1)
.raise();
})
.on("mouseout", function(d,i) {
d3.select(this)
.style("stroke-width", 0 );
})
.raise()
// zoom to selected features:
var featureCollection = { "type":"FeatureCollection", "features": features }
manipulate(data,features);
redraw(featureCollection);
var endProjection = d3.geoMercator();
zoom(projection,endProjection.fitExtent([[50,50],[width-50,height-50]],featureCollection));
});
dataArray = data;
redraw(featureCollectionCountries);
});
});
});
function zoom(startProjection,endProjection,middleProjection) {
if(!middleProjection) {
d3.selectAll("path")
.transition()
.attrTween("d", function(d) {
var s = d3.interpolate(startProjection.scale(), endProjection.scale());
var x = d3.interpolate(startProjection.translate()[0], endProjection.translate()[0]);
var y = d3.interpolate(startProjection.translate()[1], endProjection.translate()[1]);
return function(t) {
projection
.scale(s(t))
.translate([x(t),y(t)])
path.projection(projection);
return path(d);
}
})
.duration(1000);
}
else {
d3.selectAll("path")
.transition()
.attrTween("d", function(d) {
var s1 = d3.interpolate(startProjection.scale(),middleProjection.scale());
var s2 = d3.interpolate(middleProjection.scale(),endProjection.scale());
var x = d3.interpolate(startProjection.translate()[0], endProjection.translate()[0]);
var y = d3.interpolate(startProjection.translate()[1], endProjection.translate()[1]);
function s(t) {
if (t < 0.5) return s1; return s2;
}
return function(t) {
projection
.translate([x(t),y(t)])
.scale(s(t)(t))
path.projection(projection);
return path(d);
}
})
.duration(1500);
}
}
function redraw(featureCollection,type) {
var mapG = d3.select('svg g.country');
d3.selectAll('circle')
.remove();
let grp = gDataPoints
.attr("class", "circle")
.selectAll("circle")
.data(dataArray,function(d) { return d.NOM; })
let grpEnter = grp.enter()
let group = grpEnter
group.append("circle")
.attr('fill', 'rgba(135, 5, 151, 125)')
.attr('stroke', 'black')
.each(function(d) {
if (d.lon === null ) return;
if (isNaN(d.lon ))return;
if (d.lat === null) return;
if (isNaN(d.lat ))return;
var pos = projection([parseFloat(d.lon), parseFloat(d.lat)]);
d.cx = pos[0];
d.cy = pos[1];
})
.attr("cx", function(d) {
return d.cx;
})
.attr("cy", function(d) {
return d.cy;
})
.attr("r",0.5)
.on("mouseover", showTooltip)
.on("mouseout", hideTooltip)
.on('mousemove', function(d) {
var xPos = d3.mouse(this)[0] - 15;
var yPos = d3.mouse(this)[1] - 55;
ttooltip.attr('transform', 'translate(' + xPos + ',' + yPos + ')');
ttooltip.style('opacity', 1);
var html = "<span>" + d.lon+ "</span>, <span>" + d.lat + "</span>";
ttooltip.html(html);
});
// Setup each circle with a transition, each transition working on transform attribute,
// and using the translateFn
group
.transition()
.duration(2000)
.attrTween("transform",function(d) {
return mapG._groups[0][0] != null ? recenter(featureCollection): null;
});
group.exit().remove() // exit > remove > g
}
function recenter(featureCollection) {
console.log('recentering');
};
function manipulate(data,features){
dataArray= [];
mydataArray =[];
data.forEach(function(ddd)
{
features.forEach(function(feature)
{
var polygoneOriginal =feature;
var points = [parseFloat(ddd.lon), parseFloat(ddd.lat)];
var isIn = d3.geoContains(polygoneOriginal, points);
if(isIn)
{
var element = ddd;
mydataArray.pushIfNotExist(element, function(e) {
return e.lat === element.lat && e.lon === element.lon ;
});
}
});
});
if(mydataArray.length>0)
{
var columnsArray= ["lon","lat"];
dataArray=mydataArray;
dataArray.columns = columnsArray;
}
}
function showTooltip(d) {
var html = "<span>" + d.lon+ "</span>, <span>" + d.lat + "</span>";
ttooltip.html(html);
ttooltip
.style("left", window.pageXOffset + d3.event.x + 12 + "px")
.style("top", window.pageYOffset + d3.event.y + 12 + "px")
.transition()
.style("opacity", 1);
return d3.select(this).attr('fill', 'rgba(103, 65, 114, 0.8)');
}
function hideTooltip() {
ttooltip
.transition()
.style("opacity", 0);
return d3.select(this).attr('fill', 'rgba(103, 65, 114, 0.5)');
}
// check if an element exists in array using a comparer function
// comparer : function(currentElement)
Array.prototype.inArray = function(comparer) {
for(var i=0; i < this.length; i++) {
if(comparer(this[i])) return true;
}
return false;
};
// adds an element to the array if it does not already exist using a comparer
// function
Array.prototype.pushIfNotExist = function(element, comparer) {
if (!this.inArray(comparer)) {
this.push(element);
}
};
My Question is the following : How to make the Zooming (for points circle) to work adequately:
right now, on a map upon click the x y points not scale.
They are rendered as circles in background and I would like them to move with the map.
That means How to apply the same animation zoom (when subdivisions are triggered by click on a division) in order to those points inside the targeted subdivision follow in transition and move with the map and we could see circles points clearly distributed adequately in each correct corresponding subdivisions?
update
Andrew Reid described here How To accomplish a smooth zoom using d3.js
so following his hints.
I added the following instructions in redraw() function
var mapG = d3.select('svg g.country');
group
.transition()
.duration(2000)
.attrTween("transform",function(d) {
return mapG._groups[0][0] != null ? recenter(): null;
});
AND then we should add the code to the The function that should actually do the moving recenter(featureCollection) function to
function recenter(featureCollection) {
// TO ADD CODE TO BE IMPLEMENTED HERE
};
Thank You very much for your cooperation,participation and help !
1- To generate first iteration click on Region equal country
//GENERATE FIRST MAP
dataArray = data;
redraw();
2- To generate counties for example on click on region, we should first set startprojection and endprojection in zoom function and then trigger redraw of circles
//zoom to selected provinces features:
var countiesFeatureCollection = { "type":"FeatureCollection", "features": countiesFeatures }
//manipulate counties And Redraw
manipulateCounties(data,countiesFeatures);
baseProjection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection);
projection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection);
redraw(countiesFeatureCollection,"counties");
if ( projection.translate().toString() === baseProjection.translate().toString() && projection.scale() === baseProjection.scale() )
{
zoom(baseProjection,projection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection));
}
else
{
var endProjection = d3.geoMercator();
zoom(projection,endProjection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection));
}
3-the same thing should be applied to communities
var endProjection = d3.geoMercator();
endProjection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection);
projection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection);
redraw(communesfeatureCollection,"communes");
if ( projection.translate().toString() === projectioncommune.translate().toString() && projection.scale() === projectioncommune.scale()){
zoom(projectioncommune,projection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection));
}
else {
var endProjection = d3.geoMercator();
zoom(projection,endProjection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection));
}
4- Then reinitialise to go to first step 1 by
// start rendering points again
baseProjection.fitSize([width,height],regions);
projection.fitSize([width,height],regions);
//GENERATE AGAIN THE FIRST MAP
dataArray = data;
redraw();
zoom(projection,baseProjection);
ATTACHED WORKING PEN
I am trying to merge these circles but I keep getting a graph of accumulating circles as opposed to circles moving across the graph?
What am I missing?
I have attached the code below. This function is called updatechart. It corresponds to a slider. So whenever I move the slider across the screen. I corresponding year it lands on is where the updated circle data should move.
var filteredyears = d3.nest()
.key(function(d) {
if(year === d.year){
return d;
}
}).entries(globaldataset);
var circled = svg.selectAll('.countries')
.data(filteredyears[1].values);
var circledEnter = circled.enter()
circled.merge(circledEnter);
circledEnter.append("circle").attr("cx", function(d) {
return xScale(d.gdpPercap);
})
.attr("cy", function(d) {
return yScale(d.lifeExp);
})
.attr('transform', "translate("+[40,30]+")")
.attr( 'r', function(d) {
return rScale(d.population) / 100})
.style("fill", function(d) {
if(d.continent == 'Asia'){
return '#fc5a74';
} else if (d.continent == 'Europe') {
return '#fee633';
} else if (d.continent == 'Africa') {
return '#24d5e8';
} else if (d.continent == 'Americas') {
return '#82e92d';
} else if (d.continent == 'Oceania') {
return '#fc5a74';
}
})
.style("stroke", "black");
circled.exit().remove();
You have a couple of issues using the merge() method, which is indeed quite hard to understand initially.
First, you have to reassign your selection:
circled = circled.merge(circledEnter);
Now, from this point on, apply the changes to circled, not to circledEnter:
circled.attr("//etc...
Besides that, your exit selection won't work, since you're calling it on the merged selection. Put it before the merge.
Finally, append goes to the circledEnter selection, before merging, as well as all attributes that don't change.
Here is a very basic demo showing it:
var svg = d3.select("svg"),
color = d3.scaleOrdinal(d3.schemeCategory10);
render();
function render() {
var data = d3.range(~~(1 + Math.random() * 9));
var circles = svg.selectAll("circle")
.data(data);
circles.exit().remove();
var circlesEnter = circles.enter()
.append("circle")
.attr("r", 5)
.attr("fill", function(d) {
return color(d);
});
circles = circlesEnter.merge(circles);
circles.attr("cx", function() {
return 5 + Math.random() * 290
})
.attr("cy", function() {
return 5 + Math.random() * 140
});
}
d3.interval(render, 1000);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
I figured out how to change arc length of a pie chart with new values using d3 following Bostock's Pie Chart Update, but I'm having a hard time figuring out how to also animate the radius of the pic chart at the same time.
This a snippet of my current code:
pie_inner.value(function(d) {
return d.toward === toward || toward === "both" ? d.spent : 0;
});
path_inner
.data(function(d) {
return pie_inner(d.data).map(function(m) {
m.r = d.r;
return m;
})
})
.transition()
.duration(750)
.attrTween("d", function(d) {
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
arc_inner.innerRadius(d.r - thickness);
arc_inner.outerRadius(d.r);
return arc_inner(interpolate(t));
};
});
With this code the arc length animates to the new values nicely but the radius "jumps" to the new value.
I see that this code animates both the arc length and the radius but it does it in sequence as opposed to the same time.
I was setting the radius using the function outerRadius(), but I figured out if I just set the property, then it works:
pie_inner.value(function(d) {
return d.toward === toward || toward === "both" ? d.spent : 0;
});
path_inner
.data(function(d) {
return pie_inner(d.data).map(function(m) {
m.r = d.r;
return m;
})
})
.transition()
.duration(750)
.attrTween("d", function(d) {
d.innerRadius = d.r - thickness;
d.outerRadius = d.r;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc_inner(interpolate(t));
};
});
D3 newbie here.I am trying to target path elements on a map. When I hover on the legend's I want the corresponding data on the map be highlighted.
Like this.
https://vida.io/documents/jspBB83bm6EvEYE3n
This is what I have so far
http://bricbracs.com/map/
I have managed to target cells in the legend on mouseover. Here is the relevant code from the legend.js code.
.on("mouseover", function(d,i) {
var chosen = d.color;
console.log(chosen)
d3.select(this).style("fill", "blue");
})
Here is the relevant code from the script.
d3.csv("expenses.csv", function(data) {
data = data.filter(function(d) { return d.year == 2014 });
data.forEach(function(d) {
d.value = +d.value;
});
color.domain([
d3.min(data, function(d) { return d.value; }),
d3.max(data, function(d) { return d.value })
]);
d3.json("us-states.json", function(json) {
for (var i = 0; i < data.length; i++) {
var dataState = data[i].state;
var dataValue = parseFloat(data[i].value);
for (j = 0; j < json.features.length; j++) {
jsonState = json.features[j].properties.name;
if (dataState == jsonState) {
json.features[j].properties.value = dataValue;
break;
}
}
}
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke","#ccc")
.style("fill", function(d) {
value = d.properties.value;
if (value) {
return color(d.properties.value)
} else {
return "#000"
}
})
});
What's the best way of achieving this. Thanks in advance.
I would like to draw 4 different rectangles for each attribute in json. So i need to draw 4 rectangles for each id. in the below example i would have 16 rectangles for id 1-4.
The width, height of the rectangle are hard coded for now. Also the x and y axis.
Currently it takes each row as a rectangle.?
I have json data like this:
[
[{ "checkins":10},{"builds":11},{"oss":1},{"appsec":10},{"id":1}],
[{ "checkins":1},{"builds":1},{"oss":21},{"appsec":10},{"id":2}],
[{ "checkins":11},{"builds":3},{"oss":11},{"appsec":10},{"id":3}],
[{ "checkins":21},{"builds":20},{"oss":3},{"appsec":30},{"id":4}]
]
I have written the code:
var x_axis = 1;
var y_axis = 45;
var xvar;
var yvar;
var x;
d3.json("GraphData.json", function(data)
{
var rectangle= svggraph.selectAll("rect").data(data).enter().append("rect");
var RectangleAttrb = rectangle
.attr("id", function (d,i) { return "id" + i ; })
.attr("x", function (d,i)
{
xvar=i+1;
if(i==0) return x_axis=0;
if ((i > 0) && (xvar%4==1))
{
x_axis = 0;
}
else
{
x_axis=x_axis+22;
}
//y=i+1;
return x_axis;
})
.attr("y", function (d,i)
{
Yvar=i+1;
if ((i > 0) && (Yvar%4==1))
{
y_axis = y_axis+ 30;
}
return y_axis;
})
.attr("width",function(d) { return 20; } )
.attr("height",function(d) { return 15; })
.style("stroke", function (d) { return "black";})
.style("fill", function(d) { console.log(d);return "white"; });
});
It's still creating only 4 rectangles
I found a workaround solution. It was creating an array and looping through the same to create the rectangles and assigning the x and y axis along with the height and width.
<html>
<script>
var x_axis = 1;
var y_axis = 45;
var x;
var rectangle,RectangleAttrb;
var rect;
var rectdata;
createtinderboxes();
function createtinderboxes()
{
//console.log(" the function is called ");
d3.json("GraphData.json", function(data)
{
rectdata = data;
//console.log(rectdata.length);
for(x=0;x<rectdata.length;x++)
{
console.log(rectdata[x]);
for (index=0;index<5; index++)
{
svggraph.append("rect")
.attr("x", assignxaxis(rectdata, index))
.attr("y", assignyaxis(rectdata,yvar))
.attr("width", 20)
.attr("height", 25)
.style("fill","white")
.style("stroke","black");
}
yvar = yvar+26;
}
});
}
function assignxaxis(rectdata,x)
{
console.log(rectdata[x]);
if (x==4) return;
if(x==0)
{
return x_axis=0
}
else
{
x_axis=x_axis+22;
}
return x_axis;
}
function assignyaxis(rectdata,y)
{
return y;
}
</script>
</html>