display of rivers on map with d3js - svg

I'm building a svg map of germany based on data from Natural Earth. Most thing work fine, including provinces, places names and lakes. However, when using rivers on a low scaled map, only parts of the river get displayed. When zooming in, the whole river is visible.
Relevant portions of the code:
<style type="text/css">
.river { stroke: cadetblue;
fill: none;
stroke-linejoin: round;
stroke-linecap: round;
}
</style>
var projection = d3.geo.mollweide()
.center([10.4, 51.1])
.scale(6300)
.translate([width/2, height/2]);
var path = d3.geo.path().projection(projection);
// [...]
var svg = d3.select("#wrapper").append("svg")
.attr("width", width)
.attr("height", height)
var map = svg.append("g")
.attr("id", "map");
// [...]
d3.json("data/final.json", function(error, deu) {
// [...]
var rivers = topojson.feature(deu, deu.objects.rivers);
// [...]
map.append("path")
.datum(rivers)
.attr("d", path)
.attr("stroke-width", "2")
.attr("class" , "river");
// [...]
}

Related

Jquery - Draggable feature Containment property for a polygonal parent

Referencing https://jqueryui.com/draggable/ i am able to implement a drag drop feature within a parent element (e.g. div). However my need is to have this draggable feature to work within a polygonal element (Like a SVG polygon).
I have been searching the net, however there are examples of how to make a svg polygon draggable but not 'how to contain drag drop feature within a polygonal parent (div).
Any ideas / pointers will be helpful.
Thanks.
The short story is you need a function to check if a point is within a polygon, and then check if the four corners of your draggable object are within that shape.
Here's a rough example of doing that, using the draggable sample from jQuery, along with a point in polygon function from this answer. This example is far from perfect, but I hope it points you in the right direction.
// These are the points from the polygon
var polyPoints = [
[200, 27],
[364, 146],
[301, 339],
[98, 339],
[35, 146]
];
$("#draggable").draggable({
drag: function(e, ui) {
var element = $("#draggable")[0];
var rect = element.getBoundingClientRect();
var rectPoints = rect2points(rect);
let inside = true;
rectPoints.forEach(p => {
if(!pointInside(p, polyPoints)){
inside = false;
}
});
$("#draggable")[inside ? 'addClass' : 'removeClass']('inside').text(inside ? 'Yay!' : 'Boo!');
}
});
function rect2points(rect) {
return ([
[rect.left, rect.top],
[rect.right, rect.top],
[rect.right, rect.bottom],
[rect.left, rect.bottom]
]);
};
function pointInside(point, vs) {
var x = point[0],
y = point[1];
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0],
yi = vs[i][1];
var xj = vs[j][0],
yj = vs[j][1];
var intersect = ((yi > y) != (yj > y)) &&
(x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
};
#draggable {
width: 100px;
height: 80px;
background: red;
position: absolute;
text-align: center;
padding-top: 20px;
color:#fff;
}
#draggable.inside{
background: green;
}
html, body{
margin: 0;
}
<script src="//code.jquery.com/jquery-1.12.4.js"></script>
<script src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div id="draggable">Drag me</div>
<svg width="400px" height="400px" viewBox="0 0 400 400">
<rect width="600" height="600" fill="#efefef"></rect>
<polygon points="200,27 364,146 301,339 98,339 35,146" fill="rgba(255,200,0, 1)" stroke="rgba(255,0,0,0.2" stroke-width="2"></polygon>
</svg>

Fabric.js: How to remove padding and format selection box? (padding still exists after zoom)

