001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert and Contributors.
006 *
007 * Project Info:  http://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    /**
049     * Finds the lower index of the range of live items in the specified data
050     * series.
051     *
052     * @param dataset  the dataset ({@code null} not permitted).
053     * @param series  the series index.
054     * @param xLow  the lowest x-value in the live range.
055     * @param xHigh  the highest x-value in the live range.
056     *
057     * @return The index of the required item.
058     *
059     * @see #findLiveItemsUpperBound(XYDataset, int, double, double)
060     */
061    public static int findLiveItemsLowerBound(XYDataset dataset, int series,
062            double xLow, double xHigh) {
063        Args.nullNotPermitted(dataset, "dataset");
064        if (xLow >= xHigh) {
065            throw new IllegalArgumentException("Requires xLow < xHigh.");
066        }
067        int itemCount = dataset.getItemCount(series);
068        if (itemCount <= 1) {
069            return 0;
070        }
071        if (dataset.getDomainOrder() == DomainOrder.ASCENDING) {
072            // for data in ascending order by x-value, we are (broadly) looking
073            // for the index of the highest x-value that is less than xLow
074            int low = 0;
075            int high = itemCount - 1;
076            double lowValue = dataset.getXValue(series, low);
077            if (lowValue >= xLow) {
078                // special case where the lowest x-value is >= xLow
079                return low;
080            }
081            double highValue = dataset.getXValue(series, high);
082            if (highValue < xLow) {
083                // special case where the highest x-value is < xLow
084                return high;
085            }
086            while (high - low > 1) {
087                int mid = (low + high) / 2;
088                double midV = dataset.getXValue(series, mid);
089                if (midV >= xLow) {
090                    high = mid;
091                }
092                else {
093                    low = mid;
094                }
095            }
096            return high;
097        }
098        else if (dataset.getDomainOrder() == DomainOrder.DESCENDING) {
099            // when the x-values are sorted in descending order, the lower
100            // bound is found by calculating relative to the xHigh value
101            int low = 0;
102            int high = itemCount - 1;
103            double lowValue = dataset.getXValue(series, low);
104            if (lowValue <= xHigh) {
105                return low;
106            }
107            double highValue = dataset.getXValue(series, high);
108            if (highValue > xHigh) {
109                return high;
110            }
111            while (high - low > 1) {
112                int mid = (low + high) / 2;
113                double midV = dataset.getXValue(series, mid);
114                if (midV > xHigh) {
115                    low = mid;
116                }
117                else {
118                    high = mid;
119                }
120            }
121            return high;
122        }
123        else {
124            // we don't know anything about the ordering of the x-values,
125            // but we can still skip any initial values that fall outside the
126            // range...
127            int index = 0;
128            // skip any items that don't need including...
129            double x = dataset.getXValue(series, index);
130            while (index < itemCount && x < xLow) {
131                index++;
132                if (index < itemCount) {
133                    x = dataset.getXValue(series, index);
134                }
135            }
136            return Math.min(Math.max(0, index), itemCount - 1);
137        }
138    }
139
140    /**
141     * Finds the upper index of the range of live items in the specified data
142     * series.
143     *
144     * @param dataset  the dataset ({@code null} not permitted).
145     * @param series  the series index.
146     * @param xLow  the lowest x-value in the live range.
147     * @param xHigh  the highest x-value in the live range.
148     *
149     * @return The index of the required item.
150     *
151     * @see #findLiveItemsLowerBound(XYDataset, int, double, double)
152     */
153    public static int findLiveItemsUpperBound(XYDataset dataset, int series,
154            double xLow, double xHigh) {
155        Args.nullNotPermitted(dataset, "dataset");
156        if (xLow >= xHigh) {
157            throw new IllegalArgumentException("Requires xLow < xHigh.");
158        }
159        int itemCount = dataset.getItemCount(series);
160        if (itemCount <= 1) {
161            return 0;
162        }
163        if (dataset.getDomainOrder() == DomainOrder.ASCENDING) {
164            int low = 0;
165            int high = itemCount - 1;
166            double lowValue = dataset.getXValue(series, low);
167            if (lowValue > xHigh) {
168                return low;
169            }
170            double highValue = dataset.getXValue(series, high);
171            if (highValue <= xHigh) {
172                return high;
173            }
174            int mid = (low + high) / 2;
175            while (high - low > 1) {
176                double midV = dataset.getXValue(series, mid);
177                if (midV <= xHigh) {
178                    low = mid;
179                }
180                else {
181                    high = mid;
182                }
183                mid = (low + high) / 2;
184            }
185            return mid;
186        }
187        else if (dataset.getDomainOrder() == DomainOrder.DESCENDING) {
188            // when the x-values are descending, the upper bound is found by
189            // comparing against xLow
190            int low = 0;
191            int high = itemCount - 1;
192            int mid = (low + high) / 2;
193            double lowValue = dataset.getXValue(series, low);
194            if (lowValue < xLow) {
195                return low;
196            }
197            double highValue = dataset.getXValue(series, high);
198            if (highValue >= xLow) {
199                return high;
200            }
201            while (high - low > 1) {
202                double midV = dataset.getXValue(series, mid);
203                if (midV >= xLow) {
204                    low = mid;
205                }
206                else {
207                    high = mid;
208                }
209                mid = (low + high) / 2;
210            }
211            return mid;
212        }
213        else {
214            // we don't know anything about the ordering of the x-values,
215            // but we can still skip any trailing values that fall outside the
216            // range...
217            int index = itemCount - 1;
218            // skip any items that don't need including...
219            double x = dataset.getXValue(series, index);
220            while (index >= 0 && x > xHigh) {
221                index--;
222                if (index >= 0) {
223                    x = dataset.getXValue(series, index);
224                }
225            }
226            return Math.max(index, 0);
227        }
228    }
229
230    /**
231     * Finds a range of item indices that is guaranteed to contain all the
232     * x-values from x0 to x1 (inclusive).
233     *
234     * @param dataset  the dataset ({@code null} not permitted).
235     * @param series  the series index.
236     * @param xLow  the lower bound of the x-value range.
237     * @param xHigh  the upper bound of the x-value range.
238     *
239     * @return The indices of the boundary items.
240     */
241    public static int[] findLiveItems(XYDataset dataset, int series,
242            double xLow, double xHigh) {
243        // here we could probably be a little faster by searching for both
244        // indices simultaneously, but I'll look at that later if it seems
245        // like it matters...
246        int i0 = findLiveItemsLowerBound(dataset, series, xLow, xHigh);
247        int i1 = findLiveItemsUpperBound(dataset, series, xLow, xHigh);
248        if (i0 > i1) {
249            i0 = i1;
250        }
251        return new int[] {i0, i1};
252    }
253
254}