001/* ======================================================
002 * JFreeChart : a chart library for the Java(tm) platform
003 * ======================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert and Contributors.
006 *
007 * Project Info:  https://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ------------------
028 * RendererUtils.java
029 * ------------------
030 * (C) Copyright 2007-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.chart.renderer;
038
039import org.jfree.chart.util.Args;
040import org.jfree.data.DomainOrder;
041import org.jfree.data.xy.XYDataset;
042
043/**
044 * Utility methods related to the rendering process.
045 */
046public class RendererUtils {
047
048    private RendererUtils() {
049        // no requirement to instantiate
050    }
051
052    /**
053     * Finds the lower index of the range of live items in the specified data
054     * series.
055     *
056     * @param dataset  the dataset ({@code null} not permitted).
057     * @param series  the series index.
058     * @param xLow  the lowest x-value in the live range.
059     * @param xHigh  the highest x-value in the live range.
060     *
061     * @return The index of the required item.
062     *
063     * @see #findLiveItemsUpperBound(XYDataset, int, double, double)
064     */
065    public static int findLiveItemsLowerBound(XYDataset dataset, int series,
066            double xLow, double xHigh) {
067        Args.nullNotPermitted(dataset, "dataset");
068        if (xLow >= xHigh) {
069            throw new IllegalArgumentException("Requires xLow < xHigh.");
070        }
071        int itemCount = dataset.getItemCount(series);
072        if (itemCount <= 1) {
073            return 0;
074        }
075        if (dataset.getDomainOrder() == DomainOrder.ASCENDING) {
076            // for data in ascending order by x-value, we are (broadly) looking
077            // for the index of the highest x-value that is less than xLow
078            int low = 0;
079            int high = itemCount - 1;
080            double lowValue = dataset.getXValue(series, low);
081            if (lowValue >= xLow) {
082                // special case where the lowest x-value is >= xLow
083                return low;
084            }
085            double highValue = dataset.getXValue(series, high);
086            if (highValue < xLow) {
087                // special case where the highest x-value is < xLow
088                return high;
089            }
090            while (high - low > 1) {
091                int mid = (low + high) / 2;
092                double midV = dataset.getXValue(series, mid);
093                if (midV >= xLow) {
094                    high = mid;
095                }
096                else {
097                    low = mid;
098                }
099            }
100            return high;
101        }
102        else if (dataset.getDomainOrder() == DomainOrder.DESCENDING) {
103            // when the x-values are sorted in descending order, the lower
104            // bound is found by calculating relative to the xHigh value
105            int low = 0;
106            int high = itemCount - 1;
107            double lowValue = dataset.getXValue(series, low);
108            if (lowValue <= xHigh) {
109                return low;
110            }
111            double highValue = dataset.getXValue(series, high);
112            if (highValue > xHigh) {
113                return high;
114            }
115            while (high - low > 1) {
116                int mid = (low + high) / 2;
117                double midV = dataset.getXValue(series, mid);
118                if (midV > xHigh) {
119                    low = mid;
120                }
121                else {
122                    high = mid;
123                }
124            }
125            return high;
126        }
127        else {
128            // we don't know anything about the ordering of the x-values,
129            // but we can still skip any initial values that fall outside the
130            // range...
131            int index = 0;
132            // skip any items that don't need including...
133            double x = dataset.getXValue(series, index);
134            while (index < itemCount && x < xLow) {
135                index++;
136                if (index < itemCount) {
137                    x = dataset.getXValue(series, index);
138                }
139            }
140            return Math.min(Math.max(0, index), itemCount - 1);
141        }
142    }
143
144    /**
145     * Finds the upper index of the range of live items in the specified data
146     * series.
147     *
148     * @param dataset  the dataset ({@code null} not permitted).
149     * @param series  the series index.
150     * @param xLow  the lowest x-value in the live range.
151     * @param xHigh  the highest x-value in the live range.
152     *
153     * @return The index of the required item.
154     *
155     * @see #findLiveItemsLowerBound(XYDataset, int, double, double)
156     */
157    public static int findLiveItemsUpperBound(XYDataset dataset, int series,
158            double xLow, double xHigh) {
159        Args.nullNotPermitted(dataset, "dataset");
160        if (xLow >= xHigh) {
161            throw new IllegalArgumentException("Requires xLow < xHigh.");
162        }
163        int itemCount = dataset.getItemCount(series);
164        if (itemCount <= 1) {
165            return 0;
166        }
167        if (dataset.getDomainOrder() == DomainOrder.ASCENDING) {
168            int low = 0;
169            int high = itemCount - 1;
170            double lowValue = dataset.getXValue(series, low);
171            if (lowValue > xHigh) {
172                return low;
173            }
174            double highValue = dataset.getXValue(series, high);
175            if (highValue <= xHigh) {
176                return high;
177            }
178            int mid = (low + high) / 2;
179            while (high - low > 1) {
180                double midV = dataset.getXValue(series, mid);
181                if (midV <= xHigh) {
182                    low = mid;
183                }
184                else {
185                    high = mid;
186                }
187                mid = (low + high) / 2;
188            }
189            return mid;
190        }
191        else if (dataset.getDomainOrder() == DomainOrder.DESCENDING) {
192            // when the x-values are descending, the upper bound is found by
193            // comparing against xLow
194            int low = 0;
195            int high = itemCount - 1;
196            int mid = (low + high) / 2;
197            double lowValue = dataset.getXValue(series, low);
198            if (lowValue < xLow) {
199                return low;
200            }
201            double highValue = dataset.getXValue(series, high);
202            if (highValue >= xLow) {
203                return high;
204            }
205            while (high - low > 1) {
206                double midV = dataset.getXValue(series, mid);
207                if (midV >= xLow) {
208                    low = mid;
209                }
210                else {
211                    high = mid;
212                }
213                mid = (low + high) / 2;
214            }
215            return mid;
216        }
217        else {
218            // we don't know anything about the ordering of the x-values,
219            // but we can still skip any trailing values that fall outside the
220            // range...
221            int index = itemCount - 1;
222            // skip any items that don't need including...
223            double x = dataset.getXValue(series, index);
224            while (index >= 0 && x > xHigh) {
225                index--;
226                if (index >= 0) {
227                    x = dataset.getXValue(series, index);
228                }
229            }
230            return Math.max(index, 0);
231        }
232    }
233
234    /**
235     * Finds a range of item indices that is guaranteed to contain all the
236     * x-values from x0 to x1 (inclusive).
237     *
238     * @param dataset  the dataset ({@code null} not permitted).
239     * @param series  the series index.
240     * @param xLow  the lower bound of the x-value range.
241     * @param xHigh  the upper bound of the x-value range.
242     *
243     * @return The indices of the boundary items.
244     */
245    public static int[] findLiveItems(XYDataset dataset, int series,
246            double xLow, double xHigh) {
247        // here we could probably be a little faster by searching for both
248        // indices simultaneously, but I'll look at that later if it seems
249        // like it matters...
250        int i0 = findLiveItemsLowerBound(dataset, series, xLow, xHigh);
251        int i1 = findLiveItemsUpperBound(dataset, series, xLow, xHigh);
252        if (i0 > i1) {
253            i0 = i1;
254        }
255        return new int[] {i0, i1};
256    }
257
258}