I'm trying to generate a graphic that draws the path of shipments with a line from their origin to destination. I've converted latitude and longitude data to fit on a map of the United States in pixels (1620, 1080).
The way it is currently written the lines are drawn sequentially by the way they are ordered in my csv file. However, I would like the lines to radiate out from origin to destination. Right now I can only figure out how to drop down the lines already drawn.
I think the relevant portion of code is in // Draw Lines
.
long current;
int x;
int y;
ArrayList loads;
void setup() {
size(1620, 1080);
background(55);
smooth();
frameRate(15);
// Draw US Map
String[] lines = loadStrings("Map2.csv"); // File containing coordinates to plot US Map
stroke(55);
strokeWeight(1);
smooth();
String[] pieces = split(lines[0], ',');
for ( int i = 0; i < lines.length; i++) {
fill(0);
beginShape();
current = int(pieces[0]);
while ( current == int(pieces[0]) & i < lines.length) {
x = int(pieces[2]);
y = int(pieces[1]);
vertex(x, y);
i++;
if ( i < lines.length) {
pieces = split(lines[i], ',');
}
}
endShape();
}
// Add Lakes to Map
String[] lines2 = loadStrings("Water.csv"); // File containing coordinates to plot great lakes
smooth();
fill(22, 25, 180);
String[] pieces2 = split(lines2[0], ',');
for (int i = 0; i < lines2.length; i++)
{
fill(110);
beginShape();
current = int(pieces2[0]);
while (current == int(pieces2[0]) & i < lines2.length) {
x = int(pieces2[2]);
y = int(pieces2[1]);
vertex(x, y);
i++;
if (i < lines2.length) {
pieces2 = split(lines2[i], ',');
}
}
endShape();
}
// Draw Lines
loads = new ArrayList();
String[] loadset = loadStrings("data1.csv");
for ( int i3 = 0; i3 < loadset.length; i3++) {
String[] loads2 = split(loadset[i3], ',');
loads.add( new Lane(int(loads2[0]), int(loads2[1]), int(loads2[2]), int(loads2[3])) );
}
}
int i=1;
int imax = 1;
int incmult = 1;
void draw() {
if (i < loads.size()-imax){
for(int iadd = 0; iadd < imax; iadd++)
{
Lane Lane = (Lane) loads.get(iadd);
Lane.display();
Lane = (Lane) loads.get(i+iadd);
Lane.display();
}
i +=imax;
}
imax = imax + incmult;
}
class Lane {
int x;
int y;
int x2;
int y2;
Lane( int tempX, int tempY, int tempX2, int tempY2) {
x = tempX;
y = tempY;
x2 = tempX2;
y2 = tempY2;
}
void display() {
int r = 65;
int g = 255;
int b = 35;
strokeWeight(1);
stroke(r, g, b, 55);
line(x, y, x2, y2);
stroke(255, 255, 255); // Origin
fill(255, 255, 255, 55);
ellipse(x, y, 3, 3);
stroke(171, 62, 193); // Destination
fill(171, 62, 193);
ellipse(x2, y2, 3, 3);
}
}
My data1.csv
file contains four columns x, y, x2, y2
where (x, y)
represents the origin and (x2, y2)
represents the destination coordinates.
// data.csv
data[0] data[1] data[2] data[3]
929 327 602 507
1335 458 1327 782
1422 325 848 744
1302 280 1118 458
1041 583 1193 666
1267 616 1058 394
1215 671 1351 857
1334 851 1410 946
1334 851 1409 916
828 761 861 653
1386 323 1203 594
1037 293 1013 522
908 869 958 532
1029 331 1053 409
906 357 828 761
. . . .
. . . .
Update I've added links to my data since I've still been having some difficulty with drawing the image as I'd envisioned.
data1 <"https://docs.google.com/spreadsheets/d/1QzbCGW8H6PZgLkmWN8OyplVNTJhp3tlPGxR_Zv6lttM/pub?output=csv">
Currently the question isn't very clear so this answer in it's current form is incomplete.
Here are a few recommendations that may simplify the code and hopefully will help in reaching the end goal:
Here's a basic example using this TSV based on the data above saved as data.tsv
Table data;
PShape dataPlot;
size(1620, 1080,P2D);
//create a group to store the lines from each row
dataPlot = createShape();
//load the data, specifying it has a header and it's tab separated
data = loadTable("data.tsv", "header, tsv");
//traverse each row
dataPlot.beginShape(LINES);
for(TableRow row : data.rows()){
//extract each value
int x1 = row.getInt("x1");
int y1 = row.getInt("y1");
int x2 = row.getInt("x2");
int y2 = row.getInt("y2");
//add the coordinates as lines to the group
dataPlot.stroke(160);
dataPlot.vertex(x1,y1);
dataPlot.stroke(0);
dataPlot.vertex(x2,y2);
}
dataPlot.endShape();
//render the plot
shape(dataPlot);
Using dark to light gray to display the origin and destination of paths, here is the result using the partial sample data:
If you need to access each vertex after creating the PShape instance you can use getVertex() . It requires the index of the vertex you may want to retrieve. For example, dataPlot.getVertex(0);
and dataPlot.getVertex(1);
will return the coordinates for the first line, dataPlot.getVertex(2);
and dataPlot.getVertex(2);
will return the coordinates for the 2nd line and so on.
If it's easier to manage a tree like hierarchy instead of vertex indices, you can create PShape groups. The only caveat is the following child PShape instances created will be drawn using functions prefixed by set as opposed to the typical stroke()
/ fill()
/etc. you're already used to : setStroke()
/ setFill()
/etc.
Here's a code example using PShape groups and mapping distances to line colour and thickness:
Table data;
PShape dataPlot;
size(1620, 1080, P2D);
//create a group to store the lines from each row
dataPlot = createShape(GROUP);
//load the data, specifying it has a header and it's tab separated
data = loadTable("data.tsv", "header, tsv");
//traverse each row
for (TableRow row : data.rows ()) {
//extract each value
int x1 = row.getInt("x1");
int y1 = row.getInt("y1");
int x2 = row.getInt("x2");
int y2 = row.getInt("y2");
//add the coordinates as lines to the group
PShape line = createShape(LINE, x1, y1, x2, y2);
float dist = dist(x1, y1, x2, y2);
line.setStroke(color(map(dist, 0, height, 160, 0)));
line.setStrokeWeight(map(dist, 0, height, 10.0, 1.0));
dataPlot.addChild(line);
}
//render the plot
shape(dataPlot);
In this case retrieving first line will look like this:
PShape line0 = dataPlot.getChild(0);
which you allow you to access it's two vertices:
println(line0.getVertex(0));
println(line0.getVertex(1));
If you want to animate these positions, you can easily use lerp() on single values or PVector's lerp() method (for PVectors :)). This function will expect a pair of start/end values and a normalized value(between 0.0 an 1.0) and return the value in between. Think of it as a percentage along your path:
Here's a basic example drawing an ellipse on traversing each line and it's colour indicating the start(black) or end(white) position (dragging should allow manual control mapping the X axis to line traversal):
Table data;
PShape plot;
void setup(){
size(1620, 1080, P2D);
//create a group to store the lines from each row
plot = createShape(GROUP);
//load the data, specifying it has a header and it's tab separated
data = loadTable("data.tsv", "header, tsv");
//traverse each row
for (TableRow row : data.rows ()) {
//extract each value
int x1 = row.getInt("x1");
int y1 = row.getInt("y1");
int x2 = row.getInt("x2");
int y2 = row.getInt("y2");
//add the coordinates as lines to the group
PShape line = createShape(LINE, x1, y1, x2, y2);
float dist = dist(x1, y1, x2, y2);
line.setStroke(color(map(dist, 0, height, 160, 0)));
line.setStrokeWeight(map(dist, 0, height, 10.0, 1.0));
plot.addChild(line);
}
}
void draw(){
background(255);
//render the plot
shape(plot);
//animate the trajectories
//use normalized (between 0.0 and 1.0) value to traverse the paths (think of it as 0 and 100%, 0 is at the start 100% is at the end)
//if can be interactive
float traversal;
if(mousePressed) {
traversal = map(mouseX,0,width,0.0,1.0);
}else{//or time based, up to you :)
traversal = map(sin(frameCount * 0.1),-1.0,1.0,0.0,1.0);
}
//for each trajectory
for(int i = 0 ; i < plot.getChildCount(); i++){
PShape line = plot.getChild(i);
//access each line's start and end points
PVector start = line.getVertex(0);
PVector end = line.getVertex(1);
//calculate the linearly interpolated point in between start end using the traversal value and lerp()
PVector inbetween = PVector.lerp(start,end,traversal);
//use the interpolated value to draw
fill(traversal * 255);
ellipse(inbetween.x,inbetween.y,15,15);
}
}
Here is a very similar example fading the dots only:
Table data;
PShape plot;
void setup(){
size(1620, 1080, P2D);
//create a group to store the lines from each row
plot = createShape(GROUP);
//load the data, specifying it has a header and it's tab separated
data = loadTable("data.tsv", "header, tsv");
//traverse each row
for (TableRow row : data.rows ()) {
//extract each value
int x1 = row.getInt("x1");
int y1 = row.getInt("y1");
int x2 = row.getInt("x2");
int y2 = row.getInt("y2");
//add the coordinates as lines to the group
PShape line = createShape(LINE, x1, y1, x2, y2);
float dist = dist(x1, y1, x2, y2);
line.setStroke(color(map(dist, 0, height, 160, 0)));
line.setStrokeWeight(map(dist, 0, height, 10.0, 1.0));
plot.addChild(line);
}
//clear the background
noStroke();
shape(plot);//this needs to be drawn at least once it seems
background(255);
}
void draw(){
//hacky fade effect, change the alpha (16) transparency value to experiment with fade amount
fill(255,16);
rect(0,0,width,height);
//animate the trajectories
//use normalized (between 0.0 and 1.0) value to traverse the paths (think of it as 0 and 100%, 0 is at the start 100% is at the end)
//if can be interactive
float traversal;
if(mousePressed) {
traversal = map(mouseX,0,width,0.0,1.0);
}else{//or time based, up to you :)
traversal = map(sin(frameCount * 0.01),-1.0,1.0,0.0,1.0);
}
//for each trajectory
for(int i = 0 ; i < plot.getChildCount(); i++){
PShape line = plot.getChild(i);
//access each line's start and end points
PVector start = line.getVertex(0);
PVector end = line.getVertex(1);
//calculate the linearly interpolated point in between start end using the traversal value and lerp()
PVector inbetween = PVector.lerp(start,end,traversal);
//use the interpolated value to draw
fill(lerpColor(color(255,0,0),color(0,255,0),traversal));
ellipse(inbetween.x,inbetween.y,15,15);
}
}
Here's another fun little variation:
Table data;
PShape plot;
void setup(){
size(1620, 1080, P2D);
smooth(8);
//create a group to store the lines from each row
plot = createShape(GROUP);
//load the data, specifying it has a header and it's tab separated
data = loadTable("data.tsv", "header, tsv");
//traverse each row
for (TableRow row : data.rows ()) {
//extract each value
int x1 = row.getInt("x1");
int y1 = row.getInt("y1");
int x2 = row.getInt("x2");
int y2 = row.getInt("y2");
//add the coordinates as lines to the group
PShape line = createShape(LINE, x1, y1, x2, y2);
plot.addChild(line);
}
shape(plot);
strokeWeight(5.0);
}
void draw(){
//hacky fade effect, change the alpha/transparency value to experiment with fade amount
background(255);
//animate the trajectories
//use normalized (between 0.0 and 1.0) value to traverse the paths (think of it as 0 and 100%, 0 is at the start 100% is at the end)
//if can be interactive
float traversal;
if(mousePressed) {
traversal = map(mouseX,0,width,0.0,1.0);
}else{//or time based, up to you :)
traversal = map(sin(frameCount * 0.01),-1.0,1.0,0.0,1.0);
}
beginShape(LINES);
//for each trajectory
for(int i = 0 ; i < plot.getChildCount(); i++){
PShape line = plot.getChild(i);
//access each line's start and end points
PVector start = line.getVertex(0);
PVector end = line.getVertex(1);
//calculate the linearly interpolated point in between start end using the traversal value and lerp()
PVector inbetween = PVector.lerp(start,end,traversal);
//use the interpolated value to draw
stroke(64);
vertex(start.x,start.y);
stroke(160);
vertex(inbetween.x,inbetween.y);
}
endShape();
}
Video demo here
If linear interpolation isn't enough and you need more control over timing or the interpolation type, check out Benedikt Groß's Ani library
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.