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}