/***************************************************************************** * * Copyright (c) 2000 - 2018, Lawrence Livermore National Security, LLC * Produced at the Lawrence Livermore National Laboratory * LLNL-CODE-442911 * All rights reserved. * * This file is part of VisIt. For details, see https://visit.llnl.gov/. The * full copyright notice is contained in the file COPYRIGHT located at the root * of the VisIt distribution or at http://www.llnl.gov/visit/copyright.html. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the disclaimer below. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the disclaimer (as noted below) in the * documentation and/or other materials provided with the distribution. * - Neither the name of the LLNS/LLNL nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL LAWRENCE LIVERMORE NATIONAL SECURITY, * LLC, THE U.S. DEPARTMENT OF ENERGY OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * *****************************************************************************/ #include "QvisStripChart.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //**************************************************************************** // Class: TimeScaleDraw // // Purpose: // Implements a class for drawing the x axis based on a value. // // Note this class is not used but is present as a demonstration // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** // class TimeScaleDraw: public QwtScaleDraw // { // public: // TimeScaleDraw( const QTime &base ): // baseTime( base ) // { // // Pass the base time to init the system. // } // // virtual QwtText label( double v ) const // { // // Add the current time to the base time and return it as a string. // QTime upTime = baseTime.addSecs( static_cast( v ) ); // return upTime.toString(); // } // private: // QTime baseTime; // }; //**************************************************************************** // Class: Background // // Purpose: // Implements a class for drawing the background as a series gray bands. // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** class Background: public QwtPlotItem { public: Background() { nBands = 10; height = 0.1; start = 0; setZ( 0.0 ); } virtual int rtti() const { return QwtPlotItem::Rtti_PlotUserItem; } virtual void draw( QPainter *painter, const QwtScaleMap &, const QwtScaleMap &yMap, const QRectF &canvasRect ) const { QColor c( Qt::darkGray ); QRectF r = canvasRect; // Create a series of gradated bands to give a relative scale. for( unsigned int n=0; nfillRect( r, c ); c = c.lighter( 107 ); } } unsigned int nBands; // Number of bands. double height; // Interval between bands. double start; // Location of the first band. }; //**************************************************************************** // Class: Zoomer // // Purpose: // Implements a class for allowing zooming. // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** class Zoomer: public QwtPlotZoomer { public: Zoomer( int xAxis, int yAxis, QWidget *canvas ): QwtPlotZoomer( xAxis, yAxis, canvas ) { setTrackerMode( QwtPicker::AlwaysOff ); setRubberBand( QwtPicker::NoRubberBand ); // RightButton: zoom out by 1 // Ctrl+RightButton: zoom out to full size // setMousePattern( QwtEventPattern::MouseSelect2, // Qt::RightButton, Qt::ControlModifier ); // setMousePattern( QwtEventPattern::MouseSelect3, // Qt::RightButton ); setMousePattern( QwtEventPattern::MouseSelect2, Qt::LeftButton, Qt::ControlModifier ); setMousePattern( QwtEventPattern::MouseSelect3, Qt::LeftButton, Qt::AltModifier ); } }; //**************************************************************************** // Class: QvisStripChart::QvisStripChart // // Purpose: // Base class for the QvisStripChart // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** QvisStripChart::QvisStripChart( QWidget *parent ): QwtPlot( parent ), dataCount( 0 ) { setAutoReplot( false ); // Create and set the canvas to be used. QwtPlotCanvas *canvas = new QwtPlotCanvas(); canvas->setBorderRadius( 10 ); setCanvas( canvas ); plotLayout()->setAlignCanvasToScales( true ); // Add the picker, panning, and zooming functions to the canvas d_picker = new QwtPlotPicker( QwtPlot::xBottom, QwtPlot::yLeft, QwtPlotPicker::CrossRubberBand, QwtPicker::AlwaysOn, canvas ); d_picker->setStateMachine( new QwtPickerDragPointMachine() ); d_picker->setRubberBandPen( QColor( Qt::green ) ); d_picker->setRubberBand( QwtPicker::CrossRubberBand ); d_picker->setTrackerPen( QColor( Qt::white ) ); d_panner = new QwtPlotPanner( canvas ); d_panner->setMouseButton( Qt::LeftButton, Qt::ShiftModifier ); d_zoomer[0] = new Zoomer( QwtPlot::xBottom, QwtPlot::yLeft, canvas ); d_zoomer[0]->setRubberBand( QwtPicker::RectRubberBand ); d_zoomer[0]->setRubberBandPen( QColor( Qt::green ) ); d_zoomer[0]->setTrackerMode( QwtPicker::ActiveOnly ); d_zoomer[0]->setTrackerPen( QColor( Qt::white ) ); d_zoomer[1] = new Zoomer( QwtPlot::xTop, QwtPlot::yRight, canvas ); toggleDisplayMode( false ); // Create and add the background to the plot bg = new Background(); bg->attach( this ); // Create and add the legend and the scale QwtLegend *legend = new QwtLegend; legend->setDefaultItemMode( QwtLegendData::Checkable ); insertLegend( legend, QwtPlot::RightLegend ); setAxisScale( QwtPlot::yLeft, bg->start, bg->nBands*bg->height ); setAxisTitle( QwtPlot::xBottom, "Cycle" ); setAxisScale( QwtPlot::xBottom, 0, HISTORY ); // This call is not used but is left as demostration when on wants // to offset from a base value. // setAxisScaleDraw( QwtPlot::xBottom, new TimeScaleDraw( cpuStat.upTime() ) ); setAxisLabelRotation( QwtPlot::xBottom, -50.0 ); setAxisLabelAlignment( QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom ); // In situations, when there is a label at the most right position // of the scale, additional space is needed to display the // overlapping part of the label would be taken by reducing the // width of scale and canvas. To avoid this "jumping canvas" // effect, we add a permanent margin. We don't need to do the same // for the left border, because there is enough space for the // overlapping label below the left scale. QwtScaleWidget *scaleWidget = axisWidget( QwtPlot::xBottom ); const int fmh = QFontMetrics( scaleWidget->font() ).height(); scaleWidget->setMinBorderDist( 0, fmh / 2 ); // Set up the time array that holds the cycle values. for( unsigned int i=0; isetColor( colors[c] ); curve->setZ( curve->z() - c ); curve->attach( this ); vars[c].curve = curve; for( unsigned int i=0; i= MAX_STRIP_CHART_VARS) { debug1 << "The curve index is above the maximum (MAX_STRIP_CHART_VARS)" << endl; return; } // If blank use a default name. if( newTitle.isEmpty() ) { std::ostringstream title; title << "VAR_" << index; vars[index].curve->setTitle( title.str().c_str() ); hideCurve( vars[index].curve ); clearData = true; } else if( vars[index].curve->title() != newTitle ) { vars[index].curve->setTitle( newTitle ); showCurve( vars[index].curve, 1 ); clearData = true; } // Clear the data. if( clearData ) { for( unsigned int i=0; isetVisible( on ); QwtLegend *lgd = qobject_cast( legend() ); QList legendWidgets = lgd->legendWidgets( itemToInfo( item ) ); if( legendWidgets.size() == 1 ) { QwtLegendLabel *legendLabel = qobject_cast( legendWidgets[0] ); if( legendLabel ) { legendLabel->setVisible( true ); legendLabel->setChecked( on ); } } replot(); } //**************************************************************************** // Class: QvisStripChart::hideCurve // // Purpose: // Hide the curve on the legend and plot. // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** void QvisStripChart::hideCurve( QwtPlotItem *item ) { item->setVisible( false ); QwtLegend *lgd = qobject_cast( legend() ); QList legendWidgets = lgd->legendWidgets( itemToInfo( item ) ); if( legendWidgets.size() == 1 ) { QwtLegendLabel *legendLabel = qobject_cast( legendWidgets[0] ); if( legendLabel ) legendLabel->setVisible( false ); } replot(); } //**************************************************************************** // Class: QvisStripChart::legendChecked // // Purpose: // Hides/shows the curve and update the plots. // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** void QvisStripChart::legendChecked( const QVariant &itemInfo, bool on ) { QwtPlotItem *plotItem = infoToItem( itemInfo ); if( plotItem ) showCurve( plotItem, on ); } //**************************************************************************** // Class: QvisStripChart::toggleDisplayMode // // Purpose: Toogles between pick and pan/zoom mode. // // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** void QvisStripChart::toggleDisplayMode( bool mode ) { d_picker->setEnabled( !mode ); d_panner->setEnabled( mode ); d_zoomer[0]->setEnabled( mode ); d_zoomer[1]->setEnabled( mode ); } //**************************************************************************** // Class: QvisStripChart::reset // // Purpose: Reset the view to the full extents. // // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** void QvisStripChart::reset() { updateAxis(); replot(); } //**************************************************************************** // Class: QvisStripChart::clear // // Purpose: Clear all of the strip chart data. // // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** void QvisStripChart::clear() { dataCount = 0; for( unsigned int c=0; c0; --i ) timeData[i] = timeData[i-1]; // For each curve advance all of the of the data values. for( unsigned int c=0; c0; --i ) { if( i < HISTORY ) vars[c].varData[i] = vars[c].varData[i-1]; } } // Update the data count. if( dataCount < HISTORY ) ++dataCount; timeData[0] = time; } //**************************************************************************** // Class: QvisStripChart::addDataPoint // // Purpose: Add the next point to a curve. // // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** void QvisStripChart::addDataPoint( const unsigned int index, const double x, const double y) { if(index >= MAX_STRIP_CHART_VARS) { debug1 << "The curve index is above the maximum (MAX_STRIP_CHART_VARS)" << endl; return; } // If a new time advance the data count which also sets // the new time. if( timeData[0] != x ) advanceDataCount( x ); // Set the new Y value. vars[index].varData[0] = y; // Update the samples for all curves. updateSamples(); updateAxis(); replot(); } //**************************************************************************** // Class: QvisStripChart::updateSamples // // Purpose: Update the samples // // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** void QvisStripChart::updateSamples() { for( unsigned int c=0; csetRawSamples( timeData, vars[c].varData, dataCount ); } //**************************************************************************** // Class: QvisStripChart::updateAxis // // Purpose: Up date the y axis based on all of the curves. // // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** void QvisStripChart::updateAxis() { bool haveData = false; // Get the min/max for all of the curves. double c_min = std::numeric_limits::max(); double c_max = -std::numeric_limits::max(); for( unsigned int c=0; cisVisible() ) { for( unsigned int i=0; i vars[c].varData[i] ) c_min = vars[c].varData[i]; if( c_max < vars[c].varData[i] ) c_max = vars[c].varData[i]; haveData = true; } } } if( !haveData ) return; // Make sure there is a range. if( c_max - c_min < std::numeric_limits::min() ) c_max = c_min + 1.0; double range[2] = {c_min,c_max}; // Find some reasonable bounds the includes the min/max. double interval; unsigned int nTicks; AdjustLabelsComputeRange( range, interval, nTicks ); // Set the parameters for the background. bg->nBands = nTicks; bg->height = interval; bg->start = range[0]; // Set the axii. setAxisScale( QwtPlot::xBottom, timeData[HISTORY-1], timeData[0] ); setAxisScale( QwtPlot::yLeft, range[0], range[1] ); } // **************************************************************************** // Modifications: ffix // // Hank Childs, Fri Sep 27 13:46:14 PDT 2002 // Put in a tolerance to stop numerical precision errors from creating // jumpy behavior. // // **************************************************************************** inline double ffix(double value) { int ivalue = (int)(value); double v = (value - ivalue); if (v > 0.9999) ivalue++; return (double) ivalue; } // **************************************************************************** // Modifications: fsign // // Hank Childs, Fri Sep 27 13:46:14 PDT 2002 // Assure the sign of two values are the same // // **************************************************************************** inline double fsign(double value, double sign) { value = fabs(value); if (sign < 0.) value *= -1.; return value; } //**************************************************************************** // Class: QvisStripChart::AdjustLabelsComputeRange // // Purpose: Gets an even range for labeling axis. // // // Programmer: Allen Sanderson // Creation: 1 May 2016 // // Modifications: // //**************************************************************************** void QvisStripChart::AdjustLabelsComputeRange( double range[2], double &interval, unsigned int &nTicks ) { double sortedRange[2] = { (range[0] < range[1] ? range[0] : range[1]), (range[0] > range[1] ? range[0] : range[1]) }; double diff = sortedRange[1] - sortedRange[0]; // Find the integral points. double pow10 = log10(diff); // Build in numerical tolerance if (pow10 != 0.) { double eps = 10.0e-10; pow10 = fsign((fabs(pow10) + eps), pow10); } // ffix move you in the wrong direction if pow10 is negative. if (pow10 < 0.) { pow10 = pow10 - 1.; } double fxt = pow(10., ffix(pow10)); // Find the number of integral points in the interval. double fnt = diff / fxt; fnt = ffix(fnt); double frac = fnt; nTicks = (frac <= 0.5 ? (int)ffix(fnt) : ((int)ffix(fnt) + 1)); double div = 1.; if (nTicks < 5) div = 2.; if (nTicks <= 2) div = 5.; // If there aren't enough tick points in this decade, use the next // decade. interval = fxt; if (div != 1.) interval /= div; // Figure out the first tick locations, relative to the start of the // axis. double start; if (sortedRange[0] < 0.) start = interval*(ffix(sortedRange[0]*(1./interval)) + 0.); else start = interval*(ffix(sortedRange[0]*(1./interval)) + 1.); // Create all of the ticks. nTicks = 0; // The first tick starts one interval back so to fully include all // values. range[0] = start - interval; range[1] = range[0]; // Add ticks until the full range is reach. while (range[1] < sortedRange[1]) { range[1] += interval; ++nTicks; } // The last tick continues one interval forward so to fully include // all values. range[1] += interval; ++nTicks; }