Java tutorial
package com.vaadin.addon.timeline.gwt.client; /* * #%L * Vaadin Charts * %% * Copyright (C) 2014 Vaadin Ltd * %% * This program is available under Commercial Vaadin Add-On License 3.0 * (CVALv3). * * See the file licensing.txt distributed with this software for more * information about licensing. * * You should have received a copy of the CVALv3 along with this program. * If not, see <https://vaadin.com/license/cval-3>. * #L% */ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Touch; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveHandler; import com.google.gwt.event.dom.client.MouseWheelEvent; import com.google.gwt.event.dom.client.MouseWheelHandler; import com.google.gwt.event.dom.client.TouchEndEvent; import com.google.gwt.event.dom.client.TouchEvent; import com.google.gwt.event.dom.client.TouchMoveEvent; import com.google.gwt.event.dom.client.TouchMoveHandler; import com.google.gwt.event.dom.client.TouchStartEvent; import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Event.NativePreviewEvent; import com.google.gwt.user.client.Event.NativePreviewHandler; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; import com.vaadin.addon.timeline.gwt.canvas.client.Canvas; import com.vaadin.addon.timeline.gwt.client.VCanvasPlotter.Graph; import com.vaadin.addon.timeline.gwt.client.VCanvasPlotter.PlottingListener; import com.vaadin.addon.timeline.gwt.client.VCanvasPlotter.Point; import com.vaadin.client.DateTimeService; import com.vaadin.client.Util; import com.vaadin.client.VConsole; /** * VTimelineDisplay * */ public class VTimelineDisplay extends VTimelineCanvasComponent implements VDataListener, MouseDownHandler, MouseMoveHandler, MouseWheelHandler, NativePreviewHandler, PlottingListener, TouchStartHandler, TouchMoveHandler { private static final String CLASSNAME_BOTTOMBAR = VTimelineWidget.DISPLAY_CLASSNAME + "-bottombar"; private static final String CLASSNAME_CANVAS = VTimelineWidget.DISPLAY_CLASSNAME + "-canvas"; private static final String CLASSNAME_CANVASDRAG = CLASSNAME_CANVAS + "-drag"; private static final String CLASSNAME_MARKER = VTimelineWidget.CLASSNAME + "-marker"; private static final String CLASSNAME_MARKERTOOLTIP = CLASSNAME_MARKER + "-tooltip"; private static final String CLASSNAME_LOADINGCURTAIN = VTimelineWidget.DISPLAY_CLASSNAME + "-curtain"; private static final String CLASSNAME_EVENT = VTimelineWidget.CLASSNAME + "-event"; private static final String CLASSNAME_SCALEVALUE = VTimelineWidget.DISPLAY_CLASSNAME + "-vscale"; private static final String CLASSNAME_SCALEVALUEDRAG = CLASSNAME_SCALEVALUE + "-drag"; private static final String CLASSNAME_SCALEDATE = VTimelineWidget.DISPLAY_CLASSNAME + "-hscale"; private static final String CLASSNAME_SCALEDATE_LEFT = CLASSNAME_SCALEDATE + "-left"; private static final String CLASSNAME_DOT = VTimelineWidget.DISPLAY_CLASSNAME + "-dot"; private static final String CLASSNAME_BAR = VTimelineWidget.DISPLAY_CLASSNAME + "-bar"; private final List<Dot> dots = new ArrayList<Dot>(); private final List<HTML> bars = new ArrayList<HTML>(); private final VTimelineWidget widget; private final Element browserRoot; private final AbsolutePanel displayComponentPanel; private final Canvas canvas; private final VCanvasPlotter plotter; private final HTML bottomBar; private final Map<Integer, List<Float>> currentValues = new HashMap<Integer, List<Float>>(); private final Map<Integer, List<Date>> currentDates = new HashMap<Integer, List<Date>>(); private final Map<Integer, Float> currentMax = new HashMap<Integer, Float>(); private final Map<Integer, Float> currentMin = new HashMap<Integer, Float>(); private List<Graph> currentGraphs = new ArrayList<Graph>(); private final Set<String> currentEvents = new HashSet<String>(); private final Map<Button, List<Integer>> currentEventMap = new HashMap<Button, List<Integer>>(); private final Map<Button, List<Date>> currentEventDates = new HashMap<Button, List<Date>>(); private final Map<String, HTML> markerMap = new HashMap<String, HTML>(); private final Map<String, FlexTable> markerTooltipMap = new HashMap<String, FlexTable>(); private final List<Button> events = new ArrayList<Button>(); private final List<Integer> eventCoordinates = new ArrayList<Integer>(); private float currentTotalMin = 0f; private float currentTotalMax = 0f; // The selected date range private Date currentStartDate = null; private Date currentEndDate = null; // The date range returned from server when // the above was requested private Date currentRealStartDate = null; private Date currentRealEndDate = null; // Request mapping private final Map<Long, Integer> requestGraphMap = new HashMap<Long, Integer>(); // Mouse actions private boolean mouseIsDown = false; private boolean mouseOrTouchActive = false; private int mouseDownX = 0; private int lastMouseX = 0; // States private boolean enabled = true; private PlotMode currentMode; private boolean forcePlot = false; // Counters private int graphDataRecievedCounter = 0; // Dragging private Date currentStartDragDate = null; private Date currentEndDragDate = null; // Vertical scale private Float verticalScaleMin = null; private Float verticalScaleMax = null; // Curtains private final HTML loadingCurtain; private final HTML disabledCurtain; // Scale components lists private final List<Label> horizontalScaleComponents = new ArrayList<Label>(); private final List<Label> verticalScaleComponents = new ArrayList<Label>(); // Error and loading messages private final HTML noDataLabel = new HTML(); private float fittedVerticalScaleMinimum = Float.MAX_VALUE; private float fittedVerticalScaleMaximum = Float.MIN_VALUE; private float[] verticalScaleLines; // Graph formatting private String gridColor = "rgb(200,200,200)"; // Touch related private int lastTouchFingerDistance = 0; private boolean multitouching = false; private Date renderStart = new Date(); private Date lastPlotStartDate = null; private Date lastPlotEndDate = null; private NumberFormat verticalAxisNumberFormat = NumberFormat.getFormat("##.##"); public enum PlotMode { LINE, SCATTER, BAR } public VTimelineDisplay(VTimelineWidget w) { super(w); widget = w; currentMode = PlotMode.LINE; browserRoot = DOM.createDiv(); setElement(browserRoot); setStyleName(VTimelineWidget.DISPLAY_CLASSNAME); // Add the components displayComponentPanel = new AbsolutePanel(); browserRoot.appendChild(displayComponentPanel.getElement()); DOM.setStyleAttribute(displayComponentPanel.getElement(), "position", "relative"); // Add the canvas canvas = new Canvas(100, 100); canvas.setStyleName(CLASSNAME_CANVAS); displayComponentPanel.add(canvas, 0, 0); // Add the plotter plotter = new VCanvasPlotter(canvas); // Create the loading indicator loadingCurtain = new HTML(""); loadingCurtain.setStyleName("v-app-loading"); loadingCurtain.addStyleName(CLASSNAME_LOADINGCURTAIN); loadingCurtain.setWidth("100%"); loadingCurtain.setHeight("100%"); displayComponentPanel.add(loadingCurtain); // Create the bottom bar bottomBar = new HTML(); bottomBar.setWidth("100%"); bottomBar.setStyleName(CLASSNAME_BOTTOMBAR); displayComponentPanel.add(bottomBar); // Add no data source label noDataLabel.setVisible(false); displayComponentPanel.add(noDataLabel, 10, 10); // Create the disabled curtain disabledCurtain = new HTML(""); disabledCurtain.setVisible(false); disabledCurtain.setStyleName(CLASSNAME_LOADINGCURTAIN); disabledCurtain.setWidth("100%"); disabledCurtain.setHeight("100%"); displayComponentPanel.add(disabledCurtain); } /* * (non-Javadoc) * * @see * com.vaadin.addon.timeline.gwt.client.VTimelineCanvasComponent#onLoad() */ @Override protected void onLoad() { super.onLoad(); // Handlers are removed by super.onUnload() handlers.add(addDomHandler(this, MouseDownEvent.getType())); handlers.add(addDomHandler(this, MouseMoveEvent.getType())); handlers.add(addDomHandler(this, MouseWheelEvent.getType())); handlers.add(Event.addNativePreviewHandler(this)); handlers.add(addDomHandler(this, TouchStartEvent.getType())); handlers.add(addDomHandler(this, TouchMoveEvent.getType())); } /* * (non-Javadoc) * * @see com.google.gwt.user.client.ui.UIObject#setHeight(java.lang.String) */ @Override public void setHeight(String height) { super.setHeight(height); canvas.setHeight(getOffsetHeight() - bottomBar.getOffsetHeight()); displayComponentPanel.setHeight(getOffsetHeight() + "px"); displayComponentPanel.setWidgetPosition(bottomBar, 0, getOffsetHeight() - bottomBar.getOffsetHeight()); if (widget.isInitDone()) { plotData(true); } } /* * (non-Javadoc) * * @see com.google.gwt.user.client.ui.UIObject#setWidth(java.lang.String) */ @Override public void setWidth(String width) { super.setWidth(width); canvas.setWidth(getOffsetWidth() + "px"); displayComponentPanel.setWidth(getOffsetWidth() + "px"); if (widget.isInitDone()) { plotData(true); } } /** * Resets the display cache */ public void resetDisplayCache() { currentValues.clear(); currentDates.clear(); currentMin.clear(); currentTotalMin = 0; currentTotalMax = 0; currentMax.clear(); currentGraphs.clear(); } /** * Add a value label * * @param y * The y-coordinate * @param val * The value * @param diff * The time difference */ private void addValueLabel(int y, float val, int diff) { String str = verticalAxisNumberFormat.format(val); Label lbl = new Label(str); lbl.setStyleName(CLASSNAME_SCALEVALUE); displayComponentPanel.add(lbl, 0, y); lbl.getElement().getStyle().clearLeft(); verticalScaleComponents.add(lbl); } /** * Plots the horizontal scale * * @param startTime * The epoch time when the display area starts * @param endTime * The epoch time when the display area ends * @param unitTime * The time vs. pixel width ratio * @param xUnit * The x-coordinate time vs. pixel unit ratio * @param leftAlign * Should the label be left aligned */ @SuppressWarnings("deprecation") private void plotHorizontalScale(long startTime, long endTime, long unitTime, float xUnit, boolean leftAlign) { if (unitTime <= 0 || xUnit <= 0) { return; } float width = unitTime * xUnit; boolean shortDateFormat = width < 100; int year = widget.getStartDate().getYear(); long time = (new Date(year, 0, 1)).getTime(); DateTimeFormat formatter = shortDateFormat ? widget.getDateFormats().getShortDateFormatter(unitTime) : widget.getDateFormats().getLongDateFormatter(unitTime); if (gridColor != null) { canvas.setStrokeStyle(gridColor); canvas.setLineWidth(0.5); canvas.beginPath(); } long stepsUntilInRange = (startTime - time) / unitTime; time += stepsUntilInRange * unitTime; while (time <= endTime) { if (time >= startTime - unitTime && time <= endTime + unitTime) { Label lbl = new Label(); lbl.setStyleName(leftAlign ? CLASSNAME_SCALEDATE_LEFT : CLASSNAME_SCALEDATE); lbl.setWidth(width + "px"); Date date = new Date(time); lbl.setText(widget.getDateTimeService().formatDate(date, formatter.getPattern())); long timeFromStart = time - startTime; float x = timeFromStart * xUnit; if (gridColor != null) { canvas.moveTo(x, 0); canvas.lineTo(x, canvas.getHeight()); } displayComponentPanel.add(lbl, (int) x, displayComponentPanel.getOffsetHeight() - 15); horizontalScaleComponents.add(lbl); } if (unitTime == VDateFormatInfo.MONTH) { /* * Month resolution is not so easy since it changes depending on * the month. We use the Date to resolve the new time */ time += DateTimeService.getNumberOfDaysInMonth(new Date(time)) * VDateFormatInfo.DAY; } else if (unitTime == VDateFormatInfo.YEAR) { /* * Take leap years into account */ if (DateTimeService.isLeapYear(new Date(time))) { time += unitTime + VDateFormatInfo.DAY; } else { time += unitTime; } } else { time += unitTime; } } if (gridColor != null) { canvas.closePath(); canvas.stroke(); } } /** * Plots the horizontal scale on to the canvas * * @param xUnit * The x-coordinate time vs. pixel unit ratio * @param startTime * The epoch time when the display area starts * @param endTime * The epoch time when the display area ends */ private void plotHorizontalScale(float xUnit, long startTime, long endTime) { Date start = new Date(); long timeDiff = endTime - startTime; for (Label lbl : horizontalScaleComponents) { displayComponentPanel.remove(lbl); } horizontalScaleComponents.clear(); canvas.setGlobalCompositeOperation(Canvas.DESTINATION_OVER); // Selections is less then 100ms if (timeDiff <= 100L) { plotHorizontalScale(startTime, endTime, 10, xUnit, true); } // Selections is less then a half of a second else if (timeDiff <= 500L) { plotHorizontalScale(startTime, endTime, 50, xUnit, true); } // Selections is less then a second else if (timeDiff <= VDateFormatInfo.SECOND) { plotHorizontalScale(startTime, endTime, 100, xUnit, true); } // Selection is less then a half minute else if (timeDiff <= VDateFormatInfo.SECOND * 30L) { plotHorizontalScale(startTime, endTime, 5 * VDateFormatInfo.SECOND, xUnit, true); } // Selection is less than a minute else if (timeDiff <= VDateFormatInfo.MINUTE) { plotHorizontalScale(startTime, endTime, 10 * VDateFormatInfo.SECOND, xUnit, true); } // Selections is less the 5 minutes else if (timeDiff <= 5 * VDateFormatInfo.MINUTE) { plotHorizontalScale(startTime, endTime, VDateFormatInfo.MINUTE, xUnit, true); } // Selection is less than 30 minutes else if (timeDiff <= 30 * VDateFormatInfo.MINUTE) { plotHorizontalScale(startTime, endTime, 5 * VDateFormatInfo.MINUTE, xUnit, true); } // Selection is less than 1 hour else if (timeDiff <= VDateFormatInfo.HOUR) { plotHorizontalScale(startTime, endTime, 10 * VDateFormatInfo.MINUTE, xUnit, true); } // Selection is less then 6 hours else if (timeDiff <= 6 * VDateFormatInfo.HOUR) { plotHorizontalScale(startTime, endTime, 30 * VDateFormatInfo.MINUTE, xUnit, true); } // Selection is less then a half day else if (timeDiff <= 12 * VDateFormatInfo.HOUR) { plotHorizontalScale(startTime, endTime, VDateFormatInfo.HOUR, xUnit, false); } // Selection is less than a day else if (timeDiff <= VDateFormatInfo.DAY) { plotHorizontalScale(startTime, endTime, 2 * VDateFormatInfo.HOUR, xUnit, true); } // Selection is less than 3 days else if (timeDiff <= 3 * VDateFormatInfo.DAY) { plotHorizontalScale(startTime, endTime, 6 * VDateFormatInfo.HOUR, xUnit, true); } // Selection is less than a week. Show dayly view else if (timeDiff <= VDateFormatInfo.WEEK) { plotHorizontalScale(startTime, endTime, VDateFormatInfo.DAY, xUnit, false); } // Selection is less than two weeks else if (timeDiff <= 2 * VDateFormatInfo.WEEK) { plotHorizontalScale(startTime, endTime, 3 * VDateFormatInfo.DAY, xUnit, true); } // Selection is less than a month. Show weekly view else if (timeDiff <= 2 * VDateFormatInfo.MONTH) { plotHorizontalScale(startTime, endTime, VDateFormatInfo.WEEK, xUnit, true); } // Selection is less than two years else if (timeDiff <= 2 * VDateFormatInfo.YEAR) { plotHorizontalScale(startTime, endTime, VDateFormatInfo.MONTH, xUnit, false); } // Selection is more than two years else { plotHorizontalScale(startTime, endTime, VDateFormatInfo.YEAR, xUnit, false); } canvas.setGlobalCompositeOperation(Canvas.SOURCE_OVER); long time = new Date().getTime() - start.getTime(); VConsole.log("VTimelineDisplay: Render horizontal scale " + time + "ms"); } /* * (non-Javadoc) * * @see com.vaadin.addon.timeline.gwt.client.VTimelineCanvasComponent# * calculateVerticalZero(float, float) */ @Override protected float calculateVerticalZero(float yUnit, float canvasHeight) { float[] startAndEnd = getVerticalScaleMinAndMax(); float scaleStart = startAndEnd[0]; float scaleEnd = startAndEnd[1]; float zeroYCoordinate = canvasHeight + scaleStart * yUnit; float iNorm = 0f; float valueDiff = Math.abs(scaleEnd - scaleStart); float last = scaleStart; for (float i = scaleStart; i <= scaleEnd; i += valueDiff / 5f) { iNorm = i - scaleStart; // Catch the zero coordinate if (last < 0 && i > 0 || i == 0) { zeroYCoordinate = canvasHeight - (iNorm - i) * yUnit; } last = i; } return zeroYCoordinate; } /** * Resets the vertical fitting scales to the initial values */ public void resetVerticalScaleFitting() { fittedVerticalScaleMinimum = Float.MAX_VALUE; fittedVerticalScaleMaximum = Float.MIN_VALUE; } /* * (non-Javadoc) * * @see com.vaadin.addon.timeline.gwt.client.VTimelineCanvasComponent# * getVerticalScaleMinAndMax() */ @Override protected float[] getVerticalScaleMinAndMax() { float scaleStart = 0f; float scaleEnd = 0f; if (verticalScaleMax == null || verticalScaleMin == null) { // Vertical fitting if (widget.isGraphsStacked()) { if (currentTotalMin == currentTotalMax) { scaleStart = currentTotalMin - 5; scaleEnd = currentTotalMax * widget.getNumGraphs() + 5; } else { scaleStart = currentTotalMin; scaleEnd = currentTotalMax * widget.getNumGraphs(); } } else { if (currentTotalMin == currentTotalMax) { scaleStart = currentTotalMin - 5; scaleEnd = currentTotalMax + 5; } else { scaleStart = currentTotalMin; scaleEnd = currentTotalMax; } } // Only increase scale visiblity if (scaleStart < fittedVerticalScaleMinimum) { fittedVerticalScaleMinimum = scaleStart; } else { scaleStart = fittedVerticalScaleMinimum; } if (scaleEnd > fittedVerticalScaleMaximum) { fittedVerticalScaleMaximum = scaleEnd; } else { scaleEnd = fittedVerticalScaleMaximum; } } else { scaleStart = verticalScaleMin; scaleEnd = verticalScaleMax; fittedVerticalScaleMinimum = Float.MAX_VALUE; fittedVerticalScaleMaximum = Float.MIN_VALUE; } return new float[] { scaleStart, scaleEnd }; } /** * Plots the vertical scale * * @return Returns the y-coordinate of the zero line */ private float plotVerticalScale(float yUnit, float canvasHeight) { Date start = new Date(); for (Label lbl : verticalScaleComponents) { displayComponentPanel.remove(lbl); } verticalScaleComponents.clear(); if (gridColor != null) { canvas.setStrokeStyle(gridColor); canvas.setLineWidth(0.5); canvas.beginPath(); } float[] startAndEnd = getVerticalScaleMinAndMax(); float scaleStart = startAndEnd[0]; float scaleEnd = startAndEnd[1]; float valueDiff = Math.abs(scaleEnd - scaleStart); float last = scaleStart; float zeroYCoordinate = canvasHeight + scaleStart * yUnit; float y = 0f; float iNorm = 0f; if (verticalScaleLines == null) { /* * No vertical lines defined, draw default lines */ for (float i = scaleStart; i <= scaleEnd; i += valueDiff / 5f) { iNorm = i - scaleStart; y = canvasHeight - iNorm * yUnit; if (gridColor != null) { canvas.moveTo(0, y); canvas.lineTo(canvas.getWidth(), y); } addValueLabel(Math.round(y - 13), i, 0); // Catch the zero coordinate if (last < 0 && i > 0 || i == 0) { zeroYCoordinate = canvasHeight - (iNorm - i) * yUnit; } last = i; } } else { /* * Use predefines scales */ for (float value : verticalScaleLines) { iNorm = value - scaleStart; y = canvasHeight - iNorm * yUnit; if (gridColor != null) { canvas.moveTo(0, y); canvas.lineTo(canvas.getWidth(), y); } addValueLabel(Math.round(y - 13), value, 0); } } if (gridColor != null) { canvas.closePath(); canvas.stroke(); } // Draw the zero line if (zeroYCoordinate > 0) { canvas.setStrokeStyle("rgb(0,0,0)"); canvas.setLineWidth(1); canvas.beginPath(); canvas.moveTo(0, zeroYCoordinate); canvas.lineTo(canvas.getWidth(), zeroYCoordinate); canvas.closePath(); canvas.stroke(); addValueLabel(Math.round(zeroYCoordinate - 13), 0f, 0); } long time = new Date().getTime() - start.getTime(); VConsole.log("VTimelineDisplay: Render vertical scale " + time + "ms"); return zeroYCoordinate; } /** * Timer to ensure redrawing is done lazily */ private Timer lazyRedrawTimer = new Timer() { @Override public void run() { if (widget.isInitDone()) { lazyPlottingImminent = false; plotData(forcePlot); } } }; /** * Redraws the canvas area */ private boolean lazyPlottingImminent = false; public void redraw() { lazyPlottingImminent = true; forcePlot = true; lazyRedrawTimer.schedule(500); } private void plotData(boolean force) { /* * Optimize rendering so it does not re-render if not needed */ if (!force && lastPlotStartDate != null && lastPlotStartDate == currentRealStartDate) { return; } if (!force && lastPlotEndDate != null && lastPlotEndDate == currentRealEndDate) { return; } lastPlotStartDate = currentRealStartDate; lastPlotEndDate = currentRealEndDate; renderStart = new Date(); /* * Using a plotter listener to listen to when the plotting of the graphs * has stopped and we can plot the scales */ final long startT = getSelectionStartDate().getTime(); final long endT = getSelectionEndDate().getTime(); plotter.setListener(this); plotter.setUseShadows(widget.isGraphShadows()); // Plot graphs super.plotData(plotter, Collections.unmodifiableMap(currentValues), Collections.unmodifiableMap(currentDates)); // Add markers Set<String> removableMarkers = new HashSet<String>(); for (String markStr : markerMap.keySet()) { String m[] = markStr.split("_"); Long time = Long.parseLong(m[0]); if (time >= startT && time <= endT) { Long timeFromStart = time - startT; float x = timeFromStart * getCanvasXUnit(); float y = canvas.getHeight(); plotMarker(markStr, x, y); } else { removableMarkers.add(markStr); } } // Remove old markers for (String m : removableMarkers) { removeMarker(m); } // Remove old events for (Button btn : events) { displayComponentPanel.remove(btn); } events.clear(); eventCoordinates.clear(); currentEventMap.clear(); currentEventDates.clear(); // Add events List<String> eventList = new ArrayList<String>(currentEvents); for (int e = eventList.size() - 1; e >= 0; e--) { String eventStr = eventList.get(e); String m[] = eventStr.split("_"); Long time = Long.parseLong(m[0]); String caption = m[1]; int i = Integer.parseInt(m[2]); Long timeFromStart = time - startT; float x = timeFromStart * getCanvasXUnit(); float y = 10; plotEvent(caption, new Date(time), i, x, y); } // Always reset force plot flag after plot forcePlot = false; } /** * Removes a marker from the display and memory * * @param markString * The id of the marker */ private void removeMarker(String markString) { Label lbl = markerMap.get(markString); if (lbl != null) { displayComponentPanel.remove(lbl); } markerMap.remove(markString); FlexTable tooltip = markerTooltipMap.get(markString); if (tooltip != null) { displayComponentPanel.remove(tooltip); } markerTooltipMap.remove(markString); } /** * Removes an event from the display and memory * * @param event * The event to remove */ private void removeEvent(Button event) { displayComponentPanel.remove(event); int idx = events.indexOf(event); events.remove(event); eventCoordinates.remove(idx); currentEventMap.remove(event); currentEventDates.remove(event); currentEvents.remove(idx); } /** * Removes all in memory items */ public void clearCache() { // Remove markers Set<String> markers = new HashSet<String>(markerMap.keySet()); for (String mark : markers) { removeMarker(mark); } // Remove events Set<Button> events = new HashSet<Button>(this.events); for (Button event : events) { removeEvent(event); } } /** * Plots a marker to a gived position * * @param caption * The short caption to be displayed in the graph * @param description * The tooltip * @param x * The x-coordinate * @param y * The y-coordinate */ private void plotMarker(String markString, float x, float y) { HTML lbl; FlexTable tooltip; // Check if marker exists on display if (markerMap.get(markString) != null) { lbl = markerMap.get(markString); tooltip = markerTooltipMap.get(markString); } else { String m[] = markString.split("_"); Long time = Long.parseLong(m[0]); String caption = m[1]; String description = m[2]; lbl = new HTML("<span class=\"" + CLASSNAME_MARKER + "-text\">" + caption + "</span>"); lbl.setStyleName(CLASSNAME_MARKER); lbl.addStyleName(CLASSNAME_MARKER + "-" + caption); displayComponentPanel.add(lbl); displayComponentPanel.setWidgetPosition(lbl, (int) x - lbl.getOffsetWidth() / 2, (int) y - lbl.getOffsetHeight()); markerMap.put(markString, lbl); DateTimeFormat formatter = DateTimeFormat.getFormat("MMM d, ''yy"); tooltip = new FlexTable(); tooltip.setVisible(true); tooltip.setStyleName(CLASSNAME_MARKERTOOLTIP); tooltip.setBorderWidth(0); tooltip.setCellSpacing(0); tooltip.setCellPadding(0); tooltip.getRowFormatter().setStyleName(0, "top"); tooltip.getCellFormatter().setStyleName(0, 0, "topLeft"); tooltip.getCellFormatter().setStyleName(0, 1, "topMiddle"); tooltip.getCellFormatter().setStyleName(0, 2, "topRight"); tooltip.getRowFormatter().setStyleName(1, "middle"); tooltip.getCellFormatter().setStyleName(1, 0, "middleLeft"); tooltip.getCellFormatter().setStyleName(1, 1, "content"); tooltip.getCellFormatter().setStyleName(1, 2, "middleRight"); tooltip.getRowFormatter().setStyleName(2, "bottom"); tooltip.getCellFormatter().setStyleName(2, 0, "bottomLeft"); tooltip.getCellFormatter().setStyleName(2, 1, "bottomMiddle"); tooltip.getCellFormatter().setStyleName(2, 2, "bottomRight"); HTML content = new HTML("<span class=date>" + formatter.format(new Date(time)) + "</span> <br/>" + "<span class=text>" + description + "</span>"); tooltip.setWidget(1, 1, content); displayComponentPanel.add(tooltip); markerTooltipMap.put(markString, tooltip); } int markerX = (int) (x - Util.getRequiredWidth(lbl) / 2.0); int markerY = (int) (y - Util.getRequiredHeight(lbl)); displayComponentPanel.setWidgetPosition(lbl, markerX, markerY); // Show tooltip for measurement tooltip.setVisible(true); displayComponentPanel.setWidgetPosition(tooltip, 0, 0); markerY -= Util.getRequiredHeight(tooltip); // Check if right border is reached, if so, the move the tooltip // left if (markerX + Util.getRequiredWidth(tooltip) > Util.getRequiredWidth(displayComponentPanel)) { markerX -= Util.getRequiredWidth(tooltip); } displayComponentPanel.setWidgetPosition(tooltip, markerX, markerY); tooltip.setVisible(false); } /** * Plots an event onto the display * * @param caption * The caption that shows on the screen * @param date * The date the event happend * @param index * The server index of the event * @param x * The X-coordinate * @param y * The Y-coordinate */ private void plotEvent(String caption, Date date, int index, float x, float y) { Button event = new Button(caption); event.setWidth((caption.length() * 30) + "px"); event.setStyleName(CLASSNAME_EVENT); // Does an event with the same coordinate exist boolean exists = false; for (Integer i : eventCoordinates) { if ((i >= x && i <= x + 30) || (i >= x - 30 && i <= x)) { event = events.get(eventCoordinates.indexOf(i)); event.setText("*"); event.setWidth("30px"); if (x < i) { displayComponentPanel.setWidgetPosition(event, (int) x, displayComponentPanel.getWidgetTop(event)); eventCoordinates.set(eventCoordinates.indexOf(i), (int) x); } currentEventMap.get(event).add(index); currentEventDates.get(event).add(date); exists = true; break; } } // Else add the event to the display if (!exists) { displayComponentPanel.add(event, (int) x, (int) y); events.add(event); eventCoordinates.add((int) x); List<Integer> indexes = new ArrayList<Integer>(); indexes.add(index); currentEventMap.put(event, indexes); List<Date> dates = new ArrayList<Date>(); dates.add(date); currentEventDates.put(event, dates); } } /* * (non-Javadoc) * * @see * com.vaadin.trends.ws.client.VDataListener#dataRecieved(java.lang.Long, * java.util.List, java.util.List, java.util.Set) */ private int currentMinDensity = Integer.MAX_VALUE; /* * (non-Javadoc) * * @see * com.vaadin.addon.timeline.gwt.client.VDataListener#dataRecieved(java. * lang.Long, java.util.List, java.util.List, java.util.Set, java.util.Set, * int) */ @Override public void dataRecieved(Long requestId, List<Float> values, List<Date> dates, Set<String> markers, Set<String> events, int density) { if (!requestGraphMap.containsKey(requestId)) { return; } if (density < currentMinDensity) { currentMinDensity = density; } Integer g = requestGraphMap.get(requestId); requestGraphMap.remove(requestId); // Check if we have new markers if (markers != null) { for (String marker : markers) { if (!markerMap.containsKey(marker)) { markerMap.put(marker, null); markerTooltipMap.put(marker, null); } } } // Check if we ahve events currentEvents.clear(); if (events != null) { currentEvents.addAll(events); } currentValues.put(g, values); currentDates.put(g, dates); currentMin.put(g, VTimelineWidget.getMinValue(values)); currentMax.put(g, VTimelineWidget.getMaxValue(values)); if (dates.size() > 0) { if (currentRealStartDate == null || currentRealStartDate.after(dates.get(0))) { currentRealStartDate = dates.get(0); } if (currentRealEndDate == null || currentRealEndDate.before(dates.get(dates.size() - 1))) { currentRealEndDate = dates.get(dates.size() - 1); } } if (currentMin.get(g) < currentTotalMin) { currentTotalMin = currentMin.get(g); } if (currentMax.get(g) > currentTotalMax) { currentTotalMax = currentMax.get(g); } // If all graphs data has been recieved then plot the graph so the // graphs // does not appear one by one if (graphDataRecievedCounter == widget.getNumGraphs() - 1) { graphDataRecievedCounter = 0; requestGraphMap.clear(); plotData(forcePlot); // Hide curtain setLoadingIndicatorVisible(false); } graphDataRecievedCounter++; } /* * (non-Javadoc) * * @see * com.vaadin.addon.timeline.gwt.client.VDataListener#dataRecievedAll(java * .util.List, java.util.Map, java.util.Map, java.util.Set, java.util.Set, * java.util.Map, java.util.Map, java.lang.Float, java.lang.Float, int) */ @Override public void dataRecievedAll(List<Long> requestID, Map<Integer, List<Float>> values, Map<Integer, List<Date>> dates, Set<String> markers, Set<String> events, Map<Integer, Float> min, Map<Integer, Float> max, Float totalMinimum, Float totalMaximum, int minDensity) { currentMinDensity = minDensity; for (Long req : requestID) { requestGraphMap.remove(req); } // Check if we have new markers if (markers != null) { for (String marker : markers) { if (!markerMap.containsKey(marker)) { markerMap.put(marker, null); markerTooltipMap.put(marker, null); } } } // Check if we ahve events if (events != null) { currentEvents.addAll(events); } // Clear previous data resetDisplayCache(); // Get total minimum / maximum currentTotalMin = totalMinimum; currentTotalMax = totalMaximum; for (Entry<Integer, List<Float>> entry : values.entrySet()) { Integer graph = entry.getKey(); List<Float> fvalues = entry.getValue(); List<Date> dvalues = dates.get(graph); Float minval = min.get(graph); Float maxval = max.get(graph); // Sanity check 1 if (fvalues == null || dvalues == null) { continue; } // Sanity check 2 if (dvalues.size() != fvalues.size()) { continue; } // Sanity check 3 if (minval == null || maxval == null) { continue; } currentValues.put(graph, fvalues); currentDates.put(graph, dvalues); currentMin.put(graph, minval); currentMax.put(graph, maxval); currentRealStartDate = dvalues.get(0); currentRealEndDate = dvalues.get(dvalues.size() - 1); } plotData(forcePlot); setLoadingIndicatorVisible(false); graphDataRecievedCounter = 0; } /** * Should the spinning loading indicator be visible? * * @param visible * Is it visible */ private void setLoadingIndicatorVisible(boolean visible) { loadingCurtain.setVisible(visible); } /** * Returns TRUE if the given x- and y- coordinate is over a marker * * @param x * The x coordinate relative to the display component * @param y * The y coordinate relative to the display component * @return Returns the marker it is over, else null */ private String coordinateIsOverMarker(int x, int y) { int left, top, width, height; for (String markerId : markerMap.keySet()) { Label marker = markerMap.get(markerId); left = displayComponentPanel.getWidgetLeft(marker); top = displayComponentPanel.getWidgetTop(marker); width = marker.getOffsetWidth(); height = marker.getOffsetHeight(); if (x >= left && x <= left + width && y >= top && y <= top + height) { return markerId; } } return null; } /** * Hides all marker tooltips */ private void hideAllMarkerTooltips() { for (FlexTable tooltip : markerTooltipMap.values()) { tooltip.setVisible(false); } } /** * Highlights a bar chart * * @param graph * The graph of the bar * @param point * The point that represents the bar */ private void setSelectedBarGraphBarVisible(Graph graph, Point point) { int barY = point.y; int barWidth = point.width; int barX = point.x - barWidth; // Using left measuring (see // VCanvasPlotter) float barHeight = getCurrentZeroCoordinate() - point.y; HTML bar = bars.get(currentGraphs.indexOf(graph)); bar.setWidth(barWidth + "px"); bar.setHeight(barHeight + "px"); bar.setVisible(true); displayComponentPanel.setWidgetPosition(bar, barX, barY); } private Date setRangeTimerStart, setRangeTimerEnd; /** * Set the currently displayed time range * * @param start * The start date * @param end * The end date * @return Returns true if the range was set, false otherwise */ public boolean setRange(Date start, Date end) { if (widget.isInitDone()) { setRangeTimerStart = start; setRangeTimerEnd = end; Scheduler.get().scheduleIncremental(new Scheduler.RepeatingCommand() { @Override public boolean execute() { if (setRangeTimerStart != null && setRangeTimerEnd != null) { if (mouseOrTouchActive) { return false; } setRange(setRangeTimerStart, setRangeTimerEnd, false, false); setRangeTimerStart = null; setRangeTimerEnd = null; return true; } return false; } }); } else { setRange(start, end, true, false); } return true; } /** * Set the range to display * * @param start * The start date * @param end * The end date * @return */ private boolean setRange(Date start, Date end, boolean useCurtain, boolean forceServer) { if (!isVisible()) { return false; } if (start == null || end == null) { return false; } /* * Use previous density as refernece */ int density = 1; if (currentEndDate != null && currentStartDate != null) { long currentDiff = currentEndDate.getTime() - currentStartDate.getTime(); long diff = end.getTime() - start.getTime(); if (diff > currentDiff - VDateFormatInfo.DAY && diff < currentDiff + VDateFormatInfo.DAY) { density = currentMinDensity == Integer.MAX_VALUE ? 1 : currentMinDensity; } } currentMinDensity = Integer.MAX_VALUE; currentStartDate = start; currentEndDate = end; currentRealStartDate = null; currentRealEndDate = null; graphDataRecievedCounter = 0; resetDisplayCache(); // Get the date data boolean cached = false; if (forceServer) { // Some data not found in cache, get it from the server for (int g = 0; g < widget.getNumGraphs(); g++) { widget.getDateData(this, g, start, end, density); } // Issue request widget.getFromServer(); } else { cached = widget.getDateDataAll(this, start, end, density); } setLoadingIndicatorVisible(useCurtain && !cached); if (!isMouseActive()) { widget.fireDateRangeChangedEvent(); } return true; } /** * Sets the chart plotting mode * * @param mode * The plotting mode */ public void setChartMode(PlotMode mode, boolean doPlotting) { if (mode != null) { currentMode = mode; if (doPlotting) { redraw(); } } } /** * Returns the canvas width * * @return The width in pixels */ public int getCanvasWidth() { return canvas.getWidth(); } /** * Get the current starting date * * @return The date */ @Override public Date getSelectionStartDate() { if (currentStartDate == null) { return widget.getSelectedStartDate(); } return currentStartDate; } /** * Get the current ending date * * @return The date */ @Override public Date getSelectionEndDate() { if (currentEndDate == null) { return widget.getSelectedEndDate(); } return currentEndDate; } /** * Get the total minimal value of the graphs * * @return The minimum */ public Float getMin() { return currentTotalMin; } /** * Get the total maximal value of the graphs * * @return The maximum */ public Float getMax() { return currentTotalMax; } /** * Set the state of the vertical fitting. If vertical fitting is enabled * then the vertical scale is made depending on the maximal and minimal * values in the graphs. If min and max is given then the scale is made * using them. * * @param auto * Enable automatic scale fitting * @param min * The minimal value of the scale * @param max * The maximal value of the scale */ public void setVerticalFitting(boolean auto, float min, float max) { if (auto) { verticalScaleMax = null; verticalScaleMin = null; } else { verticalScaleMin = min; verticalScaleMax = max; } redraw(); } /** * Removes a graph from the cache so it will not be rendered when plotData * is called * * @param graph * The index of the graph to be removed */ public void removeGraph(int graph) { currentValues.put(graph, new ArrayList<Float>()); currentDates.put(graph, new ArrayList<Date>()); redraw(); } /** * Triggered when an event button is pressed * * @param eventButton */ private void eventClick(Button eventButton) { List<Integer> indexes = currentEventMap.get(eventButton); widget.fireEventButtonClickEvent(indexes); if (indexes.size() > 1) { // Resolve first and last date List<Date> dates = currentEventDates.get(eventButton); Date start = dates.get(0); Date end = dates.get(0); for (Date d : dates) { if (start.after(d)) { start = d; } if (end.before(d)) { end = d; } } Long timeDiff = end.getTime() - start.getTime(); Date leftEdge = new Date(start.getTime() - timeDiff * 2L); Date rightEdge = new Date(end.getTime() + timeDiff * 2L); widget.setBrowserRange(leftEdge, rightEdge); setRange(leftEdge, rightEdge, true, false); } } /* * (non-Javadoc) * * @see * com.vaadin.trends.ws.client.VDataListener#setCurrentRequestId(java.lang * .Long, int) */ @Override public void setCurrentRequestId(Long requestID, Integer graphIndex) { requestGraphMap.put(requestID, graphIndex); } /** * Does the component have an specific element * * @param elem * The element * @return True if the component has the element */ public boolean hasElement(com.google.gwt.dom.client.Element elem) { for (HTML dot : dots) { if (dot.getElement() == elem) { return true; } } for (HTML bar : bars) { if (bar.getElement() == elem) { return true; } } for (Label lbl : horizontalScaleComponents) { if (lbl.getElement() == elem) { return true; } } for (Label lbl : verticalScaleComponents) { if (lbl.getElement() == elem) { return true; } } for (String marker : markerMap.keySet()) { if (markerMap.get(marker) != null && markerMap.get(marker).getElement() == elem) { return true; } } for (Button eventBtn : events) { if (eventBtn.getElement() == elem) { return true; } } if (elem == displayComponentPanel.getElement() || elem == getElement() || elem == canvas.getElement() || elem == loadingCurtain.getElement() || elem == browserRoot || elem == bottomBar.getElement() || elem == disabledCurtain.getElement()) { return true; } else { return false; } } /** * Displays the no data found message * * @param enabled * Is the message displayed */ public void displayNoDataMessage(boolean enabled) { noDataLabel.setVisible(enabled); setLoadingIndicatorVisible(false); if (enabled) { // Clear canvas canvas.setWidth(canvas.getWidth()); // Clear markers Set<String> removeMarkers = new HashSet<String>(markerMap.keySet()); for (String m : removeMarkers) { removeMarker(m); } // Clear events Set<Button> removeEvents = new HashSet<Button>(events); for (Button event : removeEvents) { removeEvent(event); } // Clear vertical scale for (Label lbl : verticalScaleComponents) { displayComponentPanel.remove(lbl); } verticalScaleComponents.clear(); // Clear horizontal scale for (Label lbl : horizontalScaleComponents) { displayComponentPanel.remove(lbl); } horizontalScaleComponents.clear(); // Remove old dots for (HTML dot : dots) { displayComponentPanel.remove(dot); } dots.clear(); // Remove old bars for (HTML bar : bars) { displayComponentPanel.remove(bar); } bars.clear(); } } /** * Is the display enabled * * @param enabled * True if enabled */ public void setEnabled(boolean enabled) { this.enabled = enabled; setLoadingIndicatorVisible(false); } /** * Force plotting the the data even if it is the same time range. Forced * plotting is always turned off after a forced plot so you will have to * turn on forced plotting again before the next plot. * * @param enabled * The plotting is forced */ public void setForcedPlotting(boolean enabled) { forcePlot = enabled; } /** * Sets the scale grid color, set to null to not draw the grid * * @param color * The color */ public void setGridColor(String color) { gridColor = color; redraw(); } int dragX = 0; // TODO investigate it this is needed, not used // /** // * A timer for scrolling the canvas left or right // */ // private Timer scrollTimer = new Timer() { // @Override // public void run() { // lastTouchFingerDistance = 0; // refresh(); // widget.setBrowserRange(getSelectionStartDate(), // getSelectionEndDate()); // } // }; /** * Called when a user is dragging the canvas with the mouse * * @param event * The mouse event */ // TODO investigate it this is needed, not used @SuppressWarnings("unused") private boolean lastDragWasToTheRight = false; private void moveDragging(NativeEvent event) { if (currentEndDragDate == null || currentStartDragDate == null) { return; } Long timeDiff = currentEndDragDate.getTime() - currentStartDragDate.getTime(); float xdiff = (lastMouseX - mouseDownX) / 2f; lastDragWasToTheRight = xdiff > 0; float canvasWidth = canvas.getWidth(); float widthUnit = xdiff / canvasWidth; Float time = timeDiff * widthUnit; Long start = currentStartDate.getTime() - time.longValue(); Long end = currentEndDate.getTime() - time.longValue(); Date startDate = new Date(start); Date endDate = new Date(end); if (startDate.before(widget.getStartDate())) { startDate = widget.getStartDate(); } if (endDate.after(widget.getEndDate())) { endDate = widget.getEndDate(); } if (startDate.equals(getSelectionStartDate()) || endDate.equals(getSelectionEndDate())) { return; } setRange(startDate, endDate, false, false); widget.setBrowserRange(startDate, endDate); dragX = lastMouseX; mouseDownX = dragX; } /* * (non-Javadoc) * * @see * com.google.gwt.event.dom.client.MouseMoveHandler#onMouseMove(com.google * .gwt.event.dom.client.MouseMoveEvent) */ @Override public void onMouseMove(MouseMoveEvent event) { NativeEvent mouseEvent = event.getNativeEvent(); // Dragging action occurring.. if (mouseIsDown && enabled && currentEndDragDate != null && currentStartDragDate != null) { lastMouseX = event.getClientX(); moveDragging(mouseEvent); // Mouse is hovering over the display area } else if (enabled && !loadingCurtain.isVisible() && !mouseOrTouchActive) { int x = mouseEvent.getClientX() - getAbsoluteLeft(); int y = mouseEvent.getClientY() - getAbsoluteTop(); // Handle graph selectors handleGraphSelector(x, y); } } /** * Searches for the closest selector on the canvas and hightlights it * * @param x * The x-coordinate * @param y * The y-coordinate */ private void handleGraphSelector(int x, int y) { if (currentGraphs.size() == 0 || lazyPlottingImminent || isCurrentlyRendering()) { // If no graphs or we are currently plotting then ignore return; } hideAllMarkerTooltips(); // Over a marker String markerId = coordinateIsOverMarker(x, y); if (markerId != null) { FlexTable markerTooltip = markerTooltipMap.get(markerId); markerTooltip.setVisible(true); return; } for (int g = 0; g < currentGraphs.size(); g++) { Graph graph = currentGraphs.get(g); if (!widget.graphIsVisible(g) || graph.getPoints().size() == 0) { continue; } List<Point> points = graph.getPoints(); // Search for index of the closes point float minDiff = 10000000f; int pointIndex = 0; Point p; for (int i = 0; i < points.size(); i++) { p = points.get(i); if (p.x < 0 || p.x > canvas.getWidth()) { continue; } float diff = Math.abs(x - p.x); if (currentMode == PlotMode.BAR) { // Barchart renders to the left of the point so we have to // adjust diff = Math.abs(x - (p.x - p.width / 2f)); } if (diff < minDiff) { minDiff = diff; pointIndex = i; } } // Retrieve the selected point p = points.get(pointIndex); if (dots.size() > g) { HTML dot = dots.get(g); dot.setVisible(true); if (widget.isGraphsStacked()) { float pSum = 0; for (int j = 0; j < g; j++) { Graph graph2 = currentGraphs.get(j); Point q = graph2.getPoints().get(pointIndex); if (q != null) { pSum += q.y - getCurrentZeroCoordinate(); } } if (currentMode == PlotMode.BAR) { setSelectedBarGraphBarVisible(graph, p); displayComponentPanel.setWidgetPosition(dot, p.x - dot.getOffsetWidth() / 2, Math.round(p.y + pSum - dot.getOffsetHeight() / 2)); } else { displayComponentPanel.setWidgetPosition(dot, p.x - dot.getOffsetWidth() / 2, Math.round(p.y + pSum - dot.getOffsetHeight() / 2)); } } else { if (currentMode == PlotMode.BAR) { setSelectedBarGraphBarVisible(graph, p); displayComponentPanel.setWidgetPosition(dot, p.x - dot.getOffsetWidth() / 2, p.y - dot.getOffsetHeight() / 2); } else { displayComponentPanel.setWidgetPosition(dot, p.x - dot.getOffsetWidth() / 2, p.y - dot.getOffsetHeight() / 2); } } } widget.setLegendValue(g, (float) p.value); } } /** * Called when the user stops dragging either by releasing the mouse button * on removes the finger from the touch device * * @param event * The touch or mouse event */ private void stopDragging(NativeEvent event) { for (Label lbl : verticalScaleComponents) { lbl.setStyleName(CLASSNAME_SCALEVALUE); } canvas.setStyleName(CLASSNAME_CANVAS); mouseOrTouchActive = false; multitouching = false; DOM.releaseCapture(canvas.getElement()); VConsole.log("VTimelineDisplay: Dragging stopped"); } /* * (non-Javadoc) * * @see * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google * .gwt.event.dom.client.MouseDownEvent) */ @Override public void onMouseDown(MouseDownEvent event) { NativeEvent mouseEvent = event.getNativeEvent(); event.preventDefault(); event.stopPropagation(); // Check if element is a event button for (Button btn : events) { if (Element.as(mouseEvent.getEventTarget()) == btn.getElement()) { eventClick(btn); return; } } startDrag(mouseEvent); } private void startDrag(NativeEvent event) { if (enabled) { mouseOrTouchActive = true; widget.focus(); mouseDownX = Util.getTouchOrMouseClientX(event); canvas.setStyleName(CLASSNAME_CANVASDRAG); for (Label lbl : verticalScaleComponents) { lbl.setStyleName(CLASSNAME_SCALEVALUEDRAG); } DOM.setCapture(canvas.getElement()); currentStartDragDate = new Date(currentStartDate.getTime()); currentEndDragDate = new Date(currentEndDate.getTime()); VConsole.log("VTimelineWidget: Dragging started"); } } /** * Zooms in around a center x-coordinate * * @param centerX * The x-coordinate of the center point which to zoom in on * @param timespanRatio * The amount to zoom in */ public void zoomIn(float centerX, float timespanRatio) { float canvasWidth = canvas.getWidth(); float ratio = centerX / canvasWidth; // Calculate the minimum timeunit for the whole range long diff = widget.getEndDate().getTime() - widget.getStartDate().getTime(); float minTimeSpan = (diff / canvasWidth) * timespanRatio; Float startRatio = ratio * minTimeSpan; Float endRatio = (1f - ratio) * minTimeSpan; Date start = new Date(currentStartDate.getTime() - startRatio.longValue()); if (start.before(widget.getStartDate())) { /* * Ensure we are in bounds */ start = widget.getStartDate(); } Date end = new Date(currentEndDate.getTime() + endRatio.longValue()); if (end.after(widget.getEndDate())) { /* * Ensure we are in bounds */ end = widget.getEndDate(); } if (end.getTime() - start.getTime() > minTimeSpan) { currentStartDate = start; currentEndDate = end; lastTouchFingerDistance = 0; refresh(); widget.setBrowserRange(getSelectionStartDate(), getSelectionEndDate()); } } /** * Zooms out around a center x-coordinate * * @param x * The x-coordinate of the center point which to zoom out on * @param timespanRatio * The amount to zoom out */ public void zoomOut(float x, float timespanRatio) { float canvasWidth = canvas.getWidth(); float ratio = x / canvasWidth; // Calculate the minimum timeunit for the whole range long diff = widget.getEndDate().getTime() - widget.getStartDate().getTime(); float minTimeSpan = (diff / canvasWidth) * timespanRatio; Float startRatio = ratio * minTimeSpan; Float endRatio = (1f - ratio) * minTimeSpan; Date start = new Date(currentStartDate.getTime() + startRatio.longValue()); if (start.before(widget.getStartDate())) { /* * Ensure we are in bounds */ start = widget.getStartDate(); } Date end = new Date(currentEndDate.getTime() - endRatio.longValue()); if (end.after(widget.getEndDate())) { /* * Ensure we are in bounds */ end = widget.getEndDate(); } if (end.getTime() - start.getTime() > minTimeSpan) { currentStartDate = start; currentEndDate = end; lastTouchFingerDistance = 0; refresh(); widget.setBrowserRange(getSelectionStartDate(), getSelectionEndDate()); } } public void scrollLeft(boolean boost) { scroll(boost ? -10 : -5); } public void scrollRight(boolean boost) { scroll(boost ? 10 : 5); } private void scroll(int adjustment) { // TODO VConsole.error("Using method VTimelineDisplay.scroll() which is not implemented!"); } /* * (non-Javadoc) * * @see * com.google.gwt.event.dom.client.MouseWheelHandler#onMouseWheel(com.google * .gwt.event.dom.client.MouseWheelEvent) */ @Override public void onMouseWheel(MouseWheelEvent event) { NativeEvent mouseEvent = event.getNativeEvent(); if (hasElement(Element.as(mouseEvent.getEventTarget()))) { event.preventDefault(); boolean up = mouseEvent.getMouseWheelVelocityY() > 0; if (up) { zoomIn(mouseEvent.getClientX(), 6f); } else { zoomOut(mouseEvent.getClientX(), 6f); } } else { VConsole.error("Mouse wheel on unknowm element"); } } /* * (non-Javadoc) * * @see * com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent * (com.google.gwt.user.client.Event.NativePreviewEvent) */ @Override public void onPreviewNativeEvent(NativePreviewEvent event) { // Monitor mouse button state if (event.getTypeInt() == Event.ONMOUSEUP && event.getNativeEvent().getButton() == NativeEvent.BUTTON_LEFT) { mouseIsDown = false; if (mouseOrTouchActive) { stopDragging(null); } } else if (event.getTypeInt() == Event.ONMOUSEDOWN && event.getNativeEvent().getButton() == NativeEvent.BUTTON_LEFT && hasElement((Element) event.getNativeEvent().getEventTarget().cast())) { mouseIsDown = true; } else if (event.getTypeInt() == Event.ONTOUCHEND) { if (mouseOrTouchActive) { stopDragging(null); } } } /** * Initializes the canvas i.e. it will fetch the points for the whole * timeline and render it to the canvas. */ public void refresh() { if (!isVisible()) { return; } setRange(getSelectionStartDate(), getSelectionEndDate(), false, false); } /** * Is there currently a mouse event occurring * * @return */ public boolean isMouseActive() { return mouseOrTouchActive; } /** * Called when the plotting starts. Override to use. */ @Override public void plottingStarts() { // Hide dots for (HTML dot : dots) { dot.setVisible(false); } // Hide bars if in barchar mode if (currentMode == PlotMode.BAR) { for (HTML bar : bars) { bar.setVisible(false); } } /* * Optimization. Instead of using canvas.clear() which will do a * canvas.drawRect() of the whole area we simple reset the width of the * canvas which automatically will reset the canvas context */ canvas.setWidth(canvas.getWidth()); // Plot vertical scale if not stacked if (!widget.isGraphsStacked()) { plotVerticalScale(getCanvasYUnit(), getCanvas().getHeight()); } } /** * A dot displayed over the graph */ private class Dot extends HTML { // TODO check where if this value is needed somewhere!? Commented out as // not used. // private double value; public Dot(String colors) { super(" "); setStyleName(CLASSNAME_DOT); getElement().getStyle().setBackgroundColor(colors); } // public double getValue() { // return value; // } // // public void setValue(double value) { // this.value = value; // } } /** * Updates the highlight dots */ private void createDots() { if (dots.size() != widget.getNumGraphs()) { // Remove old dots for (HTML dot : dots) { displayComponentPanel.remove(dot); } dots.clear(); // Create the dots for (int dg = 0; dg < widget.getNumGraphs(); dg++) { Dot dot = new Dot(widget.getColorMap().get(dg)); dots.add(dot); displayComponentPanel.add(dot); } } } /** * Updates the highlight bars */ private void createBars() { if (bars.size() != widget.getNumGraphs() && currentMode == PlotMode.BAR) { // Remove old bars for (HTML bar : bars) { displayComponentPanel.remove(bar); } bars.clear(); // Create the bars for (int dg = 0; dg < widget.getNumGraphs(); dg++) { HTML bar = new HTML(""); bar.setStyleName(CLASSNAME_BAR); bars.add(bar); displayComponentPanel.add(bar); } } } /** * Called when the plotting ends. Override to use. */ @Override public void plottingEnds() { // Plot the horizontal scale plotHorizontalScale(getCanvasXUnit(), getSelectionStartDate().getTime(), getSelectionEndDate().getTime()); // Plot vertical scale (if stacked) if (widget.isGraphsStacked()) { canvas.setGlobalCompositeOperation(Canvas.DESTINATION_OVER); plotVerticalScale(getCanvasYUnit(), getCanvas().getHeight()); canvas.setGlobalCompositeOperation(Canvas.SOURCE_OVER); } // Create the highlight dots if needed createDots(); // Create the highlight bars if needed createBars(); // Set the text fields with the correct date widget.setFromDateTextField(currentStartDate); widget.setToDateTextField(currentEndDate); plotter.setListener(null); Long renderTime = new Date().getTime() - renderStart.getTime(); VConsole.log("VTimelineDisplay: Rendering time " + renderTime + "ms"); } /* * (non-Javadoc) * * @see * com.vaadin.addon.timeline.gwt.client.VTimelineCanvasComponent#getCanvas() */ @Override protected Canvas getCanvas() { return canvas; } /* * (non-Javadoc) * * @see * com.vaadin.addon.timeline.gwt.client.VTimelineCanvasComponent#getPlotMode * () */ @Override protected PlotMode getPlotMode() { return currentMode; } /* * (non-Javadoc) * * @see com.vaadin.addon.timeline.gwt.client.VTimelineCanvasComponent# * setCurrentGraphs(java.util.List) */ @Override protected void setCurrentGraphs(List<Graph> graphs) { // Called by super.plotData currentGraphs = graphs; } /* * (non-Javadoc) * * @see * com.vaadin.addon.timeline.gwt.client.VTimelineCanvasComponent#getFillColors * (int) */ @Override protected String getFillColors(int graphIndex) { return widget.getFillColorMap().get(graphIndex); } /* * (non-Javadoc) * * @see * com.vaadin.addon.timeline.gwt.client.VTimelineCanvasComponent#getColors * (int) */ @Override protected String getColors(int graphIndex) { return widget.getColorMap().get(graphIndex); } /* * (non-Javadoc) * * @see * com.google.gwt.event.dom.client.TouchEndHandler#onTouchEnd(com.google * .gwt.event.dom.client.TouchEndEvent) */ public void onTouchEnd(TouchEndEvent event) { if (lastTouchFingerDistance > 0) { touchMove(event); lastTouchFingerDistance = 0; /* * Adding a timer to prevent single multitouch end events to mess up * rendering */ Timer t = new Timer() { @Override public void run() { mouseOrTouchActive = false; multitouching = false; VConsole.log("VTimelineDisplay: Touch dragging ended (with a delay)"); } }; t.schedule(500); } else if (!multitouching) { stopDragging(event.getNativeEvent()); } } /* * (non-Javadoc) * * @see * com.google.gwt.event.dom.client.TouchMoveHandler#onTouchMove(com.google * .gwt.event.dom.client.TouchMoveEvent) */ @Override public void onTouchMove(TouchMoveEvent event) { touchMove(event); } /** * Triggered whenever a touch event occurs * * @param event * The touch event */ private void touchMove(TouchEvent<?> event) { event.preventDefault(); if (event.getTouches().length() > 1) { Touch t1 = event.getTouches().get(0); Touch t2 = event.getTouches().get(1); int fingerDistance = Math.abs(t1.getClientX() - t2.getClientX()); if (t1.getClientX() < t2.getClientX()) { /* * t1 is left finger, t2 is right finger */ lastMouseX = t1.getClientX() + fingerDistance / 2; } else { /* * t2 is left finger, t1 is right finger */ lastMouseX = t2.getClientX() + fingerDistance / 2; } if (lastTouchFingerDistance > 0) { if (fingerDistance > lastTouchFingerDistance) { zoomOut(lastMouseX, Math.abs(lastTouchFingerDistance - fingerDistance) / 10); } else { zoomIn(lastMouseX, Math.abs(lastTouchFingerDistance - fingerDistance) / 10); } } else { lastTouchFingerDistance = fingerDistance; } } else if (!multitouching) { lastMouseX = Util.getTouchOrMouseClientX(event.getNativeEvent()); moveDragging(event.getNativeEvent()); } } /* * (non-Javadoc) * * @see * com.google.gwt.event.dom.client.TouchStartHandler#onTouchStart(com.google * .gwt.event.dom.client.TouchStartEvent) */ @Override public void onTouchStart(TouchStartEvent event) { event.preventDefault(); if (event.getTouches().length() > 1) { mouseOrTouchActive = true; lastTouchFingerDistance = 0; multitouching = true; touchMove(event); } else { startDrag(event.getNativeEvent()); } } /** * Sets the vertical scale line values * * @param lines * The values of the vertical scale */ public void setVerticalScaleLines(float... lines) { verticalScaleLines = lines; redraw(); } /** * Returns the vertical scale lines or null if automatic lines are being * used. * * @return */ public float[] getVerticalScaleLines() { return verticalScaleLines; } /** * Sets the text that is displayed when no data source is present * * @param caption * The text that should be displayed */ public void setNoDataSourceCaption(String caption) { noDataLabel.setHTML(caption); } /** * Returns the number format used by the vertical axis to display the values * * @return */ public NumberFormat getVerticalAxisNumberFormat() { return verticalAxisNumberFormat; } /** * Set the format to format the vertical axis value to a string. See * http://docs.oracle.com/javase/tutorial/i18n/format/decimalFormat.html * form more information about the different formats that can be used. * * @param verticalAxisNumberFormat * The format of the number to display */ public void setVerticalAxisNumberFormat(NumberFormat verticalAxisNumberFormat) { this.verticalAxisNumberFormat = verticalAxisNumberFormat; redraw(); } }