Custom coordinates and axis range on leaflet.js - d3.js

I have a raster image with dimensions (in pixels) 16384-by-12288 which is successfully rendered in leaflet. I am using a my own CRS and I am placing point (0,0) at the bottomleft corner of the image point (16384, 12288) at its topright using the option: transformation: new L.Transformation(1 / 64, 0, -1 / 64, 256).
The axes of my image, however, have range x:[6150, 1370] and y:[12987, 18457]
How can I tell leaflet to use my range as a system of coordinates please? Hence a marker at location (6150, 12987) will correspond and show up at the bottomleft corner: (0,0). I have done this manually using the function below:
var grid = {x0: 6150, // range of plot in Matlab
x1: 13751,
y0: 12987,
y1: 18457};
var img = [16384,
12288];
function project(p, img, grid) {
var x = p[0],
y = p[1];
xx = img[0] / (grid.x1 - grid.x0) * (x - grid.x0);
yy = img[1] / (grid.y1 - grid.y0) * (y - grid.y0);
return [xx, yy]
}
I was wondering however that there must a more streamlined and better way to do this. My code is:
var yx = L.latLng;
var xy = function(x, y) {
if (L.Util.isArray(x)) { // When doing xy([x, y]);
return yx(x[1], x[0]);
}
return yx(y, x); // When doing xy(x, y);
};
var img = [
16384, // original width of image
12288 // original height of image
];
L.CRS.MySimple = L.extend({}, L.CRS.Simple, {
transformation: new L.Transformation(1 / 64, 0, -1 / 64, 256),
});
var bounds = L.latLngBounds([
xy(0, 0),
xy(img)
]);
var map = L.map('map', {
crs: L.CRS.MySimple,
maxBounds: bounds.pad(.5),
}).setView([img[1] / 2, img[0] / 2], 0);
L.tileLayer('myImage/{z}/{x}/{y}.png', {
bounds: bounds,
minZoom: 1,
maxZoom: 6
}).addTo(map);
L.marker([0, 0]).addTo(map).bindPopup("Zero");
L.marker([img[1] / 2, img[0] / 2]).addTo(map).bindPopup("[img[1] / 2, img[0] / 2]");
L.marker([img[1], img[0]]).addTo(map).bindPopup("img");

I think I did some progress. In case someone faces something similar in the future, here is my code: (comments more than welcome)
var yx = L.latLng;
var xy = function(x, y) {
if (L.Util.isArray(x)) { // When doing xy([x, y]);
return yx(x[1], x[0]);
}
return yx(y, x); // When doing xy(x, y);
};
var img = [
16384, // original width of image
12288 // original height of image
];
var mapSW = [0, 16384],
mapNE = [12288, 0];
var roi = { //range of interest
x0: 6150,
x1: 13751,
y0: 12987,
y1: 18457
};
a = img[0] / (roi.x1 - roi.x0)
b = -img[0] / (roi.x1 - roi.x0) * roi.x0
c = img[1] / (roi.y1 - roi.y0)
d = -img[1] / (roi.y1 - roi.y0) * roi.y0
// This transformation maps a point in pixel dimensions to our user defined roi
var t = new L.Transformation(a, b, c, d);
// The transformation in this CRS maps the the bottom right corner to (0,0) and the topleft to (256, 256)
L.CRS.MySimple = L.extend({}, L.CRS.Simple, {
transformation: new L.Transformation(1 / 64, 0, -1 / 64, 256),
});
var bounds = L.latLngBounds([
xy(0, 0),
xy(img)
]);
var map = L.map('map', {
crs: L.CRS.MySimple,
maxBounds: bounds.pad(.5),
}).setView([img[1] / 2, img[0] / 2], 0);
L.tileLayer('map/{z}/{x}/{y}.png', {
bounds: bounds,
minZoom: 1,
maxZoom: 6,
}).addTo(map);
// map.setMaxBounds(new L.LatLngBounds(
// map.unproject(mapSW, map.getMaxZoom()),
// map.unproject(mapNE, map.getMaxZoom()),
// ));
L.marker([0, 0]).addTo(map).bindPopup("Zero");
L.marker([img[1] / 2, img[0] / 2]).addTo(map).bindPopup("[img[1] / 2, img[0] / 2]");
L.marker([img[1], img[0]]).addTo(map).bindPopup("img");
var marker = L.marker(xy([10000, 0]), {
draggable: true
}).addTo(map);
marker.bindPopup("");
marker.on("dragend", function(e) {
m = marker.getLatLng();
proj = map.project(m, map.getMaxZoom());
marker.getPopup().setContent('Clicked ' +m.toString() + '<br />' +
'Pixels ' + proj.toString())
.openOn(map);
})
L.control.scale({
imperial: false
}).addTo(map);
var popup = L.popup();
function onMapClick(e) {
popup
.setLatLng(e.latlng)
.setContent("You clicked the map at " + e.latlng.toString())
.openOn(map);
}
map.on('click', onMapClick);
var p = t.transform(L.point(roi.x1, roi.y1));
L.circleMarker(xy([p.x, p.y])).addTo(map);
p = t.transform(L.point(10000, 12987));
L.circleMarker(xy([p.x, p.y])).addTo(map);
p = t.transform(L.point(13000, 12987));
L.circleMarker(xy([p.x, p.y])).addTo(map);
p = t.transform(L.point(6150, 18000));
L.circleMarker(xy([p.x, p.y])).addTo(map);

Related

How to a make a curved sheet (cube) in OpenSCAD?

How can I curve a sheet (cube)? I'd like to control the angle of the bend/curve.
e.g.
cube([50,50,2]);
You can rotate_extrude() an rectangle with the parameter angle. This requires the openscad version 2016.xx or newer, see documentation.
It is necessary to install a development snapshot, see download openscad
$fn= 360;
width = 10; // width of rectangle
height = 2; // height of rectangle
r = 50; // radius of the curve
a = 30; // angle of the curve
rotate_extrude(angle = a) translate([r, 0, 0]) square(size = [height, width], center = true);
looks like this:
The curve is defined by radius and angle. I think it is more realistic, to use other dimensions like length or dh in this sketch
and calculate radius and angle
$fn= 360;
w = 10; // width of rectangle
h = 2; // height of rectangle
l = 25; // length of chord of the curve
dh = 2; // delta height of the curve
module curve(width, height, length, dh) {
// calculate radius and angle
r = ((length/2)*(length/2) - dh*dh)/(2*dh);
a = asin((length/2)/r);
rotate_extrude(angle = a) translate([r, 0, 0]) square(size = [height, width], center = true);
}
curve(w, h, l, dh);
Edit 30.09.2019:
considering comment of Cfreitas, additionally moved the resulting shape to origin, so dimensions can be seen on axes of coordinates
$fn= 360;
w = 10; // width of rectangle
h = 2; // height of rectangle
l = 30; // length of chord of the curve
dh = 4; // delta height of the curve
module curve(width, height, length, dh) {
r = (pow(length/2, 2) + pow(dh, 2))/(2*dh);
a = 2*asin((length/2)/r);
translate([-(r -dh), 0, -width/2]) rotate([0, 0, -a/2]) rotate_extrude(angle = a) translate([r, 0, 0]) square(size = [height, width], center = true);
}
curve(w, h, l, dh);
and the result:
Edit 19.09.2020: There was a typo in the last edit: In the first 'translate' the local 'width' should be used instead of 'w'. Corrected it in the code above.
I can do it this way but it would be better if you could specify the bend/curve in #degrees as an argument to the function:
$fn=300;
module oval(w, h, height, center = false) {
scale([1, h/w, 1]) cylinder(h=height, r=w, center=center);
}
module curved(w,l,h) {
difference() {
oval(w,l,h);
translate([0.5,-1,-1]) color("red") oval(w,l+2,h+2);
}
}
curved(10,20,30);
Using the concept used by a_manthey_67, corrected the math and centered (aligned the chord with y axis) the resulting object:
module bentCube(width, height, length, dh) {
// calculate radius and angle
r = (length*length + 4*dh*dh)/(8*dh);
a = 2*asin(length/(2*r));
translate([-r,0,0]) rotate([0,0,-a/2])
rotate_extrude(angle = a) translate([r, 0, 0]) square(size = [height, width], center = true);}
Or, if you just want something with a fixed length, and a certain bent angle do this:
module curve(width, height, length, a) {
if( a > 0 ) {
r = (360 * (length/a)) / (2 * pi);
translate( [-r-height/2,0,0] )
rotate_extrude(angle = a)
translate([r, 0, 0])
square(size = [height, width], center = false);
} else {
translate( [-height/2,0,width] )
rotate( a=270, v=[1,0,0] )
linear_extrude( height = length )
square(size = [height, width], center = false);
}
}
The if (a > 0) statement is needed to make an exception when the bending angle is 0 (which, if drawing a curved surface, would result in an infinite radius).
Animated GIF here

How to rotate a sprite around a fixed point so it follows cursor

I'm developing a small minigolf game, where the user can shoot moving the cursor around to set an angle, and the force applied will be the length of an arrow (less force when the cursor is closer to the ball). You can check exactly how it works here: https://imgur.com/a/AQ1pi
I have figured out how to rotate the arrow sprite to follow the cursor but I don't know yet how to make it move around the ball, right now it's just rotating in its anchor, in this case the head of the arrow.
I'm using Panda.js (a Pixi.js based framework) to develop the game, but its API is similar to the native Canvas functions. I don't need an exact implementation (that's why I'm not posting any code), but I would like to get some ideas about how to rotate the sprite around a point in a given radius. In this case, the point would be the center of the ball, and the radius will be the ball radius. Thanks!
You set the point of rotation with ctx.translate or ctx.setTransform then apply the rotation with ctx.rotate(ang); Then draw the image offset so that the point of rotation is at (0,0). Ie if you want the point of rotation to be at image coordinates (100,50) then render at ctx.drawImage(image,-100,-50);
To get the angle from a point to the mouse use Math.atan2
requestAnimationFrame(update);
// draws rotated image at x,y.
// cx, cy is the image coords you want it to rotate around
function drawSprite(image, x, y, cx, cy, rotate) {
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.rotate(rotate);
ctx.drawImage(image, -cx, -cy);
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore defaults
}
// function gets the direction from point to mouse and draws an image
// rotated to point at the mouse
function rotateAroundPoint(x, y, mouse) {
const dx = mouse.x - x;
const dy = mouse.y - y;
const dir = Math.atan2(dy, dx);
drawSprite(arrow, x, y, 144, 64, dir);
}
// Main animation loop.
function update(timer) {
globalTime = timer;
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
strokeCircle(150, 75, 10);
rotateAroundPoint(150, 75, mouse);
requestAnimationFrame(update);
}
//=====================================================
// All the rest is unrelated to the answer.
const ctx = canvas.getContext("2d");
const mouse = { x: 0, y: 0, button: false };
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
function mouseEvents(e) {
mouse.bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - mouse.bounds.left - scrollX;
mouse.y = e.pageY - mouse.bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
const CImage = (w = 128, h = w) => (c = document.createElement("canvas"), c.width = w, c.height = h, c);
const CImageCtx = (w = 128, h = w) => (c = CImage(w, h), c.ctx = c.getContext("2d"), c);
const drawPath = (ctx, p) => {var i = 0;while (i < p.length) {ctx.lineTo(p[i++], p[i++])}};
const strokeCircle = (l,y=ctx,r=ctx,c=ctx) =>{if(l.p1){c=y; r=leng(l);y=l.p1.y;l=l.p1.x }else if(l.x){c=r;r=y;y=l.y;l=l.x}c.beginPath(); c.arc(l,y,r,0,Math.PI*2); c.stroke()};
const aW = 10;
const aH = 20;
const ind = 5;
const arrow = CImageCtx();
arrow.ctx.beginPath();
drawPath(arrow.ctx, [
ind, 64 - aW,
128 - ind - aH, 64 - aW,
128 - ind - aH, 64 - aH,
128 - ind, 64,
128 - ind - aH, 64 + aH,
128 - ind - aH, 64 + aW,
ind, 64 + aW,
]);
arrow.ctx.fillStyle = "red";
arrow.ctx.fill();
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
canvas {
border: 2px solid black;
}
<canvas id="canvas"></canvas>

How to make a crescent moon shape in HTML canvas

I need to make the following shape in HTML5 canvas. I have tried using cubic bezier arcs and also clipping two circles.
How can I make this shape?
Here's my work in progress, just cant get it right
https://codepen.io/matt3224/pen/oeXbdg?editors=1010
var canvas = document.getElementById("canvas1");
var ctx1 = canvas.getContext("2d");
ctx1.lineWidth = 2;
ctx1.beginPath();
ctx1.bezierCurveTo(4, 42, 0, 0, 42, 4);
ctx1.moveTo(4, 42);
ctx1.bezierCurveTo(4, 42, 0, 84, 42, 84);
ctx1.stroke();
var canvas = document.getElementById("canvas2");
var ctx2 = canvas.getContext("2d");
ctx2.lineWidth = 2;
ctx2.beginPath();
ctx2.arc(55, 75, 50, 0, Math.PI * 2, true);
ctx2.moveTo(165, 75);
ctx2.arc(75, 75, 50, 0, Math.PI * 2, true);
ctx2.fill();
Circle circle boolean operation.
Incase anyone is interested in a programmatic solution the example below finds the intercept points of the two circles and uses those points to calculate the start and end angles for the outer and inner circle.
This is a little more flexible than a masking solution as it give you a path.
Snippet shows circle, move mouse over circle to see crescent solution. Not the stroke that would not be available if using a masking solution.
const PI2 = Math.PI * 2;
const ctx = canvas.getContext("2d");
canvas.height = canvas.width = 400;
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
const m = mouse;
const bounds = canvas.getBoundingClientRect();
m.x = e.pageX - bounds.left - scrollX;
m.y = e.pageY - bounds.top - scrollY;
m.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : m.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
// generic circle circle intercept function. Returns undefined if
// no intercept.
// Circle 1 is center x1,y1 and radius r1
// Circle 2 is center x2,y2 and radius r2
// If points found returns {x1,y1,x2,y2} as two points.
function circleCircleIntercept(x1,y1,r1,x2,y2,r2){
var x = x2 - x1;
var y = y2 - y1;
var dist = Math.sqrt(x * x + y * y);
if(dist > r1 + r2 || dist < Math.abs(r1-r2)){
return; // no intercept return undefined
}
var a = (dist * dist - r1 * r1 + r2 *r2) / ( 2 * dist);
var b = Math.sqrt(r2 * r2 - a * a);
a /= dist;
x *= a;
y *= a;
var mx = x2 - x;
var my = y2 - y;
dist = b / Math.sqrt(x * x + y * y);
x *= dist;
y *= dist;
return {
x1 : mx-y,
y1 : my+x,
x2 : mx+y,
y2 : my-x,
};
}
// draws a crescent from two circles if possible
// If not then just draws the first circle
function drawCrescent(x1,y1,r1,x2,y2,r2){
// The circle circle intercept finds points
// but finding the angle of the points does not consider
// the rotation direction and you end up having to do a lot of
// checking (if statments) to determin the correct way to draw each circle
// the following normalises the direction the circle are from each other
// thus making the logic a lot easier
var dist = Math.hypot(x2-x1,y2-y1);
var ang = Math.atan2(y2-y1,x2-x1);
var intercepts = circleCircleIntercept(x1,y1,r1,x1 + dist,y1,r2);
if(intercepts === undefined){
ctx.beginPath();
ctx.arc(x1, y1, r1, 0, PI2);
if(dist < r1){
ctx.moveTo(x2 + r2, y2);
ctx.arc(x2, y2, r2, 0, PI2, true);
}
ctx.fill();
ctx.stroke();
return;
}
// get the start end angles for outer then inner circles
const p = intercepts;
var startA1 = Math.atan2(p.y1 - y1, p.x1 - x1) + ang;
var endA1 = Math.atan2(p.y2 - y1, p.x2 - x1) + ang;
var startA2 = Math.atan2(p.y1 - y1, p.x1 - (x1 + dist)) + ang;
var endA2 = Math.atan2(p.y2 - y1, p.x2 - (x1 + dist)) + ang;
ctx.beginPath();
if(endA1 < startA1){
ctx.arc(x1, y1, r1, startA1, endA1);
ctx.arc(x2, y2, r2, endA2, startA2, true);
}else{
ctx.arc(x2, y2, r2, endA2, startA2);
ctx.arc(x1, y1, r1, startA1, endA1,true);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
}
const outerRadius = 100;
const innerRadius = 80;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.lineJoin = "round";
ctx.lineWidth = 8;
ctx.strokeStyle = "#999";
// main update function
function mainLoop(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "black";
ctx.fillRect(0,0,w,h);
ctx.fillStyle = "white";
ctx.fillText("Move mouse over circle",cw,40);
drawCrescent(cw, ch-40, outerRadius, mouse.x, mouse.y, innerRadius);
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>
Solved it using globalCompositeOperation
https://codepen.io/matt3224/pen/oeXbdg?editors=1010

How to rotate image inside HTML5 canvas without drawing it?

I'm generating image programmatically inside canvas.
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
// here I have some code in loop setting individual pixels
// ...
//
// save image to variable
var dataURL = canvas.toDataURL();
How can I rotate created image by 90 degrees?
EDIT:
This is not duplicate because I don't draw image, it is never visible. I only want to generate it, rotate it and save to variable.
EDIT2:
I'm trying to rotate it with this code:
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.rotate(90 * Math.PI / 180)
But it doesn't work
EDIT3:
This is more complex example of my code:
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
canvas.setPixel = function (x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, 1, 1);
}
for (var i in data) {
for (var j in data[i]) {
switch (data[i][j]) {
case 1:
var color = '#ffff00',
type = 'w'
break
case 3:
var rgb = (256 - parseInt(pixels[i][j]) - minus.grass).toString(16),
color = '#00' + rgb + '00',
type = 'g'
break
case 4:
var rgb = (256 - parseInt(pixels[i][j]) - minus.hills).toString(16),
color = '#' + rgb + rgb + '00',
type = 'h'
break
case 5:
var rgb = (parseInt(pixels[i][j]) + minus.mountains).toString(16),
color = '#' + rgb + rgb + rgb,
type = 'm'
break
case 6:
var rgb = (parseInt(pixels[i][j]) + minus.snow).toString(16),
color = '#' + rgb + rgb + rgb,
type = 'm'
break
}
if (i % fieldSize == 0 && j % fieldSize == 0) {
if (notSet(fields[y])) {
fields[y] = []
}
fields[y][x] = type
x++
}
canvas.setPixel(i, j, color)
}
if (i % fieldSize == 0) {
x = 0
y++
}
}
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.rotate(90 * Math.PI / 180)
var token = {
type: 'save',
map: canvas.toDataURL('image/png')
}
ws.send(JSON.stringify(token))
To rotate image by 90 degrees I had to put
ctx.translate(0, canvas.height)
ctx.rotate(270 * Math.PI / 180)
before
for (var i in data) {
for (var j in data[i]) {
switch (data[i][j]) {
// ... drawing pixels
}
}
}

How do I draw horizontal bars with a label using either ChartJS or D3?

What's the best way of drawing multiple horizontal lines and labels for a simple line graph in either ChartJS or D3? I know that I could draw these as individual lines and then do a text overlay but I'm wondering if there is a simpler solution. Ideally I'd be able to create each of the labels below as one unit and move it anywhere.
If this is simpler in another JS graph library then feel free suggest.
Example below
To do it with Chart.js you have to extend the line chart
Chart.types.Line.extend({
name: "LineAlt",
initialize: function (data) {
// it's easier to programmatically update if you store the raw data in the object (vs. storing the geometric data)
this.marks = data.marks;
this.marks.xStart = Number(data.labels[0]);
this.marks.xStep = data.labels[1] - data.labels[0];
// make sure all our x labels are uniformly apart
if (!data.labels.every(function (e, i, arr) { return !i || ((e - arr[i - 1]) === this.marks.xStep); }, this))
throw "labels must be uniformly spaced";
Chart.types.Line.prototype.initialize.apply(this, arguments);
},
draw: function () {
Chart.types.Line.prototype.draw.apply(this, arguments);
// save existing context properties
var self = this;
var ctx = self.chart.ctx;
var scale = self.scale;
ctx.save();
// line properties
ctx.lineWidth = 1;
ctx.fillStyle = "#666";
ctx.strokeStyle = "#666";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.font = scale.font;
// draw marks
self.marks.forEach(function (mark) {
// assuming that the marks are always within the data range
var y = scale.calculateY(mark.y);
var x1 = scale.calculateX((mark.x1 - self.marks.xStart) / self.marks.xStep);
var x2 = scale.calculateX((mark.x2 - self.marks.xStart) / self.marks.xStep);
// draw line
ctx.beginPath();
ctx.moveTo(x1, y);
ctx.lineTo(x2, y);
// draw edges
ctx.moveTo(x1, y + 10);
ctx.lineTo(x1, y - 10);
ctx.moveTo(x2, y + 10);
ctx.lineTo(x2, y - 10);
ctx.stroke();
// draw text
ctx.fillText(mark.label, (x1 + x2) / 2, y + scale.fontSize * 1.5);
})
ctx.restore();
},
});
You pass in the data for drawing the lines like so
var data = {
...
marks: [
{
x1: 1.5,
x2: 3.5,
y: 50,
label: 'Label1'
},
{
x1: 5,
x2: 7,
y: 60,
label: 'Label2'
}
]
};
and you create the chart using this extended chart type
var myLineChart = new Chart(ctx).LineAlt(data);
You can update the lines like this
myLineChart.marks[0].y = 80;
myLineChart.marks[0].x1 = 9;
myLineChart.marks[0].x2 = 10;
and then call
myLineChart.update();
to reflect those changes on the canvas
Caveats
The (x axis) labels should be numeric and uniformly spaced.
The lines should be within the scale range of the y axis (alternatively you can do a scaleOverride to set the scale parameters so that the lines are within the y scale range)
Fiddle - http://jsfiddle.net/en92k763/2/

Resources