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@1282: zoom[2] = y[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@1282: GWT.log("Prepare Axis for zooming (factor: " + factor + ")"); ingo@561: ingo@1282: double min = axis.getMin(); ingo@1282: double max = axis.getMax(); ingo@1282: double range = max > min ? max - min : min - max; ingo@561: ingo@1282: double curFrom = axis.getFrom(); ingo@1282: double curTo = axis.getTo(); ingo@561: ingo@1282: double diff = curTo > curFrom ? curTo - curFrom : curFrom - curTo; ingo@1282: ingo@1282: GWT.log(" max from : " + min); ingo@1282: GWT.log(" max to : " + max); ingo@1282: GWT.log(" max range : " + range); ingo@1282: GWT.log(" current from: " + curFrom); ingo@1282: GWT.log(" current to : " + curTo); ingo@1282: GWT.log(" current diff: " + diff); ingo@1282: ingo@1282: double newFrom = curFrom - (diff * factor / 100); ingo@1282: double newTo = curTo + (diff * factor / 100); ingo@1282: ingo@1282: GWT.log(" new from: " + newFrom); ingo@1282: GWT.log(" new to : " + newTo); ingo@1282: ingo@1282: return new double[] { ingo@1282: (newFrom - min) / range, ingo@1282: (newTo - min) / range ingo@1282: }; 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 :