dc.js responive geojson US map - javascript

Testing with following sample code:
http://dc-js.github.io/dc.js/vc/
I would like to make this map responsive to the container/window size.
However, examples I have seen utilized topojson and SVG to scale the map.
http://techslides.com/demos/d3/d3-worldmap-boilerplate.html
Is this not possible with Geojson to update the map size in a similar fashion?
d3.json("../geo/us-states.json", function (statesJson) {
usChart.width(990)
.height(500)
.dimension(states)
.group(stateRaisedSum)
.colors(d3.scale.quantize().range(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"]))
.colorDomain([0, 200])
.colorCalculator(function (d) { return d ? usChart.colors()(d) : '#ccc'; })
.overlayGeoJson(statesJson.features, "state", function (d) {
return d.properties.name;
})
.title(function (d) {
return "State: " + d.key + "\nTotal Amount Raised: " + numberFormat(d.value ? d.value : 0) + "M";
});

You can follow the exact same approach as the one you linked to (techSlides).
It consists in replacing the whole svg by a new one re-drawn after a resize of the window. On redraw, the only thing that changes is the width and height you use as dimensions of the svg element.
Whether your data is GeoJson ot TopoJson doesn't affect this.
A smoother variant of this technique is to resize the existing svg element on resize of the window, without doing a redraw. Just add an SVG viewBox and preserveAspectRatio="xMinYMin", so that projection inside the SVG is kept.
Example in this blog, and its javascript.

Related

How to avoid overlapping and stack long text legends in D3 graph?

I have D3 graph base on Multi-line graph 3 with v7: Legend, this sample contains few and shorts legends for the graph. In my sample I want to increase the length for legends and stack the data if is necessary, I want to avoid overlapping in the legends,
https://bl.ocks.org/d3noob/d4a9e3e45094e89808095a47da19808d
dataNest.forEach(function(d,i) {
svg.append("path")
.attr("class", "line")
.style("stroke", function() { // Add the colours dynamically
return d.color = color(d.key); })
.attr("d", priceline(d.value));
// Add the Legend
svg.append("text")
.attr("x", (legendSpace/2)+i*legendSpace) // space legend
.attr("y", height + (margin.bottom/2)+ 5)
.attr("class", "legend") // style the legend
.style("fill", function() { // Add the colours dynamically
return d.color = color(d.key); })
.text(d.key);
});
There are two possible solutions I can think of, shown in this JSFiddle.
First, if it is acceptable that the legend is not part of the svg, then realize the legend with a simple unordered list in a container next to the svg. This is probably the best when there is a varying number of legend entries and there are no restrictions considering the styling via css. The browser takes care of varying lengths and does an automatic line break.
Second, if the legend has to be a part of the svg, one can use the getBBox()-method that determines the coordinates of the smallest rectangle around an object inside an svg.
In a first step select all the legend entries that have been rendered and get the bounding boxes:
const bbox = svg.selectAll(".legend")
.nodes()
.map(legend_entry => legend_entry.getBBox());
With this array and the width of the svg, we can calculate the positions for each legend entry:
bbox.reduce((pos, box) => {
let left, right, line;
if (pos.length === 0) {
left = 0;
line = 1;
} else {
/* The legend entry starts where the last one ended. */
left = pos[pos.length - 1].right;
line = pos[pos.length - 1].line;
}
/* Cumulative width of legend entries. */
right = left + box.width;
/* If right end of legend entry is outside of svg, make a line break. */
if (right > svg_width) {
line = line + 1;
left = 0;
right = box.width;
}
pos.push({
left: left,
right: right,
line: line,
});
return pos;
}, []);
Margins and paddings have to be included manually in the calculation of the positions. Of course, one could obtain the maximum width of all legend entries and make them all the same width with center alignment as in the d3noob example.
In the JSFiddle, I realized this repositioning by first rendering a hidden legend and then an additional one that is visible. It is of course possible to use only one legend, but I would not to take any chances that the process of repositioning is visible in the rendered document.

Optimize rendering a map w/ large data set

I'm currently rendering a US map along with every district's border. The grabbing the features of the topojson, we have an array of ~13,000 rows.
I'm also joining data to the topojson file, and running through a csv of ~180,000 rows. I believe I've optimized this process of joining data by ID enough using memoization, where the CSV is essentially turned into a hash, and each ID is the key to it's row data.
This process^ is run 24 times in Next JS through SSG to further the user experience, and so all 24 versions of this map is calculated before the first visit of this deployment. I'm sadly timing out during deployment phase for this specific web page^.
I've inspected the program and seem to find that painting/filling each district may be what's causing the slowdown. Are there any tips you all use to optimize rendering an SVG map of many path elements? Currently the attributes to this map:
1: tooltip for each district styled in tailwind
2: around 5 properties turned to text from topojson file w/ joined data to display upon hover, displayed by tooltip
3: filled dynamically with this snippet which runs a function based on the district's property type
.attr('fill', function (d) {return figureOutColor(d['properties'].type);})
4: adding a mouseover, mousemove, and mouseout event handler to each district.
Edit: Code snippet of adding attrs to my map
export const drawMap = (svgRef: SVGSVGElement, allDistricts: any[]) => {
const svg = d3.select(svgRef);
svg.selectAll('*').remove();
const projection = d3.geoAlbersUsa().scale(900).translate([400, 255]);
const path = d3.geoPath().projection(projection);
const tooltip = d3
.select('body')
.append('div')
.attr(
'class',
'absolute z-10 invisible bg-white',
);
svg
.selectAll('.district')
.data(allDistricts)
.enter()
.append('path')
.attr('class', 'district stroke-current stroke-0.5')
.attr('transform', 'translate(0' + margin.left + ',' + margin.top + ')')
.attr('d', path)
.attr('fill', function (d) {
return figureOutColor(d['properties'].type);
})
.on('mouseover', function (d) {
return tooltip
.style('visibility', 'visible')
.text(`${d.properties.name});
})
.on('mousemove', function (data) {
return tooltip.style('top', d3.event.pageY - 40 + 'px').style('left', d3.event.pageX + 'px');
})
.on('mouseout', function (d) {
d3.select(this).classed('selected fill-current text-white', false);
return tooltip.style('visibility', 'hidden');
});

dc.js Stretch GeoChoroplethChart to Full Screen

I'm trying to create a world map using d3.geoChoroplethChart. My problem is that I can't stretch it to the full screen. I'm setting the width of the chart, and svg is picking up the setting, but the underlying g element does not. I assume it uses the projection together with the set width to calculate its size.
The projection I'm using
var projection = d3.geo.mercator()
.center([0, 55])
.scale(180);
Here is chart code
mapChart.width(2000)
.height(1000)
.dimension(countryDim)
.group(countryDimGroup)
.colors(d3.scale.quantize().range(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"]))
.projection(projection)
.valueAccessor(function(d) { return d.value; })
.overlayGeoJson(africaJson.features, "Countries", function(d) {
return d.properties.admin.toUpperCase();
});
I use geoJSON from here http://geojson-maps.kyd.com.au/
Am I missing something?

Calculate absolute position for d3 graph nodes

I have a d3 force layout. Each node in this force layout has a label, which is a <div> code(can have table inside) and should follow it all the time. I defined the <div>'s position to be absolute and set their position like this:
var updateNode = function() {
label_xOffset=10;
label_yOffset=10
this.attr("transform", function(d,i) {
//this is where I set their position
//d.num declare corresponding div in node_lables_divs array
node_labels_divs[parseInt(d.num)].style("left",(d.x+label_xOffset)+"px").style("top",(d.y+label_yOffset)+"px");
return "translate(" + d.x + "," + d.y + ")";
});
}
function tick(e) {
node.call(updateNode);
}
Everything is good now as long as I don't pan or zoom in my SVG. But I need those features too. So in my zoom function I handled it like this:
var zoomer = d3.behavior.zoom().scaleExtent([0.1,10]).
x(xScale).
y(yScale).on("zoom", redraw);
function redraw() {
var translation=" translate(" + d3.event.translate[0] + "px,"+d3.event.translate[1]+"px)";
d3.selectAll("div.node_lables_html").style("transform",translation)
.style("-webkit-transform",translation)
.style("-ms-transform",translation);
//vis is the <g> tag that contains the whole nodes and links
vis.attr("transform","translate(" + d3.event.translate + ")"+" scale(" + d3.event.scale + ")");
}
Now every thing good when I drag and pan the SVG. But it is very bad when I try to zoom in the SVG, lables are going in different direction and they are not following the nodes. I print out the d3.event.translate and it seems that it has different scale in times of zoom and panning.
I also tried the d3 foreignElement but it seems that the transform attribute doesn't work for the foreignElements. How should I calculate the correct value to transform htmls?
As per this solution to a similar question (it's not a duplicate, quite), you need to:
Put the div into a foreignObject SVG element (which allows arbitrary HTML inside an SVG)
add the foreignObject to a group, along with anything else that needs to be with the label
Only apply the tranforms to the group

nvd3.js-Line Chart with View Finder: rotate axis labels and show line values when mouse over

I'm new to this kind of forum and my English skills are not the best but I'll try to do my best :).
There is an example of a Line Chart with View Finder at nvd3 website. This is the one (examples\lineWithFocusChart.html, nvd3 zip package) which I have been working with during the last 2 days. I have made only one change to the example's format: I use dates in the X axis instead of normal numbers.
Here are my 2 questions:
1- How can i rotate all the ticks' labels in the x axis? My dates are too long (%x %X, day and time) and I want them rotated in oder to improve their viewing. I can only get 2 ticks rotated (the max and min, the edges, of the x axis). This is the code I modify inside the "switch (axis.orient())" block at nv.d3.js:
case 'bottom':
axisLabel.enter().append('text').attr('class', 'axislabel')
.attr('text-anchor', 'middle')
.attr('y', 25);
axisLabel
.attr('x', scale.range()[1] / 2);
if (showMaxMin) {
var axisMaxMin = wrap.selectAll('g.axisMaxMin')
.data(scale.domain());
axisMaxMin.enter().append('g').attr('class', 'axisMaxMin').append('text');
axisMaxMin.exit().remove();
axisMaxMin
.attr('transform', function(d,i) {
return 'translate(' + scale(d) + ',0)'
})
.select('text')
.attr('dy', '.71em')
.attr('y', axis.tickPadding())
.attr('text-anchor', 'middle')
.text(function(d,i) {
return ('' + axis.tickFormat()(d)).match('NaN') ? '' : axis.tickFormat()(d)
})
.attr('transform', 'rotate(45)')
;
d3.transition(axisMaxMin)
.attr('transform', function(d,i) {
return 'translate(' + scale.range()[i] + ',0)'
});
}
break;
As you can check i have placed .attr('transform', 'rotate(45)') as new attribute so the max and min ticks are rotated (axisMaxMin). I have tried this way (throughout the nv.d3.js file) with the other text elements that I think are associated with the x ticks but it doesnt work. Any idea? Where I have to put the transformation in order to show all the X labels rotated?
2- In the example, when you place the mouse over the line, no event is triggered to show the value (x,y) associated with the point. How can i show those values? I've tried to copy-paste the methods used in other examples where these values are showed but it doesnt work. Any idea?
Thanks for sharing your time and knowledge :D.
There was a recent update to nvd3 that makes rotating the x-axis tick labels really easy. There is now a function of the axis model called rotateLabels(degrees) that takes an integer and will rotate your xTick labels the specified number of degrees. To rotate all xTick labels 45 degrees back, you could use it like this:
var chart = nv.models.lineChart();
chart.xAxis.rotateLabels(-45);

Categories

Resources