I am trying to format selection border style to look like in inkscape(image left side).
I have tried to set the object padding to zero with this:
fabric.Object.prototype.set({
padding: 0,
});
If I zoom in this is more apparent and padding grows.
Example: Left side inkscape selection box style, right side actual fabric.js selection box
How can i remove the padding?
Here is jsFiddle.
That's because of stroke width, set strokeWidth:0 to object.
DEMO
var canvas = window.__canvas = new fabric.Canvas('canvas');
var rect = new fabric.Rect({left: 10, top: 10,strokeWidth:0, width: 60, height: 60, fill: 'blue'});
rect.editable = true;
rect.customType = 'shape';
rect.describtion = 'fabric.js rect object';
rect.cornerColor = 'red';
rect.borderColor = 'red';
rect.cornerSize = 8;
rect.padding = 0;
canvas.add(rect);
canvas.setActiveObject(rect);
canvas.setZoom(20);
canvas{
border:2px solid #000;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="canvas" width="400" height="400"></canvas>

Live graphing of data with d3 using real data

I have seen lots of great demos for live graphing of data using D3.
http://bl.ocks.org/simenbrekken/6634070 is one I like. However, all of the examples I have seen use random generated values. I want to graph live data, and display the most recent values as an updating numeric display. I use a python script which writes data from sensor readings to csv files. The csv is 3 values on each line: unixtime,sensor1_value,sensor2_value. Every 5 seconds there is a new line of data added to a ring buffer file which has 720 lines of data. When the web page is displayed I want to read the 720 lines in the buffer file then update the graph with each new value which is written onto the end of the file. I could also create a file with just the new line of csv every 5 seconds so that the update was performed by always reading a file with just 1 line of csv rather than manipulating the entire buffer.
Does anyone know of an example, or the right code to achieve this?
The code for the above cited example is:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.graph .axis {
stroke-width: 1;
}
.graph .axis .tick line {
stroke: black;
}
.graph .axis .tick text {
fill: black;
font-size: 0.7em;
}
.graph .axis .domain {
fill: none;
stroke: black;
}
.graph .group {
fill: none;
stroke: black;
stroke-width: 1.5;
}
</style>
</head>
<body>
<div class="graph"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var limit = 60 * 1,
duration = 750,
now = new Date(Date.now() - duration)
var width = 500,
height = 200
var groups = {
current: {
value: 0,
color: 'orange',
data: d3.range(limit).map(function() {
return 0
})
},
target: {
value: 0,
color: 'green',
data: d3.range(limit).map(function() {
return 0
})
},
output: {
value: 0,
color: 'grey',
data: d3.range(limit).map(function() {
return 0
})
}
}
var x = d3.time.scale()
.domain([now - (limit - 2), now - duration])
.range([0, width])
var y = d3.scale.linear()
.domain([0, 100])
.range([height, 0])
var line = d3.svg.line()
.interpolate('basis')
.x(function(d, i) {
return x(now - (limit - 1 - i) * duration)
})
.y(function(d) {
return y(d)
})
var svg = d3.select('.graph').append('svg')
.attr('class', 'chart')
.attr('width', width)
.attr('height', height + 50)
var axis = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(x.axis = d3.svg.axis().scale(x).orient('bottom'))
var paths = svg.append('g')
for (var name in groups) {
var group = groups[name]
group.path = paths.append('path')
.data([group.data])
.attr('class', name + ' group')
.style('stroke', group.color)
}
function tick() {
now = new Date()
// Add new values
for (var name in groups) {
var group = groups[name]
//group.data.push(group.value) // Real values arrive at irregular intervals
group.data.push(20 + Math.random() * 100)
group.path.attr('d', line)
}
// Shift domain
x.domain([now - (limit - 2) * duration, now - duration])
// Slide x-axis left
axis.transition()
.duration(duration)
.ease('linear')
.call(x.axis)
// Slide paths left
paths.attr('transform', null)
.transition()
.duration(duration)
.ease('linear')
.attr('transform', 'translate(' + x(now - (limit - 1) * duration) + ')')
.each('end', tick)
// Remove oldest data point from each group
for (var name in groups) {
var group = groups[name]
group.data.shift()
}
}
tick()
</script>
</body>
</html>
Whereas the code I use for creating a static graph of one of the values (o2) from my csv versus a line:
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 900 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Set the ranges
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(10);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(10);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.o2); });
// Adds the svg canvas
var svg = d3.select("body")
.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 + ")");
// Get the data
d3.csv("./data/buffer.txt", function(error, data) {
data.forEach(function(d) {
d.time = +d.time;
d.o2 = +d.o2;
console.log(d.time);
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain([0, d3.max(data, function(d) { return d.o2; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
</script>
</body>

Make "widget" for SVG-group in D3.js

I am using D3 to manipulate an SVG, that contains several "widgets" that have behavior and can be controlled eg via events. For instance I have a spinning fan. It should be possible to turn on and off the fan. I have been able to build that in D3, but not in an elegant way, and with all the code being global. What I want to end up with is something as below:
Creation of "widget", as known from jQuery:
d3.select('svg').append('g').attr('id', 'myFan').fan();
And then I would like to turn on and off with smth like the following, also as known from jQuery:
d3.select('#myFan').fan('start')
d3.select('#myFan').fan('stop')
Is it possible to achieve this in D3, ie to create such a "widget"?
Is there generally a different approach to such a problem, when using D3?
My current unelegant solution with global code:
Robert asked for this. Code at Codepen: link.
Javascript
var fan = d3.select('body')
.append('svg')
.attr('width', 200)
.attr('height', 200)
.append('g')
.attr('class', 'fan')
.attr('id', 'myFan')
.attr('transform', 'translate(75,75)')
// Static, background disc
fan.append('circle')
.attr('cx',0)
.attr('cy',0)
.attr('r',60)
// Dynamic, rotating path
var blade = fan.append('path')
.attr('d', 'M 0 0 L -30 15 L -60 0 Z L 30 -15 L 60 0 Z ')
.attr('transform', 'translate(50,50)')
.attr('transform', 'rotate(90)');
// Static, hub
fan.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', 10)
var rotation = 0; // degrees
var speed = 10; // degree per call
function rotate() {
rotation = (rotation + speed) % 360;
blade.attr('transform', 'rotate('+rotation+')')
}
setInterval(rotate, 100);
function stop() { speed = 0; }
function start() { speed = 10; }
var ctrlPanel = d3.select('body').append('div');
ctrlPanel.append('button').text('Start').on('click', start);
ctrlPanel.append('button').text('Stop').on('click', stop);
CSS
path {
fill: red;
strok: blue;
stroke-width: 3px;
}
.fan circle {
fill: lightgray;
stroke: black;
stroke-width: 3px;
}
.fan path {
fill: darkgray;
stroke: black;
stroke-width: 3px;
}

How to use D3 selectAll with multiple class names

I'm experimenting with using multiple class names for SVG elements so that (hopefully) I could select a subset of them using selectAll and "parts" of the class name. Unfortunately nothing I've tried works and I haven't found an example online. The code below demonstrates what I'm trying to do with a simple example of four circles. Two circles have class name "mYc 101" and two circles have class name "mYc 202". selectAll(".mYc") gives all four circles. What if I want only the circles with class name "mYc 101"? Can this be done? How? Thanks times infinity!!
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<div id="my_div"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var m_div = d3.select("#my_div");
var m_svg = m_div.append("svg");
var g = m_svg.append("g");
g.append("circle")
.attr("class", "mYc 101")
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 50)
.attr("style", "stroke: green; stroke-width: 8; fill: #000000");
g.append("circle")
.attr("class", "mYc 101")
.attr("cx", 300)
.attr("cy", 100)
.attr("r", 50)
.attr("style", "stroke: green; stroke-width: 8; fill: #000000");
g.append("circle")
.attr("class", "mYc 202")
.attr("cx", 100)
.attr("cy", 300)
.attr("r", 50)
.attr("style", "stroke: blue; stroke-width: 8; fill: #000000");
g.append("circle")
.attr("class", "mYc 202")
.attr("cx", 300)
.attr("cy", 300)
.attr("r", 50)
.attr("style", "stroke: blue; stroke-width: 8; fill: #000000");
// This selects all four circles
var list = d3.selectAll(".mYc");
// But if I want to select only class "mYc 101", none of these work.
// In fact they all give an error.
// var list1 = d3.selectAll(".mYc 101");
// var list1 = d3.selectAll(".mYc .101");
// var list1 = d3.selectAll(".mYc.101");
// var list1 = d3.selectAll(".mYc,.101");
// var list1 = d3.selectAll(".101");
</script>
</body>
The most D3 way to do this would be to chain the selectors using the filter method:
var list1 = d3.selectAll(".mYc").filter(".101");
This won't work though because class names cannot start with a number. So you have to rename to something like "a101" and then you can do
var list1 = d3.selectAll(".mYc").filter(".a101");
See this fiddle.
Another way I have found to do this is to select both classes at the same time as a single string, for example:
var list1 = d3.selectAll(".mYc.a101")
It won't work if you add in a space in between, or add a comma in between (which selects things that have either class instead).

Resources