    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