All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codename1.charts.ChartComponent Maven / Gradle / Ivy

There is a newer version: 7.0.161
Show newest version
/*
 * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Codename One designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *  
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Codename One through http://www.codenameone.com/ if you 
 * need additional information or have any questions.
 */
package com.codename1.charts;

import com.codename1.charts.models.Point;
import com.codename1.charts.models.SeriesSelection;
import com.codename1.charts.renderers.XYMultipleSeriesRenderer;
import com.codename1.charts.views.AbstractChart;
import com.codename1.charts.views.XYChart;
import com.codename1.ui.Component;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Transform;
import com.codename1.ui.animations.Animation;
import com.codename1.ui.animations.Motion;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.GeneralPath;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.geom.Shape;
import java.util.ArrayList;


/**
 * 

The top level component for displaying charts

*

The charts package enables Codename One developers to add charts * and visualizations to their apps without having to include external libraries * or embedding web views. We also wanted to harness the new features in the * graphics pipeline to maximize performance.

*

Features

*
    *
  1. Built-in support for many common types of charts * including bar charts, line charts, stacked charts, scatter charts, pie charts * and more.
  2. *
  3. Pinch Zoom - The * {@link com.codename1.charts,ChartComponent} class includes optional pinch * zoom support.
  4. *
  5. Panning Support - The * {@link com.codename1.charts,ChartComponent} class includes optional support * for panning.
  6. *
*

Chart Types

*

* The com.codename1.charts package includes models and renderers * for many different types of charts. It is also extensible so that you can add * your own chart types if required. The following screen shots demonstrate a * small sampling of the types of charts that can be created. *

* Line Charts * Cubic Line Charts * * Bar Charts * Stacked Bar Charts * Range Bar Charts * Pie Charts * Doughnut Charts * Scatter Charts * Dial Charts * Combined Charts * Bubble Charts * Time Charts * * * * *
* The above screenshots were taken from the * ChartsDemo * app. Y ou can start playing with this app by checking it out from our git * repository.
*

How to Create A Chart

*

* Adding a chart to your app involves four steps:

*
    *
  1. Build the model. You can construct a model (aka data * set) for the chart using one of the existing model classes in the * com.codename1.charts.models package. Essentially, this is just * where you add the data that you want to display.
  2. *
  3. Set up a renderer. You can create a renderer for your * chart using one of the existing renderer classes in the * com.codename1.charts.renderers package. The renderer allows you * to specify how the chart should look. E.g. the colors, fonts, styles, to use. *
  4. *
  5. Create the Chart View. Use one of the existing * view classes in the * com.codename1.charts.views package. *
  6. *
  7. Create a {@link com.codename1.charts,ChartComponent} . * In order to add your chart to the UI, you need to wrap it in a * {@link com.codename1.charts,ChartComponent} object.
  8. *
*

* You can check out the * ChartsDemo * app for specific examples, but here is a high level view of some code that * creates a Pie Chart.

* *

* The charts package is derived work from the excellent * open source aChartEngine API. *

* * @author shannah */ public class ChartComponent extends Component { private ArrayList animations = new ArrayList(); /** * The chart that is to be rendered in this component. */ private AbstractChart chart; /** * Util object for rendering the chart. */ private final ChartUtil util = new ChartUtil(); /** * The transform for the chart. This can be used to scale, translate, and * rotate the chart. This transform assumes its origin at the (absoluteX, absoluteY) * of the component at the time it is drawn rather than the screen's origin as is * normally the case with transforms. This allows the transform to be applied consistently * with respect to the chart's coordinates even when the component is moved around the screen. */ private Transform transform = null; /** * The transform that was applied during the last paint() method. * This is generally the {@link #transform} concatenated with a * translation to the screen origin from the component's origin. This * is used to convert chart coordinates to screen coordinates and respond * properly to events. */ private Transform currentTransform = null; /** * Flag to enable panning the chart. Default is false. */ private boolean panEnabled = false; /** * During a pan operation, used to store the transform as it was before * the start of the pan. */ private Transform dragTransformStart = null; private Transform tmpTransform = null; /** * The starting position of a pan operation. */ private Point dragStart = null; /** * Flag to enable pinch zoom. */ private boolean zoomEnabled = false; /** * During a pinch zoom operation, this is the middle point between the * two touch points at the start of the zoom operation. This is in * screen coordinates. */ private Point zoomStart = null; /** * During a pinch zoom operation, this is the transform as it was at the start * of the zoom. */ private Transform zoomTransformStart = null; /** * During a pinch zoom operation, this is the distance between the two touch * points at the start of the zoom operation. */ private double zoomDistStart = 0; /** * Creates a new chart component to display the provided chart. * @param chart The chart to be displayed in this component. */ public ChartComponent(AbstractChart chart){ setUIID("ChartComponent"); this.chart = chart; if (chart != null && chart instanceof XYChart) { XYChart xyChart = (XYChart)chart; zoomEnabled = xyChart.getRenderer().isZoomEnabled(); panEnabled = xyChart.getRenderer().isPanEnabled(); } } /** * Gets the chart that is being displayed in this component. * @return */ public AbstractChart getChart(){ return chart; } @Override protected Dimension calcPreferredSize() { return new Dimension( Display.getInstance().convertToPixels(100, true), Display.getInstance().convertToPixels(100, false) ); } /** * Sets the chart to be displayed in this component. * @param chart */ public void setChart(AbstractChart chart){ this.chart = chart; } /** * Paints the chart. * @param g */ @Override public void paint(Graphics g) { super.paint(g); boolean oldAntialias = g.isAntiAliased(); g.setAntiAliased(true); boolean transformed = false; if ( getTransform() != null ){ transformed = true; if (tmpTransform == null) { tmpTransform = Transform.makeIdentity(); } g.getTransform(tmpTransform); if (currentTransform == null) { currentTransform = Transform.makeTranslation(getAbsoluteX(), getAbsoluteY()); } else { currentTransform.setTranslation(getAbsoluteX(), getAbsoluteY()); } currentTransform.concatenate(transform); currentTransform.translate(-getAbsoluteX(), -getAbsoluteY()); g.setTransform(currentTransform); } else { currentTransform = null; } util.paintChart(g, chart, getBounds(), getAbsoluteX(), getAbsoluteY()); if ( transformed){ g.setTransform(tmpTransform); } g.setAntiAliased(oldAntialias); } /** * Converts screen coordinates to chart coordinates. * @param x screen x position * @param y screen y position * @return The chart coordinate corresponding to the given screen coordinate. */ public Point screenToChartCoord(int x, int y){ if ( currentTransform != null ){ Transform inverse = currentTransform.getInverse(); float[] pt = inverse.transformPoint(new float[]{x,y, 0}); x = (int)pt[0]; y = (int)pt[1]; } return new Point(x-getAbsoluteX(), y-getAbsoluteY()); } /** * Returns the screen position from a chart coordinate * * @param x the x position within the chart * @param y the y position within the chart * @return a position within the screen */ public Point chartToScreenCoord(int x, int y){ x += getAbsoluteX(); y += getAbsoluteY(); if ( currentTransform != null ){ float[] pt = currentTransform.transformPoint(new float[]{x,y, 0}); x = (int)pt[0]; y = (int)pt[1]; } return new Point(x, y); } /** * Converts a chart coordinate spaced shape to the same shape in the screen coordinate space * @param s shape in screen coordinates * @return same shape using chart space coordinates */ public Shape screenToChartShape(Shape s){ GeneralPath p = new GeneralPath(); Transform t = Transform.makeIdentity(); if ( currentTransform != null ){ t.concatenate(currentTransform.getInverse()); } t.translate(-getAbsoluteX(), -getAbsoluteY()); p.append(s.getPathIterator(t), false); return p; } /** * Converts a screen coordinate spaced shape to the same shape in the chart coordinate space * @param s shape in chart coordinates * @return same shape using screen coordinate space */ public Shape chartToScreenShape(Shape s){ GeneralPath p = new GeneralPath(); Transform inverse = Transform.makeTranslation(getAbsoluteX(), getAbsoluteY()); if ( currentTransform != null ){ inverse.concatenate(currentTransform); } p.append(s.getPathIterator(inverse), false); return p; } /** * Zooms the view port to show a specified shape. The shape should be * expressed in chart coordinates (not screen coordinates). * @param s The shape that should be shown. */ public void zoomToShapeInChartCoords(Shape s){ zoomToShapeInChartCoords(s, 1); } /** * Zooms the view port to show a specified shape. The shape should be * expressed in chart coordinates (not screen coordinates). * @param s The shape that should be shown. * @param duration The duration of the transition. * @see #zoomTo(double, double, double, double, int) */ public void zoomToShapeInChartCoords(Shape s, int duration){ Rectangle r = s.getBounds(); zoomTransition(r.getX(), r.getX() + r.getWidth(), r.getY(), r.getY() + r.getHeight(), duration); } /** * Zooms the chart in an animated fashion to the specified axis ranges. This is effectively * the same as using {@link #zoomToShapeInChartCoords(com.codename1.ui.geom.Shape, int) } except * it allows you to specify coordinates as doubles. * * @param minX The lower bound of the X-axis after zoom. * @param maxX The upper bound of the X-axis after zoom. * @param minY The lower bound of the Y-axis after zoom. * @param maxY THe upper bound of the Y-axis after zoom. * @param duration Transition time (ms). */ public void zoomTo(double minX, double maxX, double minY, double maxY, int duration) { zoomTransition(minX, maxX, minY, maxY, duration); } @Override public void pointerPressed(int x, int y) { Point chartCoord = screenToChartCoord(x, y); SeriesSelection sel = chart.getSeriesAndPointForScreenCoordinate(chartCoord); if ( sel == null ){ super.pointerPressed(x, y); return; } seriesPressed(sel); super.pointerPressed(x, y); //To change body of generated methods, choose Tools | Templates. } /** * Called when a pointer is pressed on a series in the chart. This can be * overridden by subclasses to respond to this event. * @param sel */ protected void seriesPressed(SeriesSelection sel){ } @Override public void pointerReleased(int x, int y) { dragStart = null; zoomStartBBox = null; dragStartBBox = null; dragTransformStart = null; zoomStart = null; zoomDistStart = 0; zoomTransformStart = null; Point chartCoord = screenToChartCoord(x, y); SeriesSelection sel = chart.getSeriesAndPointForScreenCoordinate(chartCoord); if ( sel == null ){ super.pointerReleased(x, y); return; } seriesReleased(sel); super.pointerReleased(x, y); //To change body of generated methods, choose Tools | Templates. } /** * Called when a pointer is released from a series in the chart. This can be * overridden in subclasses to handle these events. * @param sel */ protected void seriesReleased(SeriesSelection sel){ } /** * * Gets the transform for the chart. This can be used to scale, translate, and * rotate the chart. This transform assumes its origin at the (absoluteX, absoluteY) * of the component at the time it is drawn rather than the screen's origin as is * normally the case with transforms. This allows the transform to be applied consistently * with respect to the chart's coordinates even when the component is moved around the screen. * @return The transform for the chart in component coordinates. */ public Transform getTransform() { return transform; } /** * Sets the transform for the chart. Transforms origin assumed to be at (getAbsoluteX, getAbsoluteY). * @param transform the transform to set */ public void setTransform(Transform transform) { this.transform = transform; } private BBox dragStartBBox; private BBox zoomStartBBox; private double zoomDistStartX, zoomDistStartY; private BBox initialZoomBBox; @Override public void pointerDragged(int[] x, int[] y) { if ( x.length > 1 ){ if ( !zoomEnabled ){ super.pointerDragged(x, y); return; } // Pinch zoom if (chart instanceof XYChart) { XYChart xyChart = (XYChart)chart; double[] panLimits = xyChart.getRenderer().getPanLimits(); if ( zoomStart == null ){ zoomStart = new Point((x[0]+x[1])/2, (y[0]+y[1])/2); zoomDistStartX = Math.abs(x[0]-x[1]); zoomDistStartY = Math.abs(y[0]-y[1]); zoomStartBBox = getBBox(); if (initialZoomBBox == null) { initialZoomBBox = zoomStartBBox.translateScreenCoords(0, 0); } } else { int dx = Math.abs(x[0]-x[1]); int dy = Math.abs(y[0]-y[1]); if (dx == 0) dx = 1; if (dy == 0) dy = 1; double zoomX = zoomDistStartX/dx; double zoomY = zoomDistStartY/dy; BBox newBounds = zoomStartBBox.scaleScreenCoords((float)zoomX, (float)zoomY); double minX = newBounds.minX; double minY = newBounds.minY; double maxX = newBounds.maxX; double maxY = newBounds.maxY; if (panLimits != null) { // Make sure that this zoom doesn't exceed the maxZoomIn value if (minX < panLimits[0]) { maxX = maxX + panLimits[0] - minX; minX = panLimits[0]; } if (minY < panLimits[2]) { maxY = maxY + panLimits[2] - minY; minY = panLimits[2]; } if (maxX > panLimits[1]) { minX = minX + panLimits[1] - maxX; maxX = panLimits[1]; } if (maxY > panLimits[3]) { minY = minY + panLimits[3] - maxY; maxY = panLimits[3]; } } double[] zoomLimits = xyChart.getRenderer().getZoomLimits(); if (zoomLimits != null && zoomLimits[0] != 0) { if (maxX - minX < zoomLimits[0]) { maxX = xyChart.getRenderer().getXAxisMax(); minX = xyChart.getRenderer().getXAxisMin(); } } if (zoomLimits != null && zoomLimits[1] != 0) { if (maxX - minX > zoomLimits[1]) { maxX = xyChart.getRenderer().getXAxisMax(); minX = xyChart.getRenderer().getXAxisMin(); } } if (zoomLimits != null && zoomLimits[2] != 0) { if (maxY - minY < zoomLimits[2]) { maxY = xyChart.getRenderer().getYAxisMax(); minY = xyChart.getRenderer().getYAxisMin(); } } if (zoomLimits != null && zoomLimits[3] != 0) { if (maxY - minY > zoomLimits[3]) { maxY = xyChart.getRenderer().getYAxisMax(); minY = xyChart.getRenderer().getYAxisMin(); } } if (!xyChart.getRenderer().isZoomXEnabled()) { minX = xyChart.getRenderer().getXAxisMin(); maxX = xyChart.getRenderer().getXAxisMax(); } if (!xyChart.getRenderer().isZoomYEnabled()) { minY = xyChart.getRenderer().getYAxisMin(); maxY = xyChart.getRenderer().getYAxisMax(); } xyChart.getRenderer().setRange(new double[]{minX, maxX, minY, maxY}); chartBoundsChanged(); this.repaint(); } } else { if ( zoomStart == null ){ zoomStart = new Point((x[0]+x[1])/2, (y[0]+y[1])/2); zoomTransformStart = Transform.makeIdentity(); if ( transform != null ){ zoomTransformStart.concatenate(transform); } int dx = Math.abs(x[0]-x[1])/2; int dy = Math.abs(y[0]-y[1])/2; zoomDistStart = Math.sqrt(dx*dx+dy*dy); } else { int dx = Math.abs(x[0]-x[1])/2; int dy = Math.abs(y[0]-y[1])/2; double zoomDist = Math.sqrt(dx*dx+dy*dy); if ( zoomDist == 0 ){ zoomDist = 1; } transform = Transform.makeIdentity(); transform.translate(zoomStart.getX(), zoomStart.getY()); transform.scale((float)(zoomDist/zoomDistStart), (float)(zoomDist/zoomDistStart)); transform.translate(-zoomStart.getX(), -zoomStart.getY()); transform.concatenate(zoomTransformStart); this.repaint(); } } } else { if ( !isPanEnabled() ){ super.pointerDragged(x, y); return; } if (chart instanceof XYChart) { XYChart xyChart = (XYChart)chart; double panLimits[] = xyChart.getRenderer().getPanLimits(); if (dragStartBBox == null) { dragStart = new Point(x[0], y[0]); dragStartBBox = getBBox(); } else { float tx = x[0] - dragStart.getX(); float ty = y[0] - dragStart.getY(); BBox newBounds = dragStartBBox.translateScreenCoords(-tx, ty); double minX = newBounds.minX; double minY = newBounds.minY; double maxX = newBounds.maxX; double maxY = newBounds.maxY; if (panLimits != null) { if (minX < panLimits[0]) { maxX = maxX + panLimits[0] - minX; minX = panLimits[0]; } if (minY < panLimits[2]) { maxY = maxY + panLimits[2] - minY; minY = panLimits[2]; } if (maxX > panLimits[1]) { minX = minX + panLimits[1] - maxX; maxX = panLimits[1]; } if (maxY > panLimits[3]) { minY = minY + panLimits[3] - maxY; maxY = panLimits[3]; } } if (!xyChart.getRenderer().isPanXEnabled()) { minX = xyChart.getRenderer().getXAxisMin(); maxX = xyChart.getRenderer().getXAxisMax(); } if (!xyChart.getRenderer().isPanYEnabled()) { minY = xyChart.getRenderer().getYAxisMin(); maxY = xyChart.getRenderer().getYAxisMax(); } xyChart.getRenderer().setRange(new double[]{minX, maxX, minY, maxY}); chartBoundsChanged(); } } else { if ( dragStart == null ){ dragStart = new Point(x[0],y[0]); dragTransformStart = Transform.makeIdentity(); if ( transform != null ){ dragTransformStart.concatenate(transform); } } else { transform = Transform.makeIdentity(); transform.translate(x[0]-dragStart.getX(), y[0]-dragStart.getY()); transform.concatenate(dragTransformStart); this.repaint(); } } } super.pointerDragged(x, y); //To change body of generated methods, choose Tools | Templates. } /** * Checks if panning is enabled. * @return the panEnabled */ public boolean isPanEnabled() { if (chart instanceof XYChart) { ((XYChart)chart).getRenderer().isPanEnabled(); } return panEnabled; } /** * @param panEnabled the panEnabled to set */ public void setPanEnabled(boolean panEnabled) { this.panEnabled = panEnabled; if (chart instanceof XYChart) { XYChart xyChart = (XYChart)chart; xyChart.getRenderer().setPanEnabled(panEnabled); } } /** * Enables or disables pan on x and y axes separately. * @param panXEnabled True to enable panning along the x-axis. * @param panYEnabled True to enable panning along the y-axis. */ public void setPanEnabled(boolean panXEnabled, boolean panYEnabled) { this.panEnabled = panXEnabled || panYEnabled; if (chart instanceof XYChart) { XYChart xyChart = (XYChart)chart; xyChart.getRenderer().setPanEnabled(panXEnabled, panYEnabled); } } /** * Checks whether panning is enabled along the X-axis. * @return */ public boolean isPanXEnabled() { if (chart instanceof XYChart) { return ((XYChart)chart).getRenderer().isPanXEnabled(); } return panEnabled; } /** * Checks whether panning is enabled along the Y-axis. * @return */ public boolean isPanYEnabled() { if (chart instanceof XYChart) { return ((XYChart)chart).getRenderer().isPanYEnabled(); } return panEnabled; } /** * Sets the pan limits if panning is enabled. * @param minX The minimum X-axis value for panning. * @param maxX The maximum X-axis value for panning. * @param minY The minimum Y-axis value for panning. * @param maxY The maximum Y-axis value for panning. */ public void setPanLimits(double minX, double maxX, double minY, double maxY) { if (chart instanceof XYChart) { XYChart xyChart = (XYChart)chart; XYMultipleSeriesRenderer r = xyChart.getRenderer(); r.setPanLimits(new double[]{minX, maxX, minY, maxY}); } else { throw new RuntimeException("setPanLimits() only supported for XYCharts"); } } /** * Removes the pan limits which may have been previously set with {@link #setPanLimits(double, double, double, double) } */ public void clearPanLimits() { if (chart instanceof XYChart) { ((XYChart)chart).getRenderer().setPanLimits(null); } } /** * Checks whether zoom is enabled. * @return the zoomEnabled */ public boolean isZoomEnabled() { if (chart instanceof XYChart) { ((XYChart)chart).getRenderer().isZoomEnabled(); } return zoomEnabled; } /** * Checks whether zoom is enabled on the X-axis. * @return */ public boolean isZoomXEnabled() { if (chart instanceof XYChart) { return ((XYChart)chart).getRenderer().isZoomXEnabled(); } return zoomEnabled; } /** * Checks whether zoom is enabled on the Y-axis. * @return */ public boolean isZoomYEnabled() { if (chart instanceof XYChart) { return ((XYChart)chart).getRenderer().isZoomYEnabled(); } return zoomEnabled; } /** * Enables or disables zoom on both x and y axes. * @param zoomEnabled the zoomEnabled to set */ public void setZoomEnabled(boolean zoomEnabled) { this.zoomEnabled = zoomEnabled; setFocusable(isFocusable() || zoomEnabled); if (chart instanceof XYChart) { XYChart xyChart = (XYChart)chart; xyChart.getRenderer().setZoomEnabled(zoomEnabled, zoomEnabled); } } /** * Sets the zoom limits. * *

NOTE: This method is only applicable when showing an {@link XYChart } It will throw a * RuntimeException if called while a different kind of chart is being shown.

* * @param minRangeX The minimum distance from {@link XYMultipleSeriesRenderer#getXAxisMin() } to * {@link XYMultipleSeriesRenderer#getXAxisMax() } that can be achieved by zooming in. {@literal 0} means no limit. * @param maxRangeX The maximum distance from {@link XYMultipleSeriesRenderer#getXAxisMin() } to * {@link XYMultipleSeriesRenderer#getXAxisMax() } that can be achieved by zooming out. {@literal 0} means no limit. * @param minRangeY The minimum distance from {@link XYMultipleSeriesRenderer#getYAxisMin() } to * {@link XYMultipleSeriesRenderer#getYAxisMax() } that can be achieved by zooming in. {@literal 0} means no limit. * @param maxRangeY The maximum distance from {@link XYMultipleSeriesRenderer#getYAxisMin() } to * {@link XYMultipleSeriesRenderer#getYAxisMax() } that can be achieved by zooming out. {@literal 0} means no limit. */ public void setZoomLimits(double minRangeX, double maxRangeX, double minRangeY, double maxRangeY) { if (chart instanceof XYChart) { XYChart xyChart = (XYChart)chart; xyChart.getRenderer().setZoomLimits(new double[]{minRangeX, maxRangeX, minRangeY, maxRangeY}); } else { throw new RuntimeException("setZoomLimits() only supported for XY charts"); } } /** * Enables or disables zoom on x and y axes separately. * @param zoomX True to enable zooming x axis. * @param zoomY True to enable zooming y axis. */ public void setZoomEnabled(boolean zoomX, boolean zoomY) { this.zoomEnabled = zoomX || zoomY; setFocusable(isFocusable() || zoomEnabled); if (chart instanceof XYChart) { ((XYChart)chart).getRenderer().setZoomEnabled(zoomX, zoomY); } } private void zoomTransition(double minX, double maxX, double minY, double maxY, int duration){ if (chart instanceof XYChart) { BBox currentViewPort = getBBox(); BBox targetViewPort = getBBox( minX, maxX, minY, maxY, (int)currentViewPort.topLeft.getX(), (int)currentViewPort.topLeft.getY(), (int)currentViewPort.bottomRight.getX(), (int)currentViewPort.bottomRight.getY() ); ZoomTransitionXY zt = new ZoomTransitionXY(currentViewPort, targetViewPort, duration); animations.add(zt); if (animations.size() == 1) { zt.start(); } } else { Shape currentViewPort = screenToChartShape(new Rectangle(getAbsoluteX(), getAbsoluteY(), getWidth(), getHeight())); float[] currentRect = currentViewPort.getBounds2D(); float[] newRect = new float[]{(float)minX, (float)(maxX-minX), (float)minY, (float)(maxY-minY)}; float currentAspect =currentRect[2]/currentRect[3]; float newAspect = newRect[3]/newRect[3]; Rectangle newViewPort = new Rectangle((int)newRect[0], (int)newRect[1], (int)newRect[2], (int)newRect[3]); if ( newAspect != currentAspect ){ newViewPort.setHeight((int)(((double)newViewPort.getWidth())/currentAspect)); newRect = newViewPort.getBounds2D(); newAspect = newRect[2]/newRect[3]; } ZoomTransition zt = new ZoomTransition(currentViewPort.getBounds(), newViewPort, duration); animations.add(zt); if ( animations.size() == 1 ){ zt.start(); } } } private interface IZoomTransition { public void start(); } private class ZoomTransition implements Animation, IZoomTransition { private final Rectangle currentViewPort; private final Rectangle newViewPort; private Motion motion; private final Transform origTransform; private boolean finished = false; ZoomTransition(Rectangle currentViewPort, Rectangle newViewPort, int duration){ this.currentViewPort = currentViewPort; this.newViewPort = newViewPort; this.motion = Motion.createLinearMotion(0, 100, duration); this.origTransform = Transform.makeIdentity(); if ( transform != null ){ this.origTransform.setTransform(transform); } } public void start(){ Form f = ChartComponent.this.getComponentForm(); if ( f != null ){ f.registerAnimated(this); this.motion.start(); } else { animations.remove(this); } } public void cleanup(){ Form f = ChartComponent.this.getComponentForm(); if ( f != null ){ f.deregisterAnimated(this); } } public boolean animate() { if (finished){ animations.remove(this); if ( !animations.isEmpty() ){ animations.get(0).start(); } cleanup(); return false; } else if ( motion.isFinished() ){ finished = true; } return true; } public void paint(Graphics g) { Rectangle newBounds = new Rectangle(newViewPort.getBounds()); Rectangle currentBounds = new Rectangle(currentViewPort.getBounds()); double nW = newBounds.getWidth(); double nH = newBounds.getHeight(); double cW = currentBounds.getWidth(); double cH = currentBounds.getHeight(); double scale = cW/nW; if ( nH * scale > cH){ scale = cH/nH; } Point newCenter = new Point(newBounds.getX()+newBounds.getWidth()/2, newBounds.getY()+newBounds.getHeight()/2); Point currentCenter = new Point(currentBounds.getX()+currentBounds.getWidth()/2, currentBounds.getY()+currentBounds.getHeight()/2); double motionVal = motion.getValue(); double tx = ((double)newCenter.getX()-currentCenter.getX())*motionVal/100.0; double ty = ((double)newCenter.getY()-currentCenter.getY())*motionVal/100.0; scale = 1.0 + (scale - 1f)*motionVal/100.0; Transform t = Transform.makeIdentity(); t.setTransform(origTransform); int cX = (int)(currentCenter.getX()+tx); int cY = (int)(currentCenter.getY()+ty); t.translate(currentCenter.getX(), currentCenter.getY()); t.scale((float)scale, (float)scale); t.translate(-cX, -cY); setTransform(t); ChartComponent.this.repaint(); } } private class ZoomTransitionXY implements Animation, IZoomTransition { private final BBox currentViewPort; private final BBox newViewPort; private final Motion motion; private boolean finished = false; ZoomTransitionXY(BBox currentViewPort, BBox newViewPort, int duration){ this.currentViewPort = currentViewPort; this.newViewPort = newViewPort; this.motion = Motion.createLinearMotion(0, 100, duration); } public void start(){ Form f = ChartComponent.this.getComponentForm(); if ( f != null ){ f.registerAnimated(this); this.motion.start(); } else { animations.remove(this); } } public void cleanup(){ Form f = ChartComponent.this.getComponentForm(); if ( f != null ){ f.deregisterAnimated(this); } } public boolean animate() { if (finished){ animations.remove(this); if ( !animations.isEmpty() ){ animations.get(0).start(); } cleanup(); return false; } else if ( motion.isFinished() ){ chartBoundsChanged(); finished = true; } return true; } public void paint(Graphics g) { if (chart instanceof XYChart) { XYChart xyChart = (XYChart)chart; double motionVal = motion.getValue() / 100.0; double diff = newViewPort.minX - currentViewPort.minX; double minX = currentViewPort.minX + diff * motionVal; diff = newViewPort.maxX - currentViewPort.maxX; double maxX = currentViewPort.maxX + diff * motionVal; diff = newViewPort.minY - currentViewPort.minY; double minY = currentViewPort.minY + diff * motionVal; diff = newViewPort.maxY - currentViewPort.maxY; double maxY = currentViewPort.maxY + diff * motionVal; xyChart.getRenderer().setXAxisMin(minX); xyChart.getRenderer().setXAxisMax(maxX); xyChart.getRenderer().setYAxisMin(minY); xyChart.getRenderer().setYAxisMax(maxY); ChartComponent.this.repaint(); } } } private class BBox { // screen coords Point topLeft; Point bottomRight; // Chart coords double minX, maxX, minY, maxY; double scaleX, scaleY; BBox translateScreenCoords(float x, float y) { BBox out = new BBox(); out.topLeft = new Point(topLeft.getX() + x, topLeft.getY() + y); out.bottomRight = new Point(bottomRight.getX() + x, topLeft.getY() + y); float tXChart = (float)( x / scaleX); float tYChart = (float)(y / scaleY); out.minX = minX + tXChart; out.maxX = maxX + tXChart; out.minY = minY + tYChart; out.maxY = maxY + tYChart; out.scaleX = scaleX; out.scaleY = scaleY; return out; } BBox scaleScreenCoords(float x, float y) { BBox out = new BBox(); Point center = new Point( 0.5f * (topLeft.getX() + bottomRight.getX()), 0.5f * (topLeft.getY() + bottomRight.getY()) ); double cX = (minX + maxX)/2; double cY = (minY + maxY)/2; float width = (bottomRight.getX() - topLeft.getX()) * x; float height = (bottomRight.getY() - topLeft.getY()) * y; out.topLeft = new Point(center.getX() - width/2, center.getY() - height/2); out.bottomRight = new Point(center.getX() + width/2, center.getY() + height/2); //float tXChart = (float)( x / scaleX); //float tYChart = (float)(y / scaleY); out.minX = cX - width/2/scaleX; out.maxX = cX + width/2/scaleX; out.minY = cY - height/2/scaleY; out.maxY = cY + height/2/scaleY; out.scaleX = scaleX; out.scaleY = scaleY; return out; } } private BBox getBBox() { if (chart instanceof XYChart) { XYChart xyChart = (XYChart)chart; double minX = xyChart.getRenderer().getXAxisMin(); double maxX = xyChart.getRenderer().getXAxisMax(); double minY = xyChart.getRenderer().getYAxisMin(); double maxY = xyChart.getRenderer().getYAxisMax(); return getBBox(minX, maxX, minY, maxY, getAbsoluteX() + getStyle().getPaddingLeft(false) + xyChart.getRenderer().getMargins()[1], getAbsoluteY() + getStyle().getPaddingTop() + xyChart.getRenderer().getMargins()[0], getAbsoluteX() + getWidth() - getStyle().getPaddingRight(false) - xyChart.getRenderer().getMargins()[3], getAbsoluteY() + getHeight() - getStyle().getPaddingBottom() - xyChart.getRenderer().getMargins()[2]); } return null; } private BBox getBBox(double minX, double maxX, double minY, double maxY, int topLeftX, int topLeftY, int bottomRightX, int bottomRightY) { Point topLeft = new Point(topLeftX, topLeftY); Point bottomRight = new Point(bottomRightX, bottomRightY); if (bottomRight.getX() == topLeft.getX() || topLeft.getY() == bottomRight.getY()) { // If the we don't have height or width, forget about scaling return null; } double xScale = (bottomRight.getX() - topLeft.getX()) / (maxX - minX); double yScale = (bottomRight.getY() - topLeft.getY()) / (maxY - minY); BBox out = new BBox(); out.topLeft = topLeft; out.bottomRight = bottomRight; out.minX = minX; out.maxX = maxX; out.minY = minY; out.maxY = maxY; out.scaleX =xScale; out.scaleY = yScale; return out; } /** * Subclasses can override this method to be informed when the chart bounds change * due to panning or zooming. */ protected void chartBoundsChanged() { } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy