Needing some help... i was able to find an example of a rotating globe, that works great, i even found a way to put red circles at a point. Even better to setup a timer and everything rotates with the globe great. But if i put text on the map at the same point as the red circles it shows up at the starting point that i placed it, but as the world turns the red circle moves with the globe, but the text is frozen at the points that it was written. i am trying to get the text to rotate with the world and the red circles. think in the country of united states i want to put a number, brazil would have number when the globe rotates to china the values would still be on the countries i put it and when it rotates US and Brazil back to the front the numbers are there showing. This is what i have in code, bear with me I am still a noob when working with D3. thanks for any input...
// Initialize some variables:
var element = '#home1',
width = $("#home1").width(),
height = $("#home1").height();
var diameter = 460,
radius = diameter/2,
velocity = .001,
then = Date.now();
var features, circles;
var projection = d3.geo.orthographic()
.scale(radius - 2)
.translate([radius, radius])
.clipAngle(90);
// Save the path generator for the current projection:
var path = d3.geo.path()
.projection(projection)
.pointRadius( function(d,i) {
return radius;
});
// Define the longitude and latitude scales, which allow us to map lon/lat coordinates to pixel values:
var lambda = d3.scale.linear()
.domain([0, width])
.range([-180, 180]);
var phi = d3.scale.linear()
.domain([0, height])
.range([90, -90]);
// Create the drawing canvas:
var svg = d3.select("#home1").append("svg:svg")
.attr("width", diameter)
.attr("height", diameter);
//Create a base circle: (could use this to color oceans)
var backgroundCircle = svg.append("svg:circle")
.attr('cx', diameter / 2)
.attr('cy', diameter / 2)
.attr('r', 0)
.attr('class', 'geo-globe');
// Make a tag to group all our countries, which is useful for zoom purposes. (child elements belong to a 'group', which we can zoom all-at-once)
var world = svg.append('svg:g');
var zoomScale = 1; // default
// Create the element group to mark individual locations:
var locations = svg.append('svg:g').attr('id', 'locations');
// Having defined the projection, update the backgroundCircle radius:
backgroundCircle.attr('r', projection.scale() );
// Construct our world map based on the projection:
d3.json('world-countries.json', function(collection) {
features = world.selectAll('path')
.data(collection.features)
.enter()
.append('svg:path')
.attr('class', 'geo-path')
.attr('d', path);
// features.append('svg:title')
// .text( function(d) { return d.properties.name; });
}); // end FUNCTION d3.json()
d3.json("data.geojson", function(collection) {
console.log("2");
cs = locations.selectAll('path')
.data(collection.features)
.enter().append('svg:path')
.datum(function(d) {return {type: "Point", coordinates: [d.geometry.coordinates[0], d.geometry.coordinates[1]]}; })
.attr('class', 'geo-node')
.attr("d", path.pointRadius(5))
.attr('d', path);
cs1 = locations.selectAll('text')
.data(collection.features)
.enter().append('svg:text')
.attr("transform", function(d) {return "translate(" + projection(d.geometry.coordinates) + ")"; })
.attr("dy", ".35em")
.attr('d', path)
.text(function(d) { return d.properties.name; });
}); // end FUNCTION d3.json()
d3.timer(function() {
if(offpage === 0)
{
var angle = velocity * (Date.now() - then);
projection.rotate([angle,0,0])
svg.selectAll("path").attr("d", path.projection(projection));
}
});
d3.select(window)
.on("touchmove", mousemove)
.on("touchstart", mousedown);
function mousemove() {
offpage = 0;
}
function mousedown() {
offpage=1
}
In your code, features(the world map) is a path, and cs(the city points) is a path, but cs1(the city names) is a text. In your timer you rotate the paths, which doesn't rotate the text.
My solution uses rotation degrees, instead of angle, so you'll have to adapt the formula.
d3.timer(function() {
tcounter++
rotation++
if (rotation>=360) rotation = 0
projection.rotate([rotation,0,0])
www.attr("d", path.projection(projection));
citydot.attr("d", path.projection(projection));
ctext.attr("transform", function(d) {
return "translate(" + projection(d.geometry.coordinates) + ")"; })
.text(function(d) {
if (((rotation + d.geometry.coordinates[0] > -90) && (rotation + d.geometry.coordinates[0] <90)) ||
((rotation + d.geometry.coordinates[0] > 270) && (rotation + d.geometry.coordinates[0] <450)))
return d.properties.city;
else return "" });
if (tcounter > 360) return true
else return false
})
Related
I need to add multiple arcs to one svg elements (every one got different animation). I need to fill them with radial gradients, but now, the center of radial gradient is not in the centre of whole svg element, but in the centre of specific arc. How it looks now. First i make all of the gradients i need in defs.
var tmpgrad=null;
for( var k = 0; k<data.length;k++){
tmpgrad = grads
.append("radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("r", "50%")
.attr("id", function(d, i) { return "grad" + k; });
tmpgrad
.append("stop")
.attr("offset", "0%")
.style("stop-color", data[k].endColor)
.style("stop-opasity", 0);
tmpgrad
.append("stop")
.attr("offset", "100%")
.style("stop-color",data[k].startColor)
.style("stop-opasity", 1);
}
Then I make my arcs with deferent options:
var width= 1200;
var height=360;
var oArc = d3.svg.arc()
.innerRadius(iRadius)
.outerRadius(oRadius);
var oPie = d3.layout.pie()
.startAngle( sA )
.endAngle( eA )
.sort(null);
var group = svgDrawer.append("g");
var oPath = group.selectAll("g")
.data(oPie([0,200]))
.enter()
.append("path");
oPath
.attr("fill",function(d, i){ if(i==1){ i=0; } else { i = id; } return "url(#grad" + i +")"; })
.attr("d",oArc)
.each(function(d){
this._current = d;
});
But in the result as i said before i got the separated arcs with gradient starting in the middle of every arc element (path actually). How can i solve that.
I am the beginner in svg and javascript subject.
I started to use dc.js Library to create all kinds of graphs and I bumped into a problem when I was trying to create a Geo Choropleth map using dc.js and couldn't add the ability to zoom and move the map.
All the examples I saw were using d3 and svg.. but once I used those examples, I couldn't use the data of dc.dimention and all the crossfilter calculations.
for example my code is:
d3.json("world-countries.json", function (statesJson) {
geoChart.width(1000)
.height(600)
.dimension(countryDim)
.projection(d3.geo.mercator()
.scale((960 + 1) / 4 )
.translate([960 / 4, 960 / 4])
.precision(.1))
.group(countryGroup)
.colors(d3.scale.quantize().range(["#E2F2FF","#C4E4FF","#9ED2FF","#81C5FF","#6BBAFF","#51AEFF","#36A2FF","#1E96FF","#0089FF","#0061B5"]))
.colorDomain([0, 200])
.colorCalculator(function(d){ returnd ?geoChart.colors()(d) :'#ccc'; })
.overlayGeoJson(statesJson.features,"state",function(d){
return d.properties.name;
})
.title(function (d) {
return "State: " + d.key + (d.value ? d.value : 0) + "Impressions";
});
Which works nicely, but I want to add the zoom effect and to be able to move the map. how can I do that?!?!
thanks in advance!
So, the answer is:
var width = 960,
height = 400;
var projection = d3.geo.mercator()
.scale(200)
.translate([width/2, height]);
function zoomed() {
projection
.translate(d3.event.translate)
.scale(d3.event.scale);
geoChart.render();
}
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scale(projection.scale())
.scaleExtent([height/2, 8 * height])
.on("zoom", zoomed);
var svg = d3.select("#geo-chart")
.attr("width", width)
.attr("height", height)
.call(zoom);
geoChart
.projection(projection)
.width(1000)
.height(400)
.transitionDuration(1000)
.dimension(countryDim)
.group(ctrGroup)
.filterHandler(function(dimension, filter){
dimension.filter(function(d) {return geoChart.filter() != null ? d.indexOf
(geoChart.filter()) >= 0 : true;}); // perform filtering
return filter; // return the actual filter value
})
.colors(d3.scale.quantize().range(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF",
"#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"]))
.colorDomain([0, 200])
.colorCalculator(function (d) { return d ? geoChart.colors()(d) : '#ccc'; })
.overlayGeoJson(statesJson.features, "state", function (d) { return d.id; })
.title(function (d) {
return "State: " + d.key + " " + (d.value ? d.value : 0) + " Impressions";
});
I have a curve running above or below a sloped line and I want to fill the area just above the line and below the curve, but not the area below the line and above the curve, as illustrated in the left image.
Again, it is easy to fill like right image, but I want to fill like in left image. So, how to get rid of the unwanted fill?
svg.append("path").attr("class", "line").attr("d", peak(pp)).style({ fill: peakColor, opacity: 0.5 });
Okay, this is something I came up with for me in case anybody cares about it.
var slope = (dp.rightY - dp.leftY) / (dp.rightX - dp.leftX); // slope of the line
var pp = [];
pp.push({ x: dp.leftX, y: dp.leftY });
var wasAbove = true; // track if it's above the line
data.forEach(function (d) {
if (d.x >= dp.leftX && d.x <= dp.rightX) {
var yAtLine = (d.x - dp.leftX) * slope + dp.leftY;
if (d.y > yAtLine) {
if (!wasAbove)
pp.push({ x: d.x, y: yAtLine });
pp.push(d);
wasAbove = true;
} else if (wasAbove) {
pp.push({ x: d.x, y: yAtLine });
wasAbove = false;
}
}
});
pp.push({ x: dp.rightX, y: dp.rightY });
var peak = d3.svg.line().x(function (d) { return xScale(d.x) }).y(function (d) { return yScale(d.y) });
svg.append("path").attr("class", "line").attr("d", peak(pp)).style({ fill: peakColor, opacity: 0.5 });
I am using the blur effect on the d3 map as given here: http://geoexamples.blogspot.in/2014/01/d3-map-styling-tutorial-ii-giving-style.html?
But after using this method (because of how the data is loaded..using datum) my zoom functionality behaves randomly. Irrespective of where I click it zooms to the same point. Also, the animations have become very slow after using the filter.
Is there any other way to achieve blur? Or a solution to this problem?
Any help?
Thanks.
This is the code for the world creation in case when filtering is required (use of datum as per the code on the above site).
d3.json("world-110m2.json", function(error, world) {
g.insert("path")
.datum(topojson.feature(world, world.objects.land))
.attr("d", path);
g.insert("path")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("d", path)
.append("path");
g.selectAll("path")
.on("click", click);})
This is the code used in case filtering is not required (No use of datum - maybe the datum is causing the issue)
d3.json("world-110m2.json", function(error,topology) {
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d",path)
.on("click", click);)}
This is the zoom function: got the code from here: http://bl.ocks.org/mbostock/2206590
function click(d) {
var x, y, k;
var centered;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
if (active === d) return reset();
g.selectAll(".active").classed("active", false);
d3.select(this).classed("active", active = d);
var b = path.bounds(d);
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
The blur filter consumes lots of resources, as indicated in the post. Speciallly if you combine it with other filters.
One solution would be using Canvas instead of SVG. Here you have some filters using the Canvas element. It should be possible to achieve the same result.
I can't find why the zoom stops working, but the performance is slower because you use all the data, so you are applying the filter to all the data instead of using only the part of the word you are showing, so you are using a much bigger image when you zoom.
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