001/* =========================================================== 002 * Orson Charts : a 3D chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C)opyright 2013-2016, by Object Refinery Limited. All rights reserved. 006 * 007 * http://www.object-refinery.com/orsoncharts/index.html 008 * 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as published by 011 * the Free Software Foundation, either version 3 of the License, or 012 * (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public License 020 * along with this program. If not, see <http://www.gnu.org/licenses/>. 021 * 022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 023 * Other names may be trademarks of their respective owners.] 024 * 025 * If you do not wish to be bound by the terms of the GPL, an alternative 026 * commercial license can be purchased. For details, please see visit the 027 * Orson Charts home page: 028 * 029 * http://www.object-refinery.com/orsoncharts/index.html 030 * 031 */ 032 033package com.orsoncharts.renderer.category; 034 035import java.awt.Color; 036import java.io.Serializable; 037 038import com.orsoncharts.Chart3D; 039import com.orsoncharts.Chart3DFactory; 040import com.orsoncharts.Range; 041import com.orsoncharts.axis.CategoryAxis3D; 042import com.orsoncharts.axis.ValueAxis3D; 043import com.orsoncharts.data.KeyedValues3DItemKey; 044import com.orsoncharts.data.category.CategoryDataset3D; 045import com.orsoncharts.graphics3d.Dimension3D; 046import com.orsoncharts.graphics3d.Object3D; 047import com.orsoncharts.graphics3d.Offset3D; 048import com.orsoncharts.graphics3d.World; 049import com.orsoncharts.label.ItemLabelPositioning; 050import com.orsoncharts.plot.CategoryPlot3D; 051import com.orsoncharts.renderer.Renderer3DChangeEvent; 052import com.orsoncharts.util.ObjectUtils; 053 054/** 055 * A renderer that can be used with the {@link CategoryPlot3D} class to create 056 * 3D lines charts from data in a {@link CategoryDataset3D}. The 057 * {@code createLineChart()} method in the {@link Chart3DFactory} class 058 * will construct a chart that uses this renderer. Here is a sample: 059 * <div> 060 * <object id="ABC" data="../../../../doc-files/LineChart3DDemo1.svg" 061 * type="image/svg+xml" width="500" height="359"></object> 062 * </div> 063 * (refer to {@code LineChart3DDemo1.java} for the code to generate the 064 * above chart). 065 * <br><br> 066 * Some attributes in the renderer are specified in "world units" - see the 067 * {@link Chart3D} class description for more information about world units. 068 * <br><br> 069 * There is a factory method to create a chart using this renderer - see 070 * {@link Chart3DFactory#createLineChart(String, String, CategoryDataset3D, 071 * String, String, String)}. 072 * <br><br> 073 * NOTE: This class is serializable, but the serialization format is subject 074 * to change in future releases and should not be relied upon for persisting 075 * instances of this class. 076 */ 077@SuppressWarnings("serial") 078public class LineRenderer3D extends AbstractCategoryRenderer3D 079 implements Serializable { 080 081 /** The line width (in world units). */ 082 private double lineWidth; 083 084 /** The line height (in world units). */ 085 private double lineHeight; 086 087 /** 088 * For isolated data values this attribute controls the width (x-axis) of 089 * the box representing the data item, it is expressed as a percentage of 090 * the category width. 091 */ 092 private double isolatedItemWidthPercent; 093 094 /** 095 * The color source that determines the color used to highlight clipped 096 * items in the chart. 097 */ 098 private CategoryColorSource clipColorSource; 099 100 /** 101 * Creates a new instance with default attribute values. 102 */ 103 public LineRenderer3D() { 104 this.lineWidth = 0.4; 105 this.lineHeight = 0.2; 106 this.isolatedItemWidthPercent = 0.25; 107 this.clipColorSource = new StandardCategoryColorSource(Color.RED); 108 } 109 110 /** 111 * Returns the line width in world units. The default value is 112 * {@code 0.4}. 113 * 114 * @return The line width in world units. 115 */ 116 public double getLineWidth() { 117 return this.lineWidth; 118 } 119 120 /** 121 * Sets the line width (in world units) and sends a 122 * {@link Renderer3DChangeEvent} to all registered listeners. 123 * 124 * @param width the width (in world units). 125 */ 126 public void setLineWidth(double width) { 127 this.lineWidth = width; 128 fireChangeEvent(true); 129 } 130 131 /** 132 * Returns the line height in world units. The default value is 133 * {@code 0.2}. 134 * 135 * @return The line height in world units. 136 */ 137 public double getLineHeight() { 138 return this.lineHeight; 139 } 140 141 /** 142 * Sets the line height (in world units) and sends a 143 * {@link Renderer3DChangeEvent} to all registered listeners. 144 * 145 * @param height the height (in world units). 146 */ 147 public void setLineHeight(double height) { 148 this.lineHeight = height; 149 fireChangeEvent(true); 150 } 151 152 /** 153 * Returns the width for isolated data items as a percentage of the 154 * category width. The default value is 0.25 (twenty five percent). 155 * 156 * @return The width percentage. 157 * 158 * @since 1.3 159 */ 160 public double getIsolatedItemWidthPercent() { 161 return this.isolatedItemWidthPercent; 162 } 163 164 /** 165 * Sets the width for isolated data items as a percentage of the category 166 * width and sends a change event to all registered listeners. 167 * 168 * @param percent the new percentage. 169 * 170 * @since 1.3 171 */ 172 public void setIsolatedItemWidthPercent(double percent) { 173 this.isolatedItemWidthPercent = percent; 174 fireChangeEvent(true); 175 } 176 177 /** 178 * Returns the color source used to determine the color used to highlight 179 * clipping in the chart elements. If the source is {@code null}, 180 * then the regular series color is used instead. 181 * 182 * @return The color source (possibly {@code null}). 183 */ 184 public CategoryColorSource getClipColorSource() { 185 return this.clipColorSource; 186 } 187 188 /** 189 * Sets the color source that determines the color used to highlight 190 * clipping in the chart elements, and sends a {@link Renderer3DChangeEvent} 191 * to all registered listeners. 192 * 193 * @param source the source ({@code null} permitted). 194 */ 195 public void setClipColorSource(CategoryColorSource source) { 196 this.clipColorSource = source; 197 fireChangeEvent(true); 198 } 199 200 /** 201 * Constructs and places one item from the specified dataset into the given 202 * world. This method will be called by the {@link CategoryPlot3D} class 203 * while iterating over the items in the dataset. 204 * 205 * @param dataset the dataset ({@code null} not permitted). 206 * @param series the series index. 207 * @param row the row index. 208 * @param column the column index. 209 * @param world the world ({@code null} not permitted). 210 * @param dimensions the plot dimensions ({@code null} not permitted). 211 * @param xOffset the x-offset. 212 * @param yOffset the y-offset. 213 * @param zOffset the z-offset. 214 */ 215 @Override 216 public void composeItem(CategoryDataset3D dataset, int series, int row, 217 int column, World world, Dimension3D dimensions, 218 double xOffset, double yOffset, double zOffset) { 219 220 // there is a lot of brute force code underneath this compose method 221 // because I haven't seen the pattern yet that will let me reduce it 222 // to something more elegant...probably I'm not smart enough. 223 Number y = dataset.getValue(series, row, column); 224 Number yprev = null; 225 if (column > 0) { 226 yprev = dataset.getValue(series, row, column - 1); 227 } 228 Number ynext = null; 229 if (column < dataset.getColumnCount() - 1) { 230 ynext = dataset.getValue(series, row, column + 1); 231 } 232 233 CategoryPlot3D plot = getPlot(); 234 CategoryAxis3D rowAxis = plot.getRowAxis(); 235 CategoryAxis3D columnAxis = plot.getColumnAxis(); 236 ValueAxis3D valueAxis = plot.getValueAxis(); 237 Range r = valueAxis.getRange(); 238 239 Comparable<?> seriesKey = dataset.getSeriesKey(series); 240 Comparable<?> rowKey = dataset.getRowKey(row); 241 Comparable<?> columnKey = dataset.getColumnKey(column); 242 double rowValue = rowAxis.getCategoryValue(rowKey); 243 double columnValue = columnAxis.getCategoryValue(columnKey); 244 double ww = dimensions.getWidth(); 245 double hh = dimensions.getHeight(); 246 double dd = dimensions.getDepth(); 247 248 // for any data value, we'll try to create two line segments, one to 249 // the left of the value and one to the right of the value (each 250 // halfway to the adjacent data value). If the adjacent data values 251 // are null (or don't exist, as in the case of the first and last data 252 // items, then we can create an isolated segment to represent the data 253 // item. The final consideration is whether the opening and closing 254 // faces of each segment are filled or not (if the segment connects to 255 // another segment, there is no need to fill the end face) 256 boolean createLeftSegment, createRightSegment, createIsolatedSegment; 257 boolean leftOpen = false; 258 boolean leftClose = false; 259 boolean rightOpen = false; 260 boolean rightClose = false; 261 if (column == 0) { // first column is a special case 262 createLeftSegment = false; // never for first item 263 if (dataset.getColumnCount() == 1) { 264 createRightSegment = false; 265 createIsolatedSegment = (y != null); 266 } else { 267 createRightSegment = (y != null && ynext != null); 268 rightOpen = true; 269 rightClose = false; 270 createIsolatedSegment = (y != null && ynext == null); 271 } 272 } else if (column == dataset.getColumnCount() - 1) { // last column 273 createRightSegment = false; // never for the last item 274 createLeftSegment = (y != null && yprev != null); 275 leftOpen = false; 276 leftClose = true; 277 createIsolatedSegment = (y != null && yprev == null); 278 } else { // this is the general case 279 createLeftSegment = (y != null && yprev != null); 280 leftOpen = false; 281 leftClose = (createLeftSegment && ynext == null); 282 createRightSegment = (y != null && ynext != null); 283 rightOpen = (createRightSegment && yprev == null); 284 rightClose = false; 285 createIsolatedSegment = (y != null 286 && yprev == null && ynext == null); 287 } 288 289 // now that we know what we have to create, we'll need some info 290 // for the construction 291 double xw = columnAxis.translateToWorld(columnValue, ww) + xOffset; 292 double yw = Double.NaN; 293 if (y != null) { 294 yw = valueAxis.translateToWorld(y.doubleValue(), hh) + yOffset; 295 } 296 double zw = rowAxis.translateToWorld(rowValue, dd) + zOffset; 297 double ywmin = valueAxis.translateToWorld(r.getMin(), hh) + yOffset; 298 double ywmax = valueAxis.translateToWorld(r.getMax(), hh) + yOffset; 299 Color color = getColorSource().getColor(series, row, column); 300 Color clipColor = color; 301 if (getClipColorSource() != null) { 302 Color c = getClipColorSource().getColor(series, row, column); 303 if (c != null) { 304 clipColor = c; 305 } 306 } 307 KeyedValues3DItemKey itemKey = new KeyedValues3DItemKey(seriesKey, 308 rowKey, columnKey); 309 if (createLeftSegment) { 310 Comparable<?> prevColumnKey = dataset.getColumnKey(column - 1); 311 double prevColumnValue = columnAxis.getCategoryValue(prevColumnKey); 312 double prevColumnX = columnAxis.translateToWorld(prevColumnValue, 313 ww) + xOffset; 314 double xl = (prevColumnX + xw) / 2.0; 315 double yprevw = valueAxis.translateToWorld(yprev.doubleValue(), hh) 316 + yOffset; 317 double yl = (yprevw + yw) / 2.0; 318 Object3D left = createSegment(xl, yl, xw, yw, zw, this.lineWidth, 319 this.lineHeight, ywmin, ywmax, color, clipColor, leftOpen, 320 leftClose); 321 if (left != null) { 322 left.setProperty(Object3D.ITEM_KEY, itemKey); 323 world.add(left); 324 } 325 } 326 if (createRightSegment) { 327 Comparable<?> nextColumnKey = dataset.getColumnKey(column + 1); 328 double nextColumnValue = columnAxis.getCategoryValue(nextColumnKey); 329 double nextColumnX = columnAxis.translateToWorld(nextColumnValue, 330 ww) + xOffset; 331 double xr = (nextColumnX + xw) / 2.0; 332 double ynextw = valueAxis.translateToWorld(ynext.doubleValue(), hh) 333 + yOffset; 334 double yr = (ynextw + yw) / 2.0; 335 Object3D right = createSegment(xw, yw, xr, yr, zw, this.lineWidth, 336 this.lineHeight, ywmin, ywmax, color, clipColor, rightOpen, 337 rightClose); 338 if (right != null) { 339 right.setProperty(Object3D.ITEM_KEY, itemKey); 340 world.add(right); 341 } 342 } 343 if (createIsolatedSegment) { 344 double cw = columnAxis.getCategoryWidth() 345 * this.isolatedItemWidthPercent; 346 double cww = columnAxis.translateToWorld(cw, ww); 347 Object3D isolated = Object3D.createBox(xw, cww, yw, this.lineHeight, 348 zw, this.lineWidth, color); 349 if (isolated != null) { 350 isolated.setProperty(Object3D.ITEM_KEY, itemKey); 351 world.add(isolated); 352 } 353 } 354 355 if (getItemLabelGenerator() != null && !Double.isNaN(yw) 356 && yw >= ywmin && yw <= ywmax) { 357 String label = getItemLabelGenerator().generateItemLabel(dataset, 358 seriesKey, rowKey, columnKey); 359 if (label != null) { 360 ItemLabelPositioning positioning = getItemLabelPositioning(); 361 Offset3D offsets = getItemLabelOffsets(); 362 double dy = offsets.getDY() * dimensions.getHeight(); 363 if (positioning.equals(ItemLabelPositioning.CENTRAL)) { 364 Object3D labelObj = Object3D.createLabelObject(label, 365 getItemLabelFont(), getItemLabelColor(), 366 getItemLabelBackgroundColor(), 367 xw, yw + dy, zw, false, true); 368 labelObj.setProperty(Object3D.ITEM_KEY, itemKey); 369 world.add(labelObj); 370 } else if (positioning.equals( 371 ItemLabelPositioning.FRONT_AND_BACK)) { 372 double dz = this.lineWidth ; 373 Object3D labelObj1 = Object3D.createLabelObject(label, 374 getItemLabelFont(), getItemLabelColor(), 375 getItemLabelBackgroundColor(), 376 xw, yw, zw - dz, false, false); 377 labelObj1.setProperty(Object3D.ITEM_KEY, itemKey); 378 world.add(labelObj1); 379 Object3D labelObj2 = Object3D.createLabelObject(label, 380 getItemLabelFont(), getItemLabelColor(), 381 getItemLabelBackgroundColor(), 382 xw, yw, zw + dz, true, false); 383 labelObj2.setProperty(Object3D.ITEM_KEY, itemKey); 384 world.add(labelObj2); 385 } 386 } 387 } 388 } 389 390 /** 391 * Creates a segment of a line between (x0, y0, z) and (x1, y1, z), with 392 * the specified line width and height, taking into account the minimum 393 * and maximum world coordinates (in the y-direction, because it is assumed 394 * that we have the full x and z-range required). 395 * 396 * @param x0 the starting x-coordinate. 397 * @param y0 the starting x-coordinate. 398 * @param x1 the ending x-coordinate. 399 * @param y1 the ending y-coordinate. 400 * @param z the z-coordinate. 401 * @param lineWidth the line width (z-axis). 402 * @param lineHeight the line height (y-axis). 403 * @param ymin the lower bound for y-values. 404 * @param ymax the upper bound for y-values. 405 * @param color the segment color. 406 * @param clipColor the clip color (for the faces in the segment that are 407 * clipped against the edge of the world). 408 * @param openingFace is an opening face required? 409 * @param closingFace is a closing face required? 410 * 411 * @return A 3D object that is a segment in a line. 412 */ 413 private Object3D createSegment(double x0, double y0, double x1, double y1, 414 double z, double lineWidth, double lineHeight, double ymin, 415 double ymax, Color color, Color clipColor, boolean openingFace, 416 boolean closingFace) { 417 double wdelta = lineWidth / 2.0; 418 double hdelta = lineHeight / 2.0; 419 double y0b = y0 - hdelta; 420 double y0t = y0 + hdelta; 421 double y1b = y1 - hdelta; 422 double y1t = y1 + hdelta; 423 double zf = z - wdelta; 424 double zb = z + wdelta; 425 double[] xpts = calcCrossPoints(x0, x1, y0b, y0t, y1b, y1t, ymin, ymax); 426 Object3D seg = null; 427 if (y0b >= ymax) { // CASE A 428 seg = createSegmentA(x0, x1, xpts, y0b, y0t, y1b, y1t, 429 ymin, ymax, zf, zb, color, clipColor, false, closingFace); 430 } else if (y0t > ymax && y0b > ymin) { // CASE B 431 seg = createSegmentB(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, 432 zf, zb, color, clipColor, openingFace, closingFace); 433 } else if (y0t > ymax && y0b <= ymin) { // CASE C 434 seg = createSegmentC(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, 435 zf, zb, color, clipColor, openingFace, closingFace); 436 } else if (y0t > ymin && y0b >= ymin) { // CASE D 437 seg = createSegmentD(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, 438 zf, zb, color, clipColor, openingFace, closingFace); 439 } else if (y0t > ymin && y0b < ymin) { // CASE E 440 seg = createSegmentE(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, 441 zf, zb, color, clipColor, openingFace, closingFace); 442 } else if (y0t <= ymin) { // CASE F 443 seg = createSegmentF(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, 444 zf, zb, color, clipColor, false, closingFace); 445 } 446 return seg; 447 } 448 449 /** 450 * Calculates the four intersection points between two horizontal lines 451 * (ymin and ymax) and the lines (x0, y0b, x1, y1b) and (x0, y1t, x1, y1t) 452 * and returns the x-coordinates in an array. 453 * 454 * @param x0 455 * @param x1 456 * @param y0b 457 * @param y0t 458 * @param y1b 459 * @param y1t 460 * @param ymin 461 * @param ymax 462 * 463 * @return An array of 4 x-coordinates. 464 */ 465 private double[] calcCrossPoints(double x0, double x1, double y0b, 466 double y0t, double y1b, double y1t, double ymin, double ymax) { 467 double[] xpts = new double[] { Double.NaN, Double.NaN, Double.NaN, 468 Double.NaN }; 469 double factor = (y0b - ymin) / (y0b - y1b); 470 xpts[0] = x0 + factor * (x1 - x0); 471 factor = (y0t - ymin) / (y0t - y1t); 472 xpts[1] = x0 + factor * (x1 - x0); 473 factor = (y0b - ymax) / (y0b - y1b); 474 xpts[2] = x0 + factor * (x1 - x0); 475 factor = (y0t - ymax) / (y0t - y1t); 476 xpts[3] = x0 + factor * (x1 - x0); 477 return xpts; 478 } 479 480 /** 481 * Creates a segment for the case where the start of the segment is 482 * completely above the upper bound of the axis at the left side of the 483 * chart. 484 * 485 * @param x0 486 * @param x1 487 * @param xpts 488 * @param y0b 489 * @param y0t 490 * @param y1b 491 * @param y1t 492 * @param wmin 493 * @param wmax 494 * @param zf 495 * @param zb 496 * @param color 497 * @param clipColor 498 * @param openingFace ignored because there is no opening face for this 499 * case. 500 * @param closingFace 501 * 502 * @return A segment ({@code null} if the segment is entirely clipped). 503 */ 504 private Object3D createSegmentA(double x0, double x1, double[] xpts, 505 double y0b, double y0t, double y1b, double y1t, double wmin, 506 double wmax, double zf, double zb, Color color, Color clipColor, 507 boolean openingFace, boolean closingFace) { 508 if (y1b > wmax) { 509 return null; // nothing is visible 510 } 511 if (y1t > wmax) { 512 if (y1b >= wmin) { 513 // create a triangle with the top and right 514 Object3D seg = new Object3D(color, true); 515 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 516 seg.addVertex(xpts[2], wmax, zf); 517 seg.addVertex(xpts[2], wmax, zb); 518 seg.addVertex(x1, wmax, zf); 519 seg.addVertex(x1, wmax, zb); 520 seg.addVertex(x1, y1b, zf); 521 seg.addVertex(x1, y1b, zb); 522 seg.addFace(new int[] {0, 2, 4}); // front 523 seg.addFace(new int[] {1, 5, 3}); // rear 524 seg.addFace(new int[] {0, 1, 3, 2}, "clip"); // clip top 525 seg.addFace(new int[] {4, 5, 1, 0}); // bottom 526 if (closingFace) { 527 seg.addFace(new int[] {2, 3, 5, 4}); 528 } 529 return seg; 530 } else { 531 Object3D seg = new Object3D(color, true); 532 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 533 seg.addVertex(xpts[2], wmax, zf); 534 seg.addVertex(xpts[2], wmax, zb); 535 seg.addVertex(x1, wmax, zf); 536 seg.addVertex(x1, wmax, zb); 537 seg.addVertex(x1, wmin, zf); 538 seg.addVertex(x1, wmin, zb); 539 seg.addVertex(xpts[0], wmin, zf); 540 seg.addVertex(xpts[0], wmin, zb); 541 seg.addFace(new int[] {0, 2, 4, 6}); // front 542 seg.addFace(new int[] {1, 7, 5, 3}); // rear 543 seg.addFace(new int[] {0, 1, 3, 2}); // clip top 544 seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip bottom 545 seg.addFace(new int[] {6, 7, 1, 0}); // bottom 546 if (closingFace) { 547 seg.addFace(new int[] {2, 3, 5, 4}); 548 } 549 return seg; 550 } 551 } else if (y1t >= wmin) { 552 if (y1b >= wmin) { 553 Object3D seg = new Object3D(color, true); 554 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 555 seg.addVertex(xpts[2], wmax, zf); 556 seg.addVertex(xpts[2], wmax, zb); 557 seg.addVertex(xpts[3], wmax, zf); 558 seg.addVertex(xpts[3], wmax, zb); 559 seg.addVertex(x1, y1t, zf); 560 seg.addVertex(x1, y1t, zb); 561 seg.addVertex(x1, y1b, zf); 562 seg.addVertex(x1, y1b, zb); 563 seg.addFace(new int[] {0, 2, 4, 6}); // front 564 seg.addFace(new int[] {1, 7, 5, 3}); // rear 565 seg.addFace(new int[] {0, 1, 3, 2}, "clip"); // clip top 566 seg.addFace(new int[] {2, 3, 5, 4}); // top 567 seg.addFace(new int[] {6, 7, 1, 0}); // bottom 568 if (closingFace) { 569 seg.addFace(new int[] {4, 5, 7, 6}); 570 } 571 return seg; 572 } else { 573 Object3D seg = new Object3D(color, true); 574 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 575 seg.addVertex(xpts[2], wmax, zf); 576 seg.addVertex(xpts[2], wmax, zb); 577 seg.addVertex(xpts[3], wmax, zf); 578 seg.addVertex(xpts[3], wmax, zb); 579 seg.addVertex(x1, y1t, zf); 580 seg.addVertex(x1, y1t, zb); 581 seg.addVertex(x1, wmin, zf); 582 seg.addVertex(x1, wmin, zb); 583 seg.addVertex(xpts[0], wmin, zf); 584 seg.addVertex(xpts[0], wmin, zb); 585 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 586 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 587 seg.addFace(new int[] {2, 3, 5, 4}); 588 seg.addFace(new int[] {0, 1, 3, 2}, "clip"); // clip top 589 seg.addFace(new int[] {6, 7, 9, 8}, "clip"); // clip bottom 590 seg.addFace(new int[] {8, 9, 1, 0}); 591 // there is no opening face in this case 592 if (closingFace) { 593 seg.addFace(new int[] {4, 5, 7, 6}); 594 } 595 return seg; 596 } 597 } else { 598 Object3D seg = new Object3D(color, true); 599 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 600 seg.addVertex(xpts[2], wmax, zf); 601 seg.addVertex(xpts[2], wmax, zb); 602 seg.addVertex(xpts[3], wmax, zf); 603 seg.addVertex(xpts[3], wmax, zb); 604 seg.addVertex(xpts[1], wmin, zf); 605 seg.addVertex(xpts[1], wmin, zb); 606 seg.addVertex(xpts[0], wmin, zf); 607 seg.addVertex(xpts[0], wmin, zb); 608 seg.addFace(new int[] {0, 2, 4, 6}); // front 609 seg.addFace(new int[] {1, 7, 5, 3}); // rear 610 seg.addFace(new int[] {4, 2, 3, 5}); // top 611 seg.addFace(new int[] {0, 6, 7, 1}); // bottom 612 seg.addFace(new int[] {0, 1, 3, 2}, "clip"); // clip top 613 seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip bottom 614 // there are no opening or closing faces in this case 615 return seg; 616 } 617 } 618 619 /** 620 * Creates a segment for the case where the left end of the line spans 621 * the axis maximum on the left side of the chart. 622 * 623 * @param x0 624 * @param x1 625 * @param xpts 626 * @param y0b 627 * @param y0t 628 * @param y1b 629 * @param y1t 630 * @param wmin 631 * @param wmax 632 * @param zf 633 * @param zb 634 * @param color 635 * @param clipColor 636 * @param openingFace 637 * @param closingFace 638 * 639 * @return A segment. 640 */ 641 private Object3D createSegmentB(double x0, double x1, double[] xpts, 642 double y0b, double y0t, double y1b, double y1t, double wmin, 643 double wmax, double zf, double zb, Color color, Color clipColor, 644 boolean openingFace, boolean closingFace) { 645 646 if (y1b >= wmax) { 647 Object3D seg = new Object3D(color, true); 648 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 649 seg.addVertex(x0, y0b, zf); 650 seg.addVertex(x0, y0b, zb); 651 seg.addVertex(x0, wmax, zf); 652 seg.addVertex(x0, wmax, zb); 653 seg.addVertex(xpts[2], wmax, zf); 654 seg.addVertex(xpts[2], wmax, zb); 655 seg.addFace(new int[] {0, 2, 4}); // front 656 seg.addFace(new int[] {1, 5, 3}); // rear 657 seg.addFace(new int[] {0, 4, 5, 1}); // bottom 658 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 659 if (openingFace) { 660 seg.addFace(new int[] {0, 1, 3, 2}); 661 } 662 // there is no closing face in this case 663 return seg; 664 } 665 if (y1t > wmax) { 666 if (y1b >= wmin) { 667 Object3D seg = new Object3D(color, true); 668 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 669 seg.addVertex(x0, y0b, zf); 670 seg.addVertex(x0, y0b, zb); 671 seg.addVertex(x0, wmax, zf); 672 seg.addVertex(x0, wmax, zb); 673 seg.addVertex(x1, wmax, zf); 674 seg.addVertex(x1, wmax, zb); 675 seg.addVertex(x1, y1b, zf); 676 seg.addVertex(x1, y1b, zb); 677 seg.addFace(new int[] {0, 2, 4, 6}); // front 678 seg.addFace(new int[] {1, 7, 5, 3}); // rear 679 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 680 seg.addFace(new int[] {0, 6, 7, 1}); // bottom 681 if (openingFace) { 682 seg.addFace(new int[] {0, 1, 3, 2}); 683 } 684 if (closingFace) { 685 seg.addFace(new int[] {4, 5, 7, 6}); 686 } 687 return seg; 688 } else { 689 Object3D seg = new Object3D(color, true); 690 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 691 seg.addVertex(x0, y0b, zf); 692 seg.addVertex(x0, y0b, zb); 693 seg.addVertex(x0, wmax, zf); 694 seg.addVertex(x0, wmax, zb); 695 seg.addVertex(x1, wmax, zf); 696 seg.addVertex(x1, wmax, zb); 697 seg.addVertex(x1, wmin, zf); 698 seg.addVertex(x1, wmin, zb); 699 seg.addVertex(xpts[0], wmin, zf); 700 seg.addVertex(xpts[0], wmin, zb); 701 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 702 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 703 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 704 seg.addFace(new int[] {8, 6, 7, 9}, "clip"); // clip bottom 705 seg.addFace(new int[] {0, 8, 9, 1}); 706 if (openingFace) { 707 seg.addFace(new int[] {0, 1, 3, 2}); 708 } 709 if (closingFace) { 710 seg.addFace(new int[] {4, 5, 7, 6}); 711 } 712 return seg; 713 } 714 } 715 if (y1t > wmin) { 716 if (y1b >= wmin) { 717 Object3D seg = new Object3D(color, true); 718 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 719 seg.addVertex(x0, y0b, zf); 720 seg.addVertex(x0, y0b, zb); 721 seg.addVertex(x0, wmax, zf); 722 seg.addVertex(x0, wmax, zb); 723 seg.addVertex(xpts[3], wmax, zf); 724 seg.addVertex(xpts[3], wmax, zb); 725 seg.addVertex(x1, y1t, zf); 726 seg.addVertex(x1, y1t, zb); 727 seg.addVertex(x1, y1b, zf); 728 seg.addVertex(x1, y1b, zb); 729 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 730 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 731 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 732 seg.addFace(new int[] {4, 5, 7, 6}); // top 733 seg.addFace(new int[] {0, 8, 9, 1}); // bottom 734 if (openingFace) { 735 seg.addFace(new int[] {0, 1, 3, 2}); 736 } 737 if (closingFace) { 738 seg.addFace(new int[] {6, 7, 9, 8}); 739 } 740 return seg; 741 } else { 742 Object3D seg = new Object3D(color, true); 743 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 744 seg.addVertex(x0, y0b, zf); 745 seg.addVertex(x0, y0b, zb); 746 seg.addVertex(x0, wmax, zf); 747 seg.addVertex(x0, wmax, zb); 748 seg.addVertex(xpts[3], wmax, zf); 749 seg.addVertex(xpts[3], wmax, zb); 750 seg.addVertex(x1, y1t, zf); 751 seg.addVertex(x1, y1t, zb); 752 seg.addVertex(x1, wmin, zf); 753 seg.addVertex(x1, wmin, zb); 754 seg.addVertex(xpts[0], wmin, zf); 755 seg.addVertex(xpts[0], wmin, zb); 756 seg.addFace(new int[] {0, 2, 4, 6, 8, 10}); // front 757 seg.addFace(new int[] {1, 11, 9, 7, 5, 3}); // rear 758 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 759 seg.addFace(new int[] {4, 5, 7, 6}); // top 760 seg.addFace(new int[] {8, 9, 11, 10}, "clip"); // clip bottom 761 seg.addFace(new int[] {10, 11, 1, 0}); // bottom 762 if (openingFace) { 763 seg.addFace(new int[] {0, 1, 3, 2}); 764 } 765 if (closingFace) { 766 seg.addFace(new int[] {6, 7, 9, 8}); 767 } 768 return seg; 769 } 770 } 771 Object3D seg = new Object3D(color, true); 772 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 773 seg.addVertex(x0, y0b, zf); 774 seg.addVertex(x0, y0b, zb); 775 seg.addVertex(x0, wmax, zf); 776 seg.addVertex(x0, wmax, zb); 777 seg.addVertex(xpts[3], wmax, zf); 778 seg.addVertex(xpts[3], wmax, zb); 779 seg.addVertex(xpts[1], wmin, zf); 780 seg.addVertex(xpts[1], wmin, zb); 781 seg.addVertex(xpts[0], wmin, zf); 782 seg.addVertex(xpts[0], wmin, zb); 783 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 784 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 785 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 786 seg.addFace(new int[] {4, 5, 7, 6}); // top 787 seg.addFace(new int[] {6, 7, 9, 8}, "clip"); // clip bottom 788 seg.addFace(new int[] {8, 9, 1, 0}); // bottom 789 if (openingFace) { 790 seg.addFace(new int[] {0, 1, 3, 2}); 791 } 792 // there is no closing face in this case 793 return seg; 794 } 795 796 /** 797 * Creates a segment for the case where the line end spans the entire axis 798 * range at the left side of the chart. 799 * 800 * @param x0 801 * @param x1 802 * @param xpts 803 * @param y0b 804 * @param y0t 805 * @param y1b 806 * @param y1t 807 * @param wmin 808 * @param wmax 809 * @param zf 810 * @param zb 811 * @param color 812 * @param clipColor 813 * @param openingFace 814 * @param closingFace 815 * 816 * @return A segment. 817 */ 818 private Object3D createSegmentC(double x0, double x1, double[] xpts, 819 double y0b, double y0t, double y1b, double y1t, double wmin, 820 double wmax, double zf, double zb, Color color, Color clipColor, 821 boolean openingFace, boolean closingFace) { 822 823 // the first 4 vertices and the opening face are common to all 824 // segments in this case 825 Object3D seg = new Object3D(color, true); 826 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 827 seg.addVertex(x0, wmin, zf); 828 seg.addVertex(x0, wmin, zb); 829 seg.addVertex(x0, wmax, zf); 830 seg.addVertex(x0, wmax, zb); 831 if (openingFace) { 832 seg.addFace(new int[] {0, 1, 3, 2}); 833 } 834 835 if (y1b >= wmax) { 836 seg.addVertex(xpts[2], wmax, zf); 837 seg.addVertex(xpts[2], wmax, zb); 838 seg.addVertex(xpts[0], wmin, zf); 839 seg.addVertex(xpts[0], wmin, zb); 840 seg.addFace(new int[] {0, 2, 4, 6}); // front 841 seg.addFace(new int[] {1, 7, 5, 3}); // rear 842 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 843 seg.addFace(new int[] {4, 5, 7, 6}); // bottom 844 seg.addFace(new int[] {7, 1, 0, 6}, "clip"); // bottom clip 845 return seg; 846 } 847 if (y1t > wmax) { 848 if (y1b >= wmin) { 849 seg.addVertex(x1, wmax, zf); 850 seg.addVertex(x1, wmax, zb); 851 seg.addVertex(x1, y1b, zf); 852 seg.addVertex(x1, y1b, zb); 853 seg.addVertex(xpts[0], wmin, zf); 854 seg.addVertex(xpts[0], wmin, zb); 855 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 856 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 857 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // top clip 858 seg.addFace(new int[] {6, 7, 9, 8}); // bottom 859 seg.addFace(new int[] {8, 9, 1, 0}, "clip"); // clip bottom 860 if (closingFace) { 861 seg.addFace(new int[] {4, 5, 7, 6}); 862 } 863 return seg; 864 } else { 865 seg.addVertex(x1, wmax, zf); 866 seg.addVertex(x1, wmax, zb); 867 seg.addVertex(x1, wmin, zf); 868 seg.addVertex(x1, wmin, zb); 869 seg.addFace(new int[] {0, 2, 4, 6}); // front 870 seg.addFace(new int[] {1, 7, 5, 3}); // rear 871 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 872 seg.addFace(new int[] {4, 5, 7, 6}); // bottom 873 seg.addFace(new int[] {7, 1, 0, 6}, "clip"); // bottom clip 874 return seg; 875 } 876 } 877 if (y1t > wmin) { 878 if (y1b >= wmin) { 879 return null; // in practice I don't think this case 880 // can occur 881 } else { 882 seg.addVertex(xpts[3], wmax, zf); 883 seg.addVertex(xpts[3], wmax, zb); 884 seg.addVertex(x1, y1t, zf); 885 seg.addVertex(x1, y1t, zb); 886 seg.addVertex(x1, wmin, zf); 887 seg.addVertex(x1, wmin, zb); 888 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 889 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 890 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 891 seg.addFace(new int[] {4, 5, 7, 6}); // top 892 seg.addFace(new int[] {9, 1, 0, 8}, "clip"); // clip bottom 893 if (closingFace) { 894 seg.addFace(new int[] {6, 7, 9, 8}); 895 } 896 return seg; 897 } 898 } 899 seg.addVertex(xpts[3], wmax, zf); 900 seg.addVertex(xpts[3], wmax, zb); 901 seg.addVertex(xpts[1], wmin, zf); 902 seg.addVertex(xpts[1], wmin, zb); 903 seg.addFace(new int[] {0, 2, 4, 6}); // front 904 seg.addFace(new int[] {1, 7, 5, 3}); // rear 905 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 906 seg.addFace(new int[] {4, 5, 7, 6}); // top 907 seg.addFace(new int[] {6, 7, 1, 0}, "clip"); // clip bottom 908 return seg; 909 } 910 911 /** 912 * Creates a segment for the case where the segment is contained within 913 * the axis range at the left side. 914 * 915 * @param x0 916 * @param x1 917 * @param xpts 918 * @param y0b 919 * @param y0t 920 * @param y1b 921 * @param y1t 922 * @param wmin 923 * @param wmax 924 * @param zf 925 * @param zb 926 * @param color 927 * @param clipColor 928 * @param openingFace 929 * @param closingFace 930 * 931 * @return A segment. 932 */ 933 private Object3D createSegmentD(double x0, double x1, double[] xpts, 934 double y0b, double y0t, double y1b, double y1t, double wmin, 935 double wmax, double zf, double zb, Color color, Color clipColor, 936 boolean openingFace, boolean closingFace) { 937 938 Object3D seg = new Object3D(color, true); 939 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 940 seg.addVertex(x0, y0b, zf); 941 seg.addVertex(x0, y0b, zb); 942 seg.addVertex(x0, y0t, zf); 943 seg.addVertex(x0, y0t, zb); 944 if (y1b >= wmax) { 945 seg.addVertex(xpts[3], wmax, zf); 946 seg.addVertex(xpts[3], wmax, zb); 947 seg.addVertex(xpts[2], wmax, zf); 948 seg.addVertex(xpts[2], wmax, zb); 949 seg.addFace(new int[] {0, 2, 4, 6}); // front 950 seg.addFace(new int[] {1, 7, 5, 3}); // rear 951 seg.addFace(new int[] {2, 3, 5, 4}); // top 952 seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip top 953 seg.addFace(new int[] {0, 6, 7, 1}); // bottom 954 if (openingFace) { 955 seg.addFace(new int[] {0, 1, 3, 2}); 956 } 957 // there is no closing face in this case 958 return seg; 959 } 960 if (y1t > wmax) { 961 if (y1b >= wmin) { 962 seg.addVertex(xpts[3], wmax, zf); 963 seg.addVertex(xpts[3], wmax, zb); 964 seg.addVertex(x1, wmax, zf); 965 seg.addVertex(x1, wmax, zb); 966 seg.addVertex(x1, y1b, zf); 967 seg.addVertex(x1, y1b, zb); 968 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 969 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 970 seg.addFace(new int[] {2, 3, 5, 4}); // top 971 seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip top 972 seg.addFace(new int[] {0, 8, 9, 1}); 973 if (openingFace) { 974 seg.addFace(new int[] {0, 1, 3, 2}); 975 } 976 if (closingFace) { 977 seg.addFace(new int[] {6, 7, 9, 8}); 978 } 979 return seg; 980 } else { 981 return null; // this case should not be possible 982 } 983 } 984 if (y1t > wmin) { 985 if (y1b >= wmin) { 986 // this is the regular segment, no clipping 987 seg.addVertex(x1, y1t, zf); 988 seg.addVertex(x1, y1t, zb); 989 seg.addVertex(x1, y1b, zf); 990 seg.addVertex(x1, y1b, zb); 991 seg.addFace(new int[] {0, 2, 4, 6}); // front 992 seg.addFace(new int[] {1, 7, 5, 3}); // rear 993 seg.addFace(new int[] {2, 3, 5, 4}); // top 994 seg.addFace(new int[] {0, 6, 7, 1}); // bottom 995 if (openingFace) { 996 seg.addFace(new int[] {0, 1, 3, 2}); 997 } 998 if (closingFace) { 999 seg.addFace(new int[] {4, 5, 7, 6}); 1000 } 1001 return seg; 1002 } else { 1003 seg.addVertex(x1, y1t, zf); 1004 seg.addVertex(x1, y1t, zb); 1005 seg.addVertex(x1, wmin, zf); 1006 seg.addVertex(x1, wmin, zb); 1007 seg.addVertex(xpts[0], wmin, zf); 1008 seg.addVertex(xpts[0], wmin, zb); 1009 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 1010 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 1011 seg.addFace(new int[] {2, 3, 5, 4}); // top 1012 seg.addFace(new int[] {0, 8, 9, 1}); // bottom 1013 seg.addFace(new int[] {6, 7, 9, 8}, "clip"); // clip bottom 1014 if (openingFace) { 1015 seg.addFace(new int[] {0, 1, 3, 2}); 1016 } 1017 if (closingFace) { 1018 seg.addFace(new int[] {4, 5, 7, 6}); 1019 } 1020 return seg; 1021 } 1022 } else { 1023 seg.addVertex(xpts[1], wmin, zf); 1024 seg.addVertex(xpts[1], wmin, zb); 1025 seg.addVertex(xpts[0], wmin, zf); 1026 seg.addVertex(xpts[0], wmin, zb); 1027 seg.addFace(new int[] {0, 2, 4, 6}); // front 1028 seg.addFace(new int[] {1, 7, 5, 3}); // rear 1029 seg.addFace(new int[] {2, 3, 5, 4}); // top 1030 seg.addFace(new int[] {0, 6, 7, 1}); // bottom 1031 seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip bottom 1032 if (openingFace) { 1033 seg.addFace(new int[] {0, 1, 3, 2}); 1034 } 1035 // there is no closing face in this case 1036 return seg; 1037 } 1038 } 1039 1040 /** 1041 * Returns a segment for the case where the line height spans the lower 1042 * bound of the axis range at the left side of the chart. 1043 * 1044 * @param x0 1045 * @param x1 1046 * @param xpts 1047 * @param y0b 1048 * @param y0t 1049 * @param y1b 1050 * @param y1t 1051 * @param wmin 1052 * @param wmax 1053 * @param zf 1054 * @param zb 1055 * @param color 1056 * @param clipColor 1057 * @param openingFace 1058 * @param closingFace 1059 * 1060 * @return The segment. 1061 */ 1062 private Object3D createSegmentE(double x0, double x1, double[] xpts, 1063 double y0b, double y0t, double y1b, double y1t, double wmin, 1064 double wmax, double zf, double zb, Color color, Color clipColor, 1065 boolean openingFace, boolean closingFace) { 1066 if (y1b > wmax) { 1067 Object3D seg = new Object3D(color, true); 1068 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1069 seg.addVertex(x0, wmin, zf); 1070 seg.addVertex(x0, wmin, zb); 1071 seg.addVertex(x0, y0t, zf); 1072 seg.addVertex(x0, y0t, zb); 1073 seg.addVertex(xpts[3], wmax, zf); 1074 seg.addVertex(xpts[3], wmax, zb); 1075 seg.addVertex(xpts[2], wmax, zf); 1076 seg.addVertex(xpts[2], wmax, zb); 1077 seg.addVertex(xpts[0], wmin, zf); 1078 seg.addVertex(xpts[0], wmin, zb); 1079 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 1080 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 1081 seg.addFace(new int[] {2, 3, 5, 4}); // top 1082 seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip top 1083 seg.addFace(new int[] {6, 7, 9, 8}); // bottom 1084 seg.addFace(new int[] {0, 8, 9, 1}, "clip"); // clip bottom 1085 if (openingFace) { 1086 seg.addFace(new int[] {0, 1, 3, 2}); 1087 } 1088 return seg; 1089 } 1090 if (y1t > wmax) { 1091 if (y1b >= wmin) { 1092 Object3D seg = new Object3D(color, true); 1093 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1094 seg.addVertex(x0, wmin, zf); 1095 seg.addVertex(x0, wmin, zb); 1096 seg.addVertex(x0, y0t, zf); 1097 seg.addVertex(x0, y0t, zb); 1098 seg.addVertex(xpts[3], wmax, zf); 1099 seg.addVertex(xpts[3], wmax, zb); 1100 seg.addVertex(x1, wmax, zf); 1101 seg.addVertex(x1, wmax, zb); 1102 seg.addVertex(x1, y1b, zf); 1103 seg.addVertex(x1, y1b, zb); 1104 seg.addVertex(xpts[0], wmin, zf); 1105 seg.addVertex(xpts[0], wmin, zb); 1106 seg.addFace(new int[] {0, 2, 4, 6, 8, 10}); // front 1107 seg.addFace(new int[] {1, 11, 9, 7, 5, 3}); // rear 1108 seg.addFace(new int[] {2, 3, 5, 4}); // top 1109 seg.addFace(new int[] {5, 7, 6, 4}, "clip"); // clip top 1110 seg.addFace(new int[] {8, 9, 11, 10}); // bottom 1111 seg.addFace(new int[] {1, 0, 10, 11}, "clip"); 1112 if (openingFace) { 1113 seg.addFace(new int[] {0, 1, 3, 2}); 1114 } 1115 if (closingFace) { 1116 seg.addFace(new int[] {6, 7, 9, 8}); 1117 } 1118 return seg; 1119 } else { 1120 Object3D seg = new Object3D(color, true); 1121 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1122 seg.addVertex(x0, wmin, zf); 1123 seg.addVertex(x0, wmin, zb); 1124 seg.addVertex(x0, y0t, zf); 1125 seg.addVertex(x0, y0t, zb); 1126 seg.addVertex(xpts[3], wmax, zf); 1127 seg.addVertex(xpts[3], wmax, zb); 1128 seg.addVertex(x1, wmax, zf); 1129 seg.addVertex(x1, wmax, zb); 1130 seg.addVertex(x1, wmin, zf); 1131 seg.addVertex(x1, wmin, zb); 1132 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 1133 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 1134 seg.addFace(new int[] {2, 3, 5, 4}); // top 1135 seg.addFace(new int[] {5, 7, 6, 4}, "clip"); // clip top 1136 seg.addFace(new int[] {0, 8, 9, 1}, "clip"); // clip bottom 1137 if (openingFace) { 1138 seg.addFace(new int[] {0, 1, 3, 2}); 1139 } 1140 if (closingFace) { 1141 seg.addFace(new int[] {6, 7, 9, 8}); 1142 } 1143 return seg; 1144 } 1145 } 1146 if (y1t > wmin) { 1147 if (y1b >= wmin) { 1148 Object3D seg = new Object3D(color, true); 1149 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1150 seg.addVertex(x0, wmin, zf); 1151 seg.addVertex(x0, wmin, zb); 1152 seg.addVertex(x0, y0t, zf); 1153 seg.addVertex(x0, y0t, zb); 1154 seg.addVertex(x1, y1t, zf); 1155 seg.addVertex(x1, y1t, zb); 1156 seg.addVertex(x1, y1b, zf); 1157 seg.addVertex(x1, y1b, zb); 1158 seg.addVertex(xpts[0], wmin, zf); 1159 seg.addVertex(xpts[0], wmin, zb); 1160 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 1161 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 1162 seg.addFace(new int[] {2, 3, 5, 4}); // top 1163 seg.addFace(new int[] {6, 7, 9, 8}); // bottom 1164 seg.addFace(new int[] {0, 8, 9, 1}, "clip"); // clip bottom 1165 if (openingFace) { 1166 seg.addFace(new int[] {0, 1, 3, 2}); 1167 } 1168 if (closingFace) { 1169 seg.addFace(new int[] {4, 5, 7, 6}); 1170 } 1171 return seg; 1172 } else { 1173 Object3D seg = new Object3D(color, true); 1174 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1175 seg.addVertex(x0, wmin, zf); 1176 seg.addVertex(x0, wmin, zb); 1177 seg.addVertex(x0, y0t, zf); 1178 seg.addVertex(x0, y0t, zb); 1179 seg.addVertex(x1, y1t, zf); 1180 seg.addVertex(x1, y1t, zb); 1181 seg.addVertex(x1, wmin, zf); 1182 seg.addVertex(x1, wmin, zb); 1183 seg.addFace(new int[] {0, 2, 4, 6}); // front 1184 seg.addFace(new int[] {1, 7, 5, 3}); // rear 1185 seg.addFace(new int[] {2, 3, 5, 4}); // top 1186 seg.addFace(new int[] {0, 6, 7, 1}, "clip"); // clip bottom 1187 if (openingFace) { 1188 seg.addFace(new int[] {0, 1, 3, 2}); 1189 } 1190 if (closingFace) { 1191 seg.addFace(new int[] {4, 5, 7, 6}); 1192 } 1193 return seg; 1194 } 1195 } 1196 Object3D seg = new Object3D(color, true); 1197 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1198 seg.addVertex(x0, wmin, zf); 1199 seg.addVertex(x0, wmin, zb); 1200 seg.addVertex(x0, y0t, zf); 1201 seg.addVertex(x0, y0t, zb); 1202 seg.addVertex(xpts[1], wmin, zf); 1203 seg.addVertex(xpts[1], wmin, zb); 1204 seg.addFace(new int[] {0, 2, 4}); // front 1205 seg.addFace(new int[] {1, 5, 3}); // rear 1206 seg.addFace(new int[] {2, 3, 5, 4}); // top 1207 seg.addFace(new int[] {0, 4, 5, 1}, "clip"); // clip bottom 1208 if (openingFace) { 1209 seg.addFace(new int[] {0, 1, 3, 2}); 1210 } 1211 // there is no closing face in this case 1212 return seg; 1213 } 1214 1215 /** 1216 * Creates and returns a segment for the case where the line is completely 1217 * below the axis range at the left side. 1218 * 1219 * @param x0 1220 * @param x1 1221 * @param xpts 1222 * @param y0b 1223 * @param y0t 1224 * @param y1b 1225 * @param y1t 1226 * @param wmin 1227 * @param wmax 1228 * @param zf 1229 * @param zb 1230 * @param color 1231 * @param clipColor 1232 * @param openingFace ignored because there is no opening face in this 1233 * case. 1234 * @param closingFace 1235 * 1236 * @return A segment. 1237 */ 1238 private Object3D createSegmentF(double x0, double x1, double[] xpts, 1239 double y0b, double y0t, double y1b, double y1t, double wmin, 1240 double wmax, double zf, double zb, Color color, Color clipColor, 1241 boolean openingFace, boolean closingFace) { 1242 1243 if (y1b > wmax) { 1244 Object3D seg = new Object3D(color, true); 1245 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1246 seg.addVertex(xpts[1], wmin, zf); 1247 seg.addVertex(xpts[1], wmin, zb); 1248 seg.addVertex(xpts[3], wmax, zf); 1249 seg.addVertex(xpts[3], wmax, zb); 1250 seg.addVertex(xpts[2], wmax, zf); 1251 seg.addVertex(xpts[2], wmax, zb); 1252 seg.addVertex(xpts[0], wmin, zf); 1253 seg.addVertex(xpts[0], wmin, zb); 1254 seg.addFace(new int[] {0, 2, 4, 6}); // front 1255 seg.addFace(new int[] {1, 7, 5, 3}); // rear 1256 seg.addFace(new int[] {0, 1, 3, 2}); // top 1257 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 1258 seg.addFace(new int[] {4, 5, 7, 6}); // bottom 1259 seg.addFace(new int[] {0, 6, 7, 1}, "clip"); // clip bottom 1260 // there are no opening and closing faces for this case 1261 return seg; 1262 } 1263 if (y1t > wmax) { 1264 if (y1b > wmin) { 1265 Object3D seg = new Object3D(color, true); 1266 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1267 seg.addVertex(xpts[1], wmin, zf); 1268 seg.addVertex(xpts[1], wmin, zb); 1269 seg.addVertex(xpts[3], wmax, zf); 1270 seg.addVertex(xpts[3], wmax, zb); 1271 seg.addVertex(x1, wmax, zf); 1272 seg.addVertex(x1, wmax, zb); 1273 seg.addVertex(x1, y1b, zf); 1274 seg.addVertex(x1, y1b, zb); 1275 seg.addVertex(xpts[0], wmin, zf); 1276 seg.addVertex(xpts[0], wmin, zb); 1277 seg.addFace(new int[] {0, 2, 4, 6, 8}); // front 1278 seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear 1279 seg.addFace(new int[] {2, 3, 5, 4}); //clip top 1280 seg.addFace(new int[] {0, 1, 3, 2}); // top 1281 seg.addFace(new int[] {0, 8, 9, 1}, "clip"); // clip bottom 1282 seg.addFace(new int[] {6, 7, 9, 8}); // bottom 1283 // there is no opening face in this case 1284 if (closingFace) { 1285 seg.addFace(new int[] {4, 5, 7, 6}); 1286 } 1287 return seg; 1288 } else { 1289 Object3D seg = new Object3D(color, true); 1290 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1291 seg.addVertex(xpts[1], wmin, zf); 1292 seg.addVertex(xpts[1], wmin, zb); 1293 seg.addVertex(xpts[3], wmax, zf); 1294 seg.addVertex(xpts[3], wmax, zb); 1295 seg.addVertex(x1, wmax, zf); 1296 seg.addVertex(x1, wmax, zb); 1297 seg.addVertex(x1, wmin, zf); 1298 seg.addVertex(x1, wmin, zb); 1299 seg.addFace(new int[] {0, 2, 4, 6}); // front 1300 seg.addFace(new int[] {1, 7, 5, 3}); // rear 1301 seg.addFace(new int[] {0, 1, 3, 2}); // top 1302 seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top 1303 seg.addFace(new int[] {6, 7, 1, 0}, "clip"); // clip bottom 1304 if (closingFace) { 1305 seg.addFace(new int[] {4, 5, 7, 6}); 1306 } 1307 return seg; 1308 } 1309 } 1310 if (y1t > wmin) { 1311 if (y1b >= wmin) { 1312 Object3D seg = new Object3D(color, true); 1313 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1314 seg.addVertex(xpts[1], wmin, zf); 1315 seg.addVertex(xpts[1], wmin, zb); 1316 seg.addVertex(x1, y1t, zf); 1317 seg.addVertex(x1, y1t, zb); 1318 seg.addVertex(x1, y1b, zf); 1319 seg.addVertex(x1, y1b, zb); 1320 seg.addVertex(xpts[0], wmin, zf); 1321 seg.addVertex(xpts[0], wmin, zb); 1322 seg.addFace(new int[] {0, 2, 4, 6}); // front 1323 seg.addFace(new int[] {1, 7, 5, 3}); // rear 1324 seg.addFace(new int[] {0, 1, 3, 2}); // top 1325 seg.addFace(new int[] {4, 5, 7, 6}); // bottom 1326 seg.addFace(new int[] {0, 6, 7, 1}, "clip"); // clip bottom 1327 if (closingFace) { 1328 seg.addFace(new int[] {2, 3, 5, 4}); 1329 } 1330 return seg; 1331 } else { 1332 Object3D seg = new Object3D(color, true); 1333 seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); 1334 seg.addVertex(xpts[1], wmin, zf); 1335 seg.addVertex(xpts[1], wmin, zb); 1336 seg.addVertex(x1, y1t, zf); 1337 seg.addVertex(x1, y1t, zb); 1338 seg.addVertex(x1, wmin, zf); 1339 seg.addVertex(x1, wmin, zb); 1340 seg.addFace(new int[] {0, 2, 4}); // front 1341 seg.addFace(new int[] {1, 5, 3}); // rear 1342 seg.addFace(new int[] {0, 1, 3, 2}); // top 1343 seg.addFace(new int[] {0, 4, 5, 1}, "clip"); // clip bottom 1344 // there is no opening face in this case 1345 if (closingFace) { 1346 seg.addFace(new int[] {2, 3, 5, 4}); 1347 } 1348 return seg; 1349 } 1350 } 1351 return null; // nothing to see 1352 } 1353 1354 /** 1355 * Tests this renderer for equality with an arbitrary object. 1356 * 1357 * @param obj the object ({@code null} not permitted). 1358 * 1359 * @return A boolean. 1360 */ 1361 @Override 1362 public boolean equals(Object obj) { 1363 if (obj == this) { 1364 return true; 1365 } 1366 if (!(obj instanceof LineRenderer3D)) { 1367 return false; 1368 } 1369 LineRenderer3D that = (LineRenderer3D) obj; 1370 if (this.lineWidth != that.lineWidth) { 1371 return false; 1372 } 1373 if (this.lineHeight != that.lineHeight) { 1374 return false; 1375 } 1376 if (this.isolatedItemWidthPercent != that.isolatedItemWidthPercent) { 1377 return false; 1378 } 1379 if (!ObjectUtils.equals(this.clipColorSource, that.clipColorSource)) { 1380 return false; 1381 } 1382 return super.equals(obj); 1383 } 1384} 1385