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