I've got the following d3 map:
but as you can see it's all bunched up there in the left corner of the screen- this is sub opimal- ideally I would like it to be centered.
How can I achieve this?
I guess it should be easy but every time I google something like "center align d3 map" I get things about zooming
:/
maybe I need to create a div or something?
the code is here on my GitHub
also below- it's pretty well commented.
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<style>
.border {
stroke: #000;
fill: none;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
div.tooltip {
position: absolute;
text-align: center;
width: 84px;
height: 64px;
padding: 2px;
font: 12px sans-serif;
background: lightgrey;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
</head>
<body>
<h1>Administrative Sub-Regions of Europe</h1>
<!-- this is the form at the bottom to change the NUTS -->
<form>
<select id="json_sources" name="json_sources" >
<option value ="nuts0" selected >Source 0</option>
<option value ="nuts1" >Source 1</option>
<option value ="nuts2" >Source 2</option>
<option value ="nuts3" >Source 3</option>
</select>
<form>
<!-- spinner -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.0.1/spin.min.js'></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://d3js.org/colorbrewer.v1.min.js"></script>
<script src="https://cdn.rawgit.com/rveciana/d3-composite-projections/v0.2.0/composite-projections.min.js"></script>
<!-- why do we need this? -->
<section id='chart'>
</section>
<script>
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var width = 600,
height = 500;
var projection = d3.geo.conicConformalEurope();
var graticule = d3.geo.graticule();
var path = d3.geo.path()
.projection(projection);
// Find new colours here: http://colorbrewer2.org/
var scale = d3.scale
.quantize()
.domain([10,60])
.range(colorbrewer.PuRd[3]);
var svg = d3.select("body")
.append("svg")
.attr("width", width, "100%")
.attr("height", height, "100%")
.call(
d3.
behavior.
zoom().
on("zoom", function () {
svg.attr("transform", "translate(" +
d3.event.translate +
")" + " scale(" +
d3.event.scale +
")")
}
)
)
.on("dblclick.zoom", null)
.append("g")
//what the hell does this do?
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
// pretty self spoken
var dropdown = d3.select("#json_sources")
// config references SPINNER RELATED
var chartConfig = {
target : 'chart',
data_url : './nuts0.json',
width: 600,
height: 500,
val: 90
};
// loader settings SPINNER RELATED
var opts = {
lines: 9, // The number of lines to draw
length: 9, // The length of each line
width: 5, // The line thickness
radius: 14, // The radius of the inner circle
color: '#EE3124', // #rgb or #rrggbb or array of colors
speed: 1.9, // Rounds per second
trail: 40, // Afterglow percentage
className: 'spinner', // The CSS class to assign to the spinner
};
// SPINNER RELATED
var target = document.getElementById(chartConfig.target);
// KICK OFF callback function wrapped for loader in 'init' function
function init() {
// trigger loader initial spinner
var spinner = new Spinner(opts).spin(target);
// load json data and trigger callback
d3.json(chartConfig.data_url, function(data) {
// stop spin.js loader
spinner.stop();
// instantiate chart within callback
chart(data);
});
}
//call that init function we define above
init();
//here where all the real stuff happens
//in fact all that init stuff is just legacy
//from the spinner example
function chart(data) {
//start of map making function
var change = function() {
// trigger loader of the spinner
var spinner = new Spinner(opts).spin(target);
// did they change the NUTS?
var source = dropdown.node().options[dropdown.node().selectedIndex].value;
//necessary data processing
var str1 = source;
var str2 = ".json";
var file = str1.concat(str2);
console.log(file);
d3.json(file, function(error, europe) {
d3.csv("povertry_rate.csv", function(error, povrate) {
//change the map to apadpt to the nuts file
if (source == "nuts1") {
var land = topojson.feature(europe, europe.objects.nuts1);
} else if (source == "nuts2") {
var land = topojson.feature(europe, europe.objects.nuts2);
} else if (source == "nuts3") {
var land = topojson.feature(europe, europe.objects.nuts3);
} else if (source == "nuts0") {
var land = topojson.feature(europe, europe.objects.nuts0);
}
data = {};
povrate.forEach(function(d) {
data[d.GEO] = d['2013'];
});
//clear way for the regeneration
d3.selectAll("path").remove();
//recreate those map lines
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
// stop spin.js loader
spinner.stop();
console.info(data);
svg
.selectAll("path")
.data(land.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke","#000")
.style("stroke-width",".5px")
.style("fill",function(d){
var value = data[d.id];
if (isNaN(value)){
value = data[d.id.substring(0,2)];
}
if (isNaN(value)){
return "#fff";
}
return scale(value);
})
.on("mouseover", function(d,i) {
var value = data[d.id];
if (isNaN(value)){
value = data[d.id.substring(0,2)];
}
div.transition()
.duration(200)
.style("opacity", 0.9);
div.html("<b>"+d.properties.name+"</b><br/>" + value + "%")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d,i) {
div.transition()
.duration(500)
.style("opacity", 0);
});
svg
.append("path")
.style("fill","none")
.style("stroke","#000")
.attr("d", projection.getCompositionBorders());
});
})
}
dropdown.on("change", change)
change(); //call that change function once
}
</script>
</body>
</html>
Not really a d3 question just a little CSS:
svg {
display: block;
margin: auto;
border: 1px solid gray;
}
Related
I have a dashboard with two separate maps of a state showing different data based on years 2014 and 2012. The map when hovered over show the name of area individually. What I need to do is display both 2012 and 2014 maps's tooltips at the same time over the respective maps when I mouseover any one of the two maps. How can I display both at the same time. I would appreciate any help with this. Thanks.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test dashboard</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" >
<style>
#gujarat-viz-2017, #buttons {
border-right: 1px solid #ccc
}
.container {
background-color: #d5e8ec;
}
.const0 {
display: none;
}
.emptyparty {
fill:#f9f9f1;
}
.emptyparty:hover, .constituency:hover {
fill:#ccc;
}
.hidden { display: none; }
.showtooltip { position: absolute; z-index: 10000; background-color: #333;
border-radius: 10px; color: #fff; padding: 5px; }
/*Party colors*/
.bjp{ fill: #f88101;}
.inc{ fill: #6da736;}
.ncp{ fill: #076598;}
.gpp{ fill: #5a469d;}
.ind{ fill: #25a29a;}
.jdu{ fill: #eb4d4c;}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div id="gujarat-viz-2014" class="col-md-6">
<h2>2014</h2>
</div>
<div id="gujarat-viz-2012" class="col-md-6">
<h2>2012</h2>
</div>
</div> <!-- .row -->
</div>
<script src="http://www.thehindu.com/static/js/jquery-1.10.2.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script>
function map_function(map_settings){
// Global variables
var margin = { top: 50, left:50, right:50, bottom:50 },
height = 400 - margin.top - margin.bottom,
width = 500 - margin.left - margin.right;
// Create SVG canvas with responsive resizing
var svg = d3.select(map_settings["htmlelement"])
.append("svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMinYMin")
.append("g")
.attr("class", "data"+map_settings["year"])
// Add a tooltip to visualization
var tooltip = d3.select('body').append('div')
.attr('class', 'hidden showtooltip')
.attr('id', "tooltip"+map_settings["year"])
// queue and read the topojson, json data file
d3.queue()
.defer(d3.json, "https://api.myjson.com/bins/17m3if")
.defer(d3.json, map_settings.data)
.await(render_map)
var projection = d3.geoMercator()
.scale(3000)
.center([71.5, 22.3])
.translate([width / 2, height / 2])
var geoPath = d3.geoPath()
.projection(projection)
function render_map(error, mapshape, mapdata){
var constituency = topojson.feature(mapshape, mapshape.objects.collection).features;
dataMap = {};
mapdata.forEach(function(d){
dataMap[d.constNo] = d;
})
var fill_function = function(d) {
// d3.select(this).attr('fill', "white")
} // end of mousemove_function
var mousemove_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
// console.log(constinfo.constituencyName)
// console.log(d3.select(this).data()[0].properties)
var html = "<p>"+constinfo.constituencyName+"</p>"
tooltip.classed('hidden', false)
.html(html)
.style("left", (d3.event.clientX - 10) + "px")
.style("top", (d3.event.clientY - 45) + "px");
} // end of mousemove_function
var class_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
var className = "constituency ";
if(constinfo !== undefined) {
className += ("c"+constinfo.constNo+" ")
className += constinfo.leadingParty.replace(/[^a-zA-Z ]/g, "").toLowerCase()
} else {
className += "emptyparty"
className += " const"
className += d.properties.AC_NO
}
return className;
} // end of class_function
var mouseout_function = function(d) {
tooltip.classed('hidden', true)
} // end of mousemove_function
svg.selectAll(".constituency")
.data(constituency)
.enter().append("path")
.attr("d", geoPath)
.attr('class', class_function)
.attr('fill', "white")
.attr('stroke', "#e8e8e8")
.attr('stroke-width', "0.5")
.on('mouseover', mousemove_function)
.on('mouseout', mouseout_function)
} // render_map
} // map_function
var gujarat_data_2014 = {
htmlelement: "#gujarat-viz-2014",
data: "https://api.myjson.com/bins/yolfr",
year: "2014"
};
var gujarat_data_2012 = {
htmlelement: "#gujarat-viz-2012",
data: "https://api.myjson.com/bins/19ztxj",
year: "2012"
};
map_function(gujarat_data_2014);
map_function(gujarat_data_2012);
</script>
</body>
</html>
I'd modify your mousemove and mouseout to operate on both maps at the same time:
var mousemove_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
var html = "<p>" + constinfo.constituencyName + "</p>"
var tooltips = d3.selectAll('.showtooltip');
// get paths from all maps
d3.selectAll('.c' + constinfo.constNo)
.each(function(d,i){
var pos = this.getBoundingClientRect();
// operate on appropriate tooltip
d3.select(tooltips.nodes()[i]).classed('hidden', false)
.html(html)
.style("left", (pos.x + pos.width/2) + "px")
.style("top", (pos.y - pos.height/2) + "px");
});
} // end of mousemove_function
var mouseout_function = function(d) {
d3.selectAll('.showtooltip').classed('hidden', true);
} // end of mousemove_function
Running code here.
I don't understand why the color of legend's labels does not always correspond to the color in the map in my code:
This is my code (that is the modification of this code). The file provincias.json is available here:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.nombre{
stroke: #000;
stroke-width: 0.5px
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
.legendLinear
{
font-family: "Lato";
fill:#c2b59b;
}
.legendTitle {
font-size: 1em;
}
#tooltip {
position: absolute;
top: 0;
left: 0;
z-index: 10;
margin: 0;
padding: 10px;
width: 200px;
height: 70px;
color: white;
font-family: sans-serif;
font-size: 1.0em;
font-weight: bold;
text-align: center;
background-color: rgba(0, 0, 0, 0.55);
opacity: 0;
pointer-events: none;
border-radius:5px;
transition: .2s;
}
</style>
<body>
<div id="container">
<div id="tooltip">
</div>
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-composite-projections/0.3.5/conicConformalSpain-proj.min.js"></script>
<script>
var width = 1000,
height = 900;
var projection = d3.geo.conicConformalSpain()
var graticule = d3.geo.graticule().step([2, 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#container").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("provincias.json", function(error, provincias) {
d3.json("hdi.json", function(error, hdi) {
var land = topojson.feature(provincias, provincias.objects.provincias);
var color = d3.scale.linear()
.domain([0, 10, 1000, 10000, 100000, 300000])
.range(["#feebe2","#e5d1ff","#ba93ef", "#8D4CE5","#6100E5","#C94D8C"]); //#feebe2
svg.selectAll(".nombre")
.data(land.features)
.enter()
.append("path")
.attr("d", path)
.attr("class","nombre")
.style("fill",function(d){ return color(hdi[d.properties.nombre]) })
.on("mouseover", function(d){
//Show the tooltip
var x = d3.event.pageX;
var y = d3.event.pageY - 40;
d3.select("#tooltip")
.style("left", x + "px")
.style("top", y + "px")
.style("opacity", 1)
.text(d.properties.nombre + "," + hdi[d.properties.nombre]);
})
.on("mouseout", function(){
//Hide the tooltip
d3.select("#tooltip")
.style("opacity", 0);
});
svg
.append("path")
.style("fill","none")
.style("stroke","#000")
.attr("d", projection.getCompositionBorders());
d3.select("svg").append("g")
.attr("class", "legendLinear")
.attr("transform", "translate(100,500)");
var legendLinear = d3.legend.color()
.title("...")
.shapeHeight(20)
.shapeWidth(90)
.shapeRadius(10)
.cells([0, 10, 1000, 10000, 100000, 300000])
.orient("horizontal")
.labelFormat(d3.format(".00f"))
.labelAlign("start")
.scale(color);
svg.select(".legendLinear")
.call(legendLinear);
});
});
</script>
The content of hdi.json is the following:
{"Coruña, A":9, "Alicante":158, "Albacete":3,"Almería":0,"Asturias":13,"Álava":12,"Ávila":0,
"Badajoz":10,"Balears, Illes":331,"Barcelona":250000,"Burgos":5,
"Cantabria":12,"Castellón":316,"Ceuta":9,"Ciudad Real":9,"Cádiz":9,"Cuenca":4,
"Córdoba":11,"Cáceres":2,"Girona":21808,"Jaén":0,
"Granada":9,"Huelva":3,"Huesca":74,
"León":5,"Lleida":9672,"Lugo":3,
"Madrid":507,"Murcia":24,"Málaga":25,"Palencia":2,"Pontevedra":6,
"Navarra":23,"Salamanca":6,"Segovia":4,"Sevilla":16,"Soria":2,
"Santa Cruz de Tenerife":16,"Tarragona":22790,
"Teruel":23,"Toledo":4,"Valladolid":44,
"Valencia":423,"Vizcaya":19,"Zamora":0,"Zaragoza":56,"Guipúzcoa":21,
"Guadalajara":5,"Jaen":2,"Rioja, La": 12, "Palmas, Las": 10,"Ourense":2}
The particular problem is that Tarragona that has the value 22790 is colored in the same color as Lleida that has the value 9672. However, according to my code, 22790 (Tarragona) is smaller than 100000 and bigger than 10000, so it should be colored in #6100E5, but it's colored in #8D4CE5.
But, for example, 9672 (Lleida) is smaller than 10000, so it should be colored in #8D4CE5 (and it is colored in this color, so it's ok).
You shouldn't be using a linear scale, by definition it has a continuous range and will interpolate between the colors. What you are describing is a threshold scale. It has a discrete range mapped to subsets of domain values. Further, you must call it with a range that's N + 1 of the domain, so, this is what you should be after:
var color = d3.scale.threshold()
.domain([10, 1000, 10000, 100000, 30000])
.range(["#feebe2","#e5d1ff","#ba93ef", "#8D4CE5","#6100E5","#C94D8C"]);
Here's an example creating the legend using a threshold scale and d3-legend.
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>
</head>
<body>
<svg></svg>
<script>
var color = d3.scale.threshold()
.domain([10, 1000, 10000, 100000, 300000])
.range(["#feebe2", "#e5d1ff", "#ba93ef", "#8D4CE5", "#6100E5", "#C94D8C"]);
var svg = d3.select("svg");
svg.append("g")
.attr("class", "legendLog")
.attr("transform", "translate(20,20)");
var logLegend = d3.legend.color()
.labels([0, 10, 1000, 10000, 100000, 300000])
.scale(color);
svg.select(".legendLog")
.call(logLegend);
</script>
</body>
</html>
Actually it works as expected.
The exact colors of Lleida and Tarragona are different, the former being #8f4fe5, the latter #8741e5.
Because of the linear scales you use these colors are calculated as:
((9672-1000) * #8d4ce5 + (10000-9672) * #ba93ef)/(10000-1000) = #8f4fe5
((22790-10000) * #6100e5 + (100000-22790) * #8d4ce5)/(100000-10000) = #8741e5
Intuitively, your problem is that 9672 is much closer to 10000 as to 1000, and 22790 is also much closer to 10000 than to 100000, so even if one of them is below 10000, and the other one above it, they are still closer to it, than to the other ends of the ranges.
As Mark suggested in his answer, probably you do not want to use continuous linear scales.
I am new to JavaScript and coding in general and I have run into an issue trying to resolve a problem with a JSON column. Los Angeles operates a very handy data portal (https://data.lacity.org/A-Safe-City/LAPD-Crime-and-Collision-Raw-Data-2014/eta5-h8qx), the endpoint of which I have been pointing to in my script. However, the lat/long (found under location_1 column at the above link) is in the format (34.0000, -118.0000). I am attempting to make heat maps using this crime data but I need the lat/long values individually to assign as parameters for the library I am attempting to use. Can someone show me how I can separate those values and assign them to individual variables? Here is an excerpt from the script I am borrowing but I would be happy to show the entire thing if you all think it would help.
//let Socrata do the sorting:
http://data.lacity.org/resource/eta5-h8qx.json
var sodaUrl = "http://data.lacity.org/resource/eta5-h8qx.json?crm_cd=330"
//get json from the Socrata API
$.getJSON( sodaUrl, function( rawData ) {
var goodData = [];
for(var i=0;i<rawData.length;i++){
rawData[i].value = 0;
rawData[i].fresh=true;
if(rawData[i].location_1) {
rawData[i].lat = parseFloat(rawData[i].location_1.latitude);
rawData[i].lng = parseFloat(rawData[i].location_1.longitude);
}
rawData[i].date = new Date(rawData[i].date_occ + "-04:00");
if(rawData[i].location_1) {
if (rawData[i].location_1.latitude && rawData[i].location_1.longitude) {
goodData.push(rawData[i]);
};
};
};
Any help is greatly appreciated and I am happy to be more explicit if I have done poorly thus far.
Edit: here is the whole HTML document. When I try to preview in Dreamweaver I only get the basemap.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>PGC Crime Heatmap Test</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<style>
body {
margin:0; padding:0;
color:#fff;
font-family:verdana,sans-serif;
}
#map {
position:absolute; top:0; bottom:0; width:100%;
}
#titleBox {
position: absolute;
height: 50px;
width: 800px;
background: #666;
border-radius: 10px;
right: 0;
left: 0;
top: 20px;
margin: 0 auto;
border: 2px solid #AFAFAF;
font-size: 17px;
font-weight: bold;
line-height: 46px;
text-align: center;
}
#chartBox {
position:absolute;
height:100px;
width:800px;
background:#666;
border-radius:10px;
right:0;
left:0;
bottom:20px;
margin: 0 auto;
border: 2px solid #AFAFAF;
}
svg text {
fill: #fff;
}
svg line {
stroke: #fff;
stroke-width: 1px;
}
svg .domain {
stroke: #fff;
fill: none;
}
svg rect {
fill: #00AEF7;
opacity: .4;
stroke: #FFF;
}
</style>
</head>
<body>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<script src='heatmap.js'></script>
<script src='leaflet-heatmap.js'></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id = 'map'></div>
<div id = 'titleBox'>
Zip Code 11201 Vehicle Collisions Animated Heatmap (September 1 - Now)
</div>
<div id = 'chartBox'></div>
<!-- Example data. -->
<script>
//heatmap.js config
var cfg = {
// radius should be small ONLY if scaleRadius is true (or small radius is intended)
// if scaleRadius is false it will be the constant radius used in pixels
"radius": .001,
"maxOpacity": 1,
// scales the radius based on map zoom
"scaleRadius": true,
// if set to false the heatmap uses the global maximum for colorization
// if activated: uses the data maximum within the current map boundaries
// (there will always be a red spot with useLocalExtremas true)
"useLocalExtrema": false,
// which field name in your data represents the latitude - default "lat"
//latField: 'latitude',
// which field name in your data represents the longitude - default "lng"
//lngField: 'longitude',
// which field name in your data represents the data value - default "value"
valueField: 'value'
};
var heatmapLayer = new HeatmapOverlay(cfg);
var baseLayer = L.tileLayer(
'https://{s}.tiles.mapbox.com/v3/cwhong.map-hziyh867/{z}/{x}/{y}.png',{
attribution: "<a href='https://www.mapbox.com/about/maps/' target='_blank'>© Mapbox © OpenStreetMap</a> <a class='mapbox-improve-map' href='https://www.mapbox.com/map-feedback/' target='_blank'>Improve this map</a>",
maxZoom: 18
}
);
//setup map and add layers
var map = new L.Map('map', {
center: new L.LatLng(34.0224, -118.2870),
zoom: 14,
layers: [baseLayer, heatmapLayer]
});
//let Socrata do the sorting:
http://data.lacity.org/resource/eta5-h8qx.json
var sodaUrl = "http://data.lacity.org/resource/eta5-h8qx.json?crm_cd=330"
//get json from the Socrata API
$.getJSON( sodaUrl, function( rawData ) {
var goodData = [];
for(var i=0;i<rawData.length;i++){
rawData[i].value = 0;
rawData[i].fresh=true;
if(rawData[i].location_1) {
rawData[i].lat = parseFloat(rawData[i].location_1.latitude);
rawData[i].lng = parseFloat(rawData[i].location_1.longitude);
}
rawData[i].date = new Date(rawData[i].date_occ + "-04:00");
if(rawData[i].location_1) {
if (rawData[i].location_1.latitude && rawData[i].location_1.longitude) {
goodData.push(rawData[i]);
};
};
};
console.log(goodData);
var nextDate = new Date(goodData[0].date);
console.log("first nextDate: " + nextDate);
//initilaize variables for the D3 chart
var countArray = [],
svg,
day,
x,
y,
margin,
height,
width,
intervalCounter = 10,
index = 0,
lastDate,
data = {
max:15,
min:0,
data:[]
};
initializeChart();
//iterate
setInterval(function () {
//iterates 10 times for each day
if (intervalCounter == 10){
intervalCounter = 0;
getAnotherDay();
} else {
intervalCounter++;
}
//create new array for live points, push it to the map
var newData = [];
for(var j=0;j<data.data.length;j++) {
var point = data.data[j];
if(point.value >= 10) {
point.fresh = false;
}
//fade in fresh points, fade out unfresh points
if(point.fresh) {
point.value = point.value + .8;
} else {
point.value = point.value - .1;
}
if(point.value > 0) {
newData.push(data.data[j]);
}
}
data.data = newData;
heatmapLayer.setData(data);
//update the chart
day = svg.selectAll(".day")
.data(countArray)
.enter()
.append("g")
.attr("class", "day")
.attr("transform", function (d) {
//var yesterday = new Date(d.date);
//yesterday = yesterday.setDate(yesterday.getDate() - 1)
return "translate(" + x(d.date) + ",0)";
})
.append("rect")
.attr("width", 28)
.attr("y", function (d) {
return height - y(d.count);
})
.attr("height", function (d) {
return y(d.count);
})
.attr("class", function (d) {
return (d.date);
})
}, 100);
function getAnotherDay() {
nextDate = new Date(nextDate.setHours(24,0,0,0));
var todayCounter = 0;
//iterate over goodData, push today's events to data.data
for (;;index++) {
var thisDate = goodData[index].date;
console.log(thisDate + nextDate);
if(thisDate.getTime() < nextDate.getTime()) {
data.data.push(goodData[index]);
todayCounter++;
lastDate = thisDate;
} else {
//Still need to increment lastDate if there is no data
if(todayCounter == 0) {
console.log(lastDate);
lastDate = lastDate.getDate() - 1;
}
var todayCount = {
date:lastDate,
count:todayCounter
};
countArray.push(todayCount);
break;
}
}
}
//sets margins and axes for the D3 chart. Borrowed from Chris Metcalf's example on dev.socrata.com
function initializeChart() {
// Set our margins
margin = {
top: 20,
right: 20,
bottom: 30,
left: 60
},
width = 800 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom;
// Our X scale
x = d3.time.scale()
.domain([new Date(goodData[0].date), d3.time.day.offset(new Date(goodData[goodData.length - 1].date), 1)])
.rangeRound([0, width - margin.left - margin.right])
//.ticks(d3.time.day, 1);
// Our Y scale
y = d3.scale.linear()
.domain([0,100])
.rangeRound([height, 0]);
// Our color bands
var color = d3.scale.ordinal()
.range(["#308fef", "#5fa9f3", "#1176db"]);
// Use our X scale to set a bottom axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// Same for our left axis
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickValues([0,50,100]);
// Add our chart to the #chart div
svg = d3.select("#chartBox").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
};
});
</script>
</body>
</html>
It should function exactly like this site, I am merely using a different dataportal and mine is centered on LA. http://chriswhong.github.io/nyc-heatmap/
I have drawn a line graph and area under the line is appears to be colored to make it appear like a area graph. The code is shown below
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body {
font: 12px Arial;
}
path {
stroke-width: 1;
stroke : 1;
}
.axis path,.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<script type="text/javascript" src="d3.min.js"></script>
<script type="text/javascript" src="jquery-1.8.0.js"></script>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<script type="text/javascript">
var baseSvg = d3.select("body")
.append("svg")
.attr("height",800)
.attr("width",800)
.append("g")
.attr("transform","translate(50,50)");
$.ajax({
method : 'GET',
url:"URL", //Called my URL here
success:function(data){
var res = data.d.results;
/* res.forEach(function(d){
console.log(new Date(parseInt(d.DATE_SQL.substring(6))));
}) */
buildTrend(res);
}
}) ;
function buildTrend(res) {
var x = d3.time.scale().range([ 50, 700 ]);
var y = d3.scale.linear().range([ 500, 0 ]);
res.forEach(function(d){
d.DATE_SQL = new Date(parseInt(d.DATE_SQL.substring(6)));
});
var line = d3.svg.line().interpolate("basis").x(function(d) {
return x(d.DATE_SQL)
}).y(function(d) {
return y(d.M_COUNT)
});
x.domain(d3.extent(res, function(d) {
return d.DATE_SQL;
}));
y.domain([0,d3.max(res, function(d) {
return d.M_COUNT;
})]);
var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(15);
baseSvg.append("g")
.attr("class", "x axis").attr("transform",
"translate(0," + 500 + ")").call(xAxis)
.selectAll("text").attr("transform",function(d){ return "rotate(-90)"})
.attr("dx", "-.8em").attr("dy", ".15em").style("text-anchor", "end");
var yAxis = d3.svg.axis().scale(y).orient("left").ticks(8);
baseSvg.append("g")
.attr("transform","translate(50,0)")// Add the Y Axis
.attr("class", "y axis").call(yAxis);
baseSvg.append("g")
.attr("transform","translate(0,10)")
.append("path")
.attr("d",line(res))
.attr("stroke","blue");
}
</script>
</body>
</html>
However the result looks like this
I have checked all my code to search "Black" to identify possible cause of the color and i dont find any. Maybe it is default color. Cannot figure out the reason for this.
Thanks,
Veera
Try adding this css to your line:
path line {
fill: none;
stroke: #000;
}
I've had this happen to me in the past, if I remember correctly what is happening is that d3 thinks that the first point and the last point on the line are joined and therefore making it an area and filling it by default with the color black.
If you set the fill to none and add a stroke it should fix this.
Hope this helps.
I am plotting points on a Google Map (so far so good) and then plotting a line between each point based on the point order in the underlying data (not so good). Like a trail or route.
Unlike the handful of examples I've seen doing this, my data are not in GeoJSON format, and I would really like to keep it that way if at all possible. I have tried to adapt the exampels posted here and here but without success.
My results end up with no lines being drawn, and I can't tell if that's because of a projection error or something else syntactical with D3. I have tried to debug through console.log() statements, but I am very week on GIS projections.
Here is the code to plots the points
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=true"></script>
<script src="../js/d3.v3.min.js"></script>
<style type="text/css">
html, body, #map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.markers {
position: absolute;
}
svg.pts {
position: absolute;
}
.markers border {
position: absolute;
stroke: black;
stroke-width: 2px;
}
.markers svg.pts {
width: 60px;
height: 20px;
padding-right: 100px;
font: 10px sans-serif;
}
.markers circle {
fill: brown;
stroke: black;
stroke-width: 1.5px;
}
.SvgOverlay path {
stroke: Orange;
stroke-width: 2px;
fill: Orange;
fill-opacity: .3;
}
</style>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
var map = new google.maps.Map(d3.select("#map").node(), {
zoom: 15,
center: new google.maps.LatLng(29.371397, -81.54938), //N/S E/W
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var data = [ //note this is not in GeoJSON format
{name:"pt1",lng:-81.55082967,lat:29.374915304},
{name:"pt2",lng:-81.55211713,lat:29.373504039},
{name:"pt3",lng:-81.5842252,lat:29.417969924},
{name:"pt4",lng:-81.55230021,lat:29.374245073},
{name:"pt5",lng:-81.55115,lat:29.37263},
{name:"pt6",lng:-81.58737814,lat:29.358476912},
{name:"pt7",lng:-81.59230268,lat:29.359308171},
{name:"pt8",lng:-81.58783883,lat:29.356449048},
{name:"pt9",lng:-81.58189168,lat:29.420264027},
{name:"pt10",lng:-81.58288,lat:29.4202},
{name:"pt11",lng:-81.56079477,lat:29.359527893},
{name:"pt12",lng:-81.55861145,lat:29.356670068},
{name:"pt13",lng:-81.57961314,lat:29.420893275},
{name:"pt14",lng:-81.579302,lat:29.419368},
{name:"pt15",lng:-81.55979967,lat:29.359768002},
{name:"pt16",lng:-81.55823261,lat:29.36122515},
{name:"pt17",lng:-81.58189168,lat:29.420264027},
{name:"pt18",lng:-81.57997524,lat:29.421120323},
{name:"pt19",lng:-81.58148399,lat:29.420030491},
{name:"pt20",lng:-81.57839075,lat:29.420766158},
{name:"pt21",lng:-81.57982489,lat:29.42002304},
{name:"pt22",lng:-81.580266,lat:29.420212},
{name:"pt23",lng:-81.5820392,lat:29.42048164},
{name:"pt24",lng:-81.57894731,lat:29.420509033},
{name:"pt25",lng:-81.57819629,lat:29.418834169}
];
var overlay = new google.maps.OverlayView();
overlay.onAdd = function() {
var layer = d3.select(this.getPanes().overlayLayer).append("div")
.attr("height", "100%")
.attr("width", "100%")
.attr("class", "markers")
.attr("id", "layer");
layer[0][0].style.width = "1366px";
layer[0][0].parentNode.style.width = "100%";
layer[0][0].parentNode.style.height = "100%";
layer[0][0].parentNode.parentNode.style.width = "100%";
layer[0][0].parentNode.parentNode.style.height = "100%";
layer[0][0].parentNode.parentNode.parentNode.style.width = "100%";
layer[0][0].parentNode.parentNode.parentNode.style.height = "100%";
layer[0][0].parentNode.parentNode.parentNode.parentNode.style.width = "100%";
layer[0][0].parentNode.parentNode.parentNode.parentNode.style.height = "100%";
// Add points
overlay.draw = function() {
var projection = this.getProjection(),
padding = 10;
var point = layer.selectAll("svg")
.data( data )
.each(transform) // update existing markers
.enter().append("svg:svg")
.each(transform)
.attr("class", "point pts")
// Add marker on points
point.append("svg:circle")
.attr("r", 4.5)
.attr("cx", padding )
.attr("cy", padding );
// Add a label on points
point.append("svg:text")
.attr("x", padding + 7)
.attr("y", padding)
.attr("dy", ".31em")
.text( function(d) {
return d.name; }
);
//Here is where I'd like to add lines connecting the points, in order
//of appearance in the data object
function _projection( lat, lng ) {
e = new google.maps.LatLng( lat, lng );
e = projection.fromLatLngToDivPixel(e);
return [ e.x - padding, e.y - padding]
// return [ e.x, e.y ]
}
function transform(d) {
//console.log(d);
e = _projection( d.lat, d.lng )
return d3.select(this)
.style("left", e[0] + "px")
.style("top", e[1] + "px");
}
};
};
// Bind overlay to the map…
overlay.setMap(map);
</script>
</body>
</html>
And here is a JSFiddle
Suggestions to get the path added via the data object as presented are most appreciated.
Ok, so I took a look at your code and refactored it a bit. But here is a basic working version of a path drawn between the points: http://jsfiddle.net/AJvt4/3/. There is one caveat though, and that is that the overlayPane doesn't expand when the map pans. I'm not too familiar with google maps so not sure how much I can help there. Here's an explanation of the changes made:
First I created a encompassing svg to house all of your d3 elements in the onAdd event:
var svg = layer.append('svg')
.attr('x', 0)
.attr('y', 0)
Also in the onAdd event I added a d3 path generator (you can read more here):
var lineFn = d3.svg.line()
.x(function (d) {
e = _projection(d.lat, d.lng);
return e[0] + padding
})
.y(function (d) {
e = _projection(d.lat, d.lng);
return e[1] + padding
})
To actually draw the line, I added this in the add event handler:
var line = svg.selectAll('.path').data([data])
line.enter().append('path')
line.attr('class', 'path')
.attr('d', lineFn)
It's important to note the array around the data ([data]). This is because d3 expects an array of arrays with each inner array holding points to a line. This makes it easier to draw multiple lines. In your case there is only one line.
You'll notice a few other changes to make the code a bit more d3-esque. Hope that helps get you started!