    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}