/*
* This file is part of the OpenSCADA project
* Copyright (C) 2006-2010 TH4 SYSTEMS GmbH (http://th4-systems.com)
*
* OpenSCADA is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* only, as published by the Free Software Foundation.
*
* OpenSCADA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License version 3 for more details
* (a copy is included in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with OpenSCADA. If not, see
* <http://opensource.org/licenses/lgpl-3.0.html> for a copy of the LGPLv3 License.
*/
package org.openscada.hd.chart;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.swtchart.BackgroundOverlay;
import org.swtchart.Chart;
public class TrendChart extends Chart implements PaintListener
{
volatile int currentX = -1;
volatile int currentY = -1;
volatile boolean showInfo = false;
private DataAtPoint dataAtPoint;
private final NumberFormat decimalFormat;
private final NumberFormat percentFormat;
private FontData smallFontData;
private final AtomicReference<double[]> quality = new AtomicReference<double[]> ( null );
private final AtomicReference<Double> qualityThreshold = new AtomicReference<Double> ( 0.0 );
private final AtomicReference<Color> qualityColor = new AtomicReference<Color> ( null );
private final AtomicReference<double[]> manual = new AtomicReference<double[]> ( null );
private final AtomicReference<Double> manualThreshold = new AtomicReference<Double> ( 0.0 );
private final AtomicReference<Color> manualColor = new AtomicReference<Color> ( null );
public DataAtPoint getDataAtPoint ()
{
return this.dataAtPoint;
}
public void setDataAtPoint ( final DataAtPoint dataAtPoint )
{
this.dataAtPoint = dataAtPoint;
}
/**
* @param parent
* @param style
*/
public TrendChart ( final Composite parent, final int style )
{
super ( parent, style );
this.decimalFormat = NumberFormat.getNumberInstance ();
this.decimalFormat.setGroupingUsed ( true );
this.decimalFormat.setMaximumFractionDigits ( 3 );
this.decimalFormat.setMinimumFractionDigits ( 3 );
this.percentFormat = NumberFormat.getPercentInstance ();
getPlotArea ().addMouseMoveListener ( new MouseMoveListener () {
@Override
public void mouseMove ( final MouseEvent e )
{
TrendChart.this.showInfo = false;
TrendChart.this.currentX = e.x;
TrendChart.this.currentY = e.y;
TrendChart.this.redraw ();
}
} );
getPlotArea ().addMouseTrackListener ( new MouseTrackListener () {
@Override
public void mouseHover ( final MouseEvent e )
{
TrendChart.this.showInfo = true;
TrendChart.this.redraw ();
}
@Override
public void mouseExit ( final MouseEvent e )
{
TrendChart.this.currentX = -1;
TrendChart.this.currentY = -1;
TrendChart.this.redraw ();
}
@Override
public void mouseEnter ( final MouseEvent e )
{
TrendChart.this.currentX = e.x;
TrendChart.this.currentY = e.y;
TrendChart.this.redraw ();
}
} );
getPlotArea ().addPaintListener ( this );
final List<FontData> fontDataList = new ArrayList<FontData> ();
for ( final FontData fontData : getDisplay ().getFontList ( "Monaco", true ) )
{
fontDataList.add ( fontData );
}
for ( final FontData fontData : getDisplay ().getFontList ( "Bitstream Vera Sans Mono", true ) )
{
fontDataList.add ( fontData );
}
for ( final FontData fontData : getDisplay ().getFontList ( "Courier New", true ) )
{
fontDataList.add ( fontData );
}
for ( final FontData fontData : getDisplay ().getFontList ( "Courier", true ) )
{
fontDataList.add ( fontData );
}
final FontData[] fontDataDefault = getDisplay ().getSystemFont ().getFontData ();
if ( fontDataList.size () > 0 )
{
this.smallFontData = fontDataList.get ( 0 );
}
else
{
this.smallFontData = fontDataDefault[0];
}
this.smallFontData.setHeight ( 8 );
final BackgroundOverlayPainter qualityBackgroundOverlay = new BackgroundOverlayPainter ();
final BackgroundOverlayPainter manualBackgroundOverlay = new BackgroundOverlayPainter ();
manualBackgroundOverlay.setInvert ( true );
setBackgroundOverlay ( new BackgroundOverlay () {
@Override
public void draw ( final GC gc, final int x, final int y )
{
qualityBackgroundOverlay.setData ( TrendChart.this.quality.get () );
qualityBackgroundOverlay.setThreshold ( TrendChart.this.qualityThreshold.get () );
qualityBackgroundOverlay.setColor ( TrendChart.this.qualityColor.get () );
qualityBackgroundOverlay.draw ( gc, x, y );
manualBackgroundOverlay.setData ( TrendChart.this.manual.get () );
manualBackgroundOverlay.setThreshold ( TrendChart.this.manualThreshold.get () );
manualBackgroundOverlay.setColor ( TrendChart.this.manualColor.get () );
manualBackgroundOverlay.draw ( gc, x, y );
}
} );
}
@Override
public void paintControl ( final PaintEvent e )
{
final GC gc = e.gc;
if ( this.currentX > -1 )
{
gc.drawLine ( this.currentX, 0, this.currentX, getPlotArea ().getBounds ().height - 1 );
}
if ( this.showInfo )
{
drawInfo ( gc );
}
}
private void drawInfo ( final GC gc )
{
if ( this.dataAtPoint == null )
{
return;
}
final double quality = this.dataAtPoint.getQuality ( this.currentX );
final double manual = this.dataAtPoint.getManual ( this.currentX );
final Date timestamp = this.dataAtPoint.getTimestamp ( this.currentX );
final Map<String, Double> data = this.dataAtPoint.getData ( this.currentX );
gc.setAntialias ( SWT.ON );
int xoffset = 10;
int yoffset = 10;
final int corner = 10;
final int padding = 5;
gc.setBackground ( getDisplay ().getSystemColor ( SWT.COLOR_INFO_BACKGROUND ) );
gc.setForeground ( getDisplay ().getSystemColor ( SWT.COLOR_INFO_FOREGROUND ) );
final Font smallFont = new Font ( gc.getDevice (), this.smallFontData );
gc.setFont ( smallFont );
final String timestampText = String.format ( "%-16s: ", Messages.getString ( "TrendChart.timestamp" ) ) + DateFormat.getDateTimeInstance ( DateFormat.LONG, DateFormat.LONG ).format ( timestamp ); //$NON-NLS-1$
final String qualityText = String.format ( "%-16s: ", Messages.getString ( "TrendChart.quality" ) ) + this.percentFormat.format ( quality ); //$NON-NLS-1$
final String manualText = String.format ( "%-16s: ", Messages.getString ( "TrendChart.manual" ) ) + this.percentFormat.format ( manual ); //$NON-NLS-1$
final String soureValuesText = String.format ( "%-16s: ", Messages.getString ( "TrendChart.numOfValues" ) ) + this.dataAtPoint.getSourceValues ( this.currentX ); //$NON-NLS-1$
final Point textSize = gc.textExtent ( timestampText );
final int textWidth = textSize.x;
final int textHeight = textSize.y;
final int height = textHeight * 5 + ( textHeight + padding ) * data.keySet ().size () + padding * 6;
if ( this.currentY + height > getPlotArea ().getSize ().y )
{
yoffset = -10 - height;
}
final int width = textWidth + padding * 3;
if ( this.currentX + width > getPlotArea ().getSize ().x )
{
xoffset = -10 - width;
}
gc.fillRoundRectangle ( this.currentX + xoffset, this.currentY + yoffset, width, height, corner, corner );
gc.drawRoundRectangle ( this.currentX + xoffset, this.currentY + yoffset, width, height, corner, corner );
gc.drawLine ( this.currentX + xoffset + padding, this.currentY + yoffset + ( padding + textHeight ) * 5 - padding, this.currentX + xoffset + width - padding, this.currentY + yoffset + ( padding + textHeight ) * 5 - padding );
gc.drawText ( timestampText, this.currentX + xoffset + padding, this.currentY + yoffset + padding );
gc.drawText ( qualityText, this.currentX + xoffset + padding, this.currentY + yoffset + padding * 2 + textHeight );
gc.drawText ( manualText, this.currentX + xoffset + padding, this.currentY + yoffset + padding * 3 + textHeight * 2 );
gc.drawText ( soureValuesText, this.currentX + xoffset + padding, this.currentY + yoffset + padding * 4 + textHeight * 3 );
int i = 5;
for ( final Entry<String, Double> entry : data.entrySet () )
{
gc.drawText ( String.format ( "%16s: ", entry.getKey () ) + String.format ( "%16s", Double.isNaN ( entry.getValue () ) ? "-" : this.decimalFormat.format ( entry.getValue () ) ), this.currentX + xoffset + padding, this.currentY + yoffset + ( padding + textHeight ) * i + padding ); //$NON-NLS-1$ //$NON-NLS-2$
i++;
}
smallFont.dispose ();
}
public void setQuality ( final double[] quality )
{
this.quality.set ( quality );
}
public void setManual ( final double[] manual )
{
this.manual.set ( manual );
}
public void setQualityThreshold ( final double qualityThreshold )
{
this.qualityThreshold.set ( qualityThreshold );
}
public void setManualThreshold ( final double manualThreshold )
{
this.manualThreshold.set ( manualThreshold );
}
public void setQualityColor ( final Color qualityColor )
{
this.qualityColor.set ( qualityColor );
}
public void setManualColor ( final Color manualColor )
{
this.manualColor.set ( manualColor );
}
}