/*
* Apache License
* Version 2.0, January 2004
* http://www.apache.org/licenses/
*
* Copyright 2013 Aurelian Tutuianu
* Copyright 2014 Aurelian Tutuianu
* Copyright 2015 Aurelian Tutuianu
* Copyright 2016 Aurelian Tutuianu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package rapaio.graphics.plot.plotcomp;
import rapaio.data.Numeric;
import rapaio.data.Var;
import rapaio.graphics.base.Range;
import rapaio.graphics.opt.ColorPalette;
import rapaio.graphics.opt.GOpt;
import rapaio.graphics.plot.PlotComponent;
import java.awt.*;
import java.awt.geom.Line2D;
/**
* @author <a href="mailto:padreati@yahoo.com">Aurelian Tutuianu</a>
*/
public class Lines extends PlotComponent {
private static final long serialVersionUID = 9183829873670164532L;
private final Var x;
private final Var y;
public Lines(Var y, GOpt... opts) {
this(Numeric.seq(0, y.rowCount() - 1), y, opts);
}
public Lines(Var x, Var y, GOpt... opts) {
this.x = x;
this.y = y;
this.options.apply(opts);
}
@Override
public Range buildRange() {
if (x.rowCount() == 0) {
return null;
}
Range range = new Range();
for (int i = 0; i < x.rowCount(); i++) {
if (x.missing(i) || y.missing(i)) {
continue;
}
range.union(x.value(i), y.value(i));
}
return range;
}
@Override
public void paint(Graphics2D g2d) {
g2d.setStroke(new BasicStroke(options.getLwd()));
g2d.setBackground(ColorPalette.STANDARD.getColor(255));
for (int i = 1; i < x.rowCount(); i++) {
g2d.setColor(options.getColor(i));
double x1 = x.value(i - 1);
double y1 = y.value(i - 1);
double x2 = x.value(i);
double y2 = y.value(i);
Range r = new Clip(parent.getRange()).lineClip(x1, y1, x2, y2);
if (r != null) {
x1 = xScale(r.x1());
x2 = xScale(r.x2());
y1 = yScale(r.y1());
y2 = yScale(r.y2());
g2d.draw(new Line2D.Double(x1, y1, x2, y2));
}
}
}
}
/**
* Code to compute a line segment clipped by a rectangle
* <p>
* Code copied from wikipedia page for Cohen-Sutherland algorithm:
* <p>
* https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm
*/
class Clip {
private final int INSIDE = 0; // 0000
private final int LEFT = 1; // 0001
private final int RIGHT = 2; // 0010
private final int BOTTOM = 4; // 0100
private final int TOP = 8; // 1000
private final double xmin;
private final double ymin;
private final double xmax;
private final double ymax;
public Clip(Range r) {
this.xmin = Math.min(r.x1(), r.x2());
this.ymin = Math.min(r.y1(), r.y2());
this.xmax = Math.max(r.x1(), r.x2());
this.ymax = Math.max(r.y1(), r.y2());
}
// Compute the bit code for a point (x, y) using the clip rectangle
// bounded diagonally by (xmin, ymin), and (xmax, ymax)
// ASSUME THAT xmax, xmin, ymax and ymin are global constants.
private int computeOutCode(double x, double y) {
int code;
code = INSIDE; // initialised as being inside of clip window
if (x < xmin) // to the left of clip window
code |= LEFT;
else if (x > xmax) // to the right of clip window
code |= RIGHT;
if (y < ymin) // below the clip window
code |= BOTTOM;
else if (y > ymax) // above the clip window
code |= TOP;
return code;
}
// Cohen–Sutherland clipping algorithm clips a line from
// P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with
// diagonal from (xmin, ymin) to (xmax, ymax).
public Range lineClip(double x0, double y0, double x1, double y1) {
// compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
int outcode0 = computeOutCode(x0, y0);
int outcode1 = computeOutCode(x1, y1);
boolean accept = false;
while (true) {
if ((outcode0 | outcode1) == 0) { // Bitwise OR is 0. Trivially accept and get out of loop
accept = true;
break;
} else if ((outcode0 & outcode1) > 0) { // Bitwise AND is not 0. Trivially reject and get out of loop
break;
} else {
// failed both tests, so calculate the line segment to clip
// from an outside point to an intersection with clip edge
double x = 0;
double y = 0;
// At least one endpoint is outside the clip rectangle; pick it.
int outcodeOut = outcode0 > 0 ? outcode0 : outcode1;
// Now find the intersection point;
// use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
if ((outcodeOut & TOP) > 0) { // point is above the clip rectangle
x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0);
y = ymax;
} else if ((outcodeOut & BOTTOM) > 0) { // point is below the clip rectangle
x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0);
y = ymin;
} else if ((outcodeOut & RIGHT) > 0) { // point is to the right of clip rectangle
y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0);
x = xmax;
} else if ((outcodeOut & LEFT) > 0) { // point is to the left of clip rectangle
y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0);
x = xmin;
}
// Now we move outside point to intersection point to clip
// and get ready for next pass.
if (outcodeOut == outcode0) {
x0 = x;
y0 = y;
outcode0 = computeOutCode(x0, y0);
} else {
x1 = x;
y1 = y;
outcode1 = computeOutCode(x1, y1);
}
}
}
return accept ? new Range(x0, y0, x1, y1) : null;
}
}