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