Work in progress on a d3 navigation menu :
thatOneGuy suggested I set hover state for level 2 to keep level 2 rect's visible, that way they wont hide after 1 second but I need some help to get started. How do I set the hover state?
code snippet:
lvl2 = svg.selectAll(".lvl2")
.data(data2, function (d) { return d.name; });
gEnter2 = lvl2.enter()
.append("g")
.attr("class", "lvl2")
.style("fill", function (d, i) { return "url(#gradA" + d.id + ")"; })
.attr("id", function (d, i) { return d.id; })
.on('mouseenter', lighten)
.on('mouseleave', darken)
.attr("transform", function (d, i) { return "translate(" + d.x + "," + 0 + ")"; })
.style("visibility", "hidden");
Here is an acceptable alternative: https://jsfiddle.net/sjp700/3stdk6L6/2/
var tree = d3.layout.tree().nodeSize([70, 40]);
var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
Related
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";
});
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
})
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 have this Line Chart here:
jsfiddle.net/yfqQ4/
My Problem now is, that I don't get a proper grid in the background working with a legend (y and x-Axis) like this: http://lab.creativebrains.net/linechart.png
Can anybody can post me a code snippet how I should implement it or something like that?
Thanks!
I would suggest to use d3.svg.axis().scale() to tie up the grid to your coordinates. I drew a quick example based on your code: http://jsfiddle.net/yfqQ4/5/
The gist is to use the existing scales, x and y, and to use ticks as grid. Notice that height and width are the variable defining the size of your container. Here is the relevant code:
var numberOfTicks = 6;
var yAxisGrid = d3.svg.axis().scale(y)
.ticks(numberOfTicks)
.tickSize(width, 0)
.tickFormat("")
.orient("right");
var xAxisGrid = d3.svg.axis().scale(x)
.ticks(numberOfTicks)
.tickSize(-height, 0)
.tickFormat("")
.orient("top");
svg.append("g")
.classed('y', true)
.classed('axis', true)
.call(yAxisGrid);
svg.append("g")
.classed('x', true)
.classed('axis', true)
.call(xAxisGrid);
You can draw background grid like this:
//vertical lines
svg.selectAll(".vline").data(d3.range(26)).enter()
.append("line")
.attr("x1", function (d) {
return d * 20;
})
.attr("x2", function (d) {
return d * 20;
})
.attr("y1", function (d) {
return 0;
})
.attr("y2", function (d) {
return 500;
})
.style("stroke", "#eee");
// horizontal lines
svg.selectAll(".vline").data(d3.range(26)).enter()
.append("line")
.attr("y1", function (d) {
return d * 20;
})
.attr("y2", function (d) {
return d * 20;
})
.attr("x1", function (d) {
return 0;
})
.attr("x2", function (d) {
return 500;
})
.style("stroke", "#eee");
You can see here how it works with your jsfiddle (updated):
http://jsfiddle.net/cuckovic/Phzvy/
I'm upgrading my map to v3 of D3 and using the click to zoom transformation outlined in the D3 example code found here.
My code is nearly identical except that my map has slightly smaller dimensions (564 x 300 instead of 960 x 500). In addition, I have my map nested within a div and off to the top left of my page (though I don't think this matters)
The initial load of my map loads is fine (using black background for distinction currently)
// Clear existing map in case there is remnant data.
$("#map").html(null);
var mapWidth = 564;
var mapHeight = 300;
var projection = d3.geo.albersUsa()
.scale(mapWidth)
.translate([0, 0]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#map")
.append("svg")
.attr("id", "map-svg")
.attr("width", mapWidth)
.attr("height", mapHeight);
svg.append("rect")
.attr("id", "map-background")
.attr("class", "background")
.attr("width", mapWidth)
.attr("height", mapHeight)
.on("click", click);
// Create placeholders for shapes and labels
var states = svg.append("g")
.attr("transform", "translate(" + mapWidth / 2 + "," + mapHeight / 2 + ")")
.attr("id", "states");
However, when a state is clicked, and the click function runs, my transform seems to be off. In my case, the state of Arkansas was clicked (indicated with blue shading)
function click(d)
{
var x = 0,
y = 0,
k = 1;
if (d && centered !== d)
{
var centroid = path.centroid(d);
x = -centroid[0];
y = -centroid[1];
k = 4;
centered = d;
}
else
{
centered = null;
}
d3.select("#states").selectAll("path")
.classed("active", centered && function (d) { return d === centered; });
d3.select("#states").transition()
.duration(1000)
.attr("transform", "scale(" + k + ")translate(" + x + "," + y + ")")
.style("stroke-width", 1.5 / k + "px");
}
My only thought is that my centroid calculations need to be adjusted slightly for the smaller size or different position of the map, but this doesn't seem right either.
How do I make the proper adjustments?
EDIT: I found that if I add a "negative translation" at the end of the transform (translate(" + -x + "," + -y + ")) that it gets closer to properly centering on the zoom, but not perfectly
You're missing one of two transforms from the example; but read on for a simpler solution.
The example you're using has two nested transforms. First, a static transform on the outer G element:
var g = svg.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.append("g")
.attr("id", "states");
This transform serves the same purpose as projection.translate normally does, but the example is using the translate([0, 0]). (As I said, there will be a simpler solution…)
The second transform is set dynamically on the inner G (with id "states") to zoom in:
g.transition()
.attr("transform", "scale(" + k + ")translate(" + x + "," + y + ")");
Note that the var g here refers to the inner G element, because when g was defined, there was an append("g") that was chained with a second append("g"); the variable is thus defined as the second, inner G, rather than the first, outer G.
The resulting SVG looks like this:
<g transform="translate(480,250)">
<g id="states" transform="translate(75.746,-439.514)scale(4,4)">
…
</g>
</g>
Your derivation is missing the nested, inner G. So when you set the "transform" attribute on your #states G element, you're overwriting the outer transform, giving you this:
<g id="states" transform="scale(4)translate(18.936679862557288,-109.8787070159044)">
…
</g>
So, you're missing the static transform, "translate(480,250)".
I'd recommend combining these transforms together. Then you don't need the outer G, and you can :
g.transition()
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"
+ "scale(" + k + ")"
+ "translate(" + x + "," + y + ")");
This also eliminates the need to set the projection’s translate to [0, 0], so you can use the standard translate [width / 2, height / 2] instead. I've updated the example to do just that!