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