0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: * The Original Software is NetBeans. The Initial Developer of the Original
0026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0027: * Microsystems, Inc. All Rights Reserved.
0028: *
0029: * If you wish your version of this file to be governed by only the CDDL
0030: * or only the GPL Version 2, indicate your decision by adding
0031: * "[Contributor] elects to include this software in this distribution
0032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0033: * single choice of license, a recipient has the option to distribute
0034: * your version of this file under either the CDDL, the GPL Version 2 or
0035: * to extend the choice of license to its licensees as provided above.
0036: * However, if you add GPL Version 2 code and therefore, elected the GPL
0037: * Version 2 license, then the option applies only if the new code is
0038: * made subject to such option by the copyright holder.
0039: */
0040:
0041: package org.netbeans.lib.profiler.ui.charts;
0042:
0043: import java.awt.*;
0044: import java.awt.event.*;
0045: import java.awt.geom.*;
0046: import java.awt.image.*;
0047: import java.util.*;
0048: import javax.accessibility.Accessible;
0049: import javax.accessibility.AccessibleContext;
0050: import javax.swing.*;
0051:
0052: /**
0053: *
0054: * @author Tomas Hurka
0055: * @author Jiri Sedlacek
0056: */
0057: public class SynchronousXYChart extends JComponent implements
0058: ComponentListener, ChartModelListener, MouseListener,
0059: MouseMotionListener, AdjustmentListener, Accessible {
0060: //~ Static fields/initializers -----------------------------------------------------------------------------------------------
0061:
0062: // -----
0063: // I18N String constants
0064: private static final ResourceBundle messages = ResourceBundle
0065: .getBundle("org.netbeans.lib.profiler.ui.charts.Bundle"); // NOI18N
0066: private static final String FIT_TO_WINDOW_STRING = messages
0067: .getString("SynchronousXYChart_FitToWindowString"); // NOI18N
0068: // -----
0069:
0070: // --- Constants -------------------------------------------------------------
0071: public static final int TYPE_LINE = 1; // chart draws line segments
0072: public static final int TYPE_FILL = 2; // chart draws filled areas
0073: public static final int VALUES_INTERPOLATED = 50; // smooth joining of subsequent values
0074: public static final int VALUES_DISCRETE = 51; // discrete values => "stairs" effect
0075: public static final int COPY_ACCEL_GENERIC = 100; // Graphics.copyArea() used, optimal for UNIXes, works well also for Windows (default)
0076: public static final int COPY_ACCEL_RASTER = 101; // BufferedImage.getRaster().setDataElements() used, seems to have better performance on Windows (HW acceleration)
0077:
0078: // --- Legend-related variables
0079: private static final int HORIZONTAL_LEGEND_MARGIN = 5; // margin between component left/right side and clipping area for horizontal axis legend
0080: private static final double minimumVisibleDataWidthRel = 0.1d;
0081: private static final long minimumOptimalUnits = 100;
0082: private static final double maximumZoom = DateTimeAxisUtils
0083: .getMaximumScale(minimumOptimalUnits);
0084:
0085: //~ Instance fields ----------------------------------------------------------------------------------------------------------
0086:
0087: // --- Variables -------------------------------------------------------------
0088: private AccessibleContext accessibleContext; // Accessibility support
0089:
0090: //private VolatileImage offScreenImage; // volatile offscreen image buffer (alternative to BufferedImage)
0091: private BufferedImage offScreenImage; // offscreen image buffer
0092: private Color evenSelectionSegmentsColor; // color of even segments of selection boundary
0093: private Color limitYColor = Color.WHITE;
0094: private Color oddSelectionSegmentColor; // color of odd segments of selection boundary
0095: private Font horizontalAxisFont;
0096: private Font horizontalAxisFontSmall;
0097: private Font verticalAxisFont;
0098: private Graphics2D offScreenGraphics; // graphics instance of the offscreen image buffer (= offScreenImage.getGraphics())
0099: private Insets chartInsets; // chart insets
0100: private Insets insets; // component insets (=getInsets())
0101: private JScrollBar scrollBar; // JScrollBar attached to the chart
0102: private Paint backgroundPaint; // paint of the component
0103: private Paint chartPaint; // paint of the chart area
0104: private Paint horizontalAxisPaint;
0105: private Paint horizontalMeshPaint;
0106: private Paint verticalAxisPaint;
0107: private Paint verticalMeshPaint;
0108: private Rectangle horizontalAxisClip = new Rectangle();
0109: private Rectangle horizontalAxisMarksClip = new Rectangle();
0110: private Rectangle verticalAxisClip = new Rectangle();
0111: private Rectangle verticalAxisClip2 = new Rectangle();
0112: private String verticalAxisValueString;
0113: private String verticalAxisValueString2;
0114: private Stroke chartStroke; // stroke used for drawing chart items
0115: private Stroke evenSelectionSegmentsStroke; // stroke of even segments of selection boundary
0116: private Stroke horizontalAxisStroke;
0117: private Stroke horizontalMeshStroke;
0118: private Stroke oddSelectionSegmentStroke; // stroke of odd segments of selection boundary
0119: private Stroke verticalAxisStroke;
0120: private Stroke verticalMeshStroke;
0121: private SynchronousXYChartModel model; // chart model
0122:
0123: // --- ChartActionProducer variables
0124: private Vector chartActionListeners;
0125: private long[] dataOffsetsY; // first Y-values for each series
0126: private long[] lastMaxYs; // last Y-value maximums for each series
0127: private long[] lastMinYs; // last Y-value minimums for each series
0128: private double[] scaleFactorsY; // vertical scale factors for each series
0129: private boolean allowSelection; // allow selection in chart?
0130: private boolean autoTrackingEnd; // should viewing mode be automatically switched to tracking end when scrollbar reaches the right end?
0131:
0132: // --- Initial appearance (without any data) support
0133: private boolean customizedEmptyAppearance = false;
0134: private boolean fitToWindow; // is "fit to window" mode currently active?
0135: private boolean internalScrollBarChange; // are scrollbar values changed due to chart change?
0136: private boolean lastLeadingItemIsForBuffer; // index of first visible item contains some offset from optimized algorithm, not valid for non-optimized algorithms
0137: private boolean lastScaleXValid; // did X-axis scale changed since last iteration (either scaleFactorX changed or component resized)?
0138: private boolean lastScaleYValid; // did any of the Y-axis scales changed since last iteration (either any scaleFactorsY changed or component resized)
0139: private boolean lastTrailingItemIndexValid; // is value of lastTrailingItemIndex from the last iteration still valid?
0140: private boolean lastViewOffsetXValid; // is value of viewOffsetX from the last iteration still valid?
0141: private boolean mouseInProgress; // is the selection currently being defined by the user? (mouse was pressed and not yet released)
0142: private boolean offScreenImageInvalid; // does the offscreen image need to be repainted?
0143: private boolean scaleFactorsNeedUpdate; // do the data scale factors need to be updated?
0144: private boolean scrollBarValuesDirty; // should scrollbar values be updated to current values?
0145: private boolean selectionTracksMovement; // should left side of selection track data movement when tracking end?
0146: private boolean trackingEnd; // is "tracking end" mode currently active?
0147: private boolean trailingItemVisible; // has the chart to be repainted due to some data-tail change in visible area?
0148: private boolean useDayInTimeLegend;
0149: private boolean useSecondaryVerticalAxis;
0150: private boolean verticalAxisValueAdaptDivider;
0151: private boolean verticalAxisValueAdaptDivider2;
0152:
0153: // --- Telemetry Overview workaround
0154: private double dataWidthAtTrackingEndSwitch; // actual "width" of data when switching from fit to window to tracking end mode and chartWidth is still 0
0155: private double initialZoom;
0156: private double scaleFactorX; // horizontal scale factor (== viewScaleX)
0157: private double scrollBarLongToIntFactor; // scale factor mapping chart (long) data to scrollbar (int) values
0158: private double viewScaleX; // current scale for X-axis
0159: private int chartHeight; // height of the chart area
0160: private int chartWidth; // width of the chart area
0161:
0162: // --- Copy acceleration support (generic / HW raster)
0163: private int copyAccel = COPY_ACCEL_GENERIC;
0164: private int dataType; // data type (interpolated / discrete)
0165: private int drawHeight; // height of the offscreen image buffer
0166: private int drawWidth; // width of the offscreen image buffer
0167: private int lastLeadingItemIndex; // index of first visible item from the last iteration
0168: private int lastTrailingItemIndex; // index of last visible item used when trailingItemVisible = true
0169: private int minimumVerticalMarksDistance;
0170: private int selectionHeight; // selection height
0171: private int selectionWidth; // selection width
0172: private int selectionX; // x-coordinate of the selection start
0173: private int selectionY; // y-coordinate of the selection start
0174: private int topChartMargin; // extra space between maximum value and top of the chart
0175: private int type; // chart type (line segments / filled segments)
0176: private int verticalAxisValueDivider;
0177: private int verticalAxisValueDivider2;
0178: private long dataOffsetX; // first X-value
0179: private long dataViewWidth; // width of all the data in chart(component) coordinate units according to current viewScaleX value
0180: private long firstValueH;
0181: private long firstValueV;
0182: private long lastMaxY; // last global Y-value maximum
0183: private long lastMinY; // last global Y-value minimum
0184: private long lastValueH;
0185: private long lastValueV;
0186: private long lastViewOffsetX; // value of viewOffsetX from the last iteration
0187: private long limitYValue = Long.MAX_VALUE;
0188: private long optimalUnits;
0189: private long viewOffsetX;
0190:
0191: //~ Constructors -------------------------------------------------------------------------------------------------------------
0192:
0193: // --- Constructors ----------------------------------------------------------
0194:
0195: /** Creates a new instance of SynchronousXYChart */
0196: public SynchronousXYChart() {
0197: this (TYPE_FILL);
0198: }
0199:
0200: /** Creates a new instance of SynchronousXYChart */
0201: public SynchronousXYChart(int type) {
0202: this (type, VALUES_INTERPOLATED);
0203: }
0204:
0205: public SynchronousXYChart(int type, int dataType) {
0206: this (type, dataType, 1.0d);
0207: }
0208:
0209: public SynchronousXYChart(int type, int dataType, double initialZoom) {
0210: this .type = type;
0211: this .dataType = dataType;
0212:
0213: allowSelection = false;
0214:
0215: chartInsets = new Insets(10, 20, 10, 20);
0216:
0217: topChartMargin = 20;
0218:
0219: backgroundPaint = UIManager.getColor("Panel.background"); // NOI18N
0220:
0221: chartPaint = Color.WHITE;
0222: chartStroke = new BasicStroke(2);
0223:
0224: mouseInProgress = false;
0225:
0226: lastViewOffsetXValid = false;
0227:
0228: lastScaleXValid = false;
0229: lastScaleYValid = false;
0230:
0231: lastTrailingItemIndex = 0;
0232: lastTrailingItemIndexValid = false;
0233:
0234: changeTrackingEnd(false);
0235: changeFitToWindow(false);
0236:
0237: autoTrackingEnd = true;
0238:
0239: selectionTracksMovement = true;
0240:
0241: this .initialZoom = initialZoom;
0242: viewScaleX = initialZoom;
0243: viewOffsetX = 0;
0244: //viewScaleX = 10/(double)Integer.MAX_VALUE;
0245: //changeZoom(0.22898975409836064);
0246: verticalMeshPaint = new Color(80, 80, 80, 50);
0247: verticalMeshStroke = new BasicStroke();
0248: horizontalAxisFont = UIManager.getFont("Panel.font"); // NOI18N
0249: horizontalAxisFontSmall = horizontalAxisFont
0250: .deriveFont((float) (horizontalAxisFont.getSize() - 2));
0251:
0252: verticalAxisFont = UIManager.getFont("Panel.font"); // NOI18N
0253:
0254: horizontalAxisPaint = Color.BLACK;
0255: horizontalAxisStroke = new BasicStroke();
0256:
0257: verticalAxisPaint = Color.BLACK;
0258: verticalAxisStroke = new BasicStroke();
0259:
0260: useSecondaryVerticalAxis = false;
0261:
0262: verticalAxisValueDivider = 1;
0263: verticalAxisValueString = ""; // NOI18N
0264: setVerticalAxisValueAdaptDivider(false);
0265:
0266: verticalAxisValueDivider2 = 1;
0267: verticalAxisValueString2 = ""; // NOI18N
0268: setVerticalAxisValueAdaptDivider2(false);
0269:
0270: useDayInTimeLegend = false;
0271:
0272: minimumVerticalMarksDistance = 50;
0273:
0274: evenSelectionSegmentsColor = Color.WHITE;
0275: evenSelectionSegmentsStroke = new BasicStroke(1,
0276: BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0,
0277: new float[] { 2, 2 }, 0);
0278:
0279: oddSelectionSegmentColor = Color.BLACK;
0280: oddSelectionSegmentStroke = new BasicStroke(1,
0281: BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0,
0282: new float[] { 2, 2 }, 2);
0283:
0284: addComponentListener(this );
0285: addMouseListener(this );
0286: addMouseMotionListener(this );
0287: }
0288:
0289: //~ Methods ------------------------------------------------------------------------------------------------------------------
0290:
0291: // --- Accessibility support -------------------------------------------------
0292: public void setAccessibleContext(AccessibleContext accessibleContext) {
0293: this .accessibleContext = accessibleContext;
0294: }
0295:
0296: public AccessibleContext getAccessibleContext() {
0297: return accessibleContext;
0298: }
0299:
0300: public void setAutoTrackingEnd(boolean autoTrackingEnd) {
0301: this .autoTrackingEnd = autoTrackingEnd;
0302: }
0303:
0304: public boolean getAutoTrackingEnd() {
0305: return autoTrackingEnd;
0306: }
0307:
0308: // --- Colors % Stroke customization -----------------------------------------
0309: public void setBackgroundPaint(Paint backgroundPaint) {
0310: if (((this .backgroundPaint == null) && (backgroundPaint != null))
0311: || (!this .backgroundPaint.equals(backgroundPaint))) {
0312: this .backgroundPaint = backgroundPaint;
0313: doRepaint(false);
0314: }
0315: }
0316:
0317: public Paint getBackgroundPaint() {
0318: return backgroundPaint;
0319: }
0320:
0321: public void setChartPaint(Paint chartPaint) {
0322: if (((this .chartPaint == null) && (chartPaint != null))
0323: || (!this .chartPaint.equals(chartPaint))) {
0324: this .chartPaint = chartPaint;
0325: doRepaint(false);
0326: }
0327: }
0328:
0329: public Paint getChartPaint() {
0330: return backgroundPaint;
0331: }
0332:
0333: public void setChartStroke(Stroke chartStroke) {
0334: if (((this .chartStroke == null) && (chartStroke != null))
0335: || (!this .chartStroke.equals(chartStroke))) {
0336: this .chartStroke = chartStroke;
0337: doRepaint(false);
0338: }
0339: }
0340:
0341: public Stroke getChartStroke() {
0342: return chartStroke;
0343: }
0344:
0345: // --- Copy acceleration support (generic / HW raster) -----------------------
0346: public void setCopyAcceleration(int copyAccel) {
0347: this .copyAccel = copyAccel;
0348: }
0349:
0350: public int getCopyAcceleration() {
0351: return copyAccel;
0352: }
0353:
0354: // conversion of data Y interval from data coordinates to chart coordinates
0355: public long getDataToViewHeight(long height, int seriesIndex) {
0356: return (long) Math.ceil(height * scaleFactorsY[seriesIndex]);
0357: }
0358:
0359: // conversion of data X interval from data coordinates to chart coordinates
0360: public long getDataToViewWidth(long width) {
0361: return (long) Math.ceil(width * scaleFactorX);
0362: }
0363:
0364: // conversion of X value from data coordinates to chart coordinates
0365: public long getDataToViewX(long xValue) {
0366: return (long) Math
0367: .ceil((((xValue - dataOffsetX) * scaleFactorX) + chartInsets.left)
0368: - viewOffsetX); //
0369: }
0370:
0371: // conversion of Y value from data coordinates to chart coordinates
0372: public long getDataToViewY(long yValue, int seriesIndex) {
0373: return chartHeight
0374: - (long) Math
0375: .ceil(((yValue - dataOffsetsY[seriesIndex]) * scaleFactorsY[seriesIndex])
0376: - chartInsets.top);
0377: }
0378:
0379: public void setFitToWindow(boolean fitToWindow) {
0380: if (fitToWindow) {
0381: setFitToWindow();
0382: } else {
0383: resetFitToWindow();
0384: }
0385: }
0386:
0387: public void setFitToWindow() {
0388: if (!fitToWindow) {
0389: changeFitToWindow(true);
0390: changeTrackingEnd(false);
0391: lastViewOffsetXValid = false;
0392: lastScaleXValid = false;
0393: offScreenImageInvalid = true;
0394: doRepaint(true);
0395: }
0396: }
0397:
0398: public boolean isFitToWindow() {
0399: return fitToWindow;
0400: }
0401:
0402: public void setHorizontalAxisFont(Font horizontalAxisFont) {
0403: if ((horizontalAxisFont != null)
0404: && !this .horizontalAxisFont.equals(horizontalAxisFont)) {
0405: this .horizontalAxisFont = horizontalAxisFont;
0406: horizontalAxisFontSmall = horizontalAxisFont
0407: .deriveFont((float) (horizontalAxisFont.getSize() - 2));
0408:
0409: // TODO: repaint
0410: }
0411: }
0412:
0413: public Font getHorizontalAxisFont() {
0414: return horizontalAxisFont;
0415: }
0416:
0417: public void setHorizontalMeshPaint(Paint horizontalMeshPaint) {
0418: if ((horizontalMeshPaint != null)
0419: && !this .horizontalMeshPaint
0420: .equals(horizontalMeshPaint)) {
0421: this .horizontalMeshPaint = horizontalMeshPaint;
0422:
0423: // TODO: repaint
0424: }
0425: }
0426:
0427: public Paint getHorizontalMeshPaint() {
0428: return horizontalMeshPaint;
0429: }
0430:
0431: public void setHorizontalMeshStroke(Stroke horizontalMeshStroke) {
0432: if ((horizontalMeshStroke != null)
0433: && !this .horizontalMeshStroke
0434: .equals(horizontalMeshStroke)) {
0435: this .horizontalMeshStroke = horizontalMeshStroke;
0436:
0437: // TODO: repaint
0438: }
0439: }
0440:
0441: public Stroke getHorizontalMeshStroke() {
0442: return horizontalMeshStroke;
0443: }
0444:
0445: // scrollbar block increment mapped to (int)
0446: public int getIntBlockIncrement() {
0447: return (int) ((chartWidth - 20) * scrollBarLongToIntFactor);
0448: }
0449:
0450: // current chart width mapped to (int)
0451: public int getIntExtent() {
0452: return (int) (getRealExtent() * scrollBarLongToIntFactor);
0453: }
0454:
0455: // data view width mapped to (int)
0456: public int getIntMaximum() {
0457: return (int) (getRealMaximum() * scrollBarLongToIntFactor);
0458: }
0459:
0460: // minimum (starting) position mapped to (int)
0461: public int getIntMinimum() {
0462: return 0;
0463: }
0464:
0465: // current view offset mapped to (int)
0466: public int getIntPosition() {
0467: return (int) (getRealPosition() * scrollBarLongToIntFactor);
0468: }
0469:
0470: // scrollbar unit increment mapped to (int)
0471: public int getIntUnitIncrement() {
0472: return (int) (20 * scrollBarLongToIntFactor);
0473: }
0474:
0475: public int getLeadingItemIndexForPosition(int x) {
0476: if ((model == null) || (model.getItemCount() < 2)) {
0477: return -1;
0478: }
0479:
0480: long timeAtPosition = (long) getXValueAtPosition(x);
0481:
0482: int itemIndex = lastLeadingItemIndex;
0483:
0484: if (model.getXValue(itemIndex) == timeAtPosition) {
0485: return itemIndex;
0486: }
0487:
0488: if (model.getXValue(itemIndex) < timeAtPosition) {
0489: // searching forward (most probably)
0490: for (int i = itemIndex; i < (model.getItemCount() - 1); i++) {
0491: if (model.getXValue(i + 1) > timeAtPosition) {
0492: return i;
0493: }
0494: }
0495:
0496: return -1;
0497: } else {
0498: // searching backward
0499: for (int i = itemIndex; i >= 0; i--) {
0500: if (model.getXValue(i) < timeAtPosition) {
0501: return i;
0502: }
0503: }
0504:
0505: return -1;
0506: }
0507: }
0508:
0509: public double getMaximumZoom() {
0510: return maximumZoom;
0511: }
0512:
0513: public boolean isMaximumZoom() {
0514: return viewScaleX >= maximumZoom;
0515: }
0516:
0517: public void setMinimumVerticalMarksDistance(
0518: int minimumVerticalMarksDistance) {
0519: this .minimumVerticalMarksDistance = minimumVerticalMarksDistance;
0520: }
0521:
0522: public int getMinimumVerticalMarksDistance() {
0523: return minimumVerticalMarksDistance;
0524: }
0525:
0526: public double getMinimumZoom() {
0527: if (model == null) {
0528: return 0;
0529: }
0530:
0531: return Math.min(initialZoom,
0532: (double) (chartWidth * minimumVisibleDataWidthRel)
0533: / (double) (model.getMaxXValue() - model
0534: .getMinXValue()));
0535: }
0536:
0537: // --- Scaling support -------------------------------------------------------
0538: public boolean isMinimumZoom() {
0539: return viewScaleX <= getMinimumZoom();
0540: }
0541:
0542: // --- SynchronousXYChartModel stuff -----------------------------------------
0543: public void setModel(SynchronousXYChartModel model) {
0544: // automatically unregister itself as a ChartModelListener from current model
0545: if (this .model != null) {
0546: this .model.removeChartModelListener(this );
0547: }
0548:
0549: // automatically register itself as a ChartModelListener for new model
0550: if (model != null) {
0551: model.addChartModelListener(this );
0552: }
0553:
0554: this .model = model;
0555:
0556: lastMinYs = new long[model.getSeriesCount()];
0557: lastMaxYs = new long[model.getSeriesCount()];
0558:
0559: dataOffsetsY = new long[model.getSeriesCount()];
0560: scaleFactorsY = new double[model.getSeriesCount()];
0561:
0562: offScreenImageInvalid = true;
0563: doRepaint(true);
0564: }
0565:
0566: public SynchronousXYChartModel getModel() {
0567: return model;
0568: }
0569:
0570: // --- ToolTip support -------------------------------------------------------
0571: public boolean isOverChart(Point point) {
0572: return isOverChart(point.x, point.y);
0573: }
0574:
0575: public boolean isOverChart(int x, int y) {
0576: Insets componentInsets = getInsets();
0577:
0578: return ((x >= (componentInsets.left + chartInsets.left))
0579: && (x <= (componentInsets.left + chartInsets.left + chartWidth))
0580: && (y >= (componentInsets.top + chartInsets.top)) && (y <= (componentInsets.top
0581: + chartInsets.top + chartHeight)));
0582: }
0583:
0584: // current chart width in chart coordinates
0585: public long getRealExtent() {
0586: return chartWidth;
0587: }
0588:
0589: // data view width in chart coordinates
0590: public long getRealMaximum() {
0591: return dataViewWidth;
0592: }
0593:
0594: // --- Scrolling support -----------------------------------------------------
0595:
0596: // minimum (starting) position in chart coordinates
0597: public long getRealMinimum() {
0598: return 0;
0599: }
0600:
0601: // current view offset in chart coordinates
0602: public long getRealPosition() {
0603: return viewOffsetX;
0604: }
0605:
0606: public void setScale(double viewScaleX) {
0607: if (!fitToWindow) {
0608: if (isMinimumZoom() && (viewScaleX < getMinimumZoom())) {
0609: return;
0610: }
0611:
0612: if (isMaximumZoom() && (viewScaleX > getMaximumZoom())) {
0613: return;
0614: }
0615: }
0616:
0617: if (this .viewScaleX != viewScaleX) {
0618: double dataX = getViewToDataApproxX(chartInsets.left);
0619: changeZoom(viewScaleX);
0620: changeFitToWindow(false);
0621: changePan(Math.min((long) (((model.getMaxXValue() - model
0622: .getMinXValue()) * this .viewScaleX) - chartWidth),
0623: (long) ((dataX - dataOffsetX) * this .viewScaleX)));
0624: lastViewOffsetXValid = false;
0625: lastScaleXValid = false;
0626: offScreenImageInvalid = true;
0627: doRepaint(true);
0628: } else {
0629: repaint();
0630: }
0631: }
0632:
0633: public double getScale() {
0634: return viewScaleX;
0635: }
0636:
0637: public void setScaleAndOffsetX(double viewScaleX, long viewOffsetX) {
0638: if ((this .viewScaleX != viewScaleX)
0639: || (this .viewOffsetX != viewOffsetX)) {
0640: changeZoom(viewScaleX);
0641: changePan(viewOffsetX);
0642: changeTrackingEnd(false);
0643: changeFitToWindow(false);
0644: lastViewOffsetXValid = false;
0645: lastScaleXValid = false;
0646: offScreenImageInvalid = true;
0647: doRepaint(true);
0648: }
0649: }
0650:
0651: public boolean isSelectionAllowed() {
0652: return allowSelection;
0653: }
0654:
0655: public void setSelectionTracksMovement(
0656: boolean selectionTracksMovement) {
0657: this .selectionTracksMovement = selectionTracksMovement;
0658: }
0659:
0660: public boolean getSelectionTracksMovement() {
0661: return selectionTracksMovement;
0662: }
0663:
0664: public String getTimeAtPosition(int x) {
0665: if ((model == null) || (model.getItemCount() < 2)) {
0666: return null;
0667: }
0668:
0669: if (!useDayInTimeLegend) {
0670: return DateTimeAxisUtils.getMillisValue(
0671: (long) getXValueAtPosition(x), false);
0672: }
0673:
0674: return DateTimeAxisUtils
0675: .getMillisValueFull((long) getXValueAtPosition(x));
0676: }
0677:
0678: // --- Legend customization --------------------------------------------------
0679: public void setTopChartMargin(int topChartMargin) {
0680: if (this .topChartMargin != topChartMargin) {
0681: this .topChartMargin = topChartMargin;
0682:
0683: // TODO: repaint
0684: }
0685: }
0686:
0687: public int getTopChartMargin() {
0688: return topChartMargin;
0689: }
0690:
0691: public void setTrackingEnd(double viewScaleX) {
0692: if (this .viewScaleX != viewScaleX) {
0693: changeZoom(viewScaleX);
0694: lastScaleXValid = false;
0695: }
0696:
0697: setTrackingEnd();
0698: }
0699:
0700: public void setTrackingEnd(boolean trackingEnd) {
0701: if (trackingEnd) {
0702: setTrackingEnd();
0703: } else {
0704: resetTrackingEnd();
0705: }
0706: }
0707:
0708: public void setTrackingEnd() {
0709: if (!trackingEnd) {
0710: changeTrackingEnd(!trailingItemVisible || fitToWindow);
0711: lastViewOffsetXValid = false;
0712:
0713: if (fitToWindow && (viewScaleX == 0)) {
0714: dataWidthAtTrackingEndSwitch = (double) (model
0715: .getMaxXValue() - dataOffsetX); // workaround for Telemetry Overview initialization
0716: }
0717:
0718: changeFitToWindow(false);
0719: offScreenImageInvalid = true;
0720: doRepaint(true);
0721: }
0722: }
0723:
0724: public boolean isTrackingEnd() {
0725: return trackingEnd;
0726: }
0727:
0728: public void setUseSecondaryVerticalAxis(
0729: boolean useSecondaryVerticalAxis) {
0730: this .useSecondaryVerticalAxis = useSecondaryVerticalAxis;
0731: updateOffScreenImageSize();
0732: }
0733:
0734: public boolean getUseSecondaryVerticalAxis() {
0735: return useSecondaryVerticalAxis;
0736: }
0737:
0738: public void setVerticalAxisFont(Font verticalAxisFont) {
0739: if ((verticalAxisFont != null)
0740: && !this .verticalAxisFont.equals(verticalAxisFont)) {
0741: this .verticalAxisFont = verticalAxisFont;
0742:
0743: //verticalAxisFontSmall = verticalAxisFont.deriveFont((float)(verticalAxisFont.getSize() - 2));
0744: // TODO: repaint
0745: }
0746: }
0747:
0748: public Font getVerticalAxisFont() {
0749: return verticalAxisFont;
0750: }
0751:
0752: public void setVerticalAxisValueDivider(int verticalAxisValueDivider) {
0753: this .verticalAxisValueDivider = verticalAxisValueDivider;
0754:
0755: // TODO: check chart margins, repaint
0756: }
0757:
0758: public int getVerticalAxisValueDivider() {
0759: return verticalAxisValueDivider;
0760: }
0761:
0762: public void setVerticalAxisValueDivider2(
0763: int verticalAxisValueDivider2) {
0764: this .verticalAxisValueDivider2 = verticalAxisValueDivider2;
0765:
0766: // TODO: check chart margins, repaint
0767: }
0768:
0769: public int getVerticalAxisValueDivider2() {
0770: return verticalAxisValueDivider2;
0771: }
0772:
0773: public void setVerticalAxisValueString(
0774: String verticalAxisValueString) {
0775: if (verticalAxisValueString == null) {
0776: this .verticalAxisValueString = ""; // NOI18N
0777: } else {
0778: this .verticalAxisValueString = verticalAxisValueString;
0779: }
0780:
0781: // TODO: check chart margins, repaint
0782: }
0783:
0784: public String getVerticalAxisValueString() {
0785: return verticalAxisValueString;
0786: }
0787:
0788: public void setVerticalAxisValueString2(
0789: String verticalAxisValueString2) {
0790: if (verticalAxisValueString2 == null) {
0791: this .verticalAxisValueString2 = ""; // NOI18N
0792: } else {
0793: this .verticalAxisValueString2 = verticalAxisValueString2;
0794: }
0795:
0796: // TODO: check chart margins, repaint
0797: }
0798:
0799: public String getVerticalAxisValueString2() {
0800: return verticalAxisValueString2;
0801: }
0802:
0803: public void setVerticalMeshPaint(Paint verticalMeshPaint) {
0804: if ((verticalMeshPaint != null)
0805: && !this .verticalMeshPaint.equals(verticalMeshPaint)) {
0806: this .verticalMeshPaint = verticalMeshPaint;
0807:
0808: // TODO: repaint
0809: }
0810: }
0811:
0812: public Paint getVerticalMeshPaint() {
0813: return verticalMeshPaint;
0814: }
0815:
0816: public void setVerticalMeshStroke(Stroke verticalMeshStroke) {
0817: if ((verticalMeshStroke != null)
0818: && !this .verticalMeshStroke.equals(verticalMeshStroke)) {
0819: this .verticalMeshStroke = verticalMeshStroke;
0820:
0821: // TODO: repaint
0822: }
0823: }
0824:
0825: public Stroke getVerticalMeshStroke() {
0826: return verticalMeshStroke;
0827: }
0828:
0829: public void setViewOffsetX(long viewOffsetX) {
0830: if (this .viewOffsetX != viewOffsetX) {
0831: changePan(viewOffsetX);
0832: changeTrackingEnd(false);
0833: changeFitToWindow(false);
0834: lastViewOffsetXValid = false;
0835: offScreenImageInvalid = true;
0836: doRepaint(true);
0837: }
0838: }
0839:
0840: public long getViewOffsetX() {
0841: return viewOffsetX;
0842: }
0843:
0844: // conversion of data Y interval from chart coordinates to data coordinates
0845: public double getViewToDataApproxHeight(long height, int seriesIndex) {
0846: // TODO, currently not supported
0847: return 0;
0848: }
0849:
0850: // conversion of data X interval from chart coordinates to data coordinates
0851: public double getViewToDataApproxWidth(long width) {
0852: return width / scaleFactorX;
0853: }
0854:
0855: // conversion of X value from chart coordinates to data coordinates
0856: public double getViewToDataApproxX(long xValue) {
0857: return ((xValue - chartInsets.left + viewOffsetX) / scaleFactorX)
0858: + dataOffsetX;
0859: }
0860:
0861: // conversion of Y value from chart coordinates to data coordinates
0862: public double getViewToDataApproxY(long yValue, int seriesIndex) {
0863: // TODO, currently not supported
0864: return 0;
0865: }
0866:
0867: public boolean isWithinData(int x) {
0868: if ((model == null) || (model.getItemCount() < 2)) {
0869: return false;
0870: }
0871:
0872: int leadingItemIndex = getLeadingItemIndexForPosition(x);
0873:
0874: if (leadingItemIndex == -1) {
0875: return false;
0876: }
0877:
0878: return ((leadingItemIndex >= 0) && (leadingItemIndex < model
0879: .getItemCount()));
0880: }
0881:
0882: public long getYValueAtPosition(int x, int seriesIndex) {
0883: if ((model == null) || (model.getItemCount() < 2)) {
0884: return -1;
0885: }
0886:
0887: double positionTime = getXValueAtPosition(x);
0888:
0889: int leadingItemIndex = getLeadingItemIndexForPosition(x);
0890: int trailingItemIndex = leadingItemIndex + 1;
0891:
0892: if (trailingItemIndex == model.getItemCount()) {
0893: return -1;
0894: }
0895:
0896: long leadingItemValue = model.getYValue(leadingItemIndex,
0897: seriesIndex);
0898:
0899: if (dataType == VALUES_DISCRETE) {
0900: return leadingItemValue;
0901: }
0902:
0903: long trailingItemValue = model.getYValue(trailingItemIndex,
0904: seriesIndex);
0905:
0906: long leadingItemTime = model.getXValue(leadingItemIndex);
0907: long trailingItemTime = model.getXValue(trailingItemIndex);
0908:
0909: double interpolationFactor = (double) (positionTime - leadingItemTime)
0910: / (double) (trailingItemTime - leadingItemTime);
0911:
0912: return (long) ((trailingItemValue - leadingItemValue) * interpolationFactor)
0913: + leadingItemValue;
0914: }
0915:
0916: // --- ChartActionProducer stuff ---------------------------------------------
0917:
0918: /**
0919: * Adds new chartActionListener.
0920: * @param chartActionListener chartActionListener to add
0921: */
0922: public synchronized void addChartActionListener(
0923: ChartActionListener chartActionListener) {
0924: if (chartActionListeners == null) {
0925: chartActionListeners = new Vector();
0926: }
0927:
0928: if (!chartActionListeners.contains(chartActionListener)) {
0929: chartActionListeners.add(chartActionListener);
0930: }
0931: }
0932:
0933: public void adjustmentValueChanged(AdjustmentEvent e) {
0934: if (internalScrollBarChange) {
0935: internalScrollBarChange = false;
0936: } else {
0937: if (autoTrackingEnd
0938: && ((e.getValue() + scrollBar.getModel()
0939: .getExtent()) == scrollBar.getMaximum())) {
0940: setTrackingEnd();
0941: } else {
0942: setViewOffsetX((long) (e.getValue() / scrollBarLongToIntFactor));
0943:
0944: if (!scrollBar.getValueIsAdjusting()
0945: && scrollBarValuesDirty) {
0946: scrollBarValuesDirty = false;
0947: updateScrollBarValues();
0948: }
0949: }
0950: }
0951: }
0952:
0953: // --- Selection stuff -------------------------------------------------------
0954: public void allowSelection() {
0955: allowSelection = true;
0956: }
0957:
0958: // --- JScrollBar support stuff ----------------------------------------------
0959: public void associateJScrollBar(JScrollBar scrollBar) {
0960: deassociateJScrollBar();
0961:
0962: if (scrollBar != null) {
0963: this .scrollBar = scrollBar;
0964: this .scrollBar.addAdjustmentListener(this );
0965: updateScrollBarValues();
0966: }
0967: }
0968:
0969: // Used for public chart update & listener implementation
0970: public void chartDataChanged() {
0971: limitYValue = model.getLimitYValue();
0972: limitYColor = model.getLimitYColor();
0973:
0974: updateScaleFactors();
0975:
0976: fireChartDataChanged();
0977:
0978: if (isShowing()) {
0979: if (trackingEnd && (scrollBar != null)
0980: && scrollBar.getValueIsAdjusting()) {
0981: return;
0982: }
0983:
0984: checkChartMargins();
0985:
0986: if (trackingEnd || fitToWindow || trailingItemVisible
0987: || !lastScaleYValid) {
0988: offScreenImageInvalid = true;
0989: doRepaint(false);
0990: }
0991: } else {
0992: lastViewOffsetXValid = false;
0993: lastScaleXValid = false;
0994: lastScaleYValid = false;
0995: lastTrailingItemIndexValid = false;
0996: lastLeadingItemIsForBuffer = false;
0997: lastLeadingItemIndex = 0;
0998: trailingItemVisible = true;
0999: scrollBarValuesDirty = true;
1000: offScreenImageInvalid = true;
1001:
1002: if (trackingEnd) {
1003: viewOffsetX = -chartWidth + dataViewWidth;
1004: }
1005:
1006: if (!fitToWindow && (model.getItemCount() > 1)) {
1007: updateTrailingItemVisible();
1008: }
1009: }
1010: }
1011:
1012: // --- ComponentListener implementation --------------------------------------
1013: public void componentHidden(ComponentEvent e) {
1014: }
1015:
1016: public void componentMoved(ComponentEvent e) {
1017: }
1018:
1019: public void componentResized(ComponentEvent e) {
1020: updateOffScreenImageSize();
1021: }
1022:
1023: public void componentShown(ComponentEvent e) {
1024: }
1025:
1026: public boolean containsValidData() {
1027: return ((model != null) && (model.getItemCount() > 1));
1028: }
1029:
1030: public void deassociateJScrollBar() {
1031: if (scrollBar != null) {
1032: scrollBar.removeAdjustmentListener(this );
1033: }
1034: }
1035:
1036: public void denySelection() {
1037: allowSelection = false;
1038: }
1039:
1040: // sets zoom & offset according to provided coordinates
1041: public void fitToViewRectangle(int viewX, int viewY, int viewWidth,
1042: int viewHeight) {
1043: double dataX = getViewToDataApproxX(viewX - getInsets().left);
1044: double dataWidth = getViewToDataApproxWidth(viewWidth + 1);
1045:
1046: double newScaleX = chartWidth / dataWidth;
1047:
1048: if (isMaximumZoom() && (newScaleX > maximumZoom)) {
1049: repaint();
1050:
1051: return;
1052: }
1053:
1054: changeZoom(newScaleX);
1055: changeFitToWindow(false);
1056: changePan((long) ((dataX - dataOffsetX) * viewScaleX));
1057: trackingEnd = false;
1058: lastViewOffsetXValid = false;
1059: lastScaleXValid = false;
1060: offScreenImageInvalid = true;
1061: doRepaint(true);
1062: }
1063:
1064: public boolean hasValidDataForPosition(Point point) {
1065: return hasValidDataForPosition(point.x, point.y);
1066: }
1067:
1068: public boolean hasValidDataForPosition(int x, int y) {
1069: return isOverChart(x, y) && isWithinData(x);
1070: }
1071:
1072: // --- Main (Tester Frame) ---------------------------------------------------
1073: public static void main(String[] args) {
1074: SynchronousXYChart xyChart = new SynchronousXYChart(
1075: SynchronousXYChart.TYPE_FILL);
1076:
1077: xyChart.setBorder(BorderFactory.createEmptyBorder(15, 20, 35,
1078: 20));
1079: //xyChart.setBackgroundColor(Color.WHITE);
1080: //xyChart.setFitToWindow();
1081: //xyChart.setTrackingEnd();
1082: xyChart.setPreferredSize(new Dimension(600, 400));
1083:
1084: DynamicSynchronousXYChartModel xyChartModel = new DynamicSynchronousXYChartModel();
1085: xyChartModel.setupModel(new String[] { "Item 1", "Item 2",
1086: "Item 3" }, // NOI18N
1087: new Color[] { Color.RED, Color.GREEN, Color.BLUE });
1088:
1089: JFrame frame = new JFrame("SynchronousXYChart Tester"); // NOI18N
1090: frame.getContentPane().setLayout(new BorderLayout());
1091: frame.getContentPane().add(xyChart, BorderLayout.CENTER);
1092:
1093: JScrollBar scrollBar = new JScrollBar(JScrollBar.HORIZONTAL);
1094: xyChart.associateJScrollBar(scrollBar);
1095:
1096: frame.getContentPane().add(scrollBar, BorderLayout.SOUTH);
1097: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
1098: frame.pack();
1099: frame.setVisible(true);
1100:
1101: xyChartModel.addItemValues(00, new long[] { 30, 20, 10 });
1102: xyChartModel.addItemValues(05, new long[] { 45, 38, 20 });
1103: //for (int i = 0; i < 3000000; i++) Math.sin(i);
1104: xyChartModel.addItemValues(20, new long[] { 62, 61, 30 });
1105: //for (int i = 0; i < 3000000; i++) Math.sin(i);
1106: xyChartModel.addItemValues(50, new long[] { 90, 80, 48 });
1107:
1108: xyChart.setModel(xyChartModel);
1109: }
1110:
1111: public void setVerticalAxisValueAdaptDivider(
1112: boolean verticalAxisValueAdaptDivider) {
1113: this .verticalAxisValueAdaptDivider = verticalAxisValueAdaptDivider;
1114: }
1115:
1116: public boolean isVerticalAxisValueAdaptDivider() {
1117: return verticalAxisValueAdaptDivider;
1118: }
1119:
1120: public void setVerticalAxisValueAdaptDivider2(
1121: boolean verticalAxisValueAdaptDivider2) {
1122: this .verticalAxisValueAdaptDivider2 = verticalAxisValueAdaptDivider2;
1123: }
1124:
1125: public boolean isVerticalAxisValueAdaptDivider2() {
1126: return verticalAxisValueAdaptDivider2;
1127: }
1128:
1129: // --- MouseListener & MouseMotionListener implementation --------------------
1130: public void mouseClicked(MouseEvent e) {
1131: }
1132:
1133: public void mouseDragged(MouseEvent e) {
1134: if (allowSelection && mouseInProgress) {
1135: int mouseX = Math.min((insets.left + drawWidth)
1136: - chartInsets.right - 1, e.getX());
1137:
1138: if (trailingItemVisible) {
1139: mouseX = Math.min(insets.left
1140: + (int) getDataToViewX(model.getXValue(model
1141: .getItemCount() - 1)), mouseX);
1142: }
1143:
1144: int mouseY = Math.min((insets.top + drawHeight)
1145: - chartInsets.bottom, e.getY());
1146:
1147: selectionWidth = mouseX - selectionX;
1148: selectionHeight = mouseY - selectionY - 1;
1149:
1150: doRepaint(false);
1151: }
1152: }
1153:
1154: public void mouseEntered(MouseEvent e) {
1155: }
1156:
1157: public void mouseExited(MouseEvent e) {
1158: }
1159:
1160: public void mouseMoved(MouseEvent e) {
1161: }
1162:
1163: public void mousePressed(MouseEvent e) {
1164: if ((model == null) || (model.getItemCount() < 2)) {
1165: return;
1166: }
1167:
1168: if (allowSelection && (e.getButton() == MouseEvent.BUTTON1)) {
1169: int leftOffset = insets.left + chartInsets.left;
1170: int topOffset = insets.top + chartInsets.top;
1171:
1172: //int mouseX = e.getX();
1173: int mouseX = Math.max(leftOffset, e.getX()); // easier selection on the left side
1174: int mouseY = e.getY();
1175:
1176: // is X coordinate inside chart area?
1177: //boolean mouseXInChart = (mouseX >= leftOffset && mouseX < leftOffset + chartWidth);
1178: boolean mouseXInChart = (mouseX < (leftOffset + chartWidth));
1179:
1180: // is X coordinate on the left of last item?
1181: if (trailingItemVisible) {
1182: mouseXInChart = (mouseXInChart && (mouseX <= (insets.left + (int) getDataToViewX(model
1183: .getXValue(model.getItemCount() - 1)))));
1184: }
1185:
1186: // is X coordinate on the right of first item?
1187: // NOTE: should never happen for Profiler implementation
1188: /*if (trackingEnd) {
1189: long firstItemX = insets.left + (int)getDataToViewX(model.getXValue(0));
1190: if (firstItemX > 0) mouseXInChart = (mouseXInChart && mouseX >= firstItemX);
1191: }*/
1192:
1193: // is Y coordinate inside chart area?
1194: boolean mouseYInChart = ((mouseY >= topOffset) && (mouseY < (topOffset + chartHeight)));
1195:
1196: if (mouseXInChart && mouseYInChart) {
1197: selectionX = mouseX;
1198: selectionWidth = 0;
1199:
1200: selectionY = mouseY;
1201: selectionHeight = mouseY - selectionY - 1;
1202:
1203: mouseInProgress = true;
1204: doRepaint(false);
1205: }
1206: }
1207: }
1208:
1209: public void mouseReleased(MouseEvent e) {
1210: if (allowSelection && mouseInProgress) {
1211: mouseInProgress = false;
1212: performSelectionDone();
1213: }
1214: }
1215:
1216: public void paintComponent(Graphics g) {
1217: // super.paintComponent
1218: super .paintComponent(g);
1219:
1220: //Graphics2D g2 = (Graphics2D) g.create();
1221:
1222: // check if ChartModel is assigned
1223: if (model == null) {
1224: return;
1225: }
1226:
1227: // check if offScreenImage has been created
1228: if (offScreenImage == null) {
1229: updateOffScreenImageSize();
1230: }
1231:
1232: // paint component to the offScreenImage
1233: if (offScreenImageInvalid) {
1234: //long startTime = System.currentTimeMillis();
1235: //for (int i = 0; i < 200; i++)
1236: drawChart(offScreenGraphics);
1237:
1238: //long endTime = System.currentTimeMillis();
1239: //System.out.println((endTime - startTime));
1240: //drawChartAxes(offScreenGraphics);
1241: }
1242:
1243: // paint offScreenImage to the output Graphics
1244: g.drawImage(offScreenImage, insets.left, insets.top, this );
1245:
1246: // paint current selection area
1247: if (allowSelection && mouseInProgress) {
1248: drawSelection((Graphics2D) g);
1249: }
1250: }
1251:
1252: /**
1253: * Removes chartActionListener.
1254: * @param chartActionListener chartActionListener to remove
1255: */
1256: public synchronized void removeChartActionListener(
1257: ChartActionListener chartActionListener) {
1258: if (chartActionListeners != null) {
1259: chartActionListeners.remove(chartActionListener);
1260: }
1261: }
1262:
1263: public void resetChart() {
1264: changeTrackingEnd(false);
1265: changeFitToWindow(false);
1266: changePan(0);
1267: changeZoom(initialZoom);
1268: mouseInProgress = false;
1269: lastViewOffsetXValid = false;
1270: lastScaleXValid = false;
1271: lastScaleYValid = false;
1272: lastLeadingItemIsForBuffer = false;
1273: lastLeadingItemIndex = 0;
1274: trailingItemVisible = true;
1275: scrollBarValuesDirty = true;
1276: offScreenImageInvalid = true;
1277: lastTrailingItemIndexValid = false;
1278: lastTrailingItemIndex = 0;
1279: useDayInTimeLegend = false;
1280: doRepaint(true);
1281: }
1282:
1283: public void resetFitToWindow() {
1284: if (fitToWindow) {
1285: changeFitToWindow(false);
1286: offScreenImageInvalid = true;
1287: doRepaint(true);
1288: }
1289: }
1290:
1291: public void resetTrackingEnd() {
1292: if (trackingEnd) {
1293: changeTrackingEnd(false);
1294: offScreenImageInvalid = true;
1295: doRepaint(true);
1296: }
1297: }
1298:
1299: // --- Initial appearance (without any data) support -------------------------
1300: public void setupInitialAppearance(long firstValueH,
1301: long lastValueH, long firstValueV, long lastValueV) {
1302: customizedEmptyAppearance = true;
1303: this .firstValueH = firstValueH;
1304: this .lastValueH = lastValueH;
1305: this .firstValueV = firstValueV;
1306: this .lastValueV = lastValueV;
1307: }
1308:
1309: public void update(Graphics g) {
1310: }
1311:
1312: /**
1313: * Notifies all listeners about chart zoom.
1314: */
1315: protected void fireChartDataChanged() {
1316: if (chartActionListeners == null) {
1317: return;
1318: }
1319:
1320: Vector toNotify;
1321:
1322: synchronized (this ) {
1323: toNotify = ((Vector) chartActionListeners.clone());
1324: }
1325:
1326: Iterator iterator = toNotify.iterator();
1327:
1328: while (iterator.hasNext()) {
1329: ((ChartActionListener) iterator.next()).chartDataChanged();
1330: }
1331: }
1332:
1333: /**
1334: * Notifies all listeners about fitToWindow change.
1335: */
1336: protected void fireChartFitToWindowChanged() {
1337: if (chartActionListeners == null) {
1338: return;
1339: }
1340:
1341: Vector toNotify;
1342:
1343: synchronized (this ) {
1344: toNotify = ((Vector) chartActionListeners.clone());
1345: }
1346:
1347: Iterator iterator = toNotify.iterator();
1348:
1349: while (iterator.hasNext()) {
1350: ((ChartActionListener) iterator.next())
1351: .chartFitToWindowChanged();
1352: }
1353: }
1354:
1355: /**
1356: * Notifies all listeners about chart pan.
1357: */
1358: protected void fireChartPanned() {
1359: if (chartActionListeners == null) {
1360: return;
1361: }
1362:
1363: Vector toNotify;
1364:
1365: synchronized (this ) {
1366: toNotify = ((Vector) chartActionListeners.clone());
1367: }
1368:
1369: Iterator iterator = toNotify.iterator();
1370:
1371: while (iterator.hasNext()) {
1372: ((ChartActionListener) iterator.next()).chartPanned();
1373: }
1374: }
1375:
1376: /**
1377: * Notifies all listeners about trackingEnd change.
1378: */
1379: protected void fireChartTrackingEndChanged() {
1380: if (chartActionListeners == null) {
1381: return;
1382: }
1383:
1384: Vector toNotify;
1385:
1386: synchronized (this ) {
1387: toNotify = ((Vector) chartActionListeners.clone());
1388: }
1389:
1390: Iterator iterator = toNotify.iterator();
1391:
1392: while (iterator.hasNext()) {
1393: ((ChartActionListener) iterator.next())
1394: .chartTrackingEndChanged();
1395: }
1396: }
1397:
1398: /**
1399: * Notifies all listeners about chart zoom.
1400: */
1401: protected void fireChartZoomed() {
1402: if (chartActionListeners == null) {
1403: return;
1404: }
1405:
1406: Vector toNotify;
1407:
1408: synchronized (this ) {
1409: toNotify = ((Vector) chartActionListeners.clone());
1410: }
1411:
1412: Iterator iterator = toNotify.iterator();
1413:
1414: while (iterator.hasNext()) {
1415: ((ChartActionListener) iterator.next()).chartZoomed();
1416: }
1417: }
1418:
1419: private int getBottomHorizontalAxisLegendHeight() {
1420: return horizontalAxisFont.getSize()
1421: + (verticalAxisFont.getSize() / 2) + 10;
1422: }
1423:
1424: // --- viewOffset to itemIndex conversion routines ---------------------------
1425:
1426: // returns index of first visible item according to provided offsetX
1427: private int getLeadingItemIndex(long offsetX) {
1428: for (int leadingItemIndex = 0; leadingItemIndex < (model
1429: .getItemCount() - 1); leadingItemIndex++) {
1430: if ((long) Math
1431: .ceil((model.getXValue(leadingItemIndex + 1) - dataOffsetX)
1432: * viewScaleX) > offsetX) {
1433: return leadingItemIndex;
1434: }
1435: }
1436:
1437: return model.getItemCount() - 1;
1438: }
1439:
1440: // returns index of first visible item according to provided offsetX, searching starts from lastLeadingItemIndex
1441: private int getLeadingItemIndex(long offsetX,
1442: int lastLeadingItemIndex) {
1443: if (offsetX > lastViewOffsetX) {
1444: // lastViewOffsetX was smaller, searching in forward direction
1445: for (int leadingItemIndex = lastLeadingItemIndex; leadingItemIndex < (model
1446: .getItemCount() - 1); leadingItemIndex++) {
1447: if ((long) Math.ceil((model
1448: .getXValue(leadingItemIndex + 1) - dataOffsetX)
1449: * viewScaleX) > offsetX) {
1450: return leadingItemIndex;
1451: }
1452: }
1453:
1454: return model.getItemCount() - 1;
1455: } else {
1456: // lastViewOffsetX was bigger, searching in backward direction
1457: for (int leadingItemIndex = lastLeadingItemIndex; leadingItemIndex >= 0; leadingItemIndex--) {
1458: if ((long) Math
1459: .ceil((model.getXValue(leadingItemIndex) - dataOffsetX)
1460: * viewScaleX) < offsetX) {
1461: return leadingItemIndex;
1462: }
1463: }
1464:
1465: return 0;
1466: }
1467: }
1468:
1469: // --- chart legend painting stuff -------------------------------------------
1470: private int getLeftVerticalAxisLegendWidth() {
1471: if (isVerticalAxisValueAdaptDivider()) {
1472: return offScreenGraphics
1473: .getFontMetrics(verticalAxisFont)
1474: .stringWidth(
1475: "2000M"
1476: + ((getVerticalAxisValueString() != null) ? getVerticalAxisValueString()
1477: : "")); // NOI18N
1478: }
1479:
1480: return offScreenGraphics.getFontMetrics(verticalAxisFont)
1481: .stringWidth(
1482: getVerticalAxisMarkString(model
1483: .getMaxDisplayYValue(0))) - 10;
1484: }
1485:
1486: private int getRightVerticalAxisLegendWidth() {
1487: if (useSecondaryVerticalAxis) {
1488: if (isVerticalAxisValueAdaptDivider2()) {
1489: return offScreenGraphics
1490: .getFontMetrics(verticalAxisFont)
1491: .stringWidth(
1492: "2000M"
1493: + ((getVerticalAxisValueString2() != null) ? getVerticalAxisValueString2()
1494: : "")); // NOI18N
1495: } else {
1496: return offScreenGraphics.getFontMetrics(
1497: verticalAxisFont).stringWidth(
1498: getVerticalAxisMarkString2(model
1499: .getMaxDisplayYValue(1))) + 7;
1500: }
1501: }
1502:
1503: return offScreenGraphics.getFontMetrics(verticalAxisFont)
1504: .stringWidth("100") - 10; // NOI18N
1505: }
1506:
1507: private String getVerticalAxisMarkString(long mark) {
1508: return mark + verticalAxisValueString;
1509: }
1510:
1511: private String getVerticalAxisMarkString2(long mark) {
1512: return mark + verticalAxisValueString2;
1513: }
1514:
1515: private double getXValueAtPosition(int x) {
1516: return getViewToDataApproxX(x - getInsets().left);
1517: }
1518:
1519: private void changeFitToWindow(boolean newValue) {
1520: if (fitToWindow != newValue) {
1521: fitToWindow = newValue;
1522: SwingUtilities.invokeLater(new Runnable() {
1523: public void run() {
1524: fireChartFitToWindowChanged();
1525: }
1526: });
1527: }
1528: }
1529:
1530: // --- Panning support -------------------------------------------------------
1531: private void changePan(long newValue) {
1532: newValue = Math.max(newValue, 0);
1533:
1534: if (viewOffsetX != newValue) {
1535: viewOffsetX = newValue;
1536: SwingUtilities.invokeLater(new Runnable() {
1537: public void run() {
1538: fireChartPanned();
1539: }
1540: });
1541: }
1542: }
1543:
1544: private void changeTrackingEnd(boolean newValue) {
1545: if (trackingEnd != newValue) {
1546: trackingEnd = newValue;
1547: SwingUtilities.invokeLater(new Runnable() {
1548: public void run() {
1549: fireChartTrackingEndChanged();
1550: }
1551: });
1552: }
1553: }
1554:
1555: private void changeZoom(double newValue) {
1556: if (!fitToWindow) {
1557: newValue = Math.max(newValue, getMinimumZoom());
1558: newValue = Math.min(newValue, getMaximumZoom());
1559: }
1560:
1561: if (viewScaleX != newValue) {
1562: viewScaleX = newValue;
1563: SwingUtilities.invokeLater(new Runnable() {
1564: public void run() {
1565: fireChartZoomed();
1566: }
1567: });
1568: }
1569: }
1570:
1571: private boolean checkBottomChartMargin() {
1572: int bottomHorizontalAxisLegendHeight = getBottomHorizontalAxisLegendHeight();
1573:
1574: if (chartInsets.bottom < bottomHorizontalAxisLegendHeight) {
1575: chartInsets.bottom = bottomHorizontalAxisLegendHeight;
1576:
1577: return true;
1578: }
1579:
1580: return false;
1581: }
1582:
1583: private void checkChartMargins() {
1584: if (checkLeftChartMargin() || checkRightChartMargin()) {
1585: updateOffScreenImageSize();
1586: }
1587: }
1588:
1589: private boolean checkLeftChartMargin() {
1590: int leftVerticalAxisLegendWidth = getLeftVerticalAxisLegendWidth();
1591:
1592: if (chartInsets.left < leftVerticalAxisLegendWidth) {
1593: chartInsets.left = leftVerticalAxisLegendWidth;
1594:
1595: return true;
1596: }
1597:
1598: return false;
1599: }
1600:
1601: private boolean checkRightChartMargin() {
1602: int rightVerticalAxisLegendWidth = getRightVerticalAxisLegendWidth();
1603:
1604: if (chartInsets.right < rightVerticalAxisLegendWidth) {
1605: chartInsets.right = rightVerticalAxisLegendWidth;
1606:
1607: return true;
1608: }
1609:
1610: return false;
1611: }
1612:
1613: // --- component painting stuff ----------------------------------------------
1614: private void doRepaint(boolean rescale) {
1615: scaleFactorsNeedUpdate = rescale;
1616: repaint();
1617: }
1618:
1619: // --- general chart painting stuff ------------------------------------------
1620: private void drawChart(Graphics2D g2) {
1621: // not enough data to draw the chart
1622: if (model.getItemCount() < 2 /*model.getItemCount() < 1*/) {
1623: if (customizedEmptyAppearance) {
1624: drawChartLegendEmpty(g2);
1625: }
1626:
1627: return;
1628: }
1629:
1630: // update scale factors and status flags
1631: if (scaleFactorsNeedUpdate) {
1632: updateScaleFactors();
1633: }
1634:
1635: if (fitToWindow) {
1636: // draws chart that fits the window (not optimized algorithm)
1637: drawChartFitToWindow(g2);
1638: } else {
1639: // draws scaled chart (optimized algorithm, according to useBufferCopy flag)
1640: drawChartFromCurrentViewOffsetX(g2);
1641: }
1642:
1643: // new offscreen buffer is now valid
1644: offScreenImageInvalid = false;
1645: }
1646:
1647: // --- fit to window painting stuff ------------------------------------------
1648:
1649: // draws chart scaled to fit the component size
1650: private void drawChartFitToWindow(Graphics2D g2) {
1651: // save current clip
1652: Shape clip = g2.getClip();
1653:
1654: // set current clip to whole graph area
1655: g2.setClip(chartInsets.left, chartInsets.top, chartWidth,
1656: chartHeight);
1657:
1658: int thresholdY = Integer.MAX_VALUE;
1659:
1660: if (limitYValue != Long.MAX_VALUE) {
1661: thresholdY = (int) getDataToViewY(limitYValue, 0);
1662: }
1663:
1664: if ((thresholdY != Integer.MAX_VALUE)
1665: && (thresholdY >= chartInsets.top)) {
1666: // fill the threshold area
1667: g2.setPaint(limitYColor);
1668: g2.fillRect(chartInsets.left, chartInsets.top, chartWidth,
1669: thresholdY - chartInsets.top + 1);
1670: // clear rest of the graph area
1671: g2.setPaint(chartPaint);
1672: g2.fillRect(chartInsets.left, thresholdY + 1, chartWidth,
1673: chartHeight - thresholdY + chartInsets.top);
1674: } else {
1675: // whole graph area needs to be cleared using user-defined background color
1676: g2.setPaint(chartPaint);
1677: g2.fillRect(chartInsets.left, chartInsets.top, chartWidth,
1678: chartHeight);
1679: }
1680:
1681: // --- Support for displaying garbage collections
1682: if (model instanceof AsyncMarksProvider) {
1683: AsyncMarksProvider amp = (AsyncMarksProvider) model;
1684: Color markColor = amp.getMarkColor();
1685: int marksCount = amp.getMarksCount();
1686:
1687: int lastXEnd = Integer.MIN_VALUE;
1688:
1689: for (int i = 0; i < marksCount; i++) {
1690: long markBegin = amp.getMarkStart(i);
1691: long markEnd = amp.getMarkEnd(i);
1692:
1693: int xBegin = (int) getDataToViewX(markBegin);
1694: int xEnd = Math.max((int) getDataToViewX(markEnd),
1695: xBegin + 1);
1696:
1697: if (xEnd > lastXEnd) {
1698: xBegin = Math.max(xBegin, lastXEnd);
1699:
1700: int xWidth = xEnd - xBegin;
1701:
1702: if (xWidth > 0) {
1703: g2.setColor(markColor);
1704: g2.fillRect(xBegin, chartInsets.top, xEnd
1705: - xBegin, chartHeight);
1706: }
1707:
1708: lastXEnd = xEnd;
1709: }
1710: }
1711: }
1712:
1713: // ---
1714: long startX;
1715: long endX;
1716:
1717: // for every item draw series area
1718: for (int itemIndex = 1; itemIndex < model.getItemCount(); itemIndex++) {
1719: // use last x-coordinate
1720: startX = getDataToViewX(model.getXValue(itemIndex - 1));
1721: endX = getDataToViewX(model.getXValue(itemIndex));
1722:
1723: for (int seriesIndex = 0; seriesIndex < model
1724: .getSeriesCount(); seriesIndex++) {
1725: drawSeriesItem(g2, model.getSeriesColor(seriesIndex),
1726: startX, getDataToViewY(model.getYValue(
1727: itemIndex - 1, seriesIndex),
1728: seriesIndex), endX,
1729: getDataToViewY(model.getYValue(itemIndex,
1730: seriesIndex), seriesIndex));
1731: }
1732: }
1733:
1734: drawChartLegend(g2, g2.getClip(), 0, chartWidth - 1);
1735:
1736: // restore original clip
1737: g2.setClip(clip);
1738: }
1739:
1740: // --- scaled chart painting stuff -------------------------------------------
1741:
1742: // draws scaled chart from current viewOffsetX
1743: private void drawChartFromCurrentViewOffsetX(Graphics2D g2) {
1744: drawChartFromOffset(g2, viewOffsetX);
1745: }
1746:
1747: // draws scaled chart from provided viewOffsetX
1748: private void drawChartFromOffset(Graphics2D g2, long offsetX) {
1749: // save current clip
1750: Shape clip = g2.getClip();
1751:
1752: // new leading item index to compute
1753: int leadingItemIndex;
1754:
1755: if (lastViewOffsetXValid) { // viewOffsetX didn't changed since last iteration
1756:
1757: if (trailingItemVisible || !lastScaleYValid) {
1758: if (lastLeadingItemIsForBuffer) {
1759: // if bufferCopy optimization is used, lastLeadingItemIndex is not valid, current leadingItemIndex must be found from the beginning
1760: leadingItemIndex = getLeadingItemIndex(offsetX);
1761: } else {
1762: if (lastScaleYValid && lastTrailingItemIndexValid) {
1763: // lastLeadingItemIndex (index of last item from last iteration) is used as leadingItemIndex
1764: leadingItemIndex = lastTrailingItemIndex;
1765: } else {
1766: leadingItemIndex = getLeadingItemIndex(offsetX,
1767: lastLeadingItemIndex);
1768: }
1769: }
1770:
1771: drawChartFromOffset(g2, leadingItemIndex,
1772: (int) getDataToViewX(model
1773: .getXValue(leadingItemIndex))
1774: - chartInsets.left, chartWidth); // some data-tail changed in visible area since last iteration
1775: //System.err.println(System.currentTimeMillis() + " Drawing from last offsetX from " + ((int)getDataToViewX(model.getXValue(leadingItemIndex)) - chartInsets.left) + " to " + chartWidth);
1776:
1777: if (lastLeadingItemIsForBuffer) {
1778: lastLeadingItemIndex = leadingItemIndex;
1779: }
1780:
1781: lastLeadingItemIsForBuffer = false;
1782: lastTrailingItemIndex = model.getItemCount() - 1;
1783: lastTrailingItemIndexValid = true;
1784:
1785: if (trackingEnd) {
1786: lastViewOffsetXValid = false;
1787: }
1788: } else {
1789: lastTrailingItemIndexValid = false;
1790: }
1791: } else {
1792: lastTrailingItemIndexValid = false;
1793:
1794: if (lastScaleXValid) { // X-scale didn't change sice last iteration but viewOffsetX did, chart area needs to be repainted ("moved" left / right)
1795:
1796: if (lastScaleYValid) { // scaleFactorsY didn't change, not-changed-areas can be just "moved" left / right
1797:
1798: if (viewOffsetX > lastViewOffsetX) { // right-move
1799: // area that needs to be redrawn
1800:
1801: int dirtyWidth = (int) (viewOffsetX - lastViewOffsetX);
1802: int copyWidth = (int) (trailingItemVisible ? getDataToViewX(model
1803: .getMaxXValue())
1804: : (chartWidth - dirtyWidth));
1805:
1806: if ((copyWidth > 0) && (dirtyWidth > 0)
1807: && (chartHeight > 0)) {
1808: // copy not-changed-areas to the left
1809: if (copyAccel == COPY_ACCEL_RASTER) {
1810: // BufferedImage.getRaster().setDataElements() used, seems to have better performance on Windows (HW acceleration)
1811: int rasterWidth = offScreenImage
1812: .getRaster().getWidth();
1813: int rasterHeight = offScreenImage
1814: .getRaster().getHeight();
1815: int startX = chartInsets.left
1816: + dirtyWidth;
1817: int startY = chartInsets.top;
1818:
1819: if ((startX >= 0)
1820: && ((startX + copyWidth) <= rasterWidth)
1821: && (startY >= 0)
1822: && ((startY + chartHeight) <= rasterHeight)
1823: && (chartInsets.left >= 0)
1824: && ((chartInsets.left + copyWidth) <= rasterWidth)
1825: && (chartInsets.top >= 0)
1826: && ((chartInsets.top + chartHeight) <= rasterHeight)) {
1827: Raster raster = offScreenImage
1828: .getRaster()
1829: .createWritableChild(
1830: startX, startY,
1831: copyWidth,
1832: chartHeight, 0, 0,
1833: null);
1834: offScreenImage.getRaster()
1835: .setDataElements(
1836: chartInsets.left,
1837: chartInsets.top,
1838: raster);
1839: }
1840: } else {
1841: // Graphics.copyArea() used, optimal for UNIXes, works well also for Windows (default)
1842: g2.copyArea(chartInsets.left
1843: + dirtyWidth, chartInsets.top,
1844: copyWidth, chartHeight,
1845: -dirtyWidth, 0);
1846: }
1847: }
1848:
1849: // update selection boundary start according to chart movement when tracking end
1850: if (selectionTracksMovement && mouseInProgress
1851: && trackingEnd) {
1852: int delta = Math.min(selectionX
1853: - chartInsets.left
1854: - getInsets().left, dirtyWidth);
1855: selectionX -= delta;
1856: selectionWidth += delta;
1857: }
1858:
1859: // update dirty area on the right side
1860: if (lastLeadingItemIsForBuffer) {
1861: leadingItemIndex = getLeadingItemIndex(
1862: offsetX + copyWidth,
1863: lastLeadingItemIndex);
1864: } else {
1865: leadingItemIndex = getLeadingItemIndex(offsetX);
1866: }
1867:
1868: drawChartFromOffset(g2, leadingItemIndex,
1869: copyWidth, chartWidth);
1870: //System.err.println(System.currentTimeMillis() + " Drawing on right from " + copyWidth + " to " + chartWidth);
1871: lastLeadingItemIsForBuffer = true;
1872: lastLeadingItemIndex = leadingItemIndex;
1873: } else { // left-move
1874:
1875: // area that needs to be redrawn
1876: int dirtyWidth = (int) (lastViewOffsetX - viewOffsetX);
1877: int copyWidth = chartWidth - dirtyWidth;
1878:
1879: if ((copyWidth > 0) && (dirtyWidth > 0)
1880: && (chartHeight > 0)) {
1881: // copy not-changed-areas to the right
1882: if (copyAccel == COPY_ACCEL_RASTER) {
1883: // BufferedImage.getRaster().setDataElements() used, seems to have better performance on Windows (HW acceleration)
1884: int rasterWidth = offScreenImage
1885: .getRaster().getWidth();
1886: int rasterHeight = offScreenImage
1887: .getRaster().getHeight();
1888: int startX = chartInsets.left
1889: + dirtyWidth;
1890: int startY = chartInsets.top;
1891:
1892: if ((startX >= 0)
1893: && ((startX + copyWidth) <= rasterWidth)
1894: && (startY >= 0)
1895: && ((startY + chartHeight) <= rasterHeight)
1896: && (chartInsets.left >= 0)
1897: && ((chartInsets.left + copyWidth) <= rasterWidth)
1898: && (chartInsets.top >= 0)
1899: && ((chartInsets.top + chartHeight) <= rasterHeight)) {
1900: Raster raster = offScreenImage
1901: .getRaster()
1902: .createWritableChild(
1903: chartInsets.left,
1904: chartInsets.top,
1905: copyWidth,
1906: chartHeight, 0, 0,
1907: null);
1908: offScreenImage.getRaster()
1909: .setDataElements(startX,
1910: startY, raster);
1911: }
1912: } else {
1913: // Graphics.copyArea() used, optimal for UNIXes, works well also for Windows (default)
1914: g2.copyArea(chartInsets.left,
1915: chartInsets.top, copyWidth,
1916: chartHeight, dirtyWidth, 0);
1917: }
1918: }
1919:
1920: // update dirty area on the left side
1921: if (lastLeadingItemIsForBuffer) {
1922: leadingItemIndex = getLeadingItemIndex(
1923: offsetX, lastLeadingItemIndex);
1924: } else {
1925: leadingItemIndex = getLeadingItemIndex(offsetX);
1926: }
1927:
1928: drawChartFromOffset(g2, leadingItemIndex, 0,
1929: dirtyWidth);
1930: //System.err.println(System.currentTimeMillis() + " Drawing on left from " + 0 + " to " + dirtyWidth);
1931: lastLeadingItemIsForBuffer = true;
1932: lastLeadingItemIndex = leadingItemIndex;
1933: }
1934: } else {
1935: if (lastLeadingItemIsForBuffer) {
1936: // if bufferCopy optimization is used, lastLeadingItemIndex is not valid, current leadingItemIndex must be found from the beginning
1937: leadingItemIndex = getLeadingItemIndex(offsetX);
1938: } else {
1939: // lastLeadingItemIndex is valid, it can be used to find current leadingItemIndex
1940: leadingItemIndex = getLeadingItemIndex(offsetX,
1941: lastLeadingItemIndex);
1942: }
1943:
1944: drawChartFromOffset(g2, leadingItemIndex, 0,
1945: chartWidth);
1946: //System.err.println(System.currentTimeMillis() + " Drawing from scratch (y changed) from " + 0 + " to " + chartWidth);
1947: lastLeadingItemIsForBuffer = false;
1948: lastLeadingItemIndex = leadingItemIndex;
1949: }
1950: } else { // whole chart area needs to be repainted from the beginning without any optimizations
1951: leadingItemIndex = getLeadingItemIndex(offsetX);
1952: drawChartFromOffset(g2, leadingItemIndex, 0, chartWidth);
1953: //System.err.println(System.currentTimeMillis() + " Drawing from scratch (x changed) from " + 0 + " to " + chartWidth);
1954: lastLeadingItemIsForBuffer = false;
1955: lastLeadingItemIndex = leadingItemIndex;
1956: }
1957: }
1958:
1959: lastViewOffsetX = offsetX; // update lastViewOffsetX value
1960:
1961: lastViewOffsetXValid = true; // lastViewOffsetXValid is now valid
1962: lastScaleXValid = true; // lastScaleXValid is now valid
1963: lastScaleYValid = true; // lastScaleYValid is now valid
1964:
1965: updateTrailingItemVisible();
1966:
1967: // restore original clip
1968: g2.setClip(clip);
1969: }
1970:
1971: // draws scaled beginning from the provided item index, with defined horizontal clip bounds
1972: private void drawChartFromOffset(Graphics2D g2,
1973: int leadingItemIndex, int startClipX, int endClipX) {
1974: startClipX = Math.max(0, startClipX);
1975: endClipX = Math.min(chartWidth, endClipX);
1976:
1977: // set clip valid for current drawing
1978: g2.setClip(chartInsets.left + startClipX, chartInsets.top,
1979: endClipX - startClipX, chartHeight);
1980:
1981: int thresholdY = Integer.MAX_VALUE;
1982:
1983: if (limitYValue != Long.MAX_VALUE) {
1984: thresholdY = (int) getDataToViewY(limitYValue, 0);
1985: }
1986:
1987: if ((thresholdY != Integer.MAX_VALUE)
1988: && (thresholdY >= chartInsets.top)) {
1989: // fill the threshold area
1990: g2.setPaint(limitYColor);
1991: g2.fillRect(chartInsets.left + startClipX, chartInsets.top,
1992: endClipX - startClipX, thresholdY - chartInsets.top
1993: + 1);
1994: // clear rest of the graph area
1995: g2.setPaint(chartPaint);
1996: g2.fillRect(chartInsets.left + startClipX, thresholdY + 1,
1997: endClipX - startClipX, chartHeight - thresholdY
1998: + chartInsets.top);
1999: } else {
2000: // whole graph area needs to be cleared using user-defined background color
2001: g2.setPaint(chartPaint);
2002: g2.fillRect(chartInsets.left + startClipX, chartInsets.top,
2003: endClipX - startClipX, chartHeight);
2004: }
2005:
2006: // --- Support for displaying garbage collections
2007: if (model instanceof AsyncMarksProvider) {
2008: AsyncMarksProvider amp = (AsyncMarksProvider) model;
2009: Color markColor = amp.getMarkColor();
2010: int marksCount = amp.getMarksCount();
2011:
2012: int lastXEnd = Integer.MIN_VALUE;
2013:
2014: long firstMarkTime = model.getXValue(leadingItemIndex);
2015:
2016: for (int i = 0; i < marksCount; i++) {
2017: long markEnd = amp.getMarkEnd(i);
2018:
2019: if (markEnd >= firstMarkTime) {
2020: long markBegin = amp.getMarkStart(i);
2021: int xBegin = (int) getDataToViewX(markBegin);
2022:
2023: if (xBegin > (chartInsets.left + endClipX + 1)) {
2024: break;
2025: }
2026:
2027: int xEnd = Math.max((int) getDataToViewX(markEnd),
2028: xBegin + 1);
2029:
2030: if (xEnd > lastXEnd) {
2031: xBegin = Math.max(xBegin, lastXEnd);
2032:
2033: int xWidth = xEnd - xBegin;
2034:
2035: if (xWidth > 0) {
2036: g2.setColor(markColor);
2037: g2.fillRect(xBegin, chartInsets.top, xEnd
2038: - xBegin, chartHeight);
2039: }
2040:
2041: lastXEnd = xEnd;
2042: }
2043: }
2044: }
2045: }
2046:
2047: // ---
2048:
2049: // previous item must be included due to line joining & vertical lines
2050: leadingItemIndex = ((type == TYPE_LINE) ? Math.max(0,
2051: leadingItemIndex - 1) : leadingItemIndex);
2052:
2053: // first and second x-coordinate
2054: long startX;
2055: long endX;
2056:
2057: // for every item draw series area
2058: for (int itemIndex = leadingItemIndex + 1; itemIndex < model
2059: .getItemCount(); itemIndex++) {
2060: // use last x-coordinate
2061: startX = getDataToViewX(model.getXValue(itemIndex - 1));
2062: endX = getDataToViewX(model.getXValue(itemIndex));
2063:
2064: for (int seriesIndex = 0; seriesIndex < model
2065: .getSeriesCount(); seriesIndex++) {
2066: drawSeriesItem(g2, model.getSeriesColor(seriesIndex),
2067: startX, getDataToViewY(model.getYValue(
2068: itemIndex - 1, seriesIndex),
2069: seriesIndex), endX,
2070: getDataToViewY(model.getYValue(itemIndex,
2071: seriesIndex), seriesIndex));
2072: }
2073:
2074: // if the last drawn item ends out of visible area, there won't be any changes/redraw needed in next iteration until viewOffset or viewScale changes
2075: if (endX > (chartInsets.left + endClipX + 1)) {
2076: break;
2077: }
2078: }
2079:
2080: // draw chart legend
2081: drawChartLegend(g2, g2.getClip(), startClipX, endClipX);
2082: }
2083:
2084: private void drawChartLegend(Graphics2D g2, Shape chartClip,
2085: int startClipX, int endClipX) {
2086: // use for absolute time:
2087: long firstValueH = (long) getViewToDataApproxWidth(viewOffsetX)
2088: + model.getMinXValue();
2089: long lastValueH = (long) getViewToDataApproxWidth(viewOffsetX
2090: + chartWidth)
2091: + model.getMinXValue();
2092: // use for relative time:
2093: //long firstValueH = (long)Math.floor(getViewToDataApproxWidth(viewOffsetX));
2094: //long lastValueH = (long)Math.floor(getViewToDataApproxWidth(viewOffsetX + chartWidth));
2095: drawHorizontalChartLegend(g2, chartClip, startClipX, endClipX,
2096: firstValueH, lastValueH);
2097:
2098: double firstValueV = model.getMinDisplayYValue(0)
2099: / (double) verticalAxisValueDivider;
2100: double lastValueV = model.getMaxDisplayYValue(0)
2101: / (double) verticalAxisValueDivider;
2102: drawVerticalChartLegend(g2, chartClip, startClipX, endClipX,
2103: firstValueV, lastValueV);
2104: }
2105:
2106: private void drawChartLegendEmpty(Graphics2D g2) {
2107: // save current clip
2108: Shape clip = g2.getClip();
2109:
2110: // set current clip to whole graph area
2111: g2.setClip(chartInsets.left, chartInsets.top, chartWidth,
2112: chartHeight);
2113:
2114: Shape newClip = g2.getClip();
2115:
2116: // whole graph area needs to be cleared using user-defined background color
2117: g2.setPaint(chartPaint);
2118: g2.fillRect(chartInsets.left, chartInsets.top, chartWidth,
2119: chartHeight);
2120:
2121: drawHorizontalChartLegend(g2, newClip, 0, chartWidth - 1,
2122: firstValueH, lastValueH);
2123: drawVerticalChartLegend(g2, newClip, 0, chartWidth - 1,
2124: firstValueV, lastValueV);
2125:
2126: // restore original clip
2127: g2.setClip(clip);
2128: }
2129:
2130: // draws one series item using filled segment
2131: private void drawFillSeriesItem(Graphics2D g2, Color color, int x1,
2132: int y1, int x2, int y2) {
2133: g2.setColor(color);
2134: g2.setStroke(chartStroke);
2135:
2136: if (dataType == VALUES_INTERPOLATED) {
2137: Polygon polygon = new Polygon(new int[] { x1, x1, x2, x2 },
2138: new int[] { y1, chartHeight + chartInsets.top,
2139: chartHeight + chartInsets.top, y2 }, 4);
2140: g2.fill(polygon);
2141: } else if (dataType == VALUES_DISCRETE) {
2142: Polygon polygon = new Polygon(new int[] { x1, x1, x2, x2 },
2143: new int[] { y1, chartHeight + chartInsets.top,
2144: chartHeight + chartInsets.top, y1 }, 4);
2145: g2.fill(polygon);
2146: }
2147: }
2148:
2149: private void drawHorizontalAxisLegendSegment(Graphics2D g2,
2150: long currentMark, int x) {
2151: g2.setClip(horizontalAxisMarksClip);
2152: g2.setPaint(horizontalAxisPaint);
2153: g2.drawLine(x, chartInsets.top + chartHeight + 1, x,
2154: chartInsets.top + chartHeight + 4);
2155:
2156: g2.setClip(horizontalAxisClip);
2157: paintHorizontalTimeMarkString(g2, currentMark, x);
2158: }
2159:
2160: private void drawHorizontalChartLegend(Graphics2D g2,
2161: Shape chartClip, int startClipX, int endClipX,
2162: long firstValue, long lastValue) {
2163: // set horizontal axis marks clip
2164: g2.setClip(horizontalAxisMarksClip);
2165:
2166: // clear horizontal axis marks area
2167: g2.setPaint(backgroundPaint);
2168: g2.fillRect(horizontalAxisMarksClip.x,
2169: horizontalAxisMarksClip.y,
2170: horizontalAxisMarksClip.width,
2171: horizontalAxisMarksClip.height);
2172:
2173: // set horizontal axis clip
2174: g2.setClip(horizontalAxisClip);
2175:
2176: // clear horizontal axis area
2177: g2.setPaint(backgroundPaint);
2178: g2.fillRect(horizontalAxisClip.x, horizontalAxisClip.y,
2179: horizontalAxisClip.width, horizontalAxisClip.height);
2180:
2181: if ((lastValue - firstValue) > 0) {
2182: double factor = (double) chartWidth
2183: / (double) (lastValue - firstValue);
2184: optimalUnits = DateTimeAxisUtils
2185: .getOptimalUnits(viewScaleX);
2186:
2187: long firstMark = Math.max((firstValue / optimalUnits)
2188: * optimalUnits, 0);
2189: long currentMark = firstMark;
2190:
2191: while (currentMark <= lastValue) {
2192: if (currentMark >= firstValue) {
2193: long currentMarkRel = currentMark - firstValue;
2194: int markPosition = (int) Math.floor(currentMarkRel
2195: * factor)
2196: + chartInsets.left;
2197:
2198: drawVerticalMeshSegment(g2, chartClip,
2199: markPosition, chartInsets.top,
2200: markPosition, chartInsets.top + chartHeight);
2201:
2202: drawHorizontalAxisLegendSegment(g2, currentMark,
2203: markPosition);
2204: }
2205:
2206: currentMark += optimalUnits;
2207: }
2208: }
2209: }
2210:
2211: private void drawHorizontalMeshSegment(Graphics2D g2,
2212: Shape chartClip, int x1, int y1, int x2, int y2) {
2213: g2.setClip(chartClip);
2214: g2.setPaint(verticalMeshPaint);
2215: g2.setStroke(verticalMeshStroke);
2216: g2.drawLine(x1, y1, x2, y2);
2217: }
2218:
2219: // draws one series item using line segment
2220: private void drawLineSeriesItem(Graphics2D g2, Color color, int x1,
2221: int y1, int x2, int y2) {
2222: g2.setColor(color);
2223: g2.setStroke(chartStroke);
2224:
2225: if (dataType == VALUES_INTERPOLATED) {
2226: g2.drawLine(x1, y1, x2, y2);
2227: } else if (dataType == VALUES_DISCRETE) {
2228: g2.drawLine(x1, y1, x2, y1);
2229: g2.drawLine(x2, y1, x2, y2);
2230: }
2231: }
2232:
2233: // --- selection painting stuff ----------------------------------------------
2234: private void drawSelection(Graphics2D g2) {
2235: int selectionTop = insets.top + chartInsets.top;
2236: int selectionBottom = (selectionTop + chartHeight) - 1;
2237:
2238: if (selectionWidth < 0) {
2239: if (fitToWindow) {
2240: return;
2241: }
2242:
2243: Shape clip = g2.getClip();
2244: Insets componentInsets = getInsets();
2245: g2.setClip(componentInsets.left, componentInsets.top,
2246: drawWidth, drawHeight);
2247:
2248: g2.setFont(getFont());
2249:
2250: g2.setColor(evenSelectionSegmentsColor);
2251: g2.setStroke(evenSelectionSegmentsStroke);
2252: g2.drawLine(selectionX, selectionTop, selectionX,
2253: selectionBottom);
2254:
2255: g2.setColor(oddSelectionSegmentColor);
2256: g2.setStroke(oddSelectionSegmentStroke);
2257: g2.drawLine(selectionX, selectionTop, selectionX,
2258: selectionBottom);
2259:
2260: g2.setColor(Color.WHITE);
2261: g2.drawString(FIT_TO_WINDOW_STRING, selectionX
2262: + selectionWidth + 1,
2263: (selectionY + selectionHeight + 1) - 5);
2264: g2.setColor(Color.BLACK);
2265: g2.drawString(FIT_TO_WINDOW_STRING, selectionX
2266: + selectionWidth,
2267: (selectionY + selectionHeight) - 5);
2268:
2269: g2.setClip(clip);
2270:
2271: return;
2272: }
2273:
2274: g2.setColor(evenSelectionSegmentsColor);
2275: g2.setStroke(evenSelectionSegmentsStroke);
2276: g2.drawLine(selectionX, selectionTop, selectionX
2277: + selectionWidth, selectionTop);
2278: g2.drawLine(selectionX + selectionWidth, selectionTop,
2279: selectionX + selectionWidth, selectionBottom);
2280: g2.drawLine(selectionX, selectionBottom, selectionX
2281: + selectionWidth, selectionBottom);
2282: g2.drawLine(selectionX, selectionTop, selectionX,
2283: selectionBottom);
2284:
2285: g2.setColor(oddSelectionSegmentColor);
2286: g2.setStroke(oddSelectionSegmentStroke);
2287: g2.drawLine(selectionX, selectionTop, selectionX
2288: + selectionWidth, selectionTop);
2289: g2.drawLine(selectionX + selectionWidth, selectionTop,
2290: selectionX + selectionWidth, selectionBottom);
2291: g2.drawLine(selectionX, selectionBottom, selectionX
2292: + selectionWidth, selectionBottom);
2293: g2.drawLine(selectionX, selectionTop, selectionX,
2294: selectionBottom);
2295: }
2296:
2297: // --- chart primitives painting stuff ---------------------------------------
2298:
2299: // draws one series item according to chart type
2300: private void drawSeriesItem(Graphics2D g2, Color color, long x1,
2301: long y1, long x2, long y2) {
2302: // workaround for Java Bug [ID:4755500] - calling Math.round(NaN) can break subsequent calls to Math.round()
2303: // which happens when some value for drawing exceeds 1000000, resulting in sun.dc.pr.PRException: endPath: bad path
2304: if ((Math.abs(x1) > 1000000) || (Math.abs(x2) > 1000000)) {
2305: x1 = Math.max(-1000000, x1);
2306: x1 = Math.min(1000000, x1);
2307: x2 = Math.max(-1000000, x2);
2308: x2 = Math.min(1000000, x2);
2309: }
2310:
2311: if (type == TYPE_FILL) {
2312: drawFillSeriesItem(g2, color, (int) x1, (int) y1, (int) x2,
2313: (int) y2);
2314: } else if (type == TYPE_LINE) {
2315: drawLineSeriesItem(g2, color, (int) x1, (int) y1, (int) x2,
2316: (int) y2);
2317: }
2318: }
2319:
2320: private void drawVerticalAxisLegendSegment(Graphics2D g2,
2321: long currentMark, long optimalUnits, int y) {
2322: if ("%".equals(verticalAxisValueString) && (currentMark > 100)) {
2323: return; // NOI18N // Ugly workaround not to display relative values over 100%
2324: }
2325:
2326: g2.setClip(verticalAxisClip);
2327: g2.setPaint(verticalAxisPaint);
2328: g2.drawLine(chartInsets.left - 4, y, chartInsets.left, y);
2329:
2330: paintVerticalTimeMarkString(g2, currentMark, optimalUnits, y);
2331: }
2332:
2333: private void drawVerticalAxisLegendSegment2(Graphics2D g2,
2334: long currentMark, long optimalUnits, int y) {
2335: if ("%".equals(verticalAxisValueString2) && (currentMark > 100)) {
2336: return; // NOI18N // Ugly workaround not to display relative values over 100%
2337: }
2338:
2339: g2.setClip(verticalAxisClip2);
2340: g2.setPaint(verticalAxisPaint);
2341: g2.drawLine(chartInsets.left + chartWidth, y, chartInsets.left
2342: + chartWidth + 3, y);
2343:
2344: paintVerticalTimeMarkString2(g2, currentMark, optimalUnits, y);
2345: }
2346:
2347: private void drawVerticalChartLegend(Graphics2D g2,
2348: Shape chartClip, int startClipX, int endClipX,
2349: double firstValue, double lastValue) {
2350: if (!lastScaleYValid) {
2351: // set horizontal axis clip
2352: g2.setClip(verticalAxisClip);
2353:
2354: // clear horizontal axis area
2355: g2.setPaint(backgroundPaint);
2356: g2.fillRect(verticalAxisClip.x, verticalAxisClip.y,
2357: verticalAxisClip.width, verticalAxisClip.height);
2358: }
2359:
2360: long div = verticalAxisValueDivider;
2361: String tmp = verticalAxisValueString;
2362:
2363: if (verticalAxisValueAdaptDivider) {
2364: if ((model.getMaxDisplayYValue(0) > 2000000000L)
2365: && (div < (1024 * 1024 * 1024L))) {
2366: div = 1024 * 1024 * 1024L;
2367: verticalAxisValueString = "G" + verticalAxisValueString; // NOI18N
2368: } else if ((model.getMaxDisplayYValue(0) > 2000000L)
2369: && (div < (1024 * 1024L))) {
2370: div = 1024 * 1024L;
2371: verticalAxisValueString = "M" + verticalAxisValueString; // NOI18N
2372: } else if ((model.getMaxDisplayYValue(0) > 2000)
2373: && (div < 1024)) {
2374: div = 1024;
2375: verticalAxisValueString = "K" + verticalAxisValueString; // NOI18N
2376: }
2377:
2378: firstValue = firstValue / (double) div;
2379: lastValue = lastValue / (double) div;
2380: }
2381:
2382: if ((lastValue - firstValue) > 0) {
2383: double factor = (double) (chartHeight - topChartMargin)
2384: / (lastValue - firstValue);
2385: optimalUnits = DecimalAxisUtils.getOptimalUnits(factor,
2386: minimumVerticalMarksDistance);
2387:
2388: if (optimalUnits > 0) {
2389: long firstMark = Math.max((long) (Math.ceil(firstValue
2390: / optimalUnits) * optimalUnits), 0);
2391: long currentMark = firstMark;
2392:
2393: double currentMarkRel = currentMark - firstValue;
2394: int markPosition = (chartInsets.top + chartHeight)
2395: - (int) (currentMarkRel * factor);
2396:
2397: while (markPosition >= chartInsets.top) {
2398: if (markPosition <= (chartInsets.top + chartHeight)) {
2399: drawHorizontalMeshSegment(g2, chartClip,
2400: startClipX + chartInsets.left,
2401: markPosition, endClipX
2402: + chartInsets.left,
2403: markPosition);
2404:
2405: if (!lastScaleYValid) {
2406: drawVerticalAxisLegendSegment(g2,
2407: currentMark, optimalUnits,
2408: markPosition);
2409: }
2410: }
2411:
2412: currentMark += optimalUnits;
2413: currentMarkRel = currentMark - firstValue;
2414: markPosition = (chartInsets.top + chartHeight)
2415: - (int) (currentMarkRel * factor);
2416: }
2417: }
2418: }
2419:
2420: verticalAxisValueString = tmp;
2421:
2422: if (useSecondaryVerticalAxis && !lastScaleYValid) {
2423: // set horizontal axis clip
2424: g2.setClip(verticalAxisClip2);
2425:
2426: // clear horizontal axis area
2427: g2.setPaint(backgroundPaint);
2428: g2.fillRect(verticalAxisClip2.x, verticalAxisClip2.y,
2429: verticalAxisClip2.width, verticalAxisClip2.height);
2430:
2431: div = verticalAxisValueDivider2;
2432: tmp = verticalAxisValueString2;
2433:
2434: if (isVerticalAxisValueAdaptDivider2()) {
2435: if ((model.getMaxDisplayYValue(1) > 2000000000L)
2436: && (div < (1024 * 1024 * 1024L))) {
2437: div = 1024 * 1024 * 1024L;
2438: verticalAxisValueString2 = "G"
2439: + verticalAxisValueString2; // NOI18N
2440: } else if ((model.getMaxDisplayYValue(1) > 2000000L)
2441: && (div < (1024 * 1024L))) {
2442: div = 1024 * 1024L;
2443: verticalAxisValueString2 = "M"
2444: + verticalAxisValueString2; // NOI18N
2445: } else if ((model.getMaxDisplayYValue(1) > 2000)
2446: && (div < 1024)) {
2447: div = 1024;
2448: verticalAxisValueString2 = "K"
2449: + verticalAxisValueString2; // NOI18N
2450: }
2451: }
2452:
2453: firstValue = model.getMinDisplayYValue(1) / (double) div;
2454: lastValue = model.getMaxDisplayYValue(1) / (double) div;
2455:
2456: if ((lastValue - firstValue) > 0) {
2457: double factor = (double) (chartHeight - topChartMargin)
2458: / (lastValue - firstValue);
2459: optimalUnits = DecimalAxisUtils.getOptimalUnits(factor,
2460: minimumVerticalMarksDistance);
2461:
2462: if (optimalUnits > 0) {
2463: long firstMark = Math
2464: .max((long) (Math.ceil(firstValue
2465: / optimalUnits) * optimalUnits), 0);
2466: long currentMark = firstMark;
2467:
2468: double currentMarkRel = currentMark - firstValue;
2469: int markPosition = (chartInsets.top + chartHeight)
2470: - (int) (currentMarkRel * factor);
2471:
2472: while (markPosition >= chartInsets.top) {
2473: if (markPosition <= (chartInsets.top + chartHeight)) {
2474: drawVerticalAxisLegendSegment2(g2,
2475: currentMark, optimalUnits,
2476: markPosition);
2477: }
2478:
2479: currentMark += optimalUnits;
2480: currentMarkRel = currentMark - firstValue;
2481: markPosition = (chartInsets.top + chartHeight)
2482: - (int) (currentMarkRel * factor);
2483: }
2484: }
2485: }
2486:
2487: verticalAxisValueString2 = tmp;
2488: }
2489: }
2490:
2491: private void drawVerticalMeshSegment(Graphics2D g2,
2492: Shape chartClip, int x1, int y1, int x2, int y2) {
2493: g2.setClip(chartClip);
2494: //g2.setClip(chartInsets.left, chartInsets.top, chartWidth, chartHeight);
2495: g2.setPaint(verticalMeshPaint);
2496: g2.setStroke(verticalMeshStroke);
2497: g2.drawLine(x1, y1, x2, y2);
2498: }
2499:
2500: // sets zoom & offset according to current selection
2501: private void fitToSelection() {
2502: fitToViewRectangle(selectionX, selectionY, selectionWidth,
2503: selectionHeight);
2504: }
2505:
2506: // applies fit to window scale
2507: private void fitToWindow() {
2508: dataViewWidth = chartWidth;
2509: changeZoom((double) (chartWidth)
2510: / (double) (model.getMaxXValue() - dataOffsetX));
2511: lastScaleXValid = false;
2512: changePan(0);
2513: lastViewOffsetXValid = false;
2514: offScreenImageInvalid = true;
2515: doRepaint(true);
2516: }
2517:
2518: private void paintHorizontalTimeMarkString(Graphics2D g2,
2519: long currentMark, int x) {
2520: int y = chartInsets.top + chartHeight
2521: + horizontalAxisFont.getSize()
2522: + (verticalAxisFont.getSize() / 2);
2523:
2524: int markStringMillisMargin = 0; // space between mark's string without milliseconds and mark's milliseconds string
2525: int markStringMillisReduce = 2; // markStringNoMillis.height - markStringMillisReduce = markStringMillis.height
2526:
2527: String markStringNoMillis = DateTimeAxisUtils
2528: .getTimeMarkNoMillisString(currentMark, optimalUnits,
2529: useDayInTimeLegend);
2530: int wMarkStringNoMillis = g2.getFontMetrics(horizontalAxisFont)
2531: .stringWidth(markStringNoMillis); // width of the mark's string without milliseconds
2532: String markStringMillis = DateTimeAxisUtils
2533: .getTimeMarkMillisString(currentMark, optimalUnits);
2534:
2535: if (!markStringMillis.equals("")) {
2536: markStringMillis = "." + markStringMillis; // NOI18N
2537: }
2538:
2539: int wMarkStringMillis = g2.getFontMetrics(
2540: horizontalAxisFontSmall).stringWidth(markStringMillis); // width of the mark's milliseconds string
2541:
2542: int xMarkStringNoMillis = x
2543: - ((wMarkStringNoMillis + wMarkStringMillis) / 2) + 1; // x-position of the mark's string without milliseconds
2544: int xMarkStringMillis = xMarkStringNoMillis
2545: + wMarkStringNoMillis + markStringMillisMargin; // x-position of the mark's milliseconds string
2546:
2547: g2.setFont(horizontalAxisFont);
2548: g2.drawString(markStringNoMillis, xMarkStringNoMillis, y);
2549:
2550: g2.setFont(horizontalAxisFontSmall);
2551: g2.drawString(markStringMillis, xMarkStringMillis, y
2552: - markStringMillisReduce + 1);
2553: }
2554:
2555: private void paintVerticalTimeMarkString(Graphics2D g2,
2556: long currentMark, long optimalUnits, int y) {
2557: String currentMarkString = getVerticalAxisMarkString(currentMark);
2558: int currentMarkWidth = g2.getFontMetrics(verticalAxisFont)
2559: .stringWidth(currentMarkString);
2560: int currentMarkX = chartInsets.left - currentMarkWidth - 6;
2561: int currentMarkY = (y + (verticalAxisFont.getSize() / 2)) - 2;
2562:
2563: if (useSecondaryVerticalAxis) {
2564: g2.setPaint(model.getSeriesColor(0));
2565: }
2566:
2567: g2.setFont(verticalAxisFont);
2568: g2.drawString(currentMarkString, currentMarkX, currentMarkY);
2569: }
2570:
2571: private void paintVerticalTimeMarkString2(Graphics2D g2,
2572: long currentMark, long optimalUnits, int y) {
2573: if (currentMark > model.getMaxDisplayYValue(1)) {
2574: return;
2575: }
2576:
2577: String currentMarkString = getVerticalAxisMarkString2(currentMark);
2578: int currentMarkX = chartInsets.left + chartWidth + 6;
2579: int currentMarkY = (y + (verticalAxisFont.getSize() / 2)) - 2;
2580:
2581: g2.setPaint(model.getSeriesColor(1));
2582: g2.setFont(verticalAxisFont);
2583: g2.drawString(currentMarkString, currentMarkX, currentMarkY);
2584: }
2585:
2586: // --- Selection interaction stuff -------------------------------------------
2587: private void performSelectionDone() {
2588: if (selectionWidth > 0) { // valid zoom-in selection
2589: fitToSelection();
2590: } else if (selectionWidth < 0) { // valid zoom-out selection
2591: fitToWindow();
2592: } else {
2593: // no selection, hide selection boundary
2594: doRepaint(false);
2595: }
2596: }
2597:
2598: // --- offscreen image buffer resize routines -------------------------------
2599: private void updateOffScreenImageSize() {
2600: // component insets
2601: insets = getInsets();
2602:
2603: // area of component
2604: drawWidth = getWidth() - insets.left - insets.right - 1;
2605: drawHeight = getHeight() - insets.top - insets.bottom - 1;
2606:
2607: if ((drawWidth > 0) && (drawHeight > 0)) {
2608: // offscreen image buffer
2609: //offScreenImage = createVolatileImage(drawWidth + 1, drawHeight + 1); // for volatile offscreen image buffer
2610: offScreenImage = (BufferedImage) createImage(drawWidth + 1,
2611: drawHeight + 1);
2612: offScreenGraphics = offScreenImage.createGraphics();
2613:
2614: // chart insets
2615: chartInsets.top = 20;
2616:
2617: if (useSecondaryVerticalAxis) {
2618: checkRightChartMargin();
2619: } else {
2620: chartInsets.right = 0; // NOI18N
2621: }
2622:
2623: chartInsets.bottom = horizontalAxisFont.getSize()
2624: + (verticalAxisFont.getSize() / 2) + 8;
2625: chartInsets.left = offScreenGraphics.getFontMetrics(
2626: verticalAxisFont).stringWidth("MMMMM"); // NOI18N
2627:
2628: // area of graph inside component
2629: chartWidth = drawWidth - chartInsets.left
2630: - chartInsets.right;
2631: chartHeight = drawHeight - chartInsets.top
2632: - chartInsets.bottom;
2633:
2634: // clear component area using user-defined background color
2635: Area chartArea = new Area(new Rectangle(chartInsets.left,
2636: chartInsets.top, chartWidth, chartHeight));
2637:
2638: Area componentArea = new Area(new Rectangle(0, 0,
2639: drawWidth + 1, drawHeight + 1));
2640: componentArea.subtract(chartArea);
2641:
2642: offScreenGraphics.setPaint(backgroundPaint);
2643: offScreenGraphics.fill(componentArea);
2644:
2645: offScreenGraphics.setPaint(chartPaint);
2646: offScreenGraphics.fill(chartArea);
2647:
2648: // vertical and horizontal axis baseline
2649: offScreenGraphics.setPaint(horizontalAxisPaint);
2650: offScreenGraphics.setStroke(horizontalAxisStroke);
2651: offScreenGraphics.drawLine(chartInsets.left,
2652: chartInsets.top + chartHeight,
2653: (chartInsets.left + chartWidth) - 1,
2654: chartInsets.top + chartHeight);
2655:
2656: offScreenGraphics.setPaint(verticalAxisPaint);
2657: offScreenGraphics.setStroke(verticalAxisStroke);
2658: offScreenGraphics.drawLine(chartInsets.left - 1,
2659: chartInsets.top, chartInsets.left - 1,
2660: chartInsets.top + chartHeight);
2661:
2662: if (useSecondaryVerticalAxis) {
2663: offScreenGraphics.drawLine(chartInsets.left
2664: + chartWidth, chartInsets.top, chartInsets.left
2665: + chartWidth, chartInsets.top + chartHeight);
2666: }
2667:
2668: // use antialiasing
2669: offScreenGraphics.setRenderingHint(
2670: RenderingHints.KEY_ANTIALIASING,
2671: RenderingHints.VALUE_ANTIALIAS_ON);
2672: offScreenGraphics.setRenderingHint(
2673: RenderingHints.KEY_RENDERING,
2674: RenderingHints.VALUE_RENDER_SPEED);
2675:
2676: // new offscreen image needs to be painted in the nearest paint()
2677: offScreenImageInvalid = true;
2678:
2679: // data on Y-axis needs to be rescaled
2680: lastScaleYValid = false;
2681:
2682: // data at the end of graph may changed (component resized => more/less data could appear)
2683: trailingItemVisible = true;
2684:
2685: // data need to be repainted from beginning of visible area
2686: lastViewOffsetXValid = false;
2687: lastScaleXValid = false;
2688:
2689: horizontalAxisMarksClip.setRect(chartInsets.left - 1,
2690: chartInsets.top + chartHeight + 1, chartWidth + 1,
2691: 3);
2692: horizontalAxisClip.setRect(HORIZONTAL_LEGEND_MARGIN,
2693: chartInsets.top + chartHeight
2694: + (verticalAxisFont.getSize() / 2) + 1,
2695: drawWidth - (2 * HORIZONTAL_LEGEND_MARGIN),
2696: horizontalAxisFont.getSize() + 2);
2697: verticalAxisClip.setRect(0, chartInsets.top
2698: - (verticalAxisFont.getSize() / 2),
2699: chartInsets.left - 1, chartHeight
2700: + verticalAxisFont.getSize());
2701:
2702: if (useSecondaryVerticalAxis) {
2703: verticalAxisClip2.setRect(chartInsets.left + chartWidth
2704: + 1, chartInsets.top
2705: - (verticalAxisFont.getSize() / 2),
2706: chartInsets.right - 1, chartHeight
2707: + verticalAxisFont.getSize());
2708: }
2709:
2710: if (model != null) {
2711: updateScaleFactors();
2712: }
2713:
2714: doRepaint(false);
2715: }
2716: }
2717:
2718: // --- data coordinates <-> chart (component) coordinates conversion routines
2719: private void updateScaleFactors() {
2720: if (!useDayInTimeLegend) {
2721: Calendar firstTimestampCalendar = Calendar.getInstance();
2722: firstTimestampCalendar.setTime(new Date(model
2723: .getMinXValue()));
2724:
2725: Calendar lastTimestampCalendar = Calendar.getInstance();
2726: lastTimestampCalendar
2727: .setTime(new Date(model.getMaxXValue()));
2728:
2729: if ((firstTimestampCalendar.get(Calendar.DAY_OF_WEEK) != lastTimestampCalendar
2730: .get(Calendar.DAY_OF_WEEK))
2731: || (firstTimestampCalendar.get(Calendar.MONTH) != lastTimestampCalendar
2732: .get(Calendar.MONTH))
2733: || (firstTimestampCalendar.get(Calendar.YEAR) != lastTimestampCalendar
2734: .get(Calendar.YEAR))) {
2735: useDayInTimeLegend = true;
2736: }
2737: }
2738:
2739: long dataLimitX;
2740:
2741: if (customizedEmptyAppearance && (model.getItemCount() < 2)) {
2742: dataLimitX = lastValueH;
2743: dataOffsetX = firstValueH;
2744: } else {
2745: dataLimitX = model.getMaxXValue();
2746: dataOffsetX = model.getMinXValue();
2747: }
2748:
2749: if (fitToWindow) {
2750: dataViewWidth = chartWidth;
2751: scaleFactorX = (double) (chartWidth)
2752: / (double) (dataLimitX - dataOffsetX);
2753: lastScaleXValid = false;
2754: lastLeadingItemIndex = 0;
2755: trailingItemVisible = false;
2756: changePan(0);
2757: lastViewOffsetXValid = false;
2758: } else {
2759: if ((viewScaleX == 0) && (chartWidth > 0)) {
2760: viewScaleX = (double) (chartWidth)
2761: / dataWidthAtTrackingEndSwitch; // workaround for Telemetry Overview initialization
2762: }
2763:
2764: dataViewWidth = (long) ((dataLimitX - dataOffsetX) * viewScaleX);
2765: scaleFactorX = viewScaleX;
2766:
2767: if (trackingEnd && !trailingItemVisible) {
2768: changePan(-chartWidth + dataViewWidth);
2769: lastViewOffsetXValid = false;
2770: }
2771: }
2772:
2773: if (fitToWindow) {
2774: changeZoom(scaleFactorX);
2775: } else {
2776: viewScaleX = scaleFactorX;
2777: }
2778:
2779: boolean yScaleChanged = false;
2780:
2781: for (int seriesIndex = 0; seriesIndex < model.getSeriesCount(); seriesIndex++) {
2782: long maxYValue = model.getMaxDisplayYValue(seriesIndex);
2783: long minYValue = model.getMinDisplayYValue(seriesIndex);
2784:
2785: if (lastMaxYs[seriesIndex] != maxYValue) {
2786: yScaleChanged = true;
2787: }
2788:
2789: if (lastMinYs[seriesIndex] != minYValue) {
2790: yScaleChanged = true;
2791: }
2792:
2793: lastMaxYs[seriesIndex] = maxYValue;
2794: lastMinYs[seriesIndex] = minYValue;
2795:
2796: dataOffsetsY[seriesIndex] = minYValue;
2797: scaleFactorsY[seriesIndex] = (double) (chartHeight - topChartMargin)
2798: / (double) (maxYValue - dataOffsetsY[seriesIndex]);
2799: }
2800:
2801: lastScaleYValid = lastScaleYValid && !yScaleChanged;
2802:
2803: updateScrollBarValues();
2804:
2805: scaleFactorsNeedUpdate = false;
2806: }
2807:
2808: private void updateScrollBarValues() {
2809: if (scrollBar != null) {
2810: scrollBarLongToIntFactor = ((dataViewWidth > Integer.MAX_VALUE) ? (Integer.MAX_VALUE / (double) dataViewWidth)
2811: : 1);
2812:
2813: int value = getIntPosition();
2814: int extent = getIntExtent();
2815: int minimum = getIntMinimum();
2816: int maximum = getIntMaximum();
2817:
2818: if (dataViewWidth <= chartWidth) {
2819: if (scrollBar.isEnabled()) {
2820: scrollBar.setEnabled(false);
2821: }
2822: } else {
2823: if (!scrollBar.isEnabled()) {
2824: scrollBar.setEnabled(true);
2825: }
2826:
2827: if (!scrollBar.getValueIsAdjusting()) {
2828: internalScrollBarChange = true;
2829: scrollBar.setUnitIncrement(getIntUnitIncrement());
2830: scrollBar.setBlockIncrement(getIntBlockIncrement());
2831: scrollBar
2832: .setValues(value, extent, minimum, maximum);
2833: } else {
2834: scrollBarValuesDirty = true;
2835: }
2836: }
2837: }
2838: }
2839:
2840: private void updateTrailingItemVisible() {
2841: if (trackingEnd) {
2842: trailingItemVisible = false;
2843: }
2844:
2845: long preTrailingItemX = getDataToViewX(model.getXValue(model
2846: .getItemCount() - 2));
2847: boolean preTrailingItemVisible = ((preTrailingItemX >= 0) && (preTrailingItemX <= ((chartWidth + chartInsets.left) - 1)));
2848:
2849: long trailingItemX = getDataToViewX(model.getXValue(model
2850: .getItemCount() - 1));
2851: trailingItemVisible = ((trailingItemX >= 0) && (trailingItemX <= ((chartWidth + chartInsets.left) - 1)));
2852:
2853: if (preTrailingItemVisible && !trailingItemVisible) {
2854: changeTrackingEnd(true);
2855: }
2856: }
2857: }
|