comparison gwt-client/src/main/java/org/dive4elements/river/client/client/ui/chart/ChartOutputTab.java @ 5838:5aa05a7a34b7

Rename modules to more fitting names.
author Sascha L. Teichmann <teichmann@intevation.de>
date Thu, 25 Apr 2013 15:23:37 +0200
parents flys-client/src/main/java/org/dive4elements/river/client/client/ui/chart/ChartOutputTab.java@821a02bbfb4e
children 172338b1407f
comparison
equal deleted inserted replaced
5837:d9901a08d0a6 5838:5aa05a7a34b7
1 package org.dive4elements.river.client.client.ui.chart;
2
3 import com.google.gwt.core.client.GWT;
4 import com.google.gwt.user.client.rpc.AsyncCallback;
5 import com.smartgwt.client.types.Overflow;
6 import com.smartgwt.client.widgets.Canvas;
7 import com.smartgwt.client.widgets.Img;
8 import com.smartgwt.client.widgets.events.ResizedEvent;
9 import com.smartgwt.client.widgets.events.ResizedHandler;
10 import com.smartgwt.client.widgets.layout.HLayout;
11 import com.smartgwt.client.widgets.layout.VLayout;
12
13 import org.dive4elements.river.client.client.Config;
14 import org.dive4elements.river.client.client.event.OutputParameterChangeEvent;
15 import org.dive4elements.river.client.client.event.OutputParameterChangeHandler;
16 import org.dive4elements.river.client.client.event.PanEvent;
17 import org.dive4elements.river.client.client.event.PanHandler;
18 import org.dive4elements.river.client.client.event.RedrawRequestEvent;
19 import org.dive4elements.river.client.client.event.RedrawRequestEvent.Type;
20 import org.dive4elements.river.client.client.event.RedrawRequestHandler;
21 import org.dive4elements.river.client.client.event.ZoomEvent;
22 import org.dive4elements.river.client.client.event.ZoomHandler;
23 import org.dive4elements.river.client.client.services.ChartInfoService;
24 import org.dive4elements.river.client.client.services.ChartInfoServiceAsync;
25 import org.dive4elements.river.client.client.ui.CollectionView;
26 import org.dive4elements.river.client.client.ui.OutputTab;
27 import org.dive4elements.river.client.shared.Transform2D;
28 import org.dive4elements.river.client.shared.model.Axis;
29 import org.dive4elements.river.client.shared.model.ChartInfo;
30 import org.dive4elements.river.client.shared.model.Collection;
31 import org.dive4elements.river.client.shared.model.OutputMode;
32 import org.dive4elements.river.client.shared.model.ZoomObj;
33
34 import java.util.Date;
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.Stack;
38
39
40 /**
41 * Tab representing and showing one Chart-output (diagram).
42 *
43 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
44 */
45 public class ChartOutputTab
46 extends OutputTab
47 implements ResizedHandler,
48 OutputParameterChangeHandler,
49 ZoomHandler,
50 PanHandler,
51 RedrawRequestHandler
52 {
53 public static final int DEFAULT_CHART_WIDTH = 600;
54 public static final int DEFAULT_CHART_HEIGHT = 500;
55
56 public static final int THEMEPANEL_MIN_WIDTH = 250;
57
58 /** The service that is used to fetch chart information. */
59 protected ChartInfoServiceAsync info = GWT.create(ChartInfoService.class);
60
61 /** The ChartInfo object that provides information about the current
62 * chart. */
63 protected ChartInfo chartInfo;
64
65 /** Transformer used to transform image pixels into chart (data) coordinates. */
66 protected Transform2D[] transformer;
67
68 /** The collection view.*/
69 protected CollectionView view;
70
71 /** The ThemePanel to expose control over themes (facettes). */
72 protected ChartThemePanel ctp;
73
74 /** The canvas that wraps the chart toolbar. */
75 protected ChartToolbar tbarPanel;
76
77 /** The canvas that wraps the theme editor. */
78 protected Canvas left;
79
80 /** The canvas that wraps the chart. */
81 protected Canvas right;
82
83 protected Img chart;
84
85 /** Chart zoom options. */
86 protected int[] xrange;
87 protected int[] yrange;
88
89 /** Stack of ZoomObj to allow 'redo last zoom'-kind of actions. */
90 protected Stack<ZoomObj> zoomStack;
91 protected Number[] zoom;
92
93
94 /**
95 * The default constructor to create a new ChartOutputTab.
96 *
97 * @param title The title of this tab.
98 * @param collection The Collection which this chart belongs to.
99 * @param mode The OutputMode.
100 * @param collectionView The shown collection.
101 */
102 public ChartOutputTab(
103 String title,
104 Collection collection,
105 OutputMode mode,
106 CollectionView collectionView
107 ){
108 super(title, collection, collectionView, mode);
109
110 view = collectionView;
111 left = new Canvas();
112 right = new Canvas();
113 xrange = new int[2];
114 yrange = new int[2];
115 zoomStack = new Stack<ZoomObj>();
116
117 zoom = new Number[] {
118 new Double(0), new Double(1),
119 new Double(0), new Double(1) };
120
121 left.setBorder("1px solid gray");
122 left.setWidth(THEMEPANEL_MIN_WIDTH);
123 left.setMinWidth(THEMEPANEL_MIN_WIDTH);
124 right.setWidth("*");
125
126 VLayout vLayout = new VLayout();
127 vLayout.setMembersMargin(2);
128
129 HLayout hLayout = new HLayout();
130 hLayout.setWidth100();
131 hLayout.setHeight100();
132 hLayout.setMembersMargin(10);
133
134 hLayout.addMember(left);
135 hLayout.addMember(right);
136
137 ctp = createThemePanel(mode, collectionView);
138 if (ctp != null) {
139 ctp.addRedrawRequestHandler(this);
140 ctp.addOutputParameterChangeHandler(this);
141 left.addChild(ctp);
142 }
143 else {
144 left.setVisible(false);
145 }
146
147 chart = createChartImg();
148 right.addChild(chart);
149 right.setOverflow(Overflow.HIDDEN);
150
151 left.setShowResizeBar(true);
152
153 tbarPanel = createChartToolbar(this);
154 vLayout.addMember(tbarPanel);
155 vLayout.addMember(hLayout);
156 vLayout.setOverflow(Overflow.HIDDEN);
157
158 setPane(vLayout);
159
160 right.addResizedHandler(this);
161 }
162
163
164 public ChartThemePanel createThemePanel(
165 OutputMode mode, CollectionView view
166 ) {
167 // Output "cross_section" needs slightly modified ThemePanel
168 // (with action buttons).
169 if (mode.getName().equals("cross_section")) {
170 return new CrossSectionChartThemePanel(mode, view);
171 }
172 else {
173 return new ChartThemePanel(mode, view);
174 }
175 }
176
177
178 public ChartToolbar createChartToolbar(ChartOutputTab tab) {
179 return new ChartToolbar(tab);
180 }
181
182
183 public void toggleThemePanel() {
184 this.left.setVisible(!left.isVisible());
185 }
186
187
188 /**
189 * This method is called after the chart panel has resized. It removes the
190 * chart - if existing - and requests a new one with adjusted size.
191 *
192 * @param event The resize event.
193 */
194 @Override
195 public void onResized(ResizedEvent event) {
196 updateChartPanel();
197 updateChartInfo();
198 }
199
200
201 /** For RESET type of events, just reset the ranges, otherwise do a
202 * complete refresh of panel, info and collection. */
203 @Override
204 public void onRedrawRequest(RedrawRequestEvent event) {
205 if (event.getType() == Type.RESET) {
206 resetRanges();
207 }
208 else {
209 ctp.updateCollection();
210 updateChartPanel();
211 updateChartInfo();
212 }
213 }
214
215
216 /**
217 * Listens to change event in the chart them panel and updates chart after
218 * receiving such an event.
219 *
220 * @param event The OutputParameterChangeEvent.
221 */
222 @Override
223 public void onOutputParameterChanged(OutputParameterChangeEvent event) {
224 updateChartInfo();
225 updateChartPanel();
226 }
227
228
229 /**
230 * Listens to zoom events and refreshes the current chart in such case.
231 *
232 * @param evt The ZoomEvent that stores the coordinates for zooming.
233 */
234 @Override
235 public void onZoom(ZoomEvent evt) {
236 zoomStack.push(new ZoomObj(zoom[0], zoom[1], zoom[2], zoom[3]));
237
238 xrange[0] = evt.getStartX();
239 xrange[1] = evt.getEndX();
240 yrange[0] = evt.getStartY();
241 yrange[1] = evt.getEndY();
242
243 xrange[0] = xrange[0] < xrange[1] ? xrange[0] : xrange[1];
244 yrange[0] = yrange[0] < yrange[1] ? yrange[0] : yrange[1];
245
246 translateCoordinates();
247
248 updateChartInfo();
249 updateChartPanel();
250 }
251
252
253 protected Number[] translateCoordinates() {
254 if (xrange == null || (xrange[0] == 0 && xrange[1] == 0)) {
255 zoom[0] = 0d;
256 zoom[1] = 1d;
257 }
258 else {
259 translateXCoordinates();
260 }
261
262 if (yrange == null || (yrange[0] == 0 && yrange[1] == 0)) {
263 zoom[2] = 0d;
264 zoom[3] = 1d;
265 }
266 else {
267 translateYCoordinates();
268 }
269
270 return zoom;
271 }
272
273
274 protected void translateXCoordinates() {
275 Axis xAxis = chartInfo.getXAxis(0);
276
277 Number xmin = xAxis.getMin();
278 Number xmax = xAxis.getMax();
279 Number xRange = subtract(xmax, xmin);
280
281 Transform2D transformer = getTransformer(0);
282
283 double[] start = transformer.transform(xrange[0], yrange[0]);
284 double[] end = transformer.transform(xrange[1], yrange[1]);
285
286 zoom[0] = divide(subtract(start[0], xmin), xRange);
287 zoom[1] = divide(subtract(end[0], xmin), xRange);
288 }
289
290
291 protected void translateYCoordinates() {
292 Axis yAxis = chartInfo.getYAxis(0);
293
294 Number ymin = yAxis.getMin();
295 Number ymax = yAxis.getMax();
296 Number yRange = subtract(ymax, ymin);
297
298 Transform2D transformer = getTransformer(0);
299
300 double[] start = transformer.transform(xrange[0], yrange[0]);
301 double[] end = transformer.transform(xrange[1], yrange[1]);
302
303 zoom[2] = divide(subtract(start[1], ymin), yRange);
304 zoom[3] = divide(subtract(end[1], ymin), yRange);
305 }
306
307
308 @Override
309 public void onPan(PanEvent event) {
310 if (chartInfo == null) {
311 return;
312 }
313
314 int[] start = event.getStartPos();
315 int[] end = event.getEndPos();
316
317 Transform2D t = getTransformer();
318
319 double[] ts = t.transform(start[0], start[1]);
320 double[] tt = t.transform(end[0], end[1]);
321
322 double diffX = ts[0] - tt[0];
323 double diffY = ts[1] - tt[1];
324
325 Axis xAxis = chartInfo.getXAxis(0);
326 Axis yAxis = chartInfo.getYAxis(0);
327
328 Number[] x = panAxis(xAxis, diffX);
329 Number[] y = panAxis(yAxis, diffY);
330
331 // Set the zoom coordinates.
332 zoom[0] = x[0];
333 zoom[1] = x[1];
334 zoom[2] = y[0];
335 zoom[3] = y[1];
336
337 updateChartInfo();
338 updateChartPanel();
339 }
340
341
342 protected Number[] panAxis(Axis axis, double diff) {
343 Number min = axis.getFrom();
344 Number max = axis.getTo();
345
346 min = add(min, diff);
347 max = add(max, diff);
348
349 return computeZoom(axis, min, max);
350 }
351
352
353 public void resetRanges() {
354 zoomStack.push(new ZoomObj(zoom[0], zoom[1], zoom[2], zoom[3]));
355
356 zoom[0] = 0d;
357 zoom[1] = 1d;
358 zoom[2] = 0d;
359 zoom[3] = 1d;
360
361 updateChartInfo();
362 updateChartPanel();
363 }
364
365
366 /**
367 * This method zooms the current chart out by a given <i>factor</i>.
368 *
369 * @param factor The factor should be between 0-100.
370 */
371 public void zoomOut(int factor) {
372 if (factor < 0 || factor > 100 || chartInfo == null) {
373 return;
374 }
375
376 zoomStack.push(new ZoomObj(zoom[0], zoom[1], zoom[2], zoom[3]));
377
378 Axis xAxis = chartInfo.getXAxis(0);
379 Axis yAxis = chartInfo.getYAxis(0);
380
381 Number[] x = zoomAxis(xAxis, factor);
382 Number[] y = zoomAxis(yAxis, factor);
383
384 zoom[0] = x[0];
385 zoom[1] = x[1];
386 zoom[2] = y[0];
387 zoom[3] = y[1];
388
389 updateChartInfo();
390 updateChartPanel();
391 }
392
393
394 /**
395 * This method is used to zoom out. Zooming out is realized with a stacked
396 * logic. Initially, you cannot zoom out. For each time you start a zoom-in
397 * action, the extent of the chart is stored and pushed onto a stack. A
398 * zoom-out will now lead you to the last extent that is popped from stack.
399 */
400 public void zoomOut() {
401 if (!zoomStack.empty()) {
402 zoom = zoomStack.pop().getZoom();
403
404 updateChartInfo();
405 updateChartPanel();
406 }
407 }
408
409
410 public Number[] zoomAxis(Axis axis, int factor) {
411 GWT.log("Prepare Axis for zooming (factor: " + factor + ")");
412
413 Number min = axis.getMin();
414 Number max = axis.getMax();
415 Number range = isBigger(max, min) ? subtract(max, min) : subtract(min, max);
416
417 Number curFrom = axis.getFrom();
418 Number curTo = axis.getTo();
419
420 Number diff = isBigger(curTo, curFrom)
421 ? subtract(curTo, curFrom)
422 : subtract(curFrom, curTo);
423
424 GWT.log(" max from : " + min);
425 GWT.log(" max to : " + max);
426 GWT.log(" max range : " + range);
427 GWT.log(" current from: " + curFrom);
428 GWT.log(" current to : " + curTo);
429 GWT.log(" current diff: " + diff);
430
431 Number newFrom = subtract(curFrom, divide(multi(diff, factor), 100));
432 Number newTo = add(curTo, divide(multi(diff, factor), 100));
433
434 GWT.log(" new from: " + newFrom);
435 GWT.log(" new to : " + newTo);
436
437 return new Number[] {
438 divide(subtract(newFrom, min), range),
439 divide(subtract(newTo, min), range)
440 };
441 }
442
443
444 public static Number[] computeZoom(Axis axis, Number min, Number max) {
445 Number[] hereZoom = new Number[2];
446
447 Number absMin = axis.getMin();
448 Number absMax = axis.getMax();
449 Number diff = isBigger(absMax, absMin)
450 ? subtract(absMax, absMin)
451 : subtract(absMin, absMax);
452
453 hereZoom[0] = divide(subtract(min, absMin), diff);
454 hereZoom[1] = divide(subtract(max, absMin), diff);
455
456 return hereZoom;
457 }
458
459
460 /** Get Collection from ChartThemePanel. .*/
461 public Collection getCtpCollection() {
462 return this.ctp.getCollection();
463 }
464
465
466 /**
467 * Updates the Transform2D object using the chart info service.
468 */
469 public void updateChartInfo() {
470 Config config = Config.getInstance();
471 String locale = config.getLocale();
472
473 info.getChartInfo(
474 view.getCollection(),
475 locale,
476 mode.getName(),
477 getChartAttributes(),
478 new AsyncCallback<ChartInfo>() {
479 @Override
480 public void onFailure(Throwable caught) {
481 GWT.log("ChartInfo ERROR: " + caught.getMessage());
482 }
483
484 @Override
485 public void onSuccess(ChartInfo chartInfo) {
486 setChartInfo(chartInfo);
487 }
488 });
489 }
490
491
492 public void updateChartPanel() {
493 int w = right.getWidth();
494 int h = right.getHeight();
495
496 chart.setSrc(getImgUrl(w, h));
497 }
498
499
500 /**
501 * Returns the existing chart panel.
502 *
503 * @return the existing chart panel.
504 */
505 public Canvas getChartPanel() {
506 return right;
507 }
508
509
510 /** Access the Canvas holding the rendered Chart. */
511 public Canvas getChartImg() {
512 return chart;
513 }
514
515
516 /** Get associated ChartInfo object. */
517 public ChartInfo getChartInfo() {
518 return chartInfo;
519 }
520
521
522 protected void setChartInfo(ChartInfo chartInfo) {
523 this.chartInfo = chartInfo;
524 }
525
526
527 public Transform2D getTransformer() {
528 return getTransformer(0);
529 }
530
531
532 /**
533 * Returns the Transform2D object used to transform image coordinates into
534 * chart (data) coordinates.
535 *
536 * @param pos The index of a specific transformer.
537 *
538 * @return the Transform2D object.
539 */
540 public Transform2D getTransformer(int pos) {
541 if (chartInfo == null) {
542 return null;
543 }
544
545 return chartInfo.getTransformer(pos);
546 }
547
548
549 /**
550 * Returns the Transform2D count.
551 *
552 * @return the Transformer2D count
553 */
554 public int getTransformerCount() {
555 if (chartInfo == null) {
556 return 0;
557 }
558
559 return chartInfo.getTransformerCount();
560 }
561
562
563 /**
564 * Creates a new chart panel with default size.
565 *
566 * @return the created chart panel.
567 */
568 protected Img createChartImg() {
569 return createChartImg(DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT);
570 }
571
572
573 /**
574 * Creates a new chart panel with specified width and height.
575 *
576 * @param width The width for the chart panel.
577 * @param height The height for the chart panel.
578 *
579 * @return the created chart panel.
580 */
581 protected Img createChartImg(int width, int height) {
582 Img chart = getChartImg(width, height);
583 chart.setWidth100();
584 chart.setHeight100();
585
586 return chart;
587 }
588
589
590 /**
591 * Builds the chart image and returns it.
592 *
593 * @param width The chart width.
594 * @param height The chart height.
595 *
596 * @return the chart image.
597 */
598 protected Img getChartImg(int width, int height) {
599 return new Img(getImgUrl(width, height));
600 }
601
602
603 /**
604 * Builds the URL that points to the chart image.
605 *
606 * @param width The width of the requested chart.
607 * @param height The height of the requested chart.
608 * @param xr Optional x range (used for zooming).
609 * @param yr Optional y range (used for zooming).
610 *
611 * @return the URL to the chart image.
612 */
613 protected String getImgUrl(int width, int height) {
614 Config config = Config.getInstance();
615
616 String imgUrl = GWT.getModuleBaseURL();
617 imgUrl += "chart";
618 imgUrl += "?uuid=" + collection.identifier();
619 imgUrl += "&type=" + mode.getName();
620 imgUrl += "&locale=" + config.getLocale();
621 imgUrl += "&timestamp=" + new Date().getTime();
622 imgUrl += "&width=" + Integer.toString(width);
623 imgUrl += "&height=" + Integer.toString(height);
624
625 Number[] zoom = getZoomValues();
626
627 if (zoom != null) {
628 if (zoom[0].intValue() != 0 || zoom[1].intValue() != 1) {
629 // a zoom range of 0-1 means displaying the whole range. In such
630 // case we don't need to zoom.
631 imgUrl += "&minx=" + zoom[0];
632 imgUrl += "&maxx=" + zoom[1];
633 }
634
635 if (zoom[2].intValue() != 0 || zoom[3].intValue() != 1) {
636 // a zoom range of 0-1 means displaying the whole range. In such
637 // case we don't need to zoom.
638 imgUrl += "&miny=" + zoom[2];
639 imgUrl += "&maxy=" + zoom[3];
640 }
641 }
642
643 return imgUrl;
644 }
645
646
647 /** Get link to export image in given dimension and format. */
648 public String getExportUrl(int width, int height, String format) {
649 String url = getImgUrl(width, height);
650 url += "&format=" + format;
651 url += "&export=true";
652
653 return url;
654 }
655
656
657 public Map <String, String> getChartAttributes() {
658 Map<String, String> attr = new HashMap<String, String>();
659
660 Canvas chart = getChartPanel();
661 attr.put("width", chart.getWidth().toString());
662 attr.put("height", chart.getHeight().toString());
663
664 Number[] zoom = getZoomValues();
665
666 if (zoom != null) {
667 if (zoom[0].intValue() != 0 || zoom[1].intValue() != 1) {
668 // a zoom range of 0-1 means displaying the whole range. In such
669 // case we don't need to zoom.
670 attr.put("minx", zoom[0].toString());
671 attr.put("maxx", zoom[1].toString());
672 }
673 if (zoom[2].intValue() != 0 || zoom[3].intValue() != 1) {
674 // a zoom range of 0-1 means displaying the whole range. In such
675 // case we don't need to zoom.
676 attr.put("miny", zoom[2].toString());
677 attr.put("maxy", zoom[3].toString());
678 }
679 }
680
681 return attr;
682 }
683
684
685 protected Number[] getZoomValues() {
686 return zoom;
687 }
688
689
690 /** Return the 'parent' CollectionView. */
691 public CollectionView getView() {
692 return this.view;
693 }
694
695
696 public static Number subtract(Number left, Number right) {
697 if (left instanceof Double) {
698 return new Double(left.doubleValue() - right.doubleValue());
699 }
700 else if (left instanceof Long) {
701 return new Long(left.longValue() - right.longValue());
702 }
703 else {
704 return new Integer(left.intValue() - right.intValue());
705 }
706 }
707
708
709 /** Add two numbers, casting to Type of param left. */
710 public static Number add(Number left, Number right) {
711 if (left instanceof Double) {
712 return new Double(left.doubleValue() + right.doubleValue());
713 }
714 else if (left instanceof Long) {
715 return new Long(left.longValue() + right.longValue());
716 }
717 else {
718 return new Integer(left.intValue() + right.intValue());
719 }
720 }
721
722
723 /** Divde left by right. Note that Long will be casted to double. */
724 public static Number divide(Number left, Number right) {
725 if (left instanceof Double) {
726 return new Double(left.doubleValue() / right.doubleValue());
727 }
728 else if (left instanceof Long) {
729 return new Double(left.doubleValue() / right.doubleValue());
730 }
731 else {
732 return new Integer(left.intValue() / right.intValue());
733 }
734 }
735
736
737 public static Number multi(Number left, Number right) {
738 if (left instanceof Double) {
739 return new Double(left.doubleValue() * right.doubleValue());
740 }
741 else if (left instanceof Long) {
742 return new Long(left.longValue() * right.longValue());
743 }
744 else {
745 return new Integer(left.intValue() * right.intValue());
746 }
747 }
748
749
750 public static boolean isBigger(Number left, Number right) {
751 if (left instanceof Double) {
752 return left.doubleValue() > right.doubleValue();
753 }
754 else if (left instanceof Long) {
755 return left.longValue() > right.longValue();
756 }
757 else {
758 return left.intValue() > right.intValue();
759 }
760 }
761 }
762 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org