ingo@521: package de.intevation.flys.client.client.ui.chart;
ingo@521:
ingo@521: import java.util.Date;
ingo@549: import java.util.HashMap;
ingo@549: import java.util.Map;
ingo@1280: import java.util.Stack;
ingo@521:
ingo@521: import com.google.gwt.core.client.GWT;
ingo@538: import com.google.gwt.user.client.rpc.AsyncCallback;
ingo@521:
ingo@610: import com.smartgwt.client.types.Overflow;
ingo@610:
ingo@521: import com.smartgwt.client.widgets.Canvas;
ingo@521: import com.smartgwt.client.widgets.Img;
ingo@521:
ingo@521: import com.smartgwt.client.widgets.layout.HLayout;
ingo@521: import com.smartgwt.client.widgets.layout.VLayout;
ingo@521:
ingo@521: import com.smartgwt.client.widgets.events.ResizedEvent;
ingo@521: import com.smartgwt.client.widgets.events.ResizedHandler;
ingo@521:
ingo@538: import de.intevation.flys.client.shared.Transform2D;
ingo@552: import de.intevation.flys.client.shared.model.Axis;
ingo@552: import de.intevation.flys.client.shared.model.ChartInfo;
ingo@521: import de.intevation.flys.client.shared.model.Collection;
ingo@521: import de.intevation.flys.client.shared.model.OutputMode;
ingo@1280: import de.intevation.flys.client.shared.model.ZoomObj;
ingo@521: import de.intevation.flys.client.client.Config;
ingo@531: import de.intevation.flys.client.client.event.OutputParameterChangeEvent;
ingo@531: import de.intevation.flys.client.client.event.OutputParameterChangeHandler;
ingo@552: import de.intevation.flys.client.client.event.PanEvent;
ingo@552: import de.intevation.flys.client.client.event.PanHandler;
felix@858: import de.intevation.flys.client.client.event.RedrawRequestHandler;
ingo@911: import de.intevation.flys.client.client.event.RedrawRequestEvent;
ingo@911: import de.intevation.flys.client.client.event.RedrawRequestEvent.Type;
ingo@541: import de.intevation.flys.client.client.event.ZoomEvent;
ingo@541: import de.intevation.flys.client.client.event.ZoomHandler;
ingo@538: import de.intevation.flys.client.client.services.ChartInfoService;
ingo@538: import de.intevation.flys.client.client.services.ChartInfoServiceAsync;
ingo@521: import de.intevation.flys.client.client.ui.CollectionView;
ingo@521: import de.intevation.flys.client.client.ui.OutputTab;
ingo@521:
ingo@521:
ingo@521: /**
ingo@521: * @author Ingo Weinzierl
ingo@521: */
ingo@531: public class ChartOutputTab
ingo@531: extends OutputTab
felix@855: implements ResizedHandler,
felix@855: OutputParameterChangeHandler,
felix@855: ZoomHandler,
felix@858: PanHandler,
felix@858: RedrawRequestHandler
ingo@531: {
ingo@521: public static final int DEFAULT_CHART_WIDTH = 600;
ingo@521: public static final int DEFAULT_CHART_HEIGHT = 500;
ingo@521:
ingo@527: public static final int THEMEPANEL_MIN_WIDTH = 200;
ingo@527:
ingo@527:
felix@855: /** The service that is used to fetch chart information. */
ingo@538: protected ChartInfoServiceAsync info = GWT.create(ChartInfoService.class);
ingo@538:
ingo@552:
ingo@552: /** The ChartInfo object that provides information about the current
felix@855: * chart. */
ingo@552: protected ChartInfo chartInfo;
ingo@552:
felix@855: /** Transformer used to transform image pixels into chart coordinates. */
ingo@561: protected Transform2D[] transformer;
ingo@538:
ingo@538: /** The collection view.*/
ingo@538: protected CollectionView view;
ingo@538:
ingo@911: protected ChartThemePanel ctp;
ingo@911:
ingo@538:
felix@855: /** The canvas that wraps the chart toolbar. */
ingo@521: protected Canvas tbarPanel;
ingo@521:
felix@855: /** The canvas that wraps the theme editor. */
ingo@521: protected Canvas left;
ingo@521:
felix@855: /** The canvas that wraps the chart. */
ingo@521: protected Canvas right;
ingo@521:
ingo@610: protected Img chart;
ingo@610:
ingo@521:
felix@855: /** Chart zoom options. */
ingo@561: protected int[] xrange;
ingo@561: protected int[] yrange;
ingo@561:
ingo@1280: protected Stack zoomStack;
ingo@1280: protected double[] zoom;
ingo@542:
ingo@542:
ingo@521: /**
ingo@521: * The default constructor to create a new ChartOutputTab.
ingo@521: *
ingo@521: * @param title The title of this tab.
ingo@521: * @param collection The Collection which this chart belongs to.
ingo@521: * @param mode The OutputMode.
felix@858: * @param collectionView The shown collection.
ingo@521: */
ingo@521: public ChartOutputTab(
sascha@615: String title,
ingo@521: Collection collection,
ingo@521: OutputMode mode,
ingo@521: CollectionView collectionView
ingo@521: ){
ingo@521: super(title, collection, mode);
ingo@521:
ingo@538: view = collectionView;
ingo@521: left = new Canvas();
ingo@521: right = new Canvas();
ingo@561: xrange = new int[2];
ingo@561: yrange = new int[2];
ingo@561: zoom = new double[4];
ingo@1280: zoomStack = new Stack();
ingo@521:
ingo@521: left.setBorder("1px solid black");
ingo@527: left.setWidth(THEMEPANEL_MIN_WIDTH);
ingo@527: left.setMinWidth(THEMEPANEL_MIN_WIDTH);
ingo@521: right.setWidth("*");
ingo@521:
ingo@521: VLayout vLayout = new VLayout();
ingo@521: vLayout.setMembersMargin(2);
ingo@521:
ingo@521: HLayout hLayout = new HLayout();
ingo@521: hLayout.setWidth100();
ingo@521: hLayout.setHeight100();
ingo@521: hLayout.setMembersMargin(10);
ingo@521:
ingo@521: hLayout.addMember(left);
ingo@521: hLayout.addMember(right);
ingo@521:
felix@858: // Output "cross_section" needs slightly modified ThemePanel
felix@858: // (with action buttons).
felix@858: if (mode.getName().equals("cross_section")) {
felix@858: ctp = new CrossSectionChartThemePanel(collection, mode);
felix@858: }
felix@858: else {
felix@858: ctp = new ChartThemePanel(collection, mode);
felix@858: }
felix@858:
felix@859: ctp.addRedrawRequestHandler(this);
ingo@531: ctp.addOutputParameterChangeHandler(this);
ingo@531:
ingo@610: chart = createChartImg();
ingo@610: right.addChild(chart);
ingo@610: right.setOverflow(Overflow.HIDDEN);
ingo@610: //right.addChild(createChartPanel());
ingo@531: left.addChild(ctp);
ingo@521:
ingo@779: tbarPanel = new ChartToolbar(collectionView, this);
ingo@521: vLayout.addMember(tbarPanel);
ingo@521: vLayout.addMember(hLayout);
ingo@521:
ingo@521: setPane(vLayout);
ingo@521:
ingo@521: right.addResizedHandler(this);
ingo@521: }
ingo@521:
ingo@521:
ingo@521: /**
ingo@521: * This method is called after the chart panel has resized. It removes the
ingo@521: * chart - if existing - and requests a new one with adjusted size.
ingo@521: *
ingo@521: * @param event The resize event.
ingo@521: */
ingo@521: public void onResized(ResizedEvent event) {
ingo@531: updateChartPanel();
ingo@561: updateChartInfo();
ingo@531: }
ingo@531:
ingo@531:
ingo@911: public void onRedrawRequest(RedrawRequestEvent event) {
ingo@911: if (event.getType() == Type.RESET) {
ingo@911: resetRanges();
ingo@911: }
ingo@911: else {
ingo@911: ctp.updateCollection();
ingo@911: updateChartPanel();
ingo@911: updateChartInfo();
ingo@911: }
felix@858: }
felix@858:
felix@858:
ingo@531: /**
ingo@531: * Listens to change event in the chart them panel and updates chart after
ingo@531: * receiving such an event.
ingo@531: *
ingo@531: * @param event The OutputParameterChangeEvent.
ingo@531: */
ingo@531: public void onOutputParameterChanged(OutputParameterChangeEvent event) {
ingo@531: updateChartPanel();
ingo@531: }
ingo@531:
ingo@531:
ingo@538: /**
ingo@541: * Listens to zoom events and refreshes the current chart in such case.
ingo@541: *
ingo@541: * @param evt The ZoomEvent that stores the coordinates for zooming.
ingo@541: */
ingo@541: public void onZoom(ZoomEvent evt) {
ingo@1280: zoomStack.push(new ZoomObj(zoom[0], zoom[1], zoom[2], zoom[3]));
ingo@1280:
ingo@561: xrange[0] = evt.getStartX();
ingo@561: xrange[1] = evt.getEndX();
ingo@561: yrange[0] = evt.getStartY();
ingo@561: yrange[1] = evt.getEndY();
ingo@541:
ingo@561: xrange[0] = xrange[0] < xrange[1] ? xrange[0] : xrange[1];
ingo@561: yrange[0] = yrange[0] < yrange[1] ? yrange[0] : yrange[1];
ingo@541:
ingo@561: translateCoordinates();
ingo@561:
ingo@561: updateChartInfo();
ingo@542: updateChartPanel();
ingo@541: }
ingo@541:
ingo@541:
ingo@561: protected double[] translateCoordinates() {
ingo@561: if (xrange == null || (xrange[0] == 0 && xrange[1] == 0)) {
ingo@561: zoom[0] = 0d;
ingo@561: zoom[1] = 1d;
ingo@561: }
ingo@561: else {
ingo@561: translateXCoordinates();
ingo@561: }
ingo@561:
ingo@561: if (yrange == null || (yrange[0] == 0 && yrange[1] == 0)) {
ingo@561: zoom[2] = 0d;
ingo@561: zoom[3] = 1d;
ingo@561: }
ingo@561: else {
ingo@561: translateYCoordinates();
ingo@561: }
ingo@561:
ingo@561: return zoom;
ingo@561: }
ingo@561:
ingo@561:
ingo@561: protected void translateXCoordinates() {
ingo@561: Axis xAxis = chartInfo.getXAxis(0);
ingo@561:
ingo@561: double xmin = xAxis.getMin();
ingo@561: double xmax = xAxis.getMax();
ingo@561: double xRange = xmax - xmin;
ingo@561:
ingo@561: Transform2D transformer = getTransformer(0);
ingo@561:
ingo@561: double[] start = transformer.transform(xrange[0], yrange[0]);
ingo@561: double[] end = transformer.transform(xrange[1], yrange[1]);
ingo@561:
ingo@561: zoom[0] = (start[0] - xmin) / xRange;
ingo@561: zoom[1] = (end[0] - xmin) / xRange;
ingo@561: }
ingo@561:
ingo@561:
ingo@561: protected void translateYCoordinates() {
ingo@561: Axis yAxis = chartInfo.getYAxis(0);
ingo@561:
ingo@561: double ymin = yAxis.getMin();
ingo@561: double ymax = yAxis.getMax();
ingo@561: double yRange = ymax - ymin;
ingo@561:
ingo@561: Transform2D transformer = getTransformer(0);
ingo@561:
ingo@561: double[] start = transformer.transform(xrange[0], yrange[0]);
ingo@561: double[] end = transformer.transform(xrange[1], yrange[1]);
ingo@561:
ingo@561: zoom[2] = (start[1] - ymin) / yRange;
ingo@561: zoom[3] = (end[1] - ymin) / yRange;
ingo@561: }
ingo@561:
ingo@561:
ingo@552: public void onPan(PanEvent event) {
ingo@561: if (chartInfo == null) {
sascha@555: return;
sascha@555: }
ingo@561:
ingo@552: int[] start = event.getStartPos();
ingo@552: int[] end = event.getEndPos();
ingo@552:
ingo@561: Transform2D t = getTransformer();
ingo@552:
ingo@561: double[] ts = t.transform(start[0], start[1]);
ingo@561: double[] tt = t.transform(end[0], end[1]);
ingo@552:
ingo@561: double diffX = ts[0] - tt[0];
ingo@561: double diffY = ts[1] - tt[1];
ingo@561:
ingo@552: Axis xAxis = chartInfo.getXAxis(0);
ingo@552: Axis yAxis = chartInfo.getYAxis(0);
ingo@552:
ingo@561: double[] x = panAxis(xAxis, diffX);
ingo@561: double[] y = panAxis(yAxis, diffY);
ingo@552:
ingo@561: zoom[0] = x[0];
ingo@561: zoom[1] = x[1];
ingo@561: zoom[2] = y[0];
ingo@561: zoom[3] = y[1];
ingo@561:
ingo@561: updateChartInfo();
ingo@552: updateChartPanel();
ingo@552: }
ingo@552:
ingo@552:
ingo@561: protected double[] panAxis(Axis axis, double diff) {
ingo@561: double min = axis.getFrom();
ingo@561: double max = axis.getTo();
ingo@561:
ingo@561: min += diff;
ingo@561: max += diff;
ingo@561:
ingo@561: return computeZoom(axis, min, max);
ingo@561: }
ingo@561:
ingo@561:
ingo@543: public void resetRanges() {
ingo@1281: zoomStack.push(new ZoomObj(zoom[0], zoom[1], zoom[2], zoom[3]));
ingo@1281:
ingo@561: zoom[0] = 0d;
ingo@561: zoom[1] = 1d;
ingo@561: zoom[2] = 0d;
ingo@561: zoom[3] = 1d;
ingo@543:
ingo@561: updateChartInfo();
ingo@543: updateChartPanel();
ingo@543: }
ingo@543:
ingo@543:
ingo@541: /**
ingo@1281: * This method zooms the current chart out by a given factor.
ingo@1281: *
ingo@1281: * @param factor The factor should be between 0-100.
ingo@1281: */
ingo@1281: public void zoomOut(int factor) {
ingo@1281: if (factor < 0 || factor > 100 || chartInfo == null) {
ingo@1281: return;
ingo@1281: }
ingo@1281:
ingo@1281: zoomStack.push(new ZoomObj(zoom[0], zoom[1], zoom[2], zoom[3]));
ingo@1281:
ingo@1281: Axis xAxis = chartInfo.getXAxis(0);
ingo@1281: Axis yAxis = chartInfo.getYAxis(0);
ingo@1281:
ingo@1281: double[] x = zoomAxis(xAxis, factor);
ingo@1281: double[] y = zoomAxis(yAxis, factor);
ingo@1281:
ingo@1281: zoom[0] = x[0];
ingo@1281: zoom[1] = x[1];
ingo@1281: zoom[2] = x[0];
ingo@1281: zoom[3] = y[1];
ingo@1281:
ingo@1281: updateChartInfo();
ingo@1281: updateChartPanel();
ingo@1281: }
ingo@1281:
ingo@1281:
ingo@1281: /**
ingo@1280: * This method is used to zoom out. Zooming out is realizied with a stacked
ingo@1280: * logic. Initially, you cannot zoom out. For each time you start a zoom-in
ingo@1280: * action, the extent of the chart is stored and pushed onto a stack. A
ingo@1280: * zoom-out will now lead you to the last extent that is poped from stack.
ingo@544: *
ingo@544: */
ingo@1280: public void zoomOut() {
ingo@1280: if (!zoomStack.empty()) {
ingo@1280: zoom = zoomStack.pop().getZoom();
ingo@548:
ingo@1280: updateChartInfo();
ingo@1280: updateChartPanel();
ingo@1280: }
ingo@544: }
ingo@544:
ingo@544:
ingo@561: public double[] zoomAxis(Axis axis, int factor) {
ingo@561: double min = axis.getFrom();
ingo@561: double max = axis.getTo();
ingo@561:
ingo@561: double add = (max - min) / 100 * factor;
ingo@561: add = add < 0 ? (-1) * add : add;
ingo@561:
ingo@561: min -= add;
ingo@561: max += add;
ingo@561:
ingo@561: return computeZoom(axis, min, max);
ingo@561: }
ingo@561:
ingo@561:
ingo@561: public static double[] computeZoom(Axis axis, double min, double max) {
ingo@561: double[] zoom = new double[2];
ingo@561:
ingo@561: double absMin = axis.getMin();
ingo@561: double absMax = axis.getMax();
ingo@561: double diff = absMax > absMin ? absMax - absMin : absMin - absMax;
ingo@561:
ingo@561: zoom[0] = (min - absMin) / diff;
ingo@561: zoom[1] = (max - absMin) / diff;
ingo@561:
ingo@561: return zoom;
ingo@561: }
ingo@561:
ingo@561:
ingo@544: /**
ingo@538: * Updates the Transform2D object using the chart info service.
ingo@538: */
ingo@561: public void updateChartInfo() {
ingo@538: Config config = Config.getInstance();
ingo@538: String url = config.getServerUrl();
ingo@538: String locale = config.getLocale();
ingo@538:
ingo@538: info.getChartInfo(
ingo@538: view.getCollection(),
ingo@538: url,
ingo@538: locale,
ingo@538: mode.getName(),
ingo@549: getChartAttributes(),
ingo@552: new AsyncCallback() {
ingo@538: public void onFailure(Throwable caught) {
ingo@610: GWT.log("ChartInfo ERROR: " + caught.getMessage());
ingo@538: }
ingo@538:
ingo@552: public void onSuccess(ChartInfo chartInfo) {
ingo@552: setChartInfo(chartInfo);
ingo@538: }
ingo@538: });
ingo@538: }
ingo@538:
ingo@538:
ingo@531: public void updateChartPanel() {
ingo@610: int w = right.getWidth();
ingo@610: int h = right.getHeight();
ingo@521:
ingo@610: chart.setSrc(getImgUrl(w, h));
ingo@521: }
ingo@521:
ingo@521:
ingo@542: /**
ingo@542: * Returns the existing chart panel.
ingo@542: *
ingo@542: * @return the existing chart panel.
ingo@542: */
ingo@534: public Canvas getChartPanel() {
ingo@610: return right;
ingo@610: }
ingo@538:
ingo@538:
ingo@610: public Canvas getChartImg() {
ingo@610: return chart;
ingo@538: }
ingo@538:
ingo@538:
ingo@552: public ChartInfo getChartInfo() {
ingo@552: return chartInfo;
ingo@552: }
ingo@552:
ingo@552:
ingo@552: protected void setChartInfo(ChartInfo chartInfo) {
ingo@552: this.chartInfo = chartInfo;
ingo@561: }
ingo@561:
ingo@561:
ingo@561: public Transform2D getTransformer() {
ingo@561: if (chartInfo == null) {
ingo@561: return null;
ingo@561: }
ingo@561:
ingo@561: return chartInfo.getTransformer(0);
ingo@552: }
ingo@552:
ingo@552:
ingo@538: /**
ingo@538: * Returns the Transform2D object used to transform image coordinates into
ingo@538: * chart coordinates.
ingo@538: *
ingo@561: * @param pos The index of a specific transformer.
ingo@561: *
ingo@538: * @return the Transform2D object.
ingo@538: */
ingo@561: public Transform2D getTransformer(int pos) {
ingo@561: if (chartInfo == null) {
ingo@561: return null;
ingo@561: }
ingo@538:
ingo@561: return chartInfo.getTransformer(pos);
ingo@534: }
ingo@534:
ingo@534:
ingo@542: /**
ingo@542: * Creates a new chart panel with default size.
ingo@542: *
ingo@542: * @return the created chart panel.
ingo@542: */
ingo@610: protected Img createChartImg() {
ingo@610: return createChartImg(DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT);
ingo@521: }
ingo@521:
ingo@521:
ingo@542: /**
ingo@542: * Creates a new chart panel with specified width and height.
ingo@542: *
ingo@542: * @param width The width for the chart panel.
ingo@542: * @param height The height for the chart panel.
ingo@542: *
ingo@542: * @return the created chart panel.
ingo@542: */
ingo@610: protected Img createChartImg(int width, int height) {
ingo@521: Img chart = getChartImg(width, height);
ingo@521: chart.setWidth100();
ingo@521: chart.setHeight100();
ingo@521:
ingo@521: return chart;
ingo@521: }
ingo@521:
ingo@521:
ingo@521: /**
ingo@521: * Builds the chart image and returns it.
ingo@521: *
ingo@521: * @param width The chart width.
ingo@521: * @param height The chart height.
ingo@521: *
ingo@521: * @return the chart image.
ingo@521: */
ingo@521: protected Img getChartImg(int width, int height) {
ingo@521: return new Img(getImgUrl(width, height));
ingo@521: }
ingo@521:
ingo@521:
ingo@521: /**
ingo@521: * Builds the URL that points to the chart image.
ingo@521: *
ingo@521: * @param width The width of the requested chart.
ingo@521: * @param height The height of the requested chart.
ingo@542: * @param xr Optional x range (used for zooming).
ingo@542: * @param yr Optional y range (used for zooming).
ingo@521: *
ingo@521: * @return the URL to the chart image.
ingo@521: */
ingo@521: protected String getImgUrl(int width, int height) {
ingo@521: Config config = Config.getInstance();
ingo@521:
ingo@521: String imgUrl = GWT.getModuleBaseURL();
ingo@521: imgUrl += "chart";
ingo@521: imgUrl += "?uuid=" + collection.identifier();
ingo@521: imgUrl += "&type=" + mode.getName();
ingo@521: imgUrl += "&server=" + config.getServerUrl();
ingo@521: imgUrl += "&locale=" + config.getLocale();
ingo@521: imgUrl += "×tamp=" + new Date().getTime();
ingo@521: imgUrl += "&width=" + Integer.toString(width);
ingo@521: imgUrl += "&height=" + Integer.toString(height);
ingo@521:
ingo@561: double[] zoom = getZoomValues();
ingo@542:
ingo@561: if (zoom != null) {
ingo@612: if (zoom[0] != 0 || zoom[1] != 1) {
ingo@612: // a zoom range of 0-1 means displaying the whole range. In such
ingo@612: // case we don't need to zoom.
ingo@612: imgUrl += "&minx=" + Double.toString(zoom[0]);
ingo@612: imgUrl += "&maxx=" + Double.toString(zoom[1]);
ingo@612: }
ingo@612: if (zoom[2] != 0 || zoom[3] != 1) {
ingo@612: // a zoom range of 0-1 means displaying the whole range. In such
ingo@612: // case we don't need to zoom.
ingo@612: imgUrl += "&miny=" + Double.toString(zoom[2]);
ingo@612: imgUrl += "&maxy=" + Double.toString(zoom[3]);
ingo@612: }
ingo@542: }
ingo@542:
ingo@521: return imgUrl;
ingo@521: }
ingo@549:
ingo@549:
ingo@549: public Map getChartAttributes() {
ingo@549: Map attr = new HashMap();
ingo@549:
ingo@549: Canvas chart = getChartPanel();
ingo@549: attr.put("width", chart.getWidth().toString());
ingo@549: attr.put("height", chart.getHeight().toString());
ingo@549:
ingo@561: double[] zoom = getZoomValues();
ingo@549:
ingo@561: if (zoom != null) {
ingo@612: if (zoom[0] != 0 || zoom[1] != 1) {
ingo@612: // a zoom range of 0-1 means displaying the whole range. In such
ingo@612: // case we don't need to zoom.
ingo@612: attr.put("minx", Double.toString(zoom[0]));
ingo@612: attr.put("maxx", Double.toString(zoom[1]));
ingo@612: }
ingo@612: if (zoom[2] != 0 || zoom[3] != 1) {
ingo@612: // a zoom range of 0-1 means displaying the whole range. In such
ingo@612: // case we don't need to zoom.
ingo@612: attr.put("miny", Double.toString(zoom[2]));
ingo@612: attr.put("maxy", Double.toString(zoom[3]));
ingo@612: }
ingo@549: }
ingo@549:
ingo@549: return attr;
ingo@549: }
ingo@561:
ingo@561:
ingo@561: protected double[] getZoomValues() {
ingo@561: return zoom;
ingo@561: }
ingo@521: }
ingo@521: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :