简体   繁体   中英

How to draw a line from point to point in Processing

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.

Map2

Water

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:

  1. Try Processing's built-in CSV parser using loadTable() . It supports the tab support and headers as your sample data. The nice thing about parsing the headers is that you can use the column label to parse each value using functions readily available functions like getInt()
  2. Try using PShape : you can setup your draw commands once in setup, then render the final shape in one command. The advantage is you can access the shapes and vertices used to later if you need to.

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:

数据图1

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); 

情节2

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:

  • passing 0.0 will be the start of the line
  • passing 0.5 will be 50% along the line
  • passing 1.0 will be the end of the line

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM