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; 034 035import java.awt.Color; 036import java.awt.geom.Point2D; 037import java.util.ArrayList; 038import java.util.Collections; 039import java.util.List; 040 041import com.orsoncharts.graphics3d.Face; 042import com.orsoncharts.graphics3d.Object3D; 043import com.orsoncharts.graphics3d.Point3D; 044import com.orsoncharts.axis.TickData; 045import com.orsoncharts.marker.MarkerData; 046import com.orsoncharts.marker.MarkerDataType; 047import com.orsoncharts.util.Anchor2D; 048import com.orsoncharts.util.ArgChecks; 049 050/** 051 * A chart box is the container within which the chart elements are drawn. 052 * The six faces of the box created so that they are visible only when they 053 * do not obscure the content of the chart (generally the back three faces 054 * will be visible and the front three faces will not be visible, although from 055 * some angles four faces will be visible at one time). There is also support 056 * provided for specifying gridlines on the visible faces, as well as markers 057 * to label specific values and value ranges. 058 */ 059public class ChartBox3D { 060 061 private double xLength, yLength, zLength; 062 private double xOffset, yOffset, zOffset; 063 064 /** 065 * Tick info for the x-axis (or column axis). 066 */ 067 private List<TickData> xTicks; 068 069 /** 070 * Tick info for the y-axis (or value axis). 071 */ 072 private List<TickData> yTicks; 073 074 /** 075 * Tick info for the z-axis (or row axis). 076 */ 077 private List<TickData> zTicks; 078 079 /** 080 * Incoming data for x-axis markers. New instances are created and 081 * assigned to chart box faces to track vertices. 082 */ 083 private List<MarkerData> xMarkers; 084 085 /** Required data for y-axis markers. */ 086 private List<MarkerData> yMarkers; 087 088 /** Required data for z-axis markers. */ 089 private List<MarkerData> zMarkers; 090 091 private Color color; 092 093 private ChartBoxFace faceA; 094 private ChartBoxFace faceB; 095 private ChartBoxFace faceC; 096 private ChartBoxFace faceD; 097 private ChartBoxFace faceE; 098 private ChartBoxFace faceF; 099 100 /** 101 * Creates a new chart box with the specified attributes. When drawn, only 102 * the faces at the rear of the box will be rendered, allowing the user to 103 * see the content of the box (typically the plot items). 104 * 105 * @param xLength the x-dimension. 106 * @param yLength the y-dimension. 107 * @param zLength the z-dimension. 108 * @param xOffset the x-offset. 109 * @param yOffset the y-offset. 110 * @param zOffset the z-offset. 111 * @param color the color for the sides of the box ({@code null} not 112 * permitted). 113 */ 114 public ChartBox3D(double xLength, double yLength, double zLength, 115 double xOffset, double yOffset, double zOffset, Color color) { 116 ArgChecks.nullNotPermitted(color, "color"); 117 this.xLength = xLength; 118 this.yLength = yLength; 119 this.zLength = zLength; 120 this.xOffset = xOffset; 121 this.yOffset = yOffset; 122 this.zOffset = zOffset; 123 this.color = color; 124 this.xTicks = new ArrayList<TickData>(0); 125 this.yTicks = new ArrayList<TickData>(0); 126 this.zTicks = new ArrayList<TickData>(0); 127 this.xMarkers = new ArrayList<MarkerData>(0); 128 this.yMarkers = new ArrayList<MarkerData>(0); 129 this.zMarkers = new ArrayList<MarkerData>(0); 130 } 131 132 /** 133 * Returns the list of tick data items for the x-axis. 134 * 135 * @return The list of tick data items for the x-axis (possibly empty, but 136 * never {@code null}). 137 * 138 * @since 1.2 139 */ 140 public List<TickData> getXTicks() { 141 return this.xTicks; 142 } 143 144 /** 145 * Sets the list of tick data items for the x-axis. 146 * 147 * @param ticks the tick data ({@code null} not permitted). 148 * 149 * @since 1.2 150 */ 151 public void setXTicks(List<TickData> ticks) { 152 ArgChecks.nullNotPermitted(ticks, "ticks"); 153 this.xTicks = ticks; 154 } 155 156 /** 157 * Returns the list of tick data items for the y-axis. 158 * 159 * @return The list of tick data items for the y-axis (possibly empty, but 160 * never {@code null}). 161 * 162 * @since 1.2 163 */ 164 public List<TickData> getYTicks() { 165 return this.yTicks; 166 } 167 168 /** 169 * Sets the list of tick data items for the y-axis. 170 * 171 * @param ticks the tick data ({@code null} not permitted). 172 * 173 * @since 1.2 174 */ 175 public void setYTicks(List<TickData> ticks) { 176 ArgChecks.nullNotPermitted(ticks, "ticks"); 177 this.yTicks = ticks; 178 } 179 180 /** 181 * Returns the list of tick data items for the z-axis. 182 * 183 * @return The list of tick data items for the z-axis (possibly empty, but 184 * never {@code null}). 185 * 186 * @since 1.2 187 */ 188 public List<TickData> getZTicks() { 189 return this.zTicks; 190 } 191 192 /** 193 * Sets the list of tick data items for the z-axis. 194 * 195 * @param ticks the tick data ({@code null} not permitted). 196 * 197 * @since 1.2 198 */ 199 public void setZTicks(List<TickData> ticks) { 200 ArgChecks.nullNotPermitted(ticks, "ticks"); 201 this.zTicks = ticks; 202 } 203 204 /** 205 * Returns the marker data for the x-axis markers, if any. 206 * 207 * @return The marker data for the x-axis markers (possibly empty but 208 * never {@code null}). 209 * 210 * @since 1.2 211 */ 212 public List<MarkerData> getXMarkers() { 213 return this.xMarkers; 214 } 215 216 /** 217 * Sets the list of marker data items for the x-axis. 218 * 219 * @param markers the list of marker data items ({@code null} not 220 * permitted). 221 * 222 * @since 1.2 223 */ 224 public void setXMarkers(List<MarkerData> markers) { 225 ArgChecks.nullNotPermitted(markers, "markers"); 226 this.xMarkers = markers; 227 } 228 229 /** 230 * Returns the marker data for the y-axis markers, if any. 231 * 232 * @return The marker data for the y-axis markers (possibly empty but 233 * never {@code null}). 234 * 235 * @since 1.2 236 */ 237 public List<MarkerData> getYMarkers() { 238 return this.yMarkers; 239 } 240 241 /** 242 * Sets the list of marker data items for the y-axis. 243 * 244 * @param markers the list of marker data items ({@code null} not 245 * permitted). 246 * 247 * @since 1.2 248 */ 249 public void setYMarkers(List<MarkerData> markers) { 250 ArgChecks.nullNotPermitted(markers, "markers"); 251 this.yMarkers = markers; 252 } 253 254 /** 255 * Returns the marker data for the z-axis markers, if any. 256 * 257 * @return The marker data for the z-axis markers (possibly empty but 258 * never {@code null}). 259 * 260 * @since 1.2 261 */ 262 public List<MarkerData> getZMarkers() { 263 return this.zMarkers; 264 } 265 266 /** 267 * Sets the list of marker data items for the x-axis. 268 * 269 * @param markers the list of marker data items ({@code null} not 270 * permitted). 271 * 272 * @since 1.2 273 */ 274 public void setZMarkers(List<MarkerData> markers) { 275 ArgChecks.nullNotPermitted(markers, "markers"); 276 this.zMarkers = markers; 277 } 278 279 /** 280 * Returns face A (the bottom face, in the XZ axis plane). 281 * 282 * @return Face A. 283 */ 284 public ChartBoxFace faceA() { 285 return this.faceA; 286 } 287 288 /** 289 * Returns face B (the front face, in the XY axis plane). 290 * 291 * @return Face B. 292 */ 293 public ChartBoxFace faceB() { 294 return this.faceB; 295 } 296 297 /** 298 * Returns face C (the top face, in the XZ axis plane). 299 * 300 * @return Face C. 301 */ 302 public ChartBoxFace faceC() { 303 return this.faceC; 304 } 305 306 /** 307 * Returns face D (the rear face, in the XY axis plane). 308 * 309 * @return Face D. 310 */ 311 public ChartBoxFace faceD() { 312 return this.faceD; 313 } 314 315 /** 316 * Returns face E (the left face, in the YZ axis plane). 317 * 318 * @return Face E. 319 */ 320 public ChartBoxFace faceE() { 321 return this.faceE; 322 } 323 324 /** 325 * Returns face F (the right face, in the YZ axis plane). 326 * 327 * @return Face F. 328 */ 329 public ChartBoxFace faceF() { 330 return this.faceF; 331 } 332 333 /** 334 * Creates an {@link Object3D} that contains the six faces for the 335 * chart box, plus the vertices for the tick marks along the edges of 336 * each face. 337 * 338 * @return A 3D object. 339 */ 340 public Object3D createObject3D() { 341 Object3D box = new Object3D(this.color); 342 Point3D v0 = new Point3D(xOffset, yOffset, zOffset); 343 Point3D v1 = new Point3D(xLength + xOffset, yOffset, zOffset); 344 Point3D v2 = new Point3D(xLength + xOffset, yLength + yOffset, zOffset); 345 Point3D v3 = new Point3D(xOffset, yLength + yOffset, zOffset); 346 Point3D v4 = new Point3D(xOffset, yLength + yOffset, zLength + zOffset); 347 Point3D v5 = new Point3D(xOffset, yOffset, zLength + zOffset); 348 Point3D v6 = new Point3D(xLength + xOffset, yOffset, zLength + zOffset); 349 Point3D v7 = new Point3D(xLength + xOffset, yLength + yOffset, zLength 350 + zOffset); 351 352 box.addVertex(v0); // 0, 0, 0 353 box.addVertex(v1); // 1, 0, 0 354 box.addVertex(v2); // 1, 1, 0 355 box.addVertex(v3); // 0, 1, 0 356 357 box.addVertex(v4); // 0, 1, 1 358 box.addVertex(v5); // 0, 0, 1 359 box.addVertex(v6); // 1, 0, 1 360 box.addVertex(v7); // 1, 1, 1 361 362 this.faceA = new ChartBoxFace(box, new int[] {0, 5, 6, 1}); // XZ 363 this.faceB = new ChartBoxFace(box, new int[] {0, 1, 2, 3}); // XY 364 this.faceC = new ChartBoxFace(box, new int[] {7, 4, 3, 2}); // XZ 365 this.faceD = new ChartBoxFace(box, new int[] {5, 4, 7, 6}); // XY 366 this.faceE = new ChartBoxFace(box, new int[] {0, 3, 4, 5}); // YZ 367 this.faceF = new ChartBoxFace(box, new int[] {6, 7, 2, 1}); // YZ 368 box.addFace(faceA); 369 box.addFace(faceB); 370 box.addFace(faceC); 371 box.addFace(faceD); 372 box.addFace(faceE); 373 box.addFace(faceF); 374 375 // add vertices for the x-grid lines (ABCD) 376 int base = 8; 377 for (TickData t : this.xTicks) { 378 double xx = this.xOffset + this.xLength * t.getPos(); 379 box.addVertex(xx, yOffset, zOffset); 380 box.addVertex(xx, yOffset, zOffset + zLength); 381 box.addVertex(xx, yOffset + yLength, zOffset + zLength); 382 box.addVertex(xx, yOffset + yLength, zOffset); 383 TickData td0 = new TickData(t, base); 384 TickData td1 = new TickData(t, base + 1); 385 TickData td2 = new TickData(t, base + 2); 386 TickData td3 = new TickData(t, base + 3); 387 this.faceA.addXTicks(td0, td1); 388 this.faceB.addXTicks(td0, td3); 389 this.faceC.addXTicks(td3, td2); 390 this.faceD.addXTicks(td2, td1); 391 base += 4; 392 } 393 394 // add vertices for the y-grid lines (BDEF) 395 for (TickData t : this.yTicks) { 396 double yy = this.yOffset + this.yLength * t.getPos(); 397 box.addVertex(xOffset, yy, zOffset); 398 box.addVertex(xOffset + xLength, yy, zOffset); 399 box.addVertex(xOffset + xLength, yy, zOffset + zLength); 400 box.addVertex(xOffset, yy, zOffset + zLength); 401 TickData td0 = new TickData(t, base); 402 TickData td1 = new TickData(t, base + 1); 403 TickData td2 = new TickData(t, base + 2); 404 TickData td3 = new TickData(t, base + 3); 405 this.faceB.addYTicks(td0, td1); 406 this.faceD.addYTicks(td2, td3); 407 this.faceE.addYTicks(td0, td3); 408 this.faceF.addYTicks(td1, td2); 409 base += 4; 410 } 411 412 // add vertices for the z-grid lines (ACEF) 413 for (TickData t : this.zTicks) { 414 double zz = this.zOffset + this.zLength * t.getPos(); 415 box.addVertex(xOffset, yOffset, zz); 416 box.addVertex(xOffset + xLength, yOffset, zz); 417 box.addVertex(xOffset + xLength, yOffset + yLength, zz); 418 box.addVertex(xOffset, yOffset + yLength, zz); 419 TickData td0 = new TickData(t, base); 420 TickData td1 = new TickData(t, base + 1); 421 TickData td2 = new TickData(t, base + 2); 422 TickData td3 = new TickData(t, base + 3); 423 this.faceA.addZTicks(td0, td1); 424 this.faceC.addZTicks(td3, td2); 425 this.faceE.addZTicks(td0, td3); 426 this.faceF.addZTicks(td1, td2); 427 base += 4; 428 } 429 430 // add vertices for the x-markers 431 for (MarkerData m : this.xMarkers) { 432 if (m.getType().equals(MarkerDataType.VALUE)) { 433 double xpos = this.xOffset + xLength 434 * m.getValueLine().getPos(); 435 base += addXMarker(box, m, xpos, base); 436 } else if (m.getType().equals(MarkerDataType.RANGE)) { 437 double startX = this.xOffset + xLength 438 * m.getStartLine().getPos(); 439 double endX = this.xOffset + xLength * m.getEndLine().getPos(); 440 base += addXRangeMarker(box, m, startX, endX, base); 441 } 442 } 443 444 // add vertices for the y-markers 445 for (MarkerData m : this.yMarkers) { 446 if (m.getType().equals(MarkerDataType.VALUE)) { 447 double ypos = this.yOffset + yLength 448 * m.getValueLine().getPos(); 449 base += addYMarker(box, m, ypos, base); 450 } else if (m.getType().equals(MarkerDataType.RANGE)) { 451 double startY = this.yOffset + yLength 452 * m.getStartLine().getPos(); 453 double endY = this.yOffset + yLength * m.getEndLine().getPos(); 454 base += addYRangeMarker(box, m, startY, endY, base); 455 } 456 } 457 458 // add vertices for the z-markers 459 for (MarkerData m : this.zMarkers) { 460 if (m.getType().equals(MarkerDataType.VALUE)) { 461 double zpos = this.zOffset + zLength 462 * m.getValueLine().getPos(); 463 base += addZMarker(box, m, zpos, base); 464 } else if (m.getType().equals(MarkerDataType.RANGE)) { 465 double startZ = this.zOffset + zLength 466 * m.getStartLine().getPos(); 467 double endZ = this.zOffset + zLength * m.getEndLine().getPos(); 468 base += addZRangeMarker(box, m, startZ, endZ, base); 469 } 470 } 471 472 return box; 473 } 474 475 /** 476 * Adds the vertices required for an x-marker (VALUE type), creates the 477 * marker data records and adds them to the four faces that will be 478 * required to draw the marker. If there is a label for the marker, this 479 * method will also add vertices to track the label anchor points. 480 * 481 * @param box the chart box. 482 * @param m the marker data record. 483 * @param x the x position for the marker. 484 * @param base the base vertex index. 485 */ 486 private int addXMarker(Object3D box, MarkerData m, double x, int base) { 487 int result = 4; 488 Point3D v0 = new Point3D(x, yOffset, zOffset); 489 Point3D v1 = new Point3D(x, yOffset, zOffset + zLength); 490 Point3D v2 = new Point3D(x, yOffset + yLength, zOffset + zLength); 491 Point3D v3 = new Point3D(x, yOffset + yLength, zOffset); 492 box.addVertex(v0); 493 box.addVertex(v1); 494 box.addVertex(v2); 495 box.addVertex(v3); 496 MarkerData md0 = new MarkerData(m, base, base + 1); // A 497 MarkerData md1 = new MarkerData(m, base + 1, base + 2); // D 498 MarkerData md2 = new MarkerData(m, base + 2, base + 3); // C 499 MarkerData md3 = new MarkerData(m, base + 3, base); // B 500 if (m.getLabelAnchor() != null) { 501 // add vertices for the label anchor 502 Point3D v4 = calcAnchorXY(m.getLabelAnchor(), v0, v3, xLength); // B 503 Point3D v5 = calcAnchorXY(m.getLabelAnchor(), v2, v1, xLength); // D 504 Point3D v6 = calcAnchorXZ(m.getLabelAnchor(), v1, v0, xLength); // A 505 Point3D v7 = calcAnchorXZ(m.getLabelAnchor(), v3, v2, xLength); 506 box.addVertex(v4); 507 box.addVertex(v5); 508 box.addVertex(v6); 509 box.addVertex(v7); 510 // now write back the label vertex indices to the marker data 511 md3.setLabelVertexIndex(base + 4); 512 md1.setLabelVertexIndex(base + 5); 513 md0.setLabelVertexIndex(base + 6); 514 md2.setLabelVertexIndex(base + 7); 515 result += 4; 516 } 517 this.faceA.addXMarker(md0); 518 this.faceD.addXMarker(md1); 519 this.faceC.addXMarker(md2); 520 this.faceB.addXMarker(md3); 521 return result; 522 } 523 524 /** 525 * Adds the vertices required for an x-marker (RANGE type), creates the 526 * marker data records and adds them to the four faces that will be 527 * required to draw the marker. If there is a label for the marker, this 528 * method will also add vertices to track the label anchor points. 529 * 530 * @param box the chart box. 531 * @param m the marker data record. 532 * @param startX the starting x position for the marker. 533 * @param endX the ending x position for the marker. 534 * @param base the base vertex index. 535 */ 536 private int addXRangeMarker(Object3D box, MarkerData m, double startX, 537 double endX, int base) { 538 int result = 8; // number of vertices added 539 Point3D v0 = new Point3D(startX, yOffset, zOffset); 540 Point3D v1 = new Point3D(startX, yOffset, zOffset + zLength); 541 Point3D v2 = new Point3D(startX, yOffset + yLength, zOffset + zLength); 542 Point3D v3 = new Point3D(startX, yOffset + yLength, zOffset); 543 Point3D v4 = new Point3D(endX, yOffset, zOffset); 544 Point3D v5 = new Point3D(endX, yOffset, zOffset + zLength); 545 Point3D v6 = new Point3D(endX, yOffset + yLength, zOffset + zLength); 546 Point3D v7 = new Point3D(endX, yOffset + yLength, zOffset); 547 box.addVertex(v0); 548 box.addVertex(v1); 549 box.addVertex(v2); 550 box.addVertex(v3); 551 box.addVertex(v4); 552 box.addVertex(v5); 553 box.addVertex(v6); 554 box.addVertex(v7); 555 MarkerData md0 = new MarkerData(m, base, base + 1, base + 4, 556 base + 5); // A 557 MarkerData md1 = new MarkerData(m, base + 1, base + 2, base + 5, 558 base + 6); // D 559 MarkerData md2 = new MarkerData(m, base + 2, base + 3, base + 6, 560 base + 7); // C 561 MarkerData md3 = new MarkerData(m, base + 3, base + 0, base + 7, 562 base + 4); // B 563 if (m.getLabelAnchor() != null) { 564 // add vertices for the label anchor 565 Point3D v8 = calcRangeAnchorXY(m.getLabelAnchor(), v2, v1, v6, v5); 566 Point3D v9 = calcRangeAnchorXY(m.getLabelAnchor(), v0, v3, v4, v7); 567 Point3D v10 = calcRangeAnchorXZ(m.getLabelAnchor(), v3, v2, v7, v6); 568 Point3D v11 = calcRangeAnchorXZ(m.getLabelAnchor(), v1, v0, v5, v4); 569 box.addVertex(v8); 570 box.addVertex(v9); 571 box.addVertex(v10); 572 box.addVertex(v11); 573 // now write back the label vertex indices to the marker data 574 md1.setLabelVertexIndex(base + 8); 575 md3.setLabelVertexIndex(base + 9); 576 md2.setLabelVertexIndex(base + 10); 577 md0.setLabelVertexIndex(base + 11); 578 result += 4; 579 } 580 this.faceA.addXMarker(md0); 581 this.faceD.addXMarker(md1); 582 this.faceC.addXMarker(md2); 583 this.faceB.addXMarker(md3); 584 return result; 585 } 586 587 /** 588 * Adds the vertices required for an y-marker (VALUE type), creates the 589 * marker data records and adds them to the four faces that will be 590 * required to draw the marker. If there is a label for the marker, this 591 * method will also add vertices to track the label anchor points. 592 * 593 * @param box the chart box. 594 * @param m the marker data record. 595 * @param y the y position for the marker. 596 * @param base the base vertex index. 597 */ 598 private int addYMarker(Object3D box, MarkerData m, double y, int base) { 599 int result = 4; // number of vertices added 600 Point3D v0 = new Point3D(xOffset, y, zOffset); 601 Point3D v1 = new Point3D(xOffset, y, zOffset + zLength); 602 Point3D v2 = new Point3D(xOffset + xLength, y, zOffset + zLength); 603 Point3D v3 = new Point3D(xOffset + xLength, y, zOffset); 604 box.addVertex(v0); 605 box.addVertex(v1); 606 box.addVertex(v2); 607 box.addVertex(v3); 608 MarkerData md0 = new MarkerData(m, base, base + 1); // E 609 MarkerData md1 = new MarkerData(m, base + 1, base + 2); // D 610 MarkerData md2 = new MarkerData(m, base + 2, base + 3); // F 611 MarkerData md3 = new MarkerData(m, base + 3, base); // B 612 if (m.getLabelAnchor() != null) { 613 // add vertices for the label anchor 614 Point3D v4 = calcAnchorYX(m.getLabelAnchor(), v1, v2, yLength); // D 615 Point3D v5 = calcAnchorYX(m.getLabelAnchor(), v3, v0, yLength); // B 616 Point3D v6 = calcAnchorYZ(m.getLabelAnchor(), v0, v1, yLength); // E 617 Point3D v7 = calcAnchorYZ(m.getLabelAnchor(), v2, v3, yLength); // F 618 box.addVertex(v4); 619 box.addVertex(v5); 620 box.addVertex(v6); 621 box.addVertex(v7); 622 // now write back the label vertex indices to the marker data 623 md1.setLabelVertexIndex(base + 4); 624 md3.setLabelVertexIndex(base + 5); 625 md0.setLabelVertexIndex(base + 6); 626 md2.setLabelVertexIndex(base + 7); 627 result += 4; 628 } 629 this.faceE.addYMarker(md0); 630 this.faceD.addYMarker(md1); 631 this.faceF.addYMarker(md2); 632 this.faceB.addYMarker(md3); 633 return result; 634 } 635 636 /** 637 * Adds the vertices required for an y-marker (RANGE type), creates the 638 * marker data records and adds them to the four faces that will be 639 * required to draw the marker. If there is a label for the marker, this 640 * method will also add vertices to track the label anchor points. 641 * 642 * @param box the chart box. 643 * @param m the marker data record. 644 * @param startY the starting y position for the marker. 645 * @param endY the ending y position for the marker. 646 * @param base the base vertex index. 647 */ 648 private int addYRangeMarker(Object3D box, MarkerData m, double startY, 649 double endY, int base) { 650 int result = 8; // number of vertices added 651 Point3D v0 = new Point3D(xOffset, startY, zOffset); 652 Point3D v1 = new Point3D(xOffset, startY, zOffset + zLength); 653 Point3D v2 = new Point3D(xOffset + xLength, startY, zOffset + zLength); 654 Point3D v3 = new Point3D(xOffset + xLength, startY, zOffset); 655 Point3D v4 = new Point3D(xOffset, endY, zOffset); 656 Point3D v5 = new Point3D(xOffset, endY, zOffset + zLength); 657 Point3D v6 = new Point3D(xOffset + xLength, endY, zOffset + zLength); 658 Point3D v7 = new Point3D(xOffset + xLength, endY, zOffset); 659 box.addVertex(v0); 660 box.addVertex(v1); 661 box.addVertex(v2); 662 box.addVertex(v3); 663 box.addVertex(v4); 664 box.addVertex(v5); 665 box.addVertex(v6); 666 box.addVertex(v7); 667 MarkerData md0 = new MarkerData(m, base, base + 1, base + 4, 668 base + 5); // E 669 MarkerData md1 = new MarkerData(m, base + 1, base + 2, base + 5, 670 base + 6); // D 671 MarkerData md2 = new MarkerData(m, base + 2, base + 3, base + 6, 672 base + 7); // F 673 MarkerData md3 = new MarkerData(m, base + 3, base, base + 7, 674 base + 4); // B 675 if (m.getLabelAnchor() != null) { 676 // add vertices for the label anchor 677 Point3D v8 = calcRangeAnchorYX(m.getLabelAnchor(), v1, v2, v5, v6); 678 Point3D v9 = calcRangeAnchorYX(m.getLabelAnchor(), v3, v0, v7, v4); 679 Point3D v10 = calcRangeAnchorYZ(m.getLabelAnchor(), v2, v3, v6, v7); 680 Point3D v11 = calcRangeAnchorYZ(m.getLabelAnchor(), v0, v1, v4, v5); 681 box.addVertex(v8); 682 box.addVertex(v9); 683 box.addVertex(v10); 684 box.addVertex(v11); 685 // now write back the label vertex indices to the marker data 686 md1.setLabelVertexIndex(base + 8); 687 md3.setLabelVertexIndex(base + 9); 688 md2.setLabelVertexIndex(base + 10); 689 md0.setLabelVertexIndex(base + 11); 690 result += 4; 691 } 692 this.faceE.addYMarker(md0); 693 this.faceD.addYMarker(md1); 694 this.faceF.addYMarker(md2); 695 this.faceB.addYMarker(md3); 696 return result; 697 } 698 699 /** 700 * Adds the vertices required for an z-marker (VALUE type), creates the 701 * marker data records and adds them to the four faces that will be 702 * required to draw the marker. If there is a label for the marker, this 703 * method will also add vertices to track the label anchor points. 704 * 705 * @param box the chart box. 706 * @param m the marker data record. 707 * @param z the z position for the marker. 708 * @param base the base vertex index. 709 */ 710 private int addZMarker(Object3D box, MarkerData m, double z, int base) { 711 int result = 4; // number of vertices added 712 Point3D v0 = new Point3D(xOffset, yOffset, z); 713 Point3D v1 = new Point3D(xOffset + xLength, yOffset, z); 714 Point3D v2 = new Point3D(xOffset + xLength, yOffset + yLength, z); 715 Point3D v3 = new Point3D(xOffset, yOffset + yLength, z); 716 box.addVertex(v0); // A 717 box.addVertex(v1); 718 box.addVertex(v2); 719 box.addVertex(v3); 720 MarkerData md0 = new MarkerData(m, base, base + 1); // A 721 MarkerData md1 = new MarkerData(m, base + 1, base + 2); // F 722 MarkerData md2 = new MarkerData(m, base + 2, base + 3); // C 723 MarkerData md3 = new MarkerData(m, base + 3, base); // E 724 if (m.getLabelAnchor() != null) { 725 // add vertices for the label anchor 726 Point3D v4 = calcAnchorZX(m.getLabelAnchor(), v0, v1, zLength); // A 727 Point3D v5 = calcAnchorZX(m.getLabelAnchor(), v2, v3, zLength); // C 728 Point3D v6 = calcAnchorZY(m.getLabelAnchor(), v1, v2, zLength); // F 729 Point3D v7 = calcAnchorZY(m.getLabelAnchor(), v3, v0, zLength); // E 730 box.addVertex(v4); 731 box.addVertex(v5); 732 box.addVertex(v6); 733 box.addVertex(v7); 734 // now write back the label vertex indices to the marker data 735 md0.setLabelVertexIndex(base + 4); 736 md2.setLabelVertexIndex(base + 5); 737 md1.setLabelVertexIndex(base + 6); 738 md3.setLabelVertexIndex(base + 7); 739 result += 4; 740 } 741 this.faceA.addZMarker(md0); 742 this.faceF.addZMarker(md1); 743 this.faceC.addZMarker(md2); 744 this.faceE.addZMarker(md3); 745 return result; 746 } 747 748 /** 749 * Adds the vertices required for an x-marker (RANGE type), creates the 750 * marker data records and adds them to the four faces that will be 751 * required to draw the marker. If there is a label for the marker, this 752 * method will also add vertices to track the label anchor points. 753 * 754 * @param box the chart box. 755 * @param m the marker data record. 756 * @param startZ the starting z position for the marker. 757 * @param endZ the ending z position for the marker. 758 * @param base the base vertex index. 759 */ 760 private int addZRangeMarker(Object3D box, MarkerData m, double startZ, 761 double endZ, int base) { 762 int result = 8; 763 Point3D v0 = new Point3D(xOffset, yOffset, startZ); 764 Point3D v1 = new Point3D(xOffset + xLength, yOffset, startZ); 765 Point3D v2 = new Point3D(xOffset + xLength, yOffset + yLength, startZ); 766 Point3D v3 = new Point3D(xOffset, yOffset + yLength, startZ); 767 Point3D v4 = new Point3D(xOffset, yOffset, endZ); 768 Point3D v5 = new Point3D(xOffset + xLength, yOffset, endZ); 769 Point3D v6 = new Point3D(xOffset + xLength, yOffset + yLength, endZ); 770 Point3D v7 = new Point3D(xOffset, yOffset + yLength, endZ); 771 box.addVertex(v0); // A 772 box.addVertex(v1); 773 box.addVertex(v2); 774 box.addVertex(v3); 775 box.addVertex(v4); // A 776 box.addVertex(v5); 777 box.addVertex(v6); 778 box.addVertex(v7); 779 MarkerData md0 = new MarkerData(m, base, base + 1, base + 4, 780 base + 5); // A 781 MarkerData md1 = new MarkerData(m, base + 1, base + 2, base + 5, 782 base + 6); // F 783 MarkerData md2 = new MarkerData(m, base + 2, base + 3, base + 6, 784 base + 7); // C 785 MarkerData md3 = new MarkerData(m, base + 3, base, base + 7, 786 base + 4); // E 787 if (m.getLabelAnchor() != null) { 788 // add vertices for the label anchor 789 Point3D v8 = calcRangeAnchorZX(m.getLabelAnchor(), v0, v1, v4, v5); 790 Point3D v9 = calcRangeAnchorZX(m.getLabelAnchor(), v2, v3, v6, v7); 791 Point3D v10 = calcRangeAnchorZY(m.getLabelAnchor(), v3, v0, v7, v4); 792 Point3D v11 = calcRangeAnchorZY(m.getLabelAnchor(), v1, v2, v5, v6); 793 box.addVertex(v8); 794 box.addVertex(v9); 795 box.addVertex(v10); 796 box.addVertex(v11); 797 // now write back the label vertex indices to the marker data 798 md0.setLabelVertexIndex(base + 8); 799 md2.setLabelVertexIndex(base + 9); 800 md3.setLabelVertexIndex(base + 10); 801 md1.setLabelVertexIndex(base + 11); 802 result += 4; 803 } 804 this.faceA.addZMarker(md0); 805 this.faceF.addZMarker(md1); 806 this.faceC.addZMarker(md2); 807 this.faceE.addZMarker(md3); 808 return result; 809 } 810 811 /** 812 * Returns the horizontal offset for an anchor assuming that (a) the delta 813 * is expressed as a percentage, and (b) the length in the offset direction 814 * is {@code length}. 815 * 816 * @param anchor the anchor. 817 * @param length the length. 818 * 819 * @return The offset. 820 */ 821 private double hoffset(Anchor2D anchor, double length) { 822 double offset = 0.0; 823 if (anchor.getRefPt().isLeft()) { 824 offset = length * anchor.getOffset().getDX(); 825 } else if (anchor.getRefPt().isRight()) { 826 offset = -length * anchor.getOffset().getDX(); 827 } 828 return offset; 829 } 830 831 /** 832 * Returns the vertical offset for an anchor assuming that (a) the delta is 833 * expressed as a percentage, and (b) the length in the offset direction 834 * is {@code length}. 835 * 836 * @param anchor the anchor. 837 * @param length the length. 838 * 839 * @return The offset. 840 */ 841 private double voffset(Anchor2D anchor, double length) { 842 double offset = 0.0; 843 if (anchor.getRefPt().isTop()) { 844 offset = length * anchor.getOffset().getDY(); 845 } else if (anchor.getRefPt().isBottom()) { 846 offset = -length * anchor.getOffset().getDY(); 847 } 848 return offset; 849 } 850 851 /** 852 * Returns the horizontal position along the line based on the anchor 853 * point. 854 * 855 * @param anchor the anchor ({@code null} not permitted). 856 * @param start the start value. 857 * @param end the end value. 858 * 859 * @return The position. 860 */ 861 private double hpos(Anchor2D anchor, double start, double end) { 862 if (anchor.getRefPt().isLeft()) { 863 return start; 864 } else if (anchor.getRefPt().isRight()) { 865 return end; 866 } 867 return (start + end) / 2.0; 868 } 869 870 // anchor for x-marker label where line runs parallel to y-axis 871 private Point3D calcAnchorXY(Anchor2D anchor, Point3D start, Point3D end, 872 double xLength) { 873 double dx = hoffset(anchor, end.getY() - start.getY()); 874 double dy = voffset(anchor, xLength); 875 double y = hpos(anchor, start.getY(), end.getY()); 876 return new Point3D(start.getX() + dy, y + dx, start.getZ()); 877 } 878 879 // anchor for x-marker label where line runs parallel to z-axis 880 private Point3D calcAnchorXZ(Anchor2D anchor, Point3D start, Point3D end, 881 double xLength) { 882 double dx = hoffset(anchor, end.getZ() - start.getZ()); 883 double dy = voffset(anchor, xLength); 884 double z = hpos(anchor, start.getZ(), end.getZ()); 885 return new Point3D(start.getX() + dy, start.getY(), z + dx); 886 } 887 888 // anchor for y-marker label where line runs parallel to x-axis 889 private Point3D calcAnchorYX(Anchor2D anchor, Point3D start, Point3D end, 890 double yLength) { 891 double dx = hoffset(anchor, end.getX() - start.getX()); 892 double dy = voffset(anchor, yLength); 893 double x = hpos(anchor, start.getX(), end.getX()); 894 return new Point3D(x + dx, start.getY() + dy, start.getZ()); 895 } 896 897 // anchor for y-marker label where line runs parallel to z-axis 898 private Point3D calcAnchorYZ(Anchor2D anchor, Point3D start, Point3D end, 899 double yLength) { 900 double dx = hoffset(anchor, end.getZ() - start.getZ()); 901 double dy = voffset(anchor, yLength); 902 double z = hpos(anchor, start.getZ(), end.getZ()); 903 return new Point3D(start.getX(), start.getY() + dy, z + dx); 904 } 905 906 // anchor for z-marker label where line runs parallel to x-axis 907 private Point3D calcAnchorZX(Anchor2D anchor, Point3D start, Point3D end, 908 double zLength) { 909 double dx = hoffset(anchor, end.getX() - start.getX()); 910 double dy = voffset(anchor, zLength); 911 double x = hpos(anchor, start.getX(), end.getX()); 912 return new Point3D(x + dx, start.getY(), start.getZ() + dy); 913 } 914 915 // anchor for z-marker label where line runs parallel to y-axis 916 private Point3D calcAnchorZY(Anchor2D anchor, Point3D start, Point3D end, 917 double zLength) { 918 double dx = hoffset(anchor, end.getY() - start.getY()); 919 double dy = voffset(anchor, zLength); 920 double y = hpos(anchor, start.getY(), end.getY()); 921 return new Point3D(start.getX(), y + dx, start.getZ() + dy); 922 } 923 924 // anchor for x-marker label where band runs parallel to y-axis 925 private Point3D calcRangeAnchorXY(Anchor2D anchor, Point3D start1, 926 Point3D end1, Point3D start2, Point3D end2) { 927 Point2D p = anchor.resolveAnchorWithPercentOffset(start1.getY(), 928 start1.getX(), end2.getY(), end2.getX()); 929 return new Point3D(p.getY(), p.getX(), end1.getZ()); 930 } 931 932 // anchor for x-marker label where line runs parallel to z-axis 933 private Point3D calcRangeAnchorXZ(Anchor2D anchor, Point3D start1, 934 Point3D end1, Point3D start2, Point3D end2) { 935 Point2D p = anchor.resolveAnchorWithPercentOffset(start1.getZ(), 936 start1.getX(), end2.getZ(), end2.getX()); 937 return new Point3D(p.getY(), end1.getY(), p.getX()); 938 } 939 940// // anchor for y-marker label where line runs parallel to x-axis 941 private Point3D calcRangeAnchorYX(Anchor2D anchor, Point3D start1, 942 Point3D end1, Point3D start2, Point3D end2) { 943 Point2D p = anchor.resolveAnchorWithPercentOffset(start1.getX(), 944 start1.getY(), end2.getX(), end2.getY()); 945 return new Point3D(p.getX(), p.getY(), end1.getZ()); 946 } 947 948// // anchor for y-marker label where line runs parallel to z-axis 949 private Point3D calcRangeAnchorYZ(Anchor2D anchor, Point3D start1, 950 Point3D end1, Point3D start2, Point3D end2) { 951 Point2D p = anchor.resolveAnchorWithPercentOffset(start1.getZ(), 952 start1.getY(), end2.getZ(), end2.getY()); 953 return new Point3D(start1.getX(), p.getY(), p.getX()); 954 } 955 956 // anchor for z-marker label where line runs parallel to x-axis 957 private Point3D calcRangeAnchorZX(Anchor2D anchor, Point3D start1, 958 Point3D end1, Point3D start2, Point3D end2) { 959 Point2D p = anchor.resolveAnchorWithPercentOffset(start1.getX(), 960 start1.getZ(), end2.getX(), end2.getZ()); 961 return new Point3D(p.getX(), end1.getY(), p.getY()); 962 } 963 964 // anchor for z-marker label where line runs parallel to y-axis 965 private Point3D calcRangeAnchorZY(Anchor2D anchor, Point3D start1, 966 Point3D end1, Point3D start2, Point3D end2) { 967 Point2D p = anchor.resolveAnchorWithPercentOffset(start1.getY(), 968 start1.getZ(), end2.getY(), end2.getZ()); 969 return new Point3D(end1.getX(), p.getX(), p.getY()); 970 } 971 972 /** 973 * A special subclass of {@link Face} that is used by the {@link ChartBox3D} 974 * so that when faces are sorted by z-order, the chart box sides are always 975 * drawn first (furthest in the background). Also, these faces track 976 * tick marks, values and anchor points. 977 */ 978 public static final class ChartBoxFace extends Face { 979 980 /** Info about the x-axis ticks on edge A. */ 981 private List<TickData> xTicksA; 982 983 /** Info about the x-axis ticks on edge B. */ 984 private List<TickData> xTicksB; 985 986 /** Info about the y-axis ticks on edge A. */ 987 private List<TickData> yTicksA; 988 989 /** Info about the y-axis ticks on edge B. */ 990 private List<TickData> yTicksB; 991 992 /** Info about the z-axis ticks on edge A. */ 993 private List<TickData> zTicksA; 994 995 /** Info about the z-axis ticks on edge B. */ 996 private List<TickData> zTicksB; 997 998 private List<MarkerData> xMarkers; 999 1000 private List<MarkerData> yMarkers; 1001 1002 private List<MarkerData> zMarkers; 1003 1004 /** 1005 * Creates a new face for a {@link ChartBox3D}. 1006 * 1007 * @param owner the object that the new face belongs to ({@code null} 1008 * not permitted). 1009 * @param vertices the indices of the vertices. 1010 * 1011 * @since 1.3 1012 */ 1013 public ChartBoxFace(Object3D owner, int[] vertices) { 1014 super(owner, vertices); 1015 this.xTicksA = new ArrayList<TickData>(); 1016 this.xTicksB = new ArrayList<TickData>(); 1017 this.yTicksA = new ArrayList<TickData>(); 1018 this.yTicksB = new ArrayList<TickData>(); 1019 this.zTicksA = new ArrayList<TickData>(); 1020 this.zTicksB = new ArrayList<TickData>(); 1021 this.xMarkers = new ArrayList<MarkerData>(); 1022 this.yMarkers = new ArrayList<MarkerData>(); 1023 this.zMarkers = new ArrayList<MarkerData>(); 1024 } 1025 1026 /** 1027 * Clears the ticks for the x-axis. 1028 */ 1029 public void clearXTicks() { 1030 this.xTicksA.clear(); 1031 this.xTicksB.clear(); 1032 } 1033 1034 /** 1035 * Returns the x-axis tick data for edge A. 1036 * 1037 * @return The x-axis tick data for edge A. 1038 */ 1039 public List<TickData> getXTicksA() { 1040 return this.xTicksA; 1041 } 1042 1043 /** 1044 * Adds tick data for edges A and B. 1045 * 1046 * @param a data for a tick on edge A. 1047 * @param b data for a tick on edge B. 1048 */ 1049 public void addXTicks(TickData a, TickData b) { 1050 this.xTicksA.add(a); 1051 this.xTicksB.add(b); 1052 } 1053 1054 /** 1055 * Returns the x-axis tick data for the edge B. 1056 * 1057 * @return The x-axis tick data for the edge B. 1058 */ 1059 public List<TickData> getXTicksB() { 1060 return this.xTicksB; 1061 } 1062 1063 /** 1064 * Adds tick data items for the y-axis. 1065 * 1066 * @param a data for a tick on edge A. 1067 * @param b data for a tick on edge B. 1068 */ 1069 public void addYTicks(TickData a, TickData b) { 1070 this.yTicksA.add(a); 1071 this.yTicksB.add(b); 1072 } 1073 1074 /** 1075 * Returns the y-axis tick data for the edge A. 1076 * 1077 * @return The y-axis tick data for the edge A. 1078 */ 1079 public List<TickData> getYTicksA() { 1080 return this.yTicksA; 1081 } 1082 1083 /** 1084 * Returns the y-axis tick data for the edge B. 1085 * 1086 * @return The y-axis tick data for the edge B. 1087 */ 1088 public List<TickData> getYTicksB() { 1089 return this.yTicksB; 1090 } 1091 1092 /** 1093 * Adds tick data items for the z-axis. 1094 * 1095 * @param a data for a tick on edge A. 1096 * @param b data for a tick on edge B. 1097 */ 1098 public void addZTicks(TickData a, TickData b) { 1099 this.zTicksA.add(a); 1100 this.zTicksB.add(b); 1101 } 1102 1103 /** 1104 * Returns the z-axis tick data for the edge A. 1105 * 1106 * @return The z-axis tick data for the edge A. 1107 */ 1108 public List<TickData> getZTicksA() { 1109 return this.zTicksA; 1110 } 1111 1112 /** 1113 * Returns the z-axis tick data for the edge B. 1114 * 1115 * @return The z-axis tick data for the edge B. 1116 */ 1117 public List<TickData> getZTicksB() { 1118 return this.zTicksB; 1119 } 1120 1121 /** 1122 * Adds marker data for the x-dimension. 1123 * 1124 * @param marker the marker data ({@code null} not permitted). 1125 */ 1126 public void addXMarker(MarkerData marker) { 1127 ArgChecks.nullNotPermitted(marker, "marker"); 1128 this.xMarkers.add(marker); 1129 } 1130 1131 /** 1132 * Returns a list of marker data for the x-dimension (the list is not 1133 * modifiable). 1134 * 1135 * @return The markers (never {@code null}). 1136 */ 1137 public List<MarkerData> getXMarkers() { 1138 return Collections.unmodifiableList(this.xMarkers); 1139 } 1140 1141 /** 1142 * Adds marker data for the y-dimension. 1143 * 1144 * @param marker the marker data ({@code null} not permitted). 1145 */ 1146 public void addYMarker(MarkerData marker) { 1147 ArgChecks.nullNotPermitted(marker, "marker"); 1148 this.yMarkers.add(marker); 1149 } 1150 1151 /** 1152 * Returns a list of marker data for the y-dimension (the list is not 1153 * modifiable). 1154 * 1155 * @return The markers (never {@code null}). 1156 */ 1157 public List<MarkerData> getYMarkers() { 1158 return Collections.unmodifiableList(this.yMarkers); 1159 } 1160 1161 /** 1162 * Adds marker data for the z-dimension. 1163 * 1164 * @param marker the marker data ({@code null} not permitted). 1165 */ 1166 public void addZMarker(MarkerData marker) { 1167 ArgChecks.nullNotPermitted(marker, "marker"); 1168 this.zMarkers.add(marker); 1169 } 1170 1171 /** 1172 * Returns a list of marker data for the z-dimension (the list is not 1173 * modifiable). 1174 * 1175 * @return The markers (never {@code null}). 1176 */ 1177 public List<MarkerData> getZMarkers() { 1178 return Collections.unmodifiableList(this.zMarkers); 1179 } 1180 1181 /** 1182 * Returns {@code -123456f} which ensures that the chart box face 1183 * is always drawn first (before any data items). 1184 * 1185 * @param points the points (ignored here). 1186 * 1187 * @return {@code -123456f}. 1188 */ 1189 @Override 1190 public float calculateAverageZValue(Point3D[] points) { 1191 return -123456f; 1192 } 1193 } 1194 1195}