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