D3 Canvas Globe With Text At LongLat - javascript

my question today is about D3 and display text on a globe at a long/lat using Canvas. What I've essentially got is a globe built using mbostock's world tour, which has a list of long/lats and rotates the globe to focus on them. I've already got markers at the points, but what I'd like is to display some text next to all of the points. At this point in time, it's just city names, but the intention is to display more information as the globe is more fully fleshed out. I've created a plunk with the code: https://plnkr.co/edit/CzfoF4bkPv93l3Yxwu3N?p=preview
The relevant code in that plunk is:
return function(t) {
projection.rotate(r(t));
c.clearRect(0, 0, width, height);
c.fillStyle = "#ccc", c.beginPath(), path(land), c.fill();
for (var j = 0; j < points.length; j++) {
c.fillStyle = "#000", c.beginPath(), path(points[j]), c.fill();
}
c.strokeStyle = "#000", c.lineWidth = 2, c.beginPath(), path(globe), c.stroke();
};
Basically what I'm asking for is a way to display text slightly to the right of specific long/lat coordinates on a globe using canvas. Any and all help is greatly appreciated. Thank you in advance!

The easiest way would be to add a the text in your tween function after you draw the features:
c.fillText(points[i].location,projection(points[i].coordinates)[0]+10,projection(points[i].coordinates)[1]+2);
With the variable projection, projection([long,lat]) returns [x,y], in other words it gives the forward projection of a given point represented by latitude and longitude, returning a coordinate in map coordinate space (your svg or canvas).
Once you have the projected coordinates, you can manipulate those as needed. In this case I added ten to the x value to push the place name further to the right. See this plunker.

Related

Converting a the outlines of a free drawn path to a polygon

I want to segment multiple objects within an image using the fabric.js javascript library.
For this example, I want to segment the surfboard. I then want want to convert the drawn shape to a polygon with the coordinates of the outer regions corresponding to the pixel coordinates of the images.
Drawing an segmentation itself it quite straightforward using fabric.js's build in free drawing brush.
I can then convert the coordinates of the drawn path to a polygon using this piece of code:
canvas.on('path:created', function(el){
var path = el.path.path
var points = []
for (var i = 0; i < path.length; i++){
point = {
x: Math.round(path[i][1]),
y: Math.round(path[i][2])
}
points.push(point)
}
shape = new fabric.Polygon(points, {
stroke: 'red',
opacity: 0.5,
strokeWidth: 1,
description: 'aaa',
fill: 'transparent',
});
canvas.add(shape)
})
The red/black link is how I was drawing using the mouse.
After turning drawing mode off and dragging them away, the 2 created objects (the Path from drawing and the polygon) look as follows.
However, because the brush has a certain width, and because of overlapping Path regions, my method of converting it to a poly is not working well.
So I don't want the current polygon I'm outputting (the red one), but instead the outline of the green section. How can I achieve this?
Working fiddle: https://jsfiddle.net/haw54v9L/3/
the free draw paths are many polylines, you must change the polylines to a ploygon. ClipperLib can do this. but there is an accuracy problem when canvas zoom out or zoom in.

P5.JS Create 3d Grid

I know of the way in p5.js to create a grid of squares/rect in 2d, however i cannot seem to be able to create this in p5's 3d setting using webGL. I am trying to create a 3d grid "of size 50x50x50 from -400 to 400 in the x-axis and -400 to 400 on the z-axis". However whenever i try to create this, it just seems to stay in a 2d setting.
For example, the 2d method to do this is below, and i want the 3d to be the same consisting of a 16x16 grid
function setup() {
createCanvas(400, 400);
background(220);
for (let x=0;x<width;x+=20){
for (let y=0;y<height;y+=20){
rect(x,y,20,20);
}
}
}
Any help adjusting this code to bring it to 3d would help massively, Ive looked around and cannot find any help on how to do this
Im brand new to 3d primitives and webgl so help would be greatly appreciated.
Below is how I am trying to get it to look
Im aware, camera adjusting would ne needed also, but just wanted to take one step at a time.
Thanks again
You're on the right track, just need 3 ingredients:
use the WEBGL renderer (e.g. canvas(400, 400, WEBGL);)
use box() instead of rect()
use push()/pop() and translate(x, y, z) to draw boxes at the grid coordinates
You can use camera() function to angle the get that perspective.
(You can optionally also use rotateY(), rotateX() calls to rotate the whole coordinate space, but using camera() will preserve the original coordinate system)
function setup(){
createCanvas(400, 400, WEBGL);
background(220);
camera(-200, -200, -200, // camera position (x, y, z)
0 , -100, 0, // camera target (look at position) (x, y, z)
0 , 1, 0); // camera up axis: Y axis here
for (let x=0; x < width; x +=20){
for (let z=0; z < height; z +=20){
push();
// ground plane is XZ, not XY (front plane)
translate(x, 0, z);
box(20);
pop();
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.min.js"></script>
Optionally you could offset the position of each box based on the centre of the grid so further transformations to the grid will occur from the centre (and not from a corner).

Drawing Starbursts – basil.js

I am trying to draw a starburst by first drawing a polygon and then adjusting its properties.
on indesignjs.de it says a polygon is any shape that is not a rectangle, ellipse, or graphic line. When you add a polygon, InDesign creates a regular polygon based on the current polygon preferences settings. But I can't for the life of me figure out how to draw the damn thing.
I wrote this:
var myPolygon = polygon(0, 0, 10, 10);
property(myPolygon, "numberOfSides", 8);
property(myPolygon, "insetPercentage", 50);
but I get an error in indesign saying that polygon is not a function. Is is truncated like rectangle is (i.e. poly)?
As fabianmoronzirfas pointed out, polygon() is not a basil.js function (as opposed to rect()). So either you would have to draw the shape yourself using basil commands like this:
beginShape();
vertex(23, 45);
vertex(34, 67);
// draw as many vertices as you need
endShape(CLOSED);
or you would have to use proper (non-basil) InDesign scripting commands to shape your polygon. The thing you want to achieve can be done by using the convertShape() method, which can be used on any shape, so you could create a rect first and then use this method on the rect:
// #include ~/Documents/basiljs/basil.js;
function draw() {
var myPoly = rect(50, 50, 200, 200);
myPoly.convertShape(ConvertShapeOptions.CONVERT_TO_POLYGON, 8, 50);
}

How to place one element inside another in raphael js?

Im trying to create an interactive seating layout like this Seats.io. However I dont need the exact features but just few things such as:
Plotting seats anywhere on the screen
Plotting list of seats from one point to another
Seats hover as circle when plotting from one mouse click point to another
After much research in Jquery and simultaneously on raphaeljs, I have decided to start working with raphaeljs. Im totally new to the vector graphics. So obviously there might be something that I may be missing. I have followed this fiddle to draw a straight line. I have also created another script to plot circles anywhere on the window(the circles will mean seats) following is the script
window.onload = function () {
var height = $(document).outerHeight(true);
var width = $(document).width();
var radius = 10;
var paper = Raphael(0, 0, width, height);
var i = 0;
$(document).click(function (e) {
i = i + 1;
var x = e.pageX;
var y = e.pageY;
var seat = paper.circle(x, y, radius)
.attr({stroke: "none", fill: "#f00", opacity: .4})
.data("i", i);
seat.mouseover(function () {
this.attr("opacity", 1);
});
seat.mouseout(function () {
this.attr("opacity", .4);
});
});
}
using the above script I'm able to plot circles(seats) on my screen. Now based on the fiddle example lines are drawn using 'path', so is it possible to load circles on every path and draw them as sequential line of circles one after the other, or do I have to take any different approach.
Also on a side note is there any opensource project or code for the Seats.io
Any help would be really appreciated
Ben from seats.io here.
http://raphaeljs.com/reference.html#Element.getPointAtLength is indeed what we use. You'll basically need to
calculate a helper path between start and end point. You already have that.
calculate the distance between seats (based on seat size): helperPath.getTotalLength() / (numberOfSeats - 1);
for each seat, call getPointAtLength and draw a circle around that
point: helperPath.getPointAtLength(distanceBetweenSeatsOnHelperPath * i++)
Obviously, it gets more interesting if you want to snap to a grid to align rows, curve rows, etc, but you should be able to get started with the above.

KineticJS click detection inside animated shapes

OK, I admit I tried to be clever: I thought if I overrode Shape's drawFunc property I could simply draw whatever inside a rectangle and still use KineticJS's click detection. Here's my attempt:
var shape = new Kinetic.Shape({
drawFunc: function(context) {
var id = 26; // Id of a region inside composite image.
context.beginPath();
context.rect(0, 0, w, h);
context.closePath();
this.fill(context);
this.stroke(context);
context.drawImage(copyCanvas, (id % 8) * w, flr(id / 8) * h,
w, h, 0, 0, w / 2, h / 2);
},
draggable: true
});
So, the idea was to draw a rectangle, and use drawImage() to draw something on top of the rectangle (like a texture, except it changes from time to time because copyCanvas itself changes). All the meanwhile, I expected event handling (drag-n-drop, in particular) to still 'just work'. Well, here's what happens: the part of the rectangle not covered by my drawImage() correctly detects clicks. However, the one fourth of the rectangle that is covered by the image refuses to respond to clicks! Now, my question is why? I dug into the KineticJS code, and looked to me that click detection simply means drawing to a buffer and seeing if a given x, y point has non-zero alpha. I can't see how this could be affected by my drawing an image on top of my rectangle.
Any ideas what's going on?
OK, so I went ahead and looked at the source code. Here's the definitive answer:
KineticJS assigns a random and unique RGB color to each shape that's created using a global map from RGB colors to shape objects. The draw() function of the shape is called twice: once with the 'real' canvas, and once with a 'buffer' canvas used for hit detection. When using the 'buffer' canvas, KineticJS switches the stroke and fill colors to the unique RGB color of the given shape. The same 'buffer' canvas is used for all shapes on a layer. Thus hit detection simply becomes reading the RGB value of a given point and looking up the corresponding shape in the global map. Now, in my example I drew an image in a way that circumvented KineticJS's juggling of colors used for hit detection. Thus, when I clicked on the image area, KineticJS saw some unknown RGB color on the buffer canvas with no known shape assigned to it.
The solution is not to draw the image for the 'buffer' (or 'hit detection') phase: a simple rectangle will do. In case you're wondering, here's the correct code for the drawFunc:
var width = 200;
var height = 100;
var myShape = new Kinetic.Shape({
drawFunc: function(context) {
if (layer.bufferCanvas.context == context) {
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
this.fill(context);
this.stroke(context);
} else {
context.drawImage(someCanvasWithAnythingOnIt, 0, 0, width, height,
0, 0, width, height);
}
}});
Can I collect my own reward?
I think your problem lies in the order. There is a depth associated with each object that you draw and the default ordering is like a stack, last drawn is on top.
Now that you have modified the code, making 2 draws inside the shape draw function, I still think the ordering is preserved and hence, the object is not able to detect the input. Try changing the order, i.e. draw image first and then the rectangle and see if the problem is solved.
Else, share a jsFiddle for an example.

Categories