/******************************************************************************* * Copyright (c) 2007, 2011 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.trend.impl; import java.awt.Color; import java.awt.Graphics2D; import java.text.Format; import java.text.NumberFormat; import org.simantics.g2d.utils.GridSpacing; import org.simantics.g2d.utils.GridUtil; import org.simantics.trend.configuration.TimeWindow; import org.simantics.trend.configuration.TrendSpec; import org.simantics.utils.format.TimeFormat; public class HorizRuler extends TrendGraphicalNode { private static final long serialVersionUID = 7155812928124259094L; GridSpacing spacing = GridSpacing.SOME_SPACING; public double from = 0; public double end = 100; public boolean autoscroll = true; // Determines if auto-scroll is on/off boolean manualscale = false; // Locked in trend spec TimeFormat timeFormat = new TimeFormat(0, 0); private Format f = timeFormat; double iFrom = Double.MAX_VALUE; double iEnd = -Double.MAX_VALUE; public double basetime = 0; public TimeWindowListener listener; public static final Color AUTOSCROLL_ON = new Color(0, 225, 34); public static final Color AUTOSCROLL_OFF = new Color(255, 115, 115); public boolean setWidth(double width) { if (getWidth()==width) return false; this.setSize(width, 20); return true; } public boolean setFromEnd(double from, double end) { if (this.from!=from || this.end!=end) { this.from = from; this.end = end; getTrend().shapedirty = true; return true; } return false; } public boolean setFromScale(double from, double scaleX) { double end = getWidth() * scaleX + from; return setFromEnd(from, end); } public void fireListener() { if (listener != null) { double wid = getWidth(); if ( wid == 0.0 ) wid = 0.1; double sx = (end-from) / wid; listener.onNewWindow(from, end, sx); } } /** * Set grid spacing and text formatter; */ public void layout() { TrendNode trend = getTrend(); if (trend.timeFormat == org.simantics.trend.configuration.TimeFormat.Time) { // Figure out how many decimals are needed timeFormat.setMaxValue(end); timeFormat.setDecimals(1); double labelWidth = 0; double maxLabelWidth = 0; for (int i=0; i<2; i++) { labelWidth = GridUtil.calcLabelWidth(from - basetime, end - basetime, timeFormat, spacing); maxLabelWidth = Math.max(labelWidth, maxLabelWidth); spacing = GridSpacing.makeGridSpacing(end-from, getWidth(), labelWidth+10); timeFormat.setDecimals((int) -spacing.segmentExp); } if (maxLabelWidth>labelWidth) { spacing = GridSpacing.makeGridSpacing(end-from, getWidth(), maxLabelWidth+10); timeFormat.setDecimals((int) -spacing.segmentExp); } f = timeFormat; } else { f = NumberFormat.getInstance(); double labelWidth = GridUtil.calcLabelWidth(from - basetime, end, f, spacing); this.spacing = GridSpacing.makeGridSpacing(end-from, getWidth(), labelWidth+10); } } @Override protected void doRender(Graphics2D g2d) { TrendNode trend = getTrend(); // Draw little "Frozen" if ( !trend.printing ) { // g2d.setColor( Color.LIGHT_GRAY ); g2d.setColor( autoscroll ? AUTOSCROLL_ON : AUTOSCROLL_OFF ); g2d.setFont( GridUtil.RULER_FONT ); String txt = !autoscroll ? (manualscale ? "*" : "Auto-scroll off") : (manualscale ? "" : "Auto-scroll on"); // Draw at bottom // g2d.drawString(txt, 0.f, (float)getHeight() + 12.f ); // Draw to right g2d.drawString(txt, (float) getWidth()+20, 20.f ); } g2d.setPaint(Color.GRAY); g2d.setStroke( GridUtil.RULER_LINE_STROKE ); GridUtil.paintHorizontalRuler( spacing, g2d, from - basetime, getWidth(), f); } /** * This method sets iFrom and iEnd values. * Read reads values from ItemNodes. * So it is good idea to call {@link ItemNode#readMinMaxFromEnd()} first. */ public void setKnownFromEnd() { TrendNode trendNode = (TrendNode) getParent(); iFrom = Double.MAX_VALUE; iEnd = -Double.MAX_VALUE; for (ItemNode item : trendNode.allItems) { if ( !Double.isNaN(item.from) ) iFrom = Math.min(iFrom, item.from); if ( !Double.isNaN(item.end ) ) iEnd = Math.max(iEnd , item.end ); } // Scale to 0..10 if there is no data if (iFrom == Double.MAX_VALUE && iEnd == -Double.MAX_VALUE) { iFrom = 0.; iEnd = 10.; } } /** * If zoomed, do nothing. * If not zoomed, set from and end according to TrendSpec's time window settings. */ public boolean autoscale() { if (!autoscroll) return false; setKnownFromEnd(); TrendNode trendNode = (TrendNode) getParent(); TrendSpec spec = trendNode.spec; double nFrom = from; double nEnd = end; TimeWindow timeWindow = spec.viewProfile.timeWindow; double len = timeWindow.timeWindowLength != null ? timeWindow.timeWindowLength : iEnd-iFrom; if (timeWindow.timeWindowStart != null) { if (timeWindow.timeWindowIncrement != null && timeWindow.timeWindowLength==null) { nFrom = timeWindow.timeWindowStart + basetime; if (nFrom>iEnd) { nEnd = nFrom + 1.0; } else { len = Math.max(0, iEnd-nFrom); double f = (100-timeWindow.timeWindowIncrement) / 100; double b = 1/f; double x = Math.log( len ) / Math.log( b ); x = Math.ceil( x ); x = Math.pow(b, x); nEnd = nFrom + x; } } else { nFrom = timeWindow.timeWindowStart + basetime; nEnd = nFrom + len; } } else { if (timeWindow.timeWindowIncrement == null) { nFrom = iEnd - len; nEnd = iEnd; // Marginal nEnd += len * 0.02; } else { if (timeWindow.timeWindowLength != null) { double f = timeWindow.timeWindowIncrement / 100; double fraction = len * f; nEnd = Math.floor( (iEnd +fraction)/ fraction ) * fraction; nFrom = nEnd - len; if (nFromnEnd) nFrom = nEnd; } return setFromEnd(nFrom, nEnd); } public void translate(double dx) { from += dx; end += dx; autoscroll = false; fireListener(); } /** * Convert x position in rulers coordinate space to time value. * This function does not apply basetime. To apply basetime deduct it from * the result. * * @param x * @return time */ public double toTime( double x ) { double sx = (end-from) / getWidth(); return from + x*sx; } public double toX( double time ) { double sx = (end-from) / getWidth(); return (time-from) / sx; } public void zoomIn(double x, double width) { autoscroll = false; double sx = (end-from) / getWidth(); double newFrom = from + x*sx; double newEnd = from + (x+width)*sx; from = newFrom; end = newEnd; fireListener(); } public void zoomOut() { autoscroll = true; autoscale(); layout(); fireListener(); } public double unitsPerPixel() { return (end-from) / getWidth(); } /** * @return the current ending sample time calculated from all visible chart * items. */ public double getItemEndTime() { return iEnd; } public interface TimeWindowListener { void onNewWindow(double from, double end, double sx); } }