code from features/plots merged
This commit is contained in:
parent
3c505652a3
commit
717a1c2ef2
|
|
@ -6,6 +6,7 @@ add_subdirectory(invoker)
|
|||
add_subdirectory(shadows)
|
||||
add_subdirectory(shapes)
|
||||
add_subdirectory(charts)
|
||||
add_subdirectory(plots)
|
||||
|
||||
if (BUILD_INPUTCONTEXT)
|
||||
add_subdirectory(inputpanel)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
############################################################################
|
||||
# QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
# This file may be used under the terms of the 3-clause BSD License
|
||||
############################################################################
|
||||
|
||||
list(APPEND HEADERS QskPlotView.h QskPlotItem.h QskPlotViewSkinlet.h QskPlotNamespace.h)
|
||||
list(APPEND SOURCES QskPlotView.cpp QskPlotItem.cpp QskPlotViewSkinlet.cpp)
|
||||
|
||||
list(APPEND HEADERS QskPlotGrid.h QskPlotGridSkinlet.h)
|
||||
list(APPEND SOURCES QskPlotGrid.cpp QskPlotGridSkinlet.cpp)
|
||||
|
||||
list(APPEND HEADERS QskPlotCurveData.h QskPlotCurve.h QskPlotCurveSkinlet.h )
|
||||
list(APPEND SOURCES QskPlotCurveData.cpp QskPlotCurve.cpp QskPlotCurveSkinlet.cpp)
|
||||
|
||||
list(APPEND HEADERS QskPlotCorridorData.h QskPlotCorridor.h QskPlotCorridorSkinlet.h )
|
||||
list(APPEND SOURCES QskPlotCorridorData.cpp QskPlotCorridor.cpp QskPlotCorridorSkinlet.cpp)
|
||||
|
||||
list(APPEND HEADERS PlotCursor.h PlotCursorSkinlet.h )
|
||||
list(APPEND SOURCES PlotCursor.cpp PlotCursorSkinlet.cpp)
|
||||
|
||||
qsk_add_example(plots ${SOURCES} ${HEADERS}
|
||||
PlotSkin.h PlotSkin.cpp Plot.h Plot.cpp main.cpp )
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "Plot.h"
|
||||
#include "PlotSkin.h"
|
||||
#include "PlotCursor.h"
|
||||
|
||||
#include <QskPlotGrid.h>
|
||||
#include <QskPlotCorridor.h>
|
||||
#include <QskPlotCorridorData.h>
|
||||
#include <QskPlotCurve.h>
|
||||
#include <QskPlotCurveData.h>
|
||||
#include <QskEvent.h>
|
||||
|
||||
#include <QskMargins.h>
|
||||
#include <QskRgbValue.h>
|
||||
|
||||
#include <QTime>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
class CurveData : public QskPlotCurveData
|
||||
{
|
||||
public:
|
||||
CurveData( QObject* parent = nullptr )
|
||||
: QskPlotCurveData( parent )
|
||||
{
|
||||
setHint( MonotonicX );
|
||||
}
|
||||
|
||||
void setSamples( const QVector< Plot::Sample >& samples )
|
||||
{
|
||||
m_samples = samples;
|
||||
Q_EMIT changed();
|
||||
}
|
||||
|
||||
qsizetype count() const override
|
||||
{
|
||||
return m_samples.count();
|
||||
}
|
||||
|
||||
QPointF pointAt( qsizetype index ) const override
|
||||
{
|
||||
const auto& sample = m_samples.at( index );
|
||||
return QPointF( sample.timestamp, sample.value );
|
||||
}
|
||||
|
||||
private:
|
||||
QVector< Plot::Sample > m_samples;
|
||||
};
|
||||
|
||||
class CorridorData : public QskPlotCorridorData
|
||||
{
|
||||
public:
|
||||
CorridorData( QObject* parent = nullptr )
|
||||
: QskPlotCorridorData( parent )
|
||||
{
|
||||
}
|
||||
|
||||
void setSamples( const QVector< Plot::Sample >& samples )
|
||||
{
|
||||
m_samples = samples;
|
||||
Q_EMIT changed();
|
||||
}
|
||||
|
||||
qsizetype count() const override
|
||||
{
|
||||
return m_samples.count();
|
||||
}
|
||||
|
||||
QskPlotCorridorSample sampleAt( qsizetype index ) const override
|
||||
{
|
||||
const auto& sample = m_samples.at( index );
|
||||
return { sample.timestamp, { sample.lowerBound, sample.upperBound } };
|
||||
}
|
||||
|
||||
private:
|
||||
QVector< Plot::Sample > m_samples;
|
||||
};
|
||||
}
|
||||
|
||||
class Plot::PrivateData
|
||||
{
|
||||
public:
|
||||
QskPlotGrid* grid = nullptr;
|
||||
QskPlotCorridor* corridor = nullptr;
|
||||
QskPlotCurve* curve = nullptr;
|
||||
PlotCursor* cursor = nullptr;
|
||||
|
||||
const QTime startTime = QTime::currentTime();
|
||||
};
|
||||
|
||||
Plot::Plot( QQuickItem* parentItem )
|
||||
: QskPlotView( parentItem )
|
||||
, m_data( new PrivateData )
|
||||
{
|
||||
PlotSkin::extendSkin( effectiveSkin() );
|
||||
|
||||
setAcceptedMouseButtons( Qt::LeftButton ); // cursor
|
||||
setWheelEnabled( true ); // zooming
|
||||
|
||||
resetAxes();
|
||||
|
||||
using namespace QskRgb;
|
||||
|
||||
m_data->grid = new QskPlotGrid( this );
|
||||
|
||||
m_data->corridor = new QskPlotCorridor( this );
|
||||
m_data->corridor->setData( new CorridorData() );
|
||||
|
||||
m_data->curve = new QskPlotCurve( this );
|
||||
m_data->curve->setData( new CurveData() );
|
||||
|
||||
m_data->cursor = new PlotCursor();
|
||||
m_data->cursor->setParent( this ); // not attached
|
||||
m_data->cursor->setPosition( -33 );
|
||||
|
||||
/*
|
||||
extra space for the overlapping labels. Actually this should be
|
||||
the job of the layout code: TODO ...
|
||||
*/
|
||||
setPaddingHint( Panel, QskMargins( 10, 15, 40, 10 ) );
|
||||
}
|
||||
|
||||
Plot::~Plot()
|
||||
{
|
||||
}
|
||||
|
||||
void Plot::setSamples( const QVector< Sample >& samples )
|
||||
{
|
||||
auto corridorData = static_cast< CorridorData* >( m_data->corridor->data() );
|
||||
corridorData->setSamples( samples );
|
||||
|
||||
auto curveData = static_cast< CurveData* >( m_data->curve->data() );
|
||||
curveData->setSamples( samples );
|
||||
}
|
||||
|
||||
void Plot::shiftXAxis( int steps )
|
||||
{
|
||||
auto range = boundaries( QskPlot::XBottom );
|
||||
//range.translate( steps * 0.2 * range.width() );
|
||||
range.translate( steps * 1.0 );
|
||||
setBoundaries( QskPlot::XBottom, range );
|
||||
}
|
||||
|
||||
void Plot::resetAxes()
|
||||
{
|
||||
QskIntervalF rangeX( -50.0, 0.0 );
|
||||
|
||||
if ( m_data->curve && m_data->curve->data() )
|
||||
{
|
||||
const auto r = m_data->curve->data()->boundingRect();
|
||||
if ( !r.isEmpty() )
|
||||
rangeX |= QskIntervalF( r.left(), r.right() );
|
||||
}
|
||||
|
||||
setBoundaries( QskPlot::XBottom, rangeX );
|
||||
setBoundaries( QskPlot::YLeft, 0.0, 100.0 );
|
||||
}
|
||||
|
||||
QVariant Plot::labelAt( QskPlot::Axis axis, qreal pos ) const
|
||||
{
|
||||
if ( axis == QskPlot::XBottom )
|
||||
{
|
||||
auto text = QString::number( pos, 'g' );
|
||||
text += '\n';
|
||||
|
||||
const auto time = m_data->startTime.addSecs( qRound( pos ) );
|
||||
text += time.toString();
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
return Inherited::labelAt( axis, pos );
|
||||
}
|
||||
|
||||
void Plot::mousePressEvent( QMouseEvent* event )
|
||||
{
|
||||
auto pos = qskMousePosition( event );
|
||||
if ( canvasRect().contains( pos ) )
|
||||
{
|
||||
m_data->cursor->attach( this );
|
||||
m_data->cursor->setCanvasPosition( pos.x() );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Inherited::mousePressEvent( event );
|
||||
}
|
||||
|
||||
void Plot::mouseMoveEvent( QMouseEvent* event )
|
||||
{
|
||||
if ( m_data->cursor->view() )
|
||||
{
|
||||
auto x = qskMousePosition( event ).x();
|
||||
|
||||
const auto r = canvasRect();
|
||||
x = qBound( r.left(), x, r.right() );
|
||||
|
||||
m_data->cursor->setCanvasPosition( x );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Inherited::mouseMoveEvent( event );
|
||||
}
|
||||
|
||||
void Plot::mouseReleaseEvent( QMouseEvent* event )
|
||||
{
|
||||
if ( m_data->cursor->view() )
|
||||
{
|
||||
m_data->cursor->detach();
|
||||
return;
|
||||
}
|
||||
|
||||
Inherited::mouseReleaseEvent( event );
|
||||
}
|
||||
|
||||
void Plot::wheelEvent( QWheelEvent* event )
|
||||
{
|
||||
const auto steps = qskWheelSteps( event );
|
||||
|
||||
double f = std::pow( 0.9, qAbs( steps ) );
|
||||
if ( steps > 0 )
|
||||
f = 1 / f;
|
||||
|
||||
auto range = boundaries( QskPlot::XBottom );
|
||||
range.setLowerBound( range.upperBound() - f * range.length() );
|
||||
|
||||
setBoundaries( QskPlot::XBottom, range );
|
||||
}
|
||||
|
||||
void Plot::changeEvent( QEvent* event )
|
||||
{
|
||||
if ( event->type() == QEvent::StyleChange )
|
||||
PlotSkin::extendSkin( effectiveSkin() );
|
||||
|
||||
Inherited::changeEvent( event );
|
||||
}
|
||||
|
||||
#include "moc_Plot.cpp"
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "QskPlotView.h"
|
||||
#include <memory>
|
||||
|
||||
class Plot : public QskPlotView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QskPlotView;
|
||||
|
||||
public:
|
||||
class Sample
|
||||
{
|
||||
public:
|
||||
qreal timestamp = 0.0;
|
||||
|
||||
qreal lowerBound = 0.0;
|
||||
qreal value = 0.0;
|
||||
qreal upperBound = 0.0;
|
||||
};
|
||||
|
||||
Plot( QQuickItem* parentItem = nullptr );
|
||||
~Plot() override;
|
||||
|
||||
void setSamples( const QVector< Sample >& );
|
||||
|
||||
public Q_SLOT:
|
||||
void resetAxes();
|
||||
void shiftXAxis( int steps );
|
||||
|
||||
protected:
|
||||
void mousePressEvent( QMouseEvent* ) override;
|
||||
void mouseMoveEvent( QMouseEvent* ) override;
|
||||
void mouseReleaseEvent( QMouseEvent* ) override;
|
||||
|
||||
void wheelEvent( QWheelEvent* ) override;
|
||||
void changeEvent( QEvent* ) override;
|
||||
|
||||
private:
|
||||
QVariant labelAt( QskPlot::Axis axis, qreal pos ) const final override;
|
||||
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "PlotCursor.h"
|
||||
#include <qtransform.h>
|
||||
|
||||
QSK_SUBCONTROL( PlotCursor, Line )
|
||||
QSK_SUBCONTROL( PlotCursor, LabelPanel )
|
||||
QSK_SUBCONTROL( PlotCursor, LabelText )
|
||||
|
||||
class PlotCursor::PrivateData
|
||||
{
|
||||
public:
|
||||
qreal position = 0.0;
|
||||
Qt::Orientation orientation = Qt::Horizontal;
|
||||
};
|
||||
|
||||
PlotCursor::PlotCursor( QObject* object )
|
||||
: Inherited( object )
|
||||
, m_data( new PrivateData )
|
||||
{
|
||||
setCoordinateType( CanvasCoordinates );
|
||||
}
|
||||
|
||||
PlotCursor::~PlotCursor()
|
||||
{
|
||||
}
|
||||
|
||||
void PlotCursor::setOrientation( Qt::Orientation orientation )
|
||||
{
|
||||
if ( m_data->orientation != orientation )
|
||||
{
|
||||
m_data->orientation = orientation;
|
||||
markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
Qt::Orientation PlotCursor::orientation() const
|
||||
{
|
||||
return m_data->orientation;
|
||||
}
|
||||
|
||||
void PlotCursor::setCanvasPosition( qreal position )
|
||||
{
|
||||
const auto t = transformation().inverted();
|
||||
|
||||
if ( m_data->orientation == Qt::Horizontal )
|
||||
position = t.map( QPointF( position, 0.0 ) ).x();
|
||||
else
|
||||
position = t.map( QPointF( 0.0, position ) ).y();
|
||||
|
||||
setPosition( position );
|
||||
}
|
||||
|
||||
void PlotCursor::setPosition( qreal position )
|
||||
{
|
||||
if ( m_data->position != position )
|
||||
{
|
||||
m_data->position = position;
|
||||
markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
qreal PlotCursor::position() const
|
||||
{
|
||||
return m_data->position;
|
||||
}
|
||||
|
||||
void PlotCursor::transformationChanged( ChangeFlags flags )
|
||||
{
|
||||
Inherited::transformationChanged( flags );
|
||||
}
|
||||
|
||||
bool PlotCursor::needsClipping() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#include "moc_PlotCursor.cpp"
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "QskPlotItem.h"
|
||||
#include <qnamespace.h>
|
||||
|
||||
class PlotCursor : public QskPlotItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QskPlotItem;
|
||||
|
||||
public:
|
||||
QSK_SUBCONTROLS( Line, LabelPanel, LabelText )
|
||||
|
||||
PlotCursor( QObject* = nullptr );
|
||||
~PlotCursor() override;
|
||||
|
||||
void setOrientation( Qt::Orientation );
|
||||
Qt::Orientation orientation() const;
|
||||
|
||||
void setCanvasPosition( qreal );
|
||||
|
||||
void setPosition( qreal );
|
||||
qreal position() const;
|
||||
|
||||
void transformationChanged( ChangeFlags ) override;
|
||||
bool needsClipping() const override;
|
||||
|
||||
private:
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "PlotCursorSkinlet.h"
|
||||
#include "PlotCursor.h"
|
||||
|
||||
#include <QskSGNode.h>
|
||||
#include <QskTextOptions.h>
|
||||
#include <QskTextColors.h>
|
||||
|
||||
#include <QskPlotCurve.h>
|
||||
#include <QskPlotCorridor.h>
|
||||
#include <QskPlotCorridorData.h>
|
||||
#include <QskPlotView.h>
|
||||
#include <QskFunctions.h>
|
||||
|
||||
#include <qtransform.h>
|
||||
#include <qfontmetrics.h>
|
||||
#include <qcolor.h>
|
||||
|
||||
enum { Lower, Upper, Value };
|
||||
|
||||
static inline QskAspect::Variation variation( int index )
|
||||
{
|
||||
if ( index == Lower )
|
||||
return QskAspect::Lower;
|
||||
|
||||
if ( index == Upper )
|
||||
return QskAspect::Upper;
|
||||
|
||||
return QskAspect::NoVariation;
|
||||
}
|
||||
|
||||
class PlotCursorSkinlet::PrivateData
|
||||
{
|
||||
public:
|
||||
|
||||
struct
|
||||
{
|
||||
qreal value;
|
||||
} labelInfo[ 3 ];
|
||||
};
|
||||
|
||||
PlotCursorSkinlet::PlotCursorSkinlet( QskSkin* skin )
|
||||
: Inherited( skin )
|
||||
, m_data( new PrivateData )
|
||||
{
|
||||
setNodeRoles( { CursorLine, TextBox, Text } );
|
||||
}
|
||||
|
||||
PlotCursorSkinlet::~PlotCursorSkinlet()
|
||||
{
|
||||
}
|
||||
|
||||
void PlotCursorSkinlet::updateNode(
|
||||
QskSkinnable* skinnable, QSGNode* parent ) const
|
||||
{
|
||||
const auto cursor = static_cast< const PlotCursor* >( skinnable );
|
||||
const auto x = cursor->position();
|
||||
|
||||
auto info = m_data->labelInfo;
|
||||
|
||||
for ( auto child : cursor->view()->children() )
|
||||
{
|
||||
if ( auto curve = qobject_cast< const QskPlotCurve* >( child ) )
|
||||
{
|
||||
const auto pos = curve->interpolatedPoint( Qt::Horizontal, x );
|
||||
info[Value].value = pos.y();
|
||||
}
|
||||
else if ( auto corridor = qobject_cast< const QskPlotCorridor* >( child ) )
|
||||
{
|
||||
const auto boundary = corridor->interpolatedSample( x ).boundary;
|
||||
|
||||
info[ Lower ].value = boundary.lowerBound();
|
||||
info[ Upper ].value = boundary.upperBound();
|
||||
}
|
||||
}
|
||||
|
||||
Inherited::updateNode( skinnable, parent );
|
||||
}
|
||||
|
||||
QSGNode* PlotCursorSkinlet::updateSubNode(
|
||||
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
|
||||
{
|
||||
using Q = PlotCursor;
|
||||
|
||||
switch( nodeRole )
|
||||
{
|
||||
case CursorLine:
|
||||
return updateCursorLineNode( skinnable, node );
|
||||
|
||||
case TextBox:
|
||||
return updateSeriesNode( skinnable, Q::LabelPanel, node );
|
||||
|
||||
case Text:
|
||||
return updateSeriesNode( skinnable, Q::LabelText, node );
|
||||
}
|
||||
|
||||
return Inherited::updateSubNode( skinnable, nodeRole, node );
|
||||
}
|
||||
|
||||
int PlotCursorSkinlet::sampleCount(
|
||||
const QskSkinnable*, QskAspect::Subcontrol ) const
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
QRectF PlotCursorSkinlet::sampleRect( const QskSkinnable* skinnable,
|
||||
const QRectF&, QskAspect::Subcontrol, int index ) const
|
||||
{
|
||||
using Q = PlotCursor;
|
||||
|
||||
const auto cursor = static_cast< const PlotCursor* >( skinnable );
|
||||
|
||||
auto pos = QPointF( cursor->position(),
|
||||
m_data->labelInfo[ index ].value );
|
||||
|
||||
pos = cursor->transformation().map( pos );
|
||||
|
||||
const QFontMetricsF fm( cursor->effectiveFont( Q::LabelText ) );
|
||||
|
||||
const qreal w = qskHorizontalAdvance( fm, "100.0" );
|
||||
const qreal h = fm.height();
|
||||
|
||||
QRectF r( 0, 0, w, h );
|
||||
r = r.marginsAdded( cursor->paddingHint( Q::LabelPanel ) );
|
||||
|
||||
r.moveRight( pos.x() - 5 );
|
||||
r.moveBottom( pos.y() );
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
QSGNode* PlotCursorSkinlet::updateSampleNode( const QskSkinnable* skinnable,
|
||||
QskAspect::Subcontrol subControl, int index, QSGNode* node ) const
|
||||
{
|
||||
using Q = PlotCursor;
|
||||
|
||||
const auto rect = sampleRect( skinnable, QRectF(), subControl, index );
|
||||
const auto aspect = subControl | variation( index );
|
||||
|
||||
if ( subControl == Q::LabelPanel )
|
||||
{
|
||||
const auto gradient = skinnable->gradientHint( aspect );
|
||||
return updateBoxNode( skinnable, node, rect, gradient, subControl );
|
||||
}
|
||||
|
||||
if ( subControl == Q::LabelText )
|
||||
{
|
||||
const auto info = m_data->labelInfo[ index ];
|
||||
|
||||
const auto text = QString::number( info.value, 'f', 1 );
|
||||
const auto color = skinnable->color( aspect );
|
||||
|
||||
const auto textOptions = skinnable->textOptionsHint( aspect );
|
||||
const auto font = skinnable->effectiveFont( aspect );
|
||||
|
||||
return updateTextNode( skinnable, node, rect, Qt::AlignCenter,
|
||||
text, font, textOptions, color, Qsk::Normal );
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QSGNode* PlotCursorSkinlet::updateCursorLineNode(
|
||||
const QskSkinnable* skinnable, QSGNode* node ) const
|
||||
{
|
||||
auto cursor = static_cast< const PlotCursor* >( skinnable );
|
||||
|
||||
const auto r = cursor->scaleRect();
|
||||
if ( r.isEmpty() )
|
||||
return nullptr;
|
||||
|
||||
QPointF p1 = r.topLeft();
|
||||
QPointF p2 = r.bottomRight();
|
||||
|
||||
if ( cursor->orientation() == Qt::Horizontal )
|
||||
{
|
||||
const auto x = cursor->position();
|
||||
if ( x < r.left() || x > r.right() )
|
||||
return nullptr;
|
||||
|
||||
p1.rx() = p2.rx() = x;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto y = cursor->position();
|
||||
if ( y < r.top() || y > r.bottom() )
|
||||
return nullptr;
|
||||
|
||||
p1.ry() = p2.ry() = y;
|
||||
}
|
||||
|
||||
if ( cursor->coordinateType() == QskPlotItem::CanvasCoordinates )
|
||||
{
|
||||
/*
|
||||
When having a non solid line we might want to use CanvasCoordinates
|
||||
to avoid the length of dashes/dots being affected from the
|
||||
plot transformation.
|
||||
*/
|
||||
const auto transform = cursor->transformation();
|
||||
p1 = transform.map( p1 );
|
||||
p2 = transform.map( p2 );
|
||||
}
|
||||
|
||||
return updateLineNode( cursor, node, QLineF( p1, p2 ), PlotCursor::Line );
|
||||
}
|
||||
|
||||
#include "moc_PlotCursorSkinlet.cpp"
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QskSkinlet.h>
|
||||
|
||||
class PlotCursorSkinlet : public QskSkinlet
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
using Inherited = QskSkinlet;
|
||||
|
||||
public:
|
||||
enum NodeRole { CursorLine, TextBox, Text };
|
||||
|
||||
Q_INVOKABLE PlotCursorSkinlet( QskSkin* = nullptr );
|
||||
~PlotCursorSkinlet() override;
|
||||
|
||||
void updateNode( QskSkinnable*, QSGNode* ) const override;
|
||||
|
||||
int sampleCount( const QskSkinnable*,
|
||||
QskAspect::Subcontrol ) const override final;
|
||||
|
||||
QRectF sampleRect( const QskSkinnable*,
|
||||
const QRectF&, QskAspect::Subcontrol, int index ) const override;
|
||||
|
||||
protected:
|
||||
QSGNode* updateSubNode( const QskSkinnable*,
|
||||
quint8 nodeRole, QSGNode* ) const override;
|
||||
|
||||
QSGNode* updateSampleNode( const QskSkinnable*,
|
||||
QskAspect::Subcontrol, int index, QSGNode* ) const override;
|
||||
|
||||
private:
|
||||
QSGNode* updateCursorLineNode( const QskSkinnable*, QSGNode* ) const;
|
||||
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "PlotSkin.h"
|
||||
|
||||
#include <QskSkin.h>
|
||||
#include <QskSkinHintTableEditor.h>
|
||||
|
||||
#include <QskPlotView.h>
|
||||
#include <QskPlotViewSkinlet.h>
|
||||
|
||||
#include <QskPlotCurve.h>
|
||||
#include <QskPlotCurveSkinlet.h>
|
||||
|
||||
#include <QskPlotGrid.h>
|
||||
#include <QskPlotGridSkinlet.h>
|
||||
|
||||
#include <QskPlotCorridor.h>
|
||||
#include <QskPlotCorridorSkinlet.h>
|
||||
|
||||
#include "PlotCursor.h"
|
||||
#include "PlotCursorSkinlet.h"
|
||||
|
||||
#include <QskRgbValue.h>
|
||||
#include <QskBoxBorderColors.h>
|
||||
#include <QskBoxShapeMetrics.h>
|
||||
#include <QskStippleMetrics.h>
|
||||
|
||||
#include <QskScaleRenderer.h>
|
||||
|
||||
#include <qbrush.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
inline bool isExtended( const QskSkin* skin )
|
||||
{
|
||||
auto metaObject = skin->skinletMetaObject( &QskPlotView::staticMetaObject );
|
||||
return metaObject != &QskSkinlet::staticMetaObject; // the fallback for controls
|
||||
}
|
||||
|
||||
class SkinEditor : private QskSkinHintTableEditor
|
||||
{
|
||||
public:
|
||||
SkinEditor( QskSkinHintTable* table );
|
||||
void setupPlotHints();
|
||||
};
|
||||
}
|
||||
|
||||
void PlotSkin::extendSkin( QskSkin* skin )
|
||||
{
|
||||
if ( skin == nullptr || isExtended( skin ) )
|
||||
return;
|
||||
|
||||
skin->declareSkinlet< QskPlotView, QskPlotViewSkinlet >();
|
||||
skin->declareSkinlet< QskPlotGrid, QskPlotGridSkinlet >();
|
||||
skin->declareSkinlet< QskPlotCurve, QskPlotCurveSkinlet >();
|
||||
skin->declareSkinlet< QskPlotCorridor, QskPlotCorridorSkinlet >();
|
||||
skin->declareSkinlet< PlotCursor, PlotCursorSkinlet >();
|
||||
|
||||
SkinEditor editor( &skin->hintTable() );
|
||||
editor.setupPlotHints();
|
||||
}
|
||||
|
||||
SkinEditor::SkinEditor( QskSkinHintTable* table )
|
||||
: QskSkinHintTableEditor( table )
|
||||
{
|
||||
}
|
||||
|
||||
void SkinEditor::setupPlotHints()
|
||||
{
|
||||
using A = QskAspect;
|
||||
using namespace QskRgb;
|
||||
|
||||
const auto rgbLower = DodgerBlue;
|
||||
const auto rgbUpper = MediumSeaGreen;
|
||||
const auto rgbValue = Yellow;
|
||||
{
|
||||
using Q = QskPlotView;
|
||||
|
||||
// Panel
|
||||
setBoxShape( Q::Panel, 10 );
|
||||
setGradient( Q::Panel, qRgb( 240, 240, 240 ) );
|
||||
setBoxBorderMetrics( Q::Panel, 1 );
|
||||
setBoxBorderColors( Q::Panel, qRgb( 220, 220, 220 ) );
|
||||
|
||||
// Canvas
|
||||
setGradient( Q::Canvas, QGradient::PremiumDark );
|
||||
setBoxBorderColors( Q::Canvas, DimGray );
|
||||
setBoxBorderMetrics( Q::Canvas, 2 );
|
||||
setBoxShape( Q::Canvas, 4 );
|
||||
|
||||
// AxisScale
|
||||
const auto padding = 4; // spacing between canvas and axis
|
||||
setPadding( Q::AxisScale | A::Left, 0, 0, padding, 0 );
|
||||
setPadding( Q::AxisScale | A::Bottom, 0, padding, 0, 0 );
|
||||
|
||||
setColor( Q::AxisScale, qRgb( 20, 20, 20 ) );
|
||||
setFontRole( Q::AxisScale, QskSkin::MediumFont );
|
||||
setFlag( Q::AxisScale | A::Style, QskScaleRenderer::Backbone );
|
||||
|
||||
// thickness/length of the major ticks
|
||||
setStrutSize( Q::AxisScale, 1.0, 8.0 );
|
||||
|
||||
// spacing between ticks and labels
|
||||
setSpacing( Q::AxisScale, 5 );
|
||||
}
|
||||
|
||||
{
|
||||
using Q = QskPlotGrid;
|
||||
|
||||
setColor( Q::MajorLine, White );
|
||||
setMetric( Q::MajorLine | A::Size, 1 );
|
||||
setStippleMetrics( Q::MajorLine, { 2, 6 } );
|
||||
|
||||
setColor( Q::MinorLine, Gainsboro );
|
||||
setMetric( Q::MinorLine | A::Size, 1 );
|
||||
setStippleMetrics( Q::MinorLine, { 1, 10 } );
|
||||
}
|
||||
|
||||
{
|
||||
using Q = QskPlotCurve;
|
||||
|
||||
setMetric( Q::Line | A::Size, 2 );
|
||||
setColor( Q::Line, rgbValue );
|
||||
}
|
||||
|
||||
{
|
||||
using Q = QskPlotCorridor;
|
||||
|
||||
setMetric( Q::Border | A::Size, 2 );
|
||||
setColor( Q::Border | A::Lower, rgbLower );
|
||||
setColor( Q::Border | A::Upper, rgbUpper );
|
||||
setColor( Q::Corridor, toTransparent( Crimson, 150 ) );
|
||||
}
|
||||
|
||||
{
|
||||
using Q = PlotCursor;
|
||||
|
||||
const int alpha = 200;
|
||||
|
||||
setColor( Q::Line, Qt::yellow );
|
||||
setMetric( Q::Line | A::Size, 1 );
|
||||
setStippleMetrics( Q::Line, { 4, 8 } );
|
||||
|
||||
setGradient( Q::LabelPanel | A::Lower, toTransparent( rgbLower, alpha ) );
|
||||
setColor( Q::LabelText | A::Lower, Qt::white );
|
||||
|
||||
setGradient( Q::LabelPanel | A::Upper, toTransparent( rgbUpper, alpha ) );
|
||||
setColor( Q::LabelText | A::Upper, Qt::white );
|
||||
|
||||
setGradient( Q::LabelPanel, toTransparent( rgbValue, alpha ) );
|
||||
setColor( Q::LabelText, Qt::black );
|
||||
|
||||
setBoxShape( Q::LabelPanel, 5 );
|
||||
setPadding( Q::LabelPanel, 5, 5, 5, 5 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
class QskSkin;
|
||||
|
||||
namespace PlotSkin
|
||||
{
|
||||
void extendSkin( QskSkin* );
|
||||
};
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotCorridor.h"
|
||||
#include "QskPlotCorridorData.h"
|
||||
|
||||
#include <QskIntervalF.h>
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qpointer.h>
|
||||
|
||||
QSK_SUBCONTROL( QskPlotCorridor, Border )
|
||||
QSK_SUBCONTROL( QskPlotCorridor, Corridor )
|
||||
|
||||
class QskPlotCorridor::PrivateData
|
||||
{
|
||||
public:
|
||||
QPointer< QskPlotCorridorData > corridorData;
|
||||
};
|
||||
|
||||
QskPlotCorridor::QskPlotCorridor( QObject* object )
|
||||
: Inherited( object )
|
||||
, m_data( new PrivateData )
|
||||
{
|
||||
}
|
||||
|
||||
QskPlotCorridor::~QskPlotCorridor()
|
||||
{
|
||||
}
|
||||
|
||||
void QskPlotCorridor::setBorderWidth( qreal lineWidth )
|
||||
{
|
||||
const auto aspect = Border | QskAspect::Size;
|
||||
|
||||
resetMetric( aspect | QskAspect::Lower );
|
||||
resetMetric( aspect | QskAspect::Upper );
|
||||
|
||||
lineWidth = qMax( lineWidth, 0.0 );
|
||||
|
||||
if ( setMetric( aspect, lineWidth ) )
|
||||
{
|
||||
markDirty();
|
||||
Q_EMIT borderWidthChanged( lineWidth );
|
||||
}
|
||||
}
|
||||
|
||||
qreal QskPlotCorridor::borderWidth() const
|
||||
{
|
||||
return metric( Border | QskAspect::Size );
|
||||
}
|
||||
|
||||
void QskPlotCorridor::setBorderColor( const QColor& color )
|
||||
{
|
||||
resetColor( Border | QskAspect::Lower );
|
||||
resetColor( Border | QskAspect::Upper );
|
||||
|
||||
if ( setColor( Border, color ) )
|
||||
{
|
||||
markDirty();
|
||||
Q_EMIT colorChanged( color );
|
||||
}
|
||||
}
|
||||
|
||||
QColor QskPlotCorridor::borderColor() const
|
||||
{
|
||||
return color( Border );
|
||||
}
|
||||
|
||||
void QskPlotCorridor::setColor( const QColor& color )
|
||||
{
|
||||
if ( setColor( Corridor, color ) )
|
||||
{
|
||||
markDirty();
|
||||
Q_EMIT colorChanged( color );
|
||||
}
|
||||
}
|
||||
|
||||
QColor QskPlotCorridor::color() const
|
||||
{
|
||||
return color( Corridor );
|
||||
}
|
||||
|
||||
void QskPlotCorridor::setSamples( const QVector< QskPlotCorridorSample >& samples )
|
||||
{
|
||||
setData( new QskPlotCorridorSamples( samples, this ) );
|
||||
}
|
||||
|
||||
void QskPlotCorridor::setData( QskPlotCorridorData* corridorData )
|
||||
{
|
||||
if ( corridorData == m_data->corridorData )
|
||||
return;
|
||||
|
||||
auto oldData = m_data->corridorData.data();
|
||||
m_data->corridorData = corridorData;
|
||||
|
||||
if ( oldData )
|
||||
{
|
||||
if ( oldData->parent() == this )
|
||||
{
|
||||
delete oldData;
|
||||
}
|
||||
else
|
||||
{
|
||||
disconnect( oldData, &QskPlotCorridorData::changed,
|
||||
this, &QskPlotItem::markDirty );
|
||||
}
|
||||
}
|
||||
|
||||
if ( corridorData )
|
||||
{
|
||||
if ( corridorData->parent() == nullptr )
|
||||
corridorData->setParent( this );
|
||||
|
||||
connect( corridorData, &QskPlotCorridorData::changed,
|
||||
this, &QskPlotItem::markDirty );
|
||||
}
|
||||
|
||||
markDirty();
|
||||
}
|
||||
|
||||
QskPlotCorridorData* QskPlotCorridor::data() const
|
||||
{
|
||||
return m_data->corridorData;
|
||||
}
|
||||
|
||||
QskPlotCorridorSample QskPlotCorridor::interpolatedSample( qreal value ) const
|
||||
{
|
||||
if ( m_data->corridorData )
|
||||
return m_data->corridorData->interpolatedSample( value );
|
||||
|
||||
return QskPlotCorridorSample();
|
||||
}
|
||||
|
||||
void QskPlotCorridor::transformationChanged( ChangeFlags flags )
|
||||
{
|
||||
Inherited::transformationChanged( flags );
|
||||
}
|
||||
|
||||
bool QskPlotCorridor::needsClipping() const
|
||||
{
|
||||
auto data = m_data->corridorData.data();
|
||||
if ( data == nullptr || data->count() == 0 )
|
||||
return false;
|
||||
|
||||
// The skinlet does basic polygon clipping in x direction
|
||||
|
||||
const auto corridorRect = data->boundingRect();
|
||||
const auto plotRect = scaleRect();
|
||||
|
||||
return ( corridorRect.top() < plotRect.top() )
|
||||
|| ( corridorRect.bottom() > plotRect.bottom() );
|
||||
}
|
||||
|
||||
#include "moc_QskPlotCorridor.cpp"
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "QskPlotItem.h"
|
||||
#include <qvector.h>
|
||||
|
||||
class QskPlotCorridorSample;
|
||||
class QskPlotCorridorData;
|
||||
class QColor;
|
||||
|
||||
// Only horizontal: TODO
|
||||
class QskPlotCorridor : public QskPlotItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QskPlotItem;
|
||||
|
||||
Q_PROPERTY( qreal borderWidth READ borderWidth
|
||||
WRITE setBorderWidth NOTIFY borderWidthChanged )
|
||||
|
||||
Q_PROPERTY( QColor borderColor READ borderColor
|
||||
WRITE setBorderColor NOTIFY borderColorChanged )
|
||||
|
||||
Q_PROPERTY( QColor color READ color
|
||||
WRITE setColor NOTIFY colorChanged )
|
||||
|
||||
public:
|
||||
QSK_SUBCONTROLS( Border, Corridor )
|
||||
|
||||
QskPlotCorridor( QObject* = nullptr );
|
||||
~QskPlotCorridor() override;
|
||||
|
||||
void setBorderWidth( qreal );
|
||||
qreal borderWidth() const;
|
||||
|
||||
void setBorderColor( const QColor& );
|
||||
QColor borderColor() const;
|
||||
|
||||
void setColor( const QColor& );
|
||||
QColor color() const;
|
||||
|
||||
void setSamples( const QVector< QskPlotCorridorSample >& );
|
||||
|
||||
void setData( QskPlotCorridorData* );
|
||||
QskPlotCorridorData* data() const;
|
||||
|
||||
QskPlotCorridorSample interpolatedSample( qreal value ) const;
|
||||
|
||||
void transformationChanged( ChangeFlags ) override;
|
||||
bool needsClipping() const override;
|
||||
|
||||
using QskSkinnable::setColor;
|
||||
using QskSkinnable::color;
|
||||
|
||||
Q_SIGNALS:
|
||||
void borderWidthChanged( qreal );
|
||||
void borderColorChanged( const QColor& );
|
||||
void colorChanged( const QColor& );
|
||||
|
||||
private:
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotCorridorData.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
inline int upperIndex( const QskPlotCorridorData* data, qreal value )
|
||||
{
|
||||
const int indexMax = data->count() - 1;
|
||||
|
||||
if ( indexMax < 0 || data->sampleAt( indexMax ).value < value )
|
||||
return -1;
|
||||
|
||||
int indexMin = 0;
|
||||
int n = indexMax;
|
||||
|
||||
while ( n > 0 )
|
||||
{
|
||||
const int half = n >> 1;
|
||||
const int indexMid = indexMin + half;
|
||||
|
||||
if ( value < data->sampleAt( indexMid ).value )
|
||||
{
|
||||
n = half;
|
||||
}
|
||||
else
|
||||
{
|
||||
indexMin = indexMid + 1;
|
||||
n -= half + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return indexMin;
|
||||
}
|
||||
|
||||
QRectF boundingRect( const QskPlotCorridorData* data )
|
||||
{
|
||||
const auto count = data->count();
|
||||
if ( count <= 0 )
|
||||
return QRectF();
|
||||
|
||||
auto boundary = data->sampleAt( 0 ).boundary;
|
||||
|
||||
for ( int i = 1; i < count; i++ )
|
||||
boundary.unite( data->sampleAt( i ).boundary );
|
||||
|
||||
const auto x1 = data->sampleAt( 0 ).value;
|
||||
const auto x2 = data->sampleAt( count - 1 ).value;
|
||||
|
||||
return QRectF( x1, boundary.lowerBound(),
|
||||
x2 - x1, boundary.length() ).normalized();
|
||||
}
|
||||
}
|
||||
|
||||
QskPlotCorridorData::QskPlotCorridorData( QObject* parent )
|
||||
: QObject( parent )
|
||||
{
|
||||
}
|
||||
|
||||
QskPlotCorridorData::~QskPlotCorridorData()
|
||||
{
|
||||
}
|
||||
|
||||
QRectF QskPlotCorridorData::boundingRect() const
|
||||
{
|
||||
if ( m_boundingRect.isNull() )
|
||||
m_boundingRect = ::boundingRect( this );
|
||||
|
||||
return m_boundingRect;
|
||||
}
|
||||
|
||||
int QskPlotCorridorData::upperIndex( qreal value ) const
|
||||
{
|
||||
const int n = count();
|
||||
|
||||
if ( n == 0 )
|
||||
return -1;
|
||||
|
||||
auto index = ::upperIndex( this, value );
|
||||
if ( ( index == -1 ) && ( value == sampleAt( n - 1 ).value ) )
|
||||
index = n - 1;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
QskPlotCorridorSample QskPlotCorridorData::interpolatedSample( qreal value ) const
|
||||
{
|
||||
const int n = count();
|
||||
if ( n == 0 )
|
||||
return QskPlotCorridorSample();
|
||||
|
||||
if ( n == 1 )
|
||||
return { value, { 0.0, 0.0 } };
|
||||
|
||||
int index = 0;
|
||||
|
||||
if ( n > 2 )
|
||||
{
|
||||
index = upperIndex( value );
|
||||
if ( index > 0 )
|
||||
index--;
|
||||
}
|
||||
|
||||
const auto s1 = sampleAt( index );
|
||||
const auto s2 = sampleAt( index + 1 );
|
||||
|
||||
const auto dv = s2.value - s1.value;
|
||||
if ( dv == 0.0 )
|
||||
return s2;
|
||||
|
||||
const auto t = ( value - s1.value ) / dv;
|
||||
return { value, s1.boundary.interpolated( s2.boundary, t ) };
|
||||
}
|
||||
|
||||
QskPlotCorridorSamples::QskPlotCorridorSamples( QObject* parent )
|
||||
: QskPlotCorridorData( parent )
|
||||
{
|
||||
}
|
||||
|
||||
QskPlotCorridorSamples::QskPlotCorridorSamples(
|
||||
const QVector< QskPlotCorridorSample >& samples, QObject* parent )
|
||||
: QskPlotCorridorData( parent )
|
||||
, m_samples( samples )
|
||||
{
|
||||
}
|
||||
|
||||
void QskPlotCorridorSamples::setSamples( const QVector< QskPlotCorridorSample >& samples )
|
||||
{
|
||||
m_samples = samples;
|
||||
m_boundingRect = QRectF(); // invalidating
|
||||
|
||||
Q_EMIT changed();
|
||||
}
|
||||
|
||||
#include "moc_QskPlotCorridorData.cpp"
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QskIntervalF.h>
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qvector.h>
|
||||
#include <qrect.h>
|
||||
|
||||
class QskPlotCorridorSample
|
||||
{
|
||||
public:
|
||||
qreal value = 0.0;
|
||||
QskIntervalF boundary;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO( QskPlotCorridorSample, Q_MOVABLE_TYPE );
|
||||
|
||||
// Hiding the layout of the data behind an abstract API
|
||||
class QskPlotCorridorData : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QskPlotCorridorData( QObject* parent = nullptr );
|
||||
virtual ~QskPlotCorridorData();
|
||||
|
||||
virtual qsizetype count() const = 0;
|
||||
virtual QskPlotCorridorSample sampleAt( qsizetype index ) const = 0;
|
||||
|
||||
virtual QRectF boundingRect() const;
|
||||
|
||||
int upperIndex( qreal value ) const;
|
||||
QskPlotCorridorSample interpolatedSample( qreal value ) const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void changed();
|
||||
|
||||
protected:
|
||||
mutable QRectF m_boundingRect;
|
||||
};
|
||||
|
||||
// A simple implementation using QVector< CorridorSample >
|
||||
class QskPlotCorridorSamples : public QskPlotCorridorData
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QskPlotCorridorData;
|
||||
|
||||
public:
|
||||
QskPlotCorridorSamples( QObject* parent = nullptr );
|
||||
QskPlotCorridorSamples(
|
||||
const QVector< QskPlotCorridorSample >&, QObject* parent = nullptr );
|
||||
|
||||
void setSamples( const QVector< QskPlotCorridorSample >& );
|
||||
QVector< QskPlotCorridorSample > samples() const;
|
||||
|
||||
qsizetype count() const override;
|
||||
QskPlotCorridorSample sampleAt( qsizetype index ) const override;
|
||||
|
||||
private:
|
||||
QVector< QskPlotCorridorSample > m_samples;
|
||||
};
|
||||
|
||||
inline QVector< QskPlotCorridorSample > QskPlotCorridorSamples::samples() const
|
||||
{
|
||||
return m_samples;
|
||||
}
|
||||
|
||||
inline qsizetype QskPlotCorridorSamples::count() const
|
||||
{
|
||||
return m_samples.count();
|
||||
}
|
||||
|
||||
inline QskPlotCorridorSample QskPlotCorridorSamples::sampleAt( qsizetype index ) const
|
||||
{
|
||||
return m_samples.at( index );
|
||||
}
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotCorridorSkinlet.h"
|
||||
#include "QskPlotCorridor.h"
|
||||
#include "QskPlotCorridorData.h"
|
||||
|
||||
#include <QskSGNode.h>
|
||||
#include <QskVertex.h>
|
||||
|
||||
#include <qvector.h>
|
||||
#include <qsgvertexcolormaterial.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
class GeometryNode : public QSGGeometryNode
|
||||
{
|
||||
protected:
|
||||
GeometryNode()
|
||||
: m_geometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 )
|
||||
{
|
||||
setGeometry( &m_geometry );
|
||||
setMaterial( &m_material );
|
||||
}
|
||||
|
||||
private:
|
||||
QSGGeometry m_geometry;
|
||||
QSGVertexColorMaterial m_material;
|
||||
};
|
||||
|
||||
class CorridorNode : public GeometryNode
|
||||
{
|
||||
public:
|
||||
void updateCorridor( const QskPlotCorridorData* data,
|
||||
const QskPlotCorridorSample& sample1, int index1,
|
||||
const QskPlotCorridorSample& sample2, int index2,
|
||||
const QColor& color )
|
||||
{
|
||||
using namespace QskVertex;
|
||||
|
||||
const Color vertexColor( color );
|
||||
|
||||
geometry()->setDrawingMode( QSGGeometry::DrawTriangleStrip );
|
||||
|
||||
auto line = allocateLines< ColoredLine >( *geometry(), index2 - index1 + 1 );
|
||||
|
||||
line++->setLine( sample1.value, sample1.boundary.lowerBound(),
|
||||
sample1.value, sample1.boundary.upperBound(), vertexColor );
|
||||
|
||||
for ( int i = index1 + 1; i < index2; i++ )
|
||||
{
|
||||
const auto sample = data->sampleAt( i );
|
||||
line++->setLine( sample.value, sample.boundary.lowerBound(),
|
||||
sample.value, sample.boundary.upperBound(), vertexColor );
|
||||
}
|
||||
|
||||
line++->setLine( sample2.value, sample2.boundary.lowerBound(),
|
||||
sample2.value, sample2.boundary.upperBound(), vertexColor );
|
||||
|
||||
markDirty( QSGNode::DirtyGeometry );
|
||||
}
|
||||
};
|
||||
|
||||
class BorderNode : public GeometryNode
|
||||
{
|
||||
public:
|
||||
void updateBorder( const QskPlotCorridorData* data,
|
||||
const QskPlotCorridorSample& sample1, int index1,
|
||||
const QskPlotCorridorSample& sample2, int index2,
|
||||
quint8 nodeRole, const QColor& color, qreal lineWidth )
|
||||
{
|
||||
auto& geometry = *this->geometry();
|
||||
|
||||
geometry.setDrawingMode( QSGGeometry::DrawLineStrip );
|
||||
|
||||
const float lineWidthF = lineWidth;
|
||||
if( lineWidthF != geometry.lineWidth() )
|
||||
geometry.setLineWidth( lineWidthF );
|
||||
|
||||
const QskVertex::Color c( color );
|
||||
|
||||
geometry.allocate( index2 - index1 + 1 );
|
||||
|
||||
auto p = geometry.vertexDataAsColoredPoint2D();
|
||||
|
||||
if( nodeRole == QskPlotCorridorSkinlet::LowerBoundRole )
|
||||
{
|
||||
p++->set( sample1.value, sample1.boundary.lowerBound(),
|
||||
c.r, c.g, c.b, c.a );
|
||||
|
||||
for ( int i = index1 + 1; i < index2; i++ )
|
||||
{
|
||||
const auto sample = data->sampleAt( i );
|
||||
p++->set( sample.value, sample.boundary.lowerBound(),
|
||||
c.r, c.g, c.b, c.a );
|
||||
}
|
||||
|
||||
p++->set( sample2.value, sample2.boundary.lowerBound(),
|
||||
c.r, c.g, c.b, c.a );
|
||||
}
|
||||
else
|
||||
{
|
||||
p++->set( sample1.value, sample1.boundary.upperBound(),
|
||||
c.r, c.g, c.b, c.a );
|
||||
|
||||
for ( int i = index1 + 1; i < index2; i++ )
|
||||
{
|
||||
const auto sample = data->sampleAt( i );
|
||||
p++->set( sample.value, sample.boundary.upperBound(),
|
||||
c.r, c.g, c.b, c.a );
|
||||
}
|
||||
|
||||
p++->set( sample2.value, sample2.boundary.upperBound(),
|
||||
c.r, c.g, c.b, c.a );
|
||||
}
|
||||
|
||||
markDirty( QSGNode::DirtyGeometry );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class QskPlotCorridorSkinlet::PrivateData
|
||||
{
|
||||
public:
|
||||
int index1, index2;
|
||||
QskPlotCorridorSample sample1, sample2;
|
||||
};
|
||||
|
||||
QskPlotCorridorSkinlet::QskPlotCorridorSkinlet( QskSkin* skin )
|
||||
: Inherited( skin )
|
||||
, m_data( new PrivateData )
|
||||
{
|
||||
setNodeRoles( { CorridorRole, LowerBoundRole, UpperBoundRole } );
|
||||
}
|
||||
|
||||
QskPlotCorridorSkinlet::~QskPlotCorridorSkinlet()
|
||||
{
|
||||
}
|
||||
|
||||
void QskPlotCorridorSkinlet::updateNode(
|
||||
QskSkinnable* skinnable, QSGNode* parent ) const
|
||||
{
|
||||
/*
|
||||
As clipping is the same for borders and corridor
|
||||
we do it only once here
|
||||
*/
|
||||
|
||||
auto corridor = static_cast< const QskPlotCorridor* >( skinnable );
|
||||
const auto data = corridor->data();
|
||||
|
||||
m_data->index1 = m_data->index2 = -1;
|
||||
|
||||
const auto n = data->count();
|
||||
if ( data && n > 0 )
|
||||
{
|
||||
auto& s1 = m_data->sample1;
|
||||
auto& s2 = m_data->sample2;
|
||||
|
||||
s1 = data->sampleAt( 0 );
|
||||
s2 = data->sampleAt( n - 1 );
|
||||
|
||||
const auto scaleRect = corridor->scaleRect();
|
||||
|
||||
const qreal x1 = scaleRect.left();
|
||||
const qreal x2 = scaleRect.right();
|
||||
|
||||
if ( !( x1 > s2.value || x2 < s1.value ) )
|
||||
{
|
||||
m_data->index1 = 0;
|
||||
m_data->index2 = n - 1;
|
||||
|
||||
const int index1 = data->upperIndex( x1 );
|
||||
if ( index1 > 0 )
|
||||
{
|
||||
m_data->index1 = index1 - 1;
|
||||
s1 = data->interpolatedSample( x1 );
|
||||
}
|
||||
|
||||
const int index2 = data->upperIndex( x2 );
|
||||
if ( index2 > 0 )
|
||||
{
|
||||
m_data->index2 = index2;
|
||||
s2 = data->interpolatedSample( x2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Inherited::updateNode( skinnable, parent );
|
||||
}
|
||||
|
||||
QSGNode* QskPlotCorridorSkinlet::updateSubNode(
|
||||
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
|
||||
{
|
||||
if ( m_data->index2 < 0 )
|
||||
return nullptr;
|
||||
|
||||
if ( nodeRole == CorridorRole )
|
||||
return updateCorridorNode( skinnable, node );
|
||||
else
|
||||
return updateBorderNode( skinnable, nodeRole, node );
|
||||
}
|
||||
|
||||
QSGNode* QskPlotCorridorSkinlet::updateCorridorNode(
|
||||
const QskSkinnable* skinnable, QSGNode* node ) const
|
||||
{
|
||||
using Q = QskPlotCorridor;
|
||||
|
||||
auto corridor = static_cast< const QskPlotCorridor* >( skinnable );
|
||||
|
||||
const auto color = corridor->color( Q::Corridor );
|
||||
if ( !color.isValid() || color.alpha() == 0 )
|
||||
return nullptr;
|
||||
|
||||
const auto corridorData = corridor->data();
|
||||
if ( corridorData->count() == 0 )
|
||||
return nullptr;
|
||||
|
||||
auto corridorNode = QskSGNode::ensureNode< CorridorNode >( node );
|
||||
|
||||
corridorNode->updateCorridor( corridorData,
|
||||
m_data->sample1, m_data->index1, m_data->sample2, m_data->index2, color );
|
||||
|
||||
return corridorNode;
|
||||
}
|
||||
|
||||
QSGNode* QskPlotCorridorSkinlet::updateBorderNode(
|
||||
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
|
||||
{
|
||||
using Q = QskPlotCorridor;
|
||||
using A = QskAspect;
|
||||
|
||||
auto corridor = static_cast< const QskPlotCorridor* >( skinnable );
|
||||
|
||||
const auto corridorData = corridor->data();
|
||||
if ( corridorData->count() == 0 )
|
||||
return nullptr;
|
||||
|
||||
QColor color;
|
||||
if( nodeRole == QskPlotCorridorSkinlet::LowerBoundRole )
|
||||
color = corridor->color( Q::Border | A::Lower );
|
||||
else
|
||||
color = corridor->color( Q::Border | A::Upper );
|
||||
|
||||
if ( !color.isValid() || color.alpha() == 0 )
|
||||
return nullptr;
|
||||
|
||||
auto lineWidth = corridor->metric( Q::Border | A::Size );
|
||||
lineWidth = qMax( lineWidth, 0.0 );
|
||||
|
||||
auto borderNode = QskSGNode::ensureNode< BorderNode >( node );
|
||||
|
||||
borderNode->updateBorder( corridorData,
|
||||
m_data->sample1, m_data->index1, m_data->sample2, m_data->index2,
|
||||
nodeRole, color, lineWidth );
|
||||
|
||||
return borderNode;
|
||||
}
|
||||
|
||||
#include "moc_QskPlotCorridorSkinlet.cpp"
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QskSkinlet.h>
|
||||
|
||||
class QskPlotCorridorSkinlet : public QskSkinlet
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
using Inherited = QskSkinlet;
|
||||
|
||||
public:
|
||||
enum NodeRole
|
||||
{
|
||||
CorridorRole,
|
||||
LowerBoundRole,
|
||||
UpperBoundRole
|
||||
};
|
||||
|
||||
Q_INVOKABLE QskPlotCorridorSkinlet( QskSkin* = nullptr );
|
||||
~QskPlotCorridorSkinlet() override;
|
||||
|
||||
void updateNode( QskSkinnable*, QSGNode* ) const override;
|
||||
|
||||
protected:
|
||||
QSGNode* updateSubNode( const QskSkinnable*,
|
||||
quint8 nodeRole, QSGNode* ) const override;
|
||||
|
||||
private:
|
||||
QSGNode* updateCorridorNode( const QskSkinnable*, QSGNode* ) const;
|
||||
QSGNode* updateBorderNode( const QskSkinnable*, quint8, QSGNode* ) const;
|
||||
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotCurve.h"
|
||||
#include "QskPlotCurveData.h"
|
||||
|
||||
#include <qpointer.h>
|
||||
#include <qcolor.h>
|
||||
|
||||
QSK_SUBCONTROL( QskPlotCurve, Line )
|
||||
|
||||
class QskPlotCurve::PrivateData
|
||||
{
|
||||
public:
|
||||
QPointer< QskPlotCurveData > curveData;
|
||||
};
|
||||
|
||||
QskPlotCurve::QskPlotCurve( QObject* object )
|
||||
: Inherited( object )
|
||||
, m_data( new PrivateData )
|
||||
{
|
||||
}
|
||||
|
||||
QskPlotCurve::~QskPlotCurve()
|
||||
{
|
||||
}
|
||||
|
||||
void QskPlotCurve::setColor( const QColor& color )
|
||||
{
|
||||
if ( setColor( Line, color ) )
|
||||
{
|
||||
markDirty();
|
||||
Q_EMIT colorChanged( color );
|
||||
}
|
||||
}
|
||||
|
||||
QColor QskPlotCurve::color() const
|
||||
{
|
||||
return color( Line );
|
||||
}
|
||||
|
||||
void QskPlotCurve::setLineWidth( qreal lineWidth )
|
||||
{
|
||||
lineWidth = qMax( lineWidth, 0.0 );
|
||||
|
||||
if ( setMetric( Line | QskAspect::Size, lineWidth ) )
|
||||
{
|
||||
markDirty();
|
||||
Q_EMIT lineWidthChanged( lineWidth );
|
||||
}
|
||||
}
|
||||
|
||||
qreal QskPlotCurve::lineWidth() const
|
||||
{
|
||||
return metric( Line | QskAspect::Size );
|
||||
}
|
||||
|
||||
void QskPlotCurve::setPoints( const QVector< QPointF >& points )
|
||||
{
|
||||
setData( new QskPlotCurvePoints( points, this ) );
|
||||
}
|
||||
|
||||
void QskPlotCurve::setData( QskPlotCurveData* curveData )
|
||||
{
|
||||
if ( curveData == m_data->curveData )
|
||||
return;
|
||||
|
||||
auto oldData = m_data->curveData.data();
|
||||
m_data->curveData = curveData;
|
||||
|
||||
if ( oldData )
|
||||
{
|
||||
if ( oldData->parent() == this )
|
||||
delete oldData;
|
||||
else
|
||||
disconnect( oldData, &QskPlotCurveData::changed, this, &QskPlotItem::markDirty );
|
||||
}
|
||||
|
||||
if ( curveData )
|
||||
{
|
||||
if ( curveData->parent() == nullptr )
|
||||
curveData->setParent( this );
|
||||
|
||||
connect( curveData, &QskPlotCurveData::changed, this, &QskPlotItem::markDirty );
|
||||
}
|
||||
|
||||
markDirty();
|
||||
}
|
||||
|
||||
QskPlotCurveData* QskPlotCurve::data() const
|
||||
{
|
||||
return m_data->curveData;
|
||||
}
|
||||
|
||||
QPointF QskPlotCurve::interpolatedPoint(
|
||||
Qt::Orientation orientation, qreal value ) const
|
||||
{
|
||||
if ( m_data->curveData )
|
||||
return m_data->curveData->interpolatedPoint( orientation, value );
|
||||
|
||||
return QPointF();
|
||||
}
|
||||
|
||||
void QskPlotCurve::transformationChanged( ChangeFlags flags )
|
||||
{
|
||||
if ( flags & ( XBoundariesChanged | YBoundariesChanged ) )
|
||||
{
|
||||
/*
|
||||
We could skip updates, when the curve is inside
|
||||
of old and new boundaries TODO ...
|
||||
*/
|
||||
}
|
||||
|
||||
Inherited::transformationChanged( flags );
|
||||
}
|
||||
|
||||
bool QskPlotCurve::needsClipping() const
|
||||
{
|
||||
auto data = m_data->curveData.data();
|
||||
if ( data == nullptr || data->count() == 0 )
|
||||
return false;
|
||||
|
||||
// The skinlet does basic polygon clipping for monotonic data.
|
||||
|
||||
using D = QskPlotCurveData;
|
||||
|
||||
const auto hints = data->hints();
|
||||
if ( ( hints & D::MonotonicX ) && ( hints & D::MonotonicY ) )
|
||||
return false;
|
||||
|
||||
if ( data->hints() & QskPlotCurveData::BoundingRectangle )
|
||||
{
|
||||
const auto curveRect = data->boundingRect();
|
||||
const auto plotRect = scaleRect();
|
||||
|
||||
if ( hints & D::MonotonicX )
|
||||
{
|
||||
return ( curveRect.top() < plotRect.top() )
|
||||
|| ( curveRect.bottom() > plotRect.bottom() );
|
||||
}
|
||||
|
||||
if ( hints & D::MonotonicY )
|
||||
{
|
||||
return ( curveRect.left() < plotRect.left() )
|
||||
&& ( curveRect.right() > plotRect.right() );
|
||||
}
|
||||
|
||||
return !plotRect.contains( curveRect );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#include "moc_QskPlotCurve.cpp"
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "QskPlotItem.h"
|
||||
|
||||
#include <qpoint.h>
|
||||
#include <qvector.h>
|
||||
|
||||
class QskPlotCurveData;
|
||||
class QColor;
|
||||
|
||||
class QskPlotCurve : public QskPlotItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY( qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged )
|
||||
Q_PROPERTY( QColor color READ color WRITE setColor NOTIFY colorChanged )
|
||||
|
||||
using Inherited = QskPlotItem;
|
||||
|
||||
public:
|
||||
QSK_SUBCONTROLS( Line )
|
||||
|
||||
QskPlotCurve( QObject* = nullptr );
|
||||
~QskPlotCurve() override;
|
||||
|
||||
void setPoints( const QVector< QPointF >& );
|
||||
|
||||
void setData( QskPlotCurveData* );
|
||||
QskPlotCurveData* data() const;
|
||||
|
||||
QPointF interpolatedPoint( Qt::Orientation, qreal ) const;
|
||||
|
||||
void setColor( const QColor& );
|
||||
QColor color() const;
|
||||
|
||||
void setLineWidth( qreal );
|
||||
qreal lineWidth() const;
|
||||
|
||||
void transformationChanged( ChangeFlags ) override;
|
||||
bool needsClipping() const override;
|
||||
|
||||
using QskSkinnable::setColor;
|
||||
using QskSkinnable::color;
|
||||
|
||||
Q_SIGNALS:
|
||||
void lineWidthChanged( qreal );
|
||||
void colorChanged( const QColor& );
|
||||
|
||||
private:
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotCurveData.h"
|
||||
|
||||
#include <qdebug.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct compareX
|
||||
{
|
||||
inline bool operator()( const double x, const QPointF& pos ) const
|
||||
{
|
||||
return ( x < pos.x() );
|
||||
}
|
||||
};
|
||||
|
||||
struct compareY
|
||||
{
|
||||
inline bool operator()( const double y, const QPointF& pos ) const
|
||||
{
|
||||
return ( y < pos.y() );
|
||||
}
|
||||
};
|
||||
|
||||
template< typename LessThan >
|
||||
inline int upperIndex(
|
||||
const QskPlotCurveData* data, qreal value, LessThan lessThan )
|
||||
{
|
||||
const int indexMax = data->count() - 1;
|
||||
|
||||
if ( indexMax < 0 || !lessThan( value, data->pointAt( indexMax ) ) )
|
||||
return -1;
|
||||
|
||||
int indexMin = 0;
|
||||
int n = indexMax;
|
||||
|
||||
while ( n > 0 )
|
||||
{
|
||||
const int half = n >> 1;
|
||||
const int indexMid = indexMin + half;
|
||||
|
||||
if ( lessThan( value, data->pointAt( indexMid ) ) )
|
||||
{
|
||||
n = half;
|
||||
}
|
||||
else
|
||||
{
|
||||
indexMin = indexMid + 1;
|
||||
n -= half + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return indexMin;
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
QRectF boundingRect( const QskPlotCurveData* data )
|
||||
{
|
||||
const auto count = data->count();
|
||||
if ( count <= 0 )
|
||||
return QRectF();
|
||||
|
||||
const auto hints = data->hints();
|
||||
|
||||
const bool montonicX = hints & QskPlotCurveData::MonotonicX;
|
||||
const bool montonicY = hints & QskPlotCurveData::MonotonicY;
|
||||
|
||||
if ( montonicX && montonicY )
|
||||
{
|
||||
const auto p1 = data->pointAt( 0 );
|
||||
const auto p2 = data->pointAt( count - 1 );
|
||||
|
||||
return QRectF( p1, p2 ).normalized();
|
||||
}
|
||||
|
||||
if ( montonicX )
|
||||
{
|
||||
const auto p1 = data->pointAt( 0 );
|
||||
const auto p2 = data->pointAt( count - 1 );
|
||||
|
||||
qreal yMin, yMax;
|
||||
yMin = yMax = p1.y();
|
||||
|
||||
for ( int i = 1; i < count; i++ )
|
||||
{
|
||||
const auto p = data->pointAt( i );
|
||||
|
||||
if ( p.y() < yMin )
|
||||
yMin = p.y();
|
||||
else if ( p.y() > yMax )
|
||||
yMax = p.y();
|
||||
}
|
||||
|
||||
return QRectF( p1.x(), yMin, p2.x() - p1.x(), yMax - yMin ).normalized();
|
||||
}
|
||||
|
||||
if ( montonicY )
|
||||
{
|
||||
const auto p1 = data->pointAt( 0 );
|
||||
const auto p2 = data->pointAt( count - 1 );
|
||||
|
||||
qreal xMin, xMax;
|
||||
xMin = xMax = p1.x();
|
||||
|
||||
for ( int i = 1; i < count; i++ )
|
||||
{
|
||||
const auto p = data->pointAt( i );
|
||||
|
||||
if ( p.x() < xMin )
|
||||
xMin = p.x();
|
||||
else if ( p.x() > xMax )
|
||||
xMax = p.x();
|
||||
}
|
||||
|
||||
return QRectF( xMin, p1.y(), xMax - xMin, p2.y() - p1.y() ).normalized();
|
||||
}
|
||||
|
||||
{
|
||||
const auto p1 = data->pointAt( 0 );
|
||||
|
||||
qreal xMin, xMax;
|
||||
qreal yMin, yMax;
|
||||
|
||||
xMin = xMax = p1.x();
|
||||
yMin = yMax = p1.y();
|
||||
|
||||
for ( int i = 1; i < count; i++ )
|
||||
{
|
||||
const auto p = data->pointAt( i );
|
||||
|
||||
if ( p.x() < xMin )
|
||||
xMin = p.x();
|
||||
else if ( p.x() > xMax )
|
||||
xMax = p.x();
|
||||
|
||||
if ( p.y() < yMin )
|
||||
yMin = p.y();
|
||||
else if ( p.y() > yMax )
|
||||
yMax = p.y();
|
||||
}
|
||||
|
||||
return QRectF( xMin, yMin, xMax - xMin, yMax - yMin );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QskPlotCurveData::QskPlotCurveData( QObject* parent )
|
||||
: QObject( parent )
|
||||
{
|
||||
}
|
||||
|
||||
QskPlotCurveData::~QskPlotCurveData()
|
||||
{
|
||||
}
|
||||
|
||||
void QskPlotCurveData::setHints( Hints hints )
|
||||
{
|
||||
if ( m_hints != hints )
|
||||
{
|
||||
m_hints = hints;
|
||||
Q_EMIT changed();
|
||||
}
|
||||
}
|
||||
|
||||
void QskPlotCurveData::setHint( Hint hint, bool on )
|
||||
{
|
||||
if ( on )
|
||||
setHints( m_hints | hint );
|
||||
else
|
||||
setHints( m_hints & ~hint );
|
||||
}
|
||||
|
||||
QRectF QskPlotCurveData::boundingRect() const
|
||||
{
|
||||
if ( m_boundingRect.isNull() )
|
||||
m_boundingRect = ::boundingRect( this );
|
||||
|
||||
return m_boundingRect;
|
||||
}
|
||||
|
||||
int QskPlotCurveData::upperIndex( Qt::Orientation orientation, qreal value ) const
|
||||
{
|
||||
const int n = count();
|
||||
|
||||
if ( n == 0 )
|
||||
return -1;
|
||||
|
||||
int index;
|
||||
|
||||
if ( orientation == Qt::Horizontal )
|
||||
{
|
||||
index = ::upperIndex( this, value, compareX() );
|
||||
if ( ( index == -1 ) && ( value == pointAt( n - 1 ).x() ) )
|
||||
index = n - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = ::upperIndex( this, value, compareY() );
|
||||
if ( ( index == -1 ) && ( value == pointAt( n - 1 ).y() ) )
|
||||
index = n - 1;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
QPointF QskPlotCurveData::interpolatedPoint(
|
||||
Qt::Orientation orientation, qreal value ) const
|
||||
{
|
||||
const int n = count();
|
||||
if ( n == 0 )
|
||||
return QPointF();
|
||||
|
||||
if ( n == 1 )
|
||||
{
|
||||
if ( orientation == Qt::Horizontal )
|
||||
return QPointF( value, 0.0 );
|
||||
else
|
||||
return QPointF( 0.0, value );
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
|
||||
if ( n > 2 )
|
||||
{
|
||||
index = upperIndex( orientation, value );
|
||||
if ( index > 0 )
|
||||
index--;
|
||||
}
|
||||
|
||||
const auto p1 = pointAt( index );
|
||||
const auto p2 = pointAt( index + 1 );
|
||||
|
||||
if ( orientation == Qt::Horizontal )
|
||||
{
|
||||
const auto dx = p2.x() - p1.x();
|
||||
if ( dx == 0.0 )
|
||||
return p2;
|
||||
|
||||
const auto t = ( value - p1.x() ) / dx;
|
||||
const auto y = p1.y() + t * ( p2.y() - p1.y() );
|
||||
|
||||
return QPointF( value, y );
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto dy = p2.y() - p1.y();
|
||||
if ( dy == 0.0 )
|
||||
return p2;
|
||||
|
||||
const auto t = ( value - p1.y() ) / dy;
|
||||
const auto x = p1.x() + t * ( p2.x() - p1.x() );
|
||||
|
||||
return QPointF( x, value );
|
||||
}
|
||||
}
|
||||
|
||||
QskPlotCurvePoints::QskPlotCurvePoints( QObject* parent )
|
||||
: QskPlotCurveData( parent )
|
||||
{
|
||||
}
|
||||
|
||||
QskPlotCurvePoints::QskPlotCurvePoints(
|
||||
const QVector< QPointF >& points, QObject* parent )
|
||||
: QskPlotCurveData( parent )
|
||||
, m_points( points )
|
||||
{
|
||||
}
|
||||
|
||||
void QskPlotCurvePoints::setPoints( const QVector< QPointF >& points )
|
||||
{
|
||||
m_points = points;
|
||||
m_boundingRect = QRectF(); // invalidating
|
||||
|
||||
Q_EMIT changed();
|
||||
}
|
||||
|
||||
#include "moc_QskPlotCurveData.cpp"
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qvector.h>
|
||||
#include <qrect.h>
|
||||
#include <qnamespace.h>
|
||||
|
||||
// Hiding the layout of the data behind an abstract API
|
||||
class QskPlotCurveData : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Hint
|
||||
{
|
||||
/*
|
||||
The points are monotonic in/decreasing. F.e a faster algos
|
||||
can be implemented with this information ( f.e polygon clipping )
|
||||
*/
|
||||
MonotonicX = 1 << 0,
|
||||
MonotonicY = 1 << 1,
|
||||
|
||||
/*
|
||||
The data offers a bounding rectangle, that can f.e be used for
|
||||
clipping or autoscaling purposes
|
||||
*/
|
||||
BoundingRectangle = 1 << 2
|
||||
};
|
||||
Q_ENUM( Hint );
|
||||
|
||||
Q_DECLARE_FLAGS( Hints, Hint )
|
||||
|
||||
QskPlotCurveData( QObject* parent = nullptr );
|
||||
virtual ~QskPlotCurveData();
|
||||
|
||||
void setHints( Hints );
|
||||
Hints hints() const;
|
||||
|
||||
void setHint( Hint, bool on = true );
|
||||
|
||||
virtual qsizetype count() const = 0;
|
||||
virtual QPointF pointAt( qsizetype index ) const = 0;
|
||||
|
||||
virtual QRectF boundingRect() const;
|
||||
|
||||
int upperIndex( Qt::Orientation, qreal value ) const;
|
||||
QPointF interpolatedPoint( Qt::Orientation, qreal value ) const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void changed();
|
||||
|
||||
protected:
|
||||
mutable QRectF m_boundingRect;
|
||||
|
||||
private:
|
||||
Hints m_hints = BoundingRectangle;
|
||||
};
|
||||
|
||||
inline QskPlotCurveData::Hints QskPlotCurveData::hints() const
|
||||
{
|
||||
return m_hints;
|
||||
}
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( QskPlotCurveData::Hints )
|
||||
|
||||
// A simple implementation using QVector< QPointF >
|
||||
class QskPlotCurvePoints : public QskPlotCurveData
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QskPlotCurveData;
|
||||
|
||||
public:
|
||||
QskPlotCurvePoints( QObject* parent = nullptr );
|
||||
QskPlotCurvePoints( const QVector< QPointF >&, QObject* parent = nullptr );
|
||||
|
||||
void setPoints( const QVector< QPointF >& );
|
||||
QVector< QPointF > points() const;
|
||||
|
||||
qsizetype count() const override;
|
||||
QPointF pointAt( qsizetype index ) const override;
|
||||
|
||||
private:
|
||||
QVector< QPointF > m_points;
|
||||
};
|
||||
|
||||
inline QVector< QPointF > QskPlotCurvePoints::points() const
|
||||
{
|
||||
return m_points;
|
||||
}
|
||||
|
||||
inline qsizetype QskPlotCurvePoints::count() const
|
||||
{
|
||||
return m_points.count();
|
||||
}
|
||||
|
||||
inline QPointF QskPlotCurvePoints::pointAt( qsizetype index ) const
|
||||
{
|
||||
return m_points.at( index );
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotCurveSkinlet.h"
|
||||
#include "QskPlotCurveData.h"
|
||||
#include "QskPlotCurve.h"
|
||||
|
||||
#include <QskSGNode.h>
|
||||
#include <QskVertex.h>
|
||||
|
||||
#include <qsggeometry.h>
|
||||
#include <qsgvertexcolormaterial.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
class CurveNode : public QSGGeometryNode
|
||||
{
|
||||
public:
|
||||
CurveNode()
|
||||
: m_geometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), 0 )
|
||||
{
|
||||
setGeometry( &m_geometry );
|
||||
setMaterial( &m_material );
|
||||
}
|
||||
|
||||
void updateCurve( const QRectF& scaleRect, const QskPlotCurveData* data,
|
||||
const QColor& color, qreal lineWidth )
|
||||
{
|
||||
m_geometry.setDrawingMode( QSGGeometry::DrawLineStrip );
|
||||
|
||||
const float lineWidthF = lineWidth;
|
||||
if( lineWidthF != m_geometry.lineWidth() )
|
||||
m_geometry.setLineWidth( lineWidthF );
|
||||
|
||||
const QskVertex::Color c( color );
|
||||
|
||||
int from = 0;
|
||||
int to = data->count() - 1;
|
||||
|
||||
auto point1 = data->pointAt( from );
|
||||
auto point2 = data->pointAt( to );
|
||||
|
||||
if ( data->hints() & QskPlotCurveData::MonotonicX )
|
||||
{
|
||||
const qreal x1 = scaleRect.left();
|
||||
const qreal x2 = scaleRect.right();
|
||||
|
||||
if ( x1 > point2.x() || x2 < point1.x() )
|
||||
{
|
||||
QskSGNode::resetGeometry( this );
|
||||
return;
|
||||
}
|
||||
|
||||
const int index1 = data->upperIndex( Qt::Horizontal, x1 );
|
||||
if ( index1 > 0 )
|
||||
{
|
||||
from = index1 - 1;
|
||||
point1 = data->interpolatedPoint( Qt::Horizontal, x1 );
|
||||
}
|
||||
|
||||
const int index2 = data->upperIndex( Qt::Horizontal, x2 );
|
||||
if ( index2 > 0 )
|
||||
{
|
||||
to = index2;
|
||||
point2 = data->interpolatedPoint( Qt::Horizontal, x2 );
|
||||
}
|
||||
}
|
||||
else if ( data->hints() & QskPlotCurveData::MonotonicY )
|
||||
{
|
||||
const qreal y1 = scaleRect.top();
|
||||
const qreal y2 = scaleRect.bottom();
|
||||
|
||||
if ( y1 > point2.y() || y2 < point1.y() )
|
||||
{
|
||||
QskSGNode::resetGeometry( this );
|
||||
return;
|
||||
}
|
||||
|
||||
const int index1 = data->upperIndex( Qt::Vertical, y1 );
|
||||
if ( index1 > 0 )
|
||||
{
|
||||
from = index1 - 1;
|
||||
point1 = data->interpolatedPoint( Qt::Vertical, y1 );
|
||||
}
|
||||
|
||||
const int index2 = data->upperIndex( Qt::Vertical, y2 );
|
||||
if ( index2 > 0 )
|
||||
{
|
||||
to = index2;
|
||||
point2 = data->interpolatedPoint( Qt::Vertical, y2 );
|
||||
}
|
||||
}
|
||||
|
||||
m_geometry.allocate( to - from + 1 );
|
||||
|
||||
auto p = m_geometry.vertexDataAsColoredPoint2D();
|
||||
|
||||
p++->set( point1.x(), point1.y(), c.r, c.g, c.b, c.a );
|
||||
|
||||
for ( int i = from + 1; i < to; i++ )
|
||||
{
|
||||
const auto point = data->pointAt( i );
|
||||
p++->set( point.x(), point.y(), c.r, c.g, c.b, c.a );
|
||||
}
|
||||
|
||||
p++->set( point2.x(), point2.y(), c.r, c.g, c.b, c.a );
|
||||
|
||||
markDirty( QSGNode::DirtyGeometry );
|
||||
}
|
||||
|
||||
private:
|
||||
QSGGeometry m_geometry;
|
||||
QSGVertexColorMaterial m_material;
|
||||
};
|
||||
}
|
||||
|
||||
QskPlotCurveSkinlet::QskPlotCurveSkinlet( QskSkin* skin )
|
||||
: Inherited( skin )
|
||||
{
|
||||
setNodeRoles( { Polygon } );
|
||||
}
|
||||
|
||||
QskPlotCurveSkinlet::~QskPlotCurveSkinlet()
|
||||
{
|
||||
}
|
||||
|
||||
QSGNode* QskPlotCurveSkinlet::updateSubNode(
|
||||
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
|
||||
{
|
||||
if ( nodeRole == Polygon )
|
||||
return updatePolygonNode( skinnable, node );
|
||||
|
||||
return Inherited::updateSubNode( skinnable, nodeRole, node );
|
||||
}
|
||||
|
||||
QSGNode* QskPlotCurveSkinlet::updatePolygonNode(
|
||||
const QskSkinnable* skinnable, QSGNode* node ) const
|
||||
{
|
||||
using Q = QskPlotCurve;
|
||||
|
||||
auto curve = static_cast< const QskPlotCurve* >( skinnable );
|
||||
|
||||
const auto curveData = curve->data();
|
||||
if ( curveData == nullptr || curveData->count() == 0 )
|
||||
return nullptr;
|
||||
|
||||
const auto color = curve->color( Q::Line );
|
||||
if ( !color.isValid() || color.alpha() == 0 )
|
||||
return nullptr;
|
||||
|
||||
const auto lineWidth = curve->metric( Q::Line | QskAspect::Size );
|
||||
if ( lineWidth <= 0.0 )
|
||||
return nullptr;
|
||||
|
||||
auto curveNode = QskSGNode::ensureNode< CurveNode >( node );
|
||||
curveNode->updateCurve( curve->scaleRect(), curveData, color, lineWidth );
|
||||
|
||||
return curveNode;
|
||||
}
|
||||
|
||||
#include "moc_QskPlotCurveSkinlet.cpp"
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QskSkinlet.h>
|
||||
|
||||
class QskPlotCurveSkinlet : public QskSkinlet
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
using Inherited = QskSkinlet;
|
||||
|
||||
public:
|
||||
enum NodeRole { Polygon };
|
||||
|
||||
Q_INVOKABLE QskPlotCurveSkinlet( QskSkin* = nullptr );
|
||||
~QskPlotCurveSkinlet() override;
|
||||
|
||||
protected:
|
||||
QSGNode* updateSubNode( const QskSkinnable*,
|
||||
quint8 nodeRole, QSGNode* ) const override;
|
||||
|
||||
private:
|
||||
QSGNode* updatePolygonNode( const QskSkinnable*, QSGNode* ) const;
|
||||
};
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotGrid.h"
|
||||
#include "QskPlotView.h"
|
||||
#include "QskStippleMetrics.h"
|
||||
|
||||
#include <QskTickmarks.h>
|
||||
|
||||
QSK_SUBCONTROL( QskPlotGrid, MajorLine )
|
||||
QSK_SUBCONTROL( QskPlotGrid, MinorLine )
|
||||
|
||||
static inline QskAspect::Subcontrol qskSubcontrol( QskPlotGrid::Type gridType )
|
||||
{
|
||||
using Q = QskPlotGrid;
|
||||
return ( gridType == Q::MinorGrid ) ? Q::MinorLine : Q::MajorLine;
|
||||
}
|
||||
|
||||
class QskPlotGrid::PrivateData
|
||||
{
|
||||
};
|
||||
|
||||
QskPlotGrid::QskPlotGrid( QObject* object )
|
||||
: Inherited( object )
|
||||
, m_data( new PrivateData )
|
||||
{
|
||||
setCoordinateType( CanvasCoordinates );
|
||||
}
|
||||
|
||||
QskPlotGrid::~QskPlotGrid()
|
||||
{
|
||||
}
|
||||
|
||||
void QskPlotGrid::setPen( Type gridType, const QPen& pen )
|
||||
{
|
||||
using A = QskAspect;
|
||||
|
||||
const auto oldPen = this->pen( gridType );
|
||||
|
||||
const auto subControl = qskSubcontrol( gridType );
|
||||
|
||||
setColor( subControl, pen.color() );
|
||||
setMetric( subControl | A::Size, pen.widthF() );
|
||||
setSkinHint( subControl | A::Metric | A::Style,
|
||||
QVariant::fromValue( QskStippleMetrics( pen ) ) );
|
||||
|
||||
if ( oldPen != pen )
|
||||
{
|
||||
markDirty();
|
||||
|
||||
if ( gridType == MinorGrid )
|
||||
Q_EMIT minorPenChanged( pen );
|
||||
else
|
||||
Q_EMIT majorPenChanged( pen );
|
||||
}
|
||||
}
|
||||
|
||||
void QskPlotGrid::resetPen( Type gridType )
|
||||
{
|
||||
using A = QskAspect;
|
||||
|
||||
const auto oldPen = pen( gridType );
|
||||
const auto subControl = qskSubcontrol( gridType );
|
||||
|
||||
resetColor( subControl );
|
||||
resetMetric( subControl | A::Size );
|
||||
resetMetric( subControl | A::Style );
|
||||
|
||||
const auto newPen = pen( gridType );
|
||||
|
||||
if ( oldPen != newPen )
|
||||
{
|
||||
markDirty();
|
||||
|
||||
if ( gridType == MinorGrid )
|
||||
Q_EMIT minorPenChanged( newPen );
|
||||
else
|
||||
Q_EMIT majorPenChanged( newPen );
|
||||
}
|
||||
}
|
||||
|
||||
QPen QskPlotGrid::pen( Type gridType ) const
|
||||
{
|
||||
using A = QskAspect;
|
||||
|
||||
const auto subControl = qskSubcontrol( gridType );
|
||||
|
||||
const auto stippleMetrics = effectiveSkinHint(
|
||||
subControl | A::Metric | A::Style ).value< QskStippleMetrics >();
|
||||
|
||||
QPen pen( Qt::NoPen );
|
||||
|
||||
if ( stippleMetrics.isValid() )
|
||||
{
|
||||
if ( stippleMetrics.isSolid() )
|
||||
{
|
||||
pen.setStyle( Qt::SolidLine );
|
||||
}
|
||||
else
|
||||
{
|
||||
pen.setStyle( Qt::CustomDashLine );
|
||||
pen.setDashOffset( stippleMetrics.offset() );
|
||||
pen.setDashPattern( stippleMetrics.pattern() );
|
||||
}
|
||||
|
||||
pen.setColor( color( subControl ) );
|
||||
pen.setWidth( metric( subControl | A::Size ) );
|
||||
}
|
||||
|
||||
return pen;
|
||||
}
|
||||
|
||||
QVector< qreal > QskPlotGrid::lines( Type gridType, Qt::Orientation orientation ) const
|
||||
{
|
||||
if ( auto view = this->view() )
|
||||
{
|
||||
const auto axis = ( orientation == Qt::Horizontal ) ? yAxis() : xAxis();
|
||||
|
||||
const auto& tickmarks = view->tickmarks( axis );
|
||||
|
||||
if ( gridType == MajorGrid )
|
||||
return tickmarks.majorTicks();
|
||||
else
|
||||
return tickmarks.mediumTicks() + tickmarks.minorTicks();
|
||||
}
|
||||
|
||||
return QVector< qreal >();
|
||||
}
|
||||
|
||||
bool QskPlotGrid::needsClipping() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#include "moc_QskPlotGrid.cpp"
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "QskPlotItem.h"
|
||||
|
||||
#include <qnamespace.h>
|
||||
#include <qvector.h>
|
||||
#include <qpen.h>
|
||||
|
||||
class QPen;
|
||||
|
||||
class QskPlotGrid : public QskPlotItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY( QPen minorPen READ minorPen
|
||||
WRITE setMinorPen RESET resetMinorPen NOTIFY minorPenChanged )
|
||||
|
||||
Q_PROPERTY( QPen majorPen READ majorPen
|
||||
WRITE setMajorPen RESET resetMajorPen NOTIFY majorPenChanged )
|
||||
|
||||
using Inherited = QskPlotItem;
|
||||
|
||||
public:
|
||||
QSK_SUBCONTROLS( MajorLine, MinorLine )
|
||||
|
||||
enum Type
|
||||
{
|
||||
MinorGrid,
|
||||
MajorGrid
|
||||
};
|
||||
Q_ENUM( Type )
|
||||
|
||||
QskPlotGrid( QObject* = nullptr );
|
||||
~QskPlotGrid() override;
|
||||
|
||||
void setPen( Type, const QPen& );
|
||||
void resetPen( Type );
|
||||
QPen pen( Type ) const;
|
||||
|
||||
void setMajorPen( const QPen& );
|
||||
void resetMajorPen();
|
||||
QPen majorPen() const;
|
||||
|
||||
void setMinorPen( const QPen& );
|
||||
void resetMinorPen();
|
||||
QPen minorPen() const;
|
||||
|
||||
// positions
|
||||
virtual QVector< qreal > lines( Type, Qt::Orientation ) const;
|
||||
|
||||
bool needsClipping() const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void minorPenChanged( const QPen& );
|
||||
void majorPenChanged( const QPen& );
|
||||
|
||||
private:
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
||||
inline void QskPlotGrid::setMinorPen( const QPen& pen )
|
||||
{
|
||||
setPen( MinorGrid, pen );
|
||||
}
|
||||
|
||||
inline void QskPlotGrid::resetMinorPen()
|
||||
{
|
||||
resetPen( MinorGrid );
|
||||
}
|
||||
|
||||
inline QPen QskPlotGrid::minorPen() const
|
||||
{
|
||||
return pen( MinorGrid );
|
||||
}
|
||||
|
||||
inline void QskPlotGrid::setMajorPen( const QPen& pen )
|
||||
{
|
||||
setPen( MajorGrid, pen );
|
||||
}
|
||||
|
||||
inline void QskPlotGrid::resetMajorPen()
|
||||
{
|
||||
resetPen( MajorGrid );
|
||||
}
|
||||
|
||||
inline QPen QskPlotGrid::majorPen() const
|
||||
{
|
||||
return pen( MajorGrid );
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotGridSkinlet.h"
|
||||
#include "QskPlotGrid.h"
|
||||
#include "QskStippleMetrics.h"
|
||||
|
||||
#include <QskSGNode.h>
|
||||
#include <QskIntervalF.h>
|
||||
#include <QskLinesNode.h>
|
||||
|
||||
#include <qpen.h>
|
||||
|
||||
QskPlotGridSkinlet::QskPlotGridSkinlet( QskSkin* skin )
|
||||
: Inherited( skin )
|
||||
{
|
||||
setNodeRoles( { MinorGrid, MajorGrid } );
|
||||
}
|
||||
|
||||
QskPlotGridSkinlet::~QskPlotGridSkinlet()
|
||||
{
|
||||
}
|
||||
|
||||
QSGNode* QskPlotGridSkinlet::updateSubNode(
|
||||
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
|
||||
{
|
||||
using Q = QskPlotGrid;
|
||||
|
||||
if ( nodeRole == MinorGrid )
|
||||
return updateGridNode( skinnable, Q::MinorLine, node );
|
||||
|
||||
if ( nodeRole == MajorGrid )
|
||||
return updateGridNode( skinnable, Q::MajorLine, node );
|
||||
|
||||
return Inherited::updateSubNode( skinnable, nodeRole, node );
|
||||
}
|
||||
|
||||
QSGNode* QskPlotGridSkinlet::updateGridNode( const QskSkinnable* skinnable,
|
||||
QskAspect::Subcontrol subControl, QSGNode* node ) const
|
||||
{
|
||||
using Q = QskPlotGrid;
|
||||
|
||||
const auto gridType =
|
||||
( subControl == Q::MinorLine ) ? Q::MinorGrid : Q::MajorGrid;
|
||||
|
||||
auto grid = static_cast< const QskPlotGrid* >( skinnable );
|
||||
|
||||
const auto r = grid->scaleRect();
|
||||
if ( r.isEmpty() )
|
||||
return nullptr;
|
||||
|
||||
const auto stipple = grid->stippleMetricsHint( subControl );
|
||||
if ( !stipple.isValid() )
|
||||
return nullptr;
|
||||
|
||||
const auto lineColor = grid->color( subControl );
|
||||
if ( !lineColor.isValid() || lineColor.alpha() == 0 )
|
||||
return nullptr;
|
||||
|
||||
const auto lineWidth = grid->metric( subControl | QskAspect::Size );
|
||||
if ( lineWidth <= 0 )
|
||||
return nullptr;
|
||||
|
||||
const auto xValues = grid->lines( gridType, Qt::Vertical );
|
||||
const auto yValues = grid->lines( gridType, Qt::Horizontal );
|
||||
|
||||
if ( xValues.isEmpty() && yValues.isEmpty() )
|
||||
return nullptr;
|
||||
|
||||
auto gridNode = QskSGNode::ensureNode< QskLinesNode >( node );
|
||||
gridNode->setPixelAlignment( Qt::Horizontal | Qt::Vertical );
|
||||
|
||||
gridNode->updateGrid( lineColor, lineWidth,
|
||||
stipple, grid->transformation(), r, xValues, yValues );
|
||||
|
||||
return gridNode;
|
||||
}
|
||||
|
||||
#include "moc_QskPlotGridSkinlet.cpp"
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QskSkinlet.h>
|
||||
|
||||
class QskPlotGridSkinlet : public QskSkinlet
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
using Inherited = QskSkinlet;
|
||||
|
||||
public:
|
||||
enum NodeRole { MinorGrid, MajorGrid };
|
||||
|
||||
Q_INVOKABLE QskPlotGridSkinlet( QskSkin* = nullptr );
|
||||
~QskPlotGridSkinlet() override;
|
||||
|
||||
protected:
|
||||
QSGNode* updateSubNode( const QskSkinnable*,
|
||||
quint8 nodeRole, QSGNode* ) const override;
|
||||
|
||||
private:
|
||||
QSGNode* updateGridNode(
|
||||
const QskSkinnable*, QskAspect::Subcontrol, QSGNode* ) const;
|
||||
};
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotItem.h"
|
||||
#include "QskPlotView.h"
|
||||
|
||||
#include <QskSkinlet.h>
|
||||
#include <QskIntervalF.h>
|
||||
#include <qpointer.h>
|
||||
|
||||
class QskPlotItem::PrivateData
|
||||
{
|
||||
public:
|
||||
QskPlot::Axis xAxis = QskPlot::XBottom;
|
||||
QskPlot::Axis yAxis = QskPlot::YLeft;
|
||||
qreal z = 0.0;
|
||||
|
||||
QPointer< QskPlotView > view;
|
||||
|
||||
CoordinateType coordinateType = PlotCoordinates;
|
||||
bool dirty = true;
|
||||
};
|
||||
|
||||
QskPlotItem::QskPlotItem( QObject* parent )
|
||||
: QObject( parent )
|
||||
, m_data( new PrivateData )
|
||||
{
|
||||
if ( auto view = qobject_cast< QskPlotView* >( parent ) )
|
||||
attach( view );
|
||||
}
|
||||
|
||||
QskPlotItem::~QskPlotItem()
|
||||
{
|
||||
attach( nullptr );
|
||||
}
|
||||
|
||||
void QskPlotItem::attach( QskPlotView* view )
|
||||
{
|
||||
if ( view == m_data->view )
|
||||
return;
|
||||
|
||||
if ( m_data->view )
|
||||
m_data->view->detachItem( this );
|
||||
|
||||
m_data->view = view;
|
||||
|
||||
if ( m_data->view )
|
||||
m_data->view->attachItem( this );
|
||||
}
|
||||
|
||||
void QskPlotItem::setAxes( QskPlot::Axis xAxis, QskPlot::Axis yAxis )
|
||||
{
|
||||
setXAxis( xAxis );
|
||||
setYAxis( yAxis );
|
||||
}
|
||||
|
||||
QskPlot::Axis QskPlotItem::xAxis() const
|
||||
{
|
||||
return m_data->xAxis;
|
||||
}
|
||||
|
||||
void QskPlotItem::setXAxis( QskPlot::Axis axis )
|
||||
{
|
||||
if ( m_data->xAxis != axis )
|
||||
{
|
||||
m_data->xAxis = axis;
|
||||
Q_EMIT axisChanged();
|
||||
|
||||
markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
QskPlot::Axis QskPlotItem::yAxis() const
|
||||
{
|
||||
return m_data->yAxis;
|
||||
}
|
||||
|
||||
void QskPlotItem::setYAxis( QskPlot::Axis axis )
|
||||
{
|
||||
if ( m_data->yAxis != axis )
|
||||
{
|
||||
m_data->yAxis = axis;
|
||||
Q_EMIT axisChanged();
|
||||
|
||||
markDirty();
|
||||
}
|
||||
}
|
||||
|
||||
qreal QskPlotItem::z() const
|
||||
{
|
||||
return m_data->z;
|
||||
}
|
||||
|
||||
bool QskPlotItem::isDirty() const
|
||||
{
|
||||
return m_data->dirty;
|
||||
}
|
||||
|
||||
void QskPlotItem::setZ( qreal z )
|
||||
{
|
||||
if ( m_data->z != z )
|
||||
{
|
||||
m_data->z = z;
|
||||
Q_EMIT zChanged( z );
|
||||
|
||||
if ( auto view = m_data->view )
|
||||
{
|
||||
view->detachItem( this );
|
||||
view->attachItem( this );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QskPlotItem::markDirty()
|
||||
{
|
||||
if ( !m_data->dirty )
|
||||
{
|
||||
m_data->dirty = true;
|
||||
updatePlot();
|
||||
}
|
||||
}
|
||||
|
||||
void QskPlotItem::resetDirty()
|
||||
{
|
||||
m_data->dirty = false;
|
||||
}
|
||||
|
||||
void QskPlotItem::updatePlot()
|
||||
{
|
||||
if ( m_data->view )
|
||||
m_data->view->update();
|
||||
}
|
||||
|
||||
void QskPlotItem::setCoordinateType( CoordinateType type )
|
||||
{
|
||||
if ( m_data->coordinateType != type )
|
||||
{
|
||||
m_data->coordinateType = type;
|
||||
m_data->dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
QskPlotItem::CoordinateType QskPlotItem::coordinateType() const
|
||||
{
|
||||
return m_data->coordinateType;
|
||||
}
|
||||
|
||||
QTransform QskPlotItem::transformation() const
|
||||
{
|
||||
if ( m_data->view )
|
||||
return m_data->view->transformation( m_data->xAxis, m_data->yAxis );
|
||||
|
||||
return QTransform();
|
||||
}
|
||||
|
||||
const QskPlotView* QskPlotItem::view() const
|
||||
{
|
||||
return m_data->view;
|
||||
}
|
||||
|
||||
QQuickItem* QskPlotItem::owningItem() const
|
||||
{
|
||||
return m_data->view;
|
||||
}
|
||||
|
||||
void QskPlotItem::updateNode( QSGNode* node )
|
||||
{
|
||||
if ( auto skinlet = effectiveSkinlet() )
|
||||
skinlet->updateNode( this, node );
|
||||
|
||||
resetDirty();
|
||||
}
|
||||
|
||||
bool QskPlotItem::needsClipping() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void QskPlotItem::transformationChanged( ChangeFlags flags )
|
||||
{
|
||||
if ( flags == CanvasGeometryChanged && coordinateType() == PlotCoordinates )
|
||||
return;
|
||||
|
||||
markDirty();
|
||||
}
|
||||
|
||||
QRectF QskPlotItem::scaleRect() const
|
||||
{
|
||||
if ( auto view = m_data->view )
|
||||
{
|
||||
return QskIntervalF::toRect(
|
||||
view->boundaries( m_data->xAxis ),
|
||||
view->boundaries( m_data->yAxis ) );
|
||||
}
|
||||
|
||||
return QRectF();
|
||||
}
|
||||
|
||||
#include "moc_QskPlotItem.cpp"
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "QskPlotNamespace.h"
|
||||
|
||||
#include <QskSkinnable.h>
|
||||
#include <qobject.h>
|
||||
|
||||
class QskPlotView;
|
||||
class QskIntervalF;
|
||||
|
||||
class QskPlotItem : public QObject, public QskSkinnable
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY( QskPlot::Axis xAxis READ xAxis WRITE setXAxis NOTIFY axisChanged )
|
||||
Q_PROPERTY( QskPlot::Axis yAxis READ yAxis WRITE setYAxis NOTIFY axisChanged )
|
||||
Q_PROPERTY( qreal z READ z WRITE setZ NOTIFY zChanged )
|
||||
|
||||
public:
|
||||
/*
|
||||
The item can decide if it wants to use plot ( scales ) coordinates or
|
||||
canvas ( QQuickItem ) coordinates for the vertexes of its scenegraph nodes.
|
||||
|
||||
PlotItems, that represent some sort of data ( f.e curves ) usually
|
||||
prefer to use plot coordinates, while decorations ( f.e legend ) are
|
||||
often aligned to the plot canvas geometry.
|
||||
|
||||
Items in plot coordinates often do not need to be updated when
|
||||
the geometry of the plot canvas or the scales have been changed.
|
||||
To opt out from these updates the plot item needs to overload
|
||||
the transformationChange() hook.
|
||||
*/
|
||||
|
||||
enum CoordinateType
|
||||
{
|
||||
CanvasCoordinates,
|
||||
PlotCoordinates
|
||||
};
|
||||
|
||||
Q_ENUM( CoordinateType );
|
||||
|
||||
enum ChangeFlag
|
||||
{
|
||||
XBoundariesChanged = 1 << 0,
|
||||
XTickmarksChanged = 1 << 1,
|
||||
|
||||
YBoundariesChanged = 1 << 1,
|
||||
YTickmarksChanged = 1 << 2,
|
||||
|
||||
CanvasGeometryChanged = 1 << 2
|
||||
};
|
||||
Q_ENUM( ChangeFlag );
|
||||
|
||||
Q_DECLARE_FLAGS( ChangeFlags, ChangeFlag );
|
||||
|
||||
QskPlotItem( QObject* = nullptr );
|
||||
~QskPlotItem() override;
|
||||
|
||||
void attach( QskPlotView* );
|
||||
void detach();
|
||||
|
||||
void setXAxis( QskPlot::Axis );
|
||||
QskPlot::Axis xAxis() const;
|
||||
|
||||
void setYAxis( QskPlot::Axis );
|
||||
QskPlot::Axis yAxis() const;
|
||||
|
||||
void setAxes( QskPlot::Axis xAxis, QskPlot::Axis yAxis );
|
||||
|
||||
void setZ( qreal );
|
||||
qreal z() const;
|
||||
|
||||
void setCoordinateType( CoordinateType );
|
||||
CoordinateType coordinateType() const;
|
||||
|
||||
/*
|
||||
Indicates if the item depends on clipping
|
||||
|
||||
Batching of node updates is one of the main performance features
|
||||
of the scene graph - however clipping breaks batching.
|
||||
A plot that has no plot item, that needs clipping, can decide
|
||||
to skip inserting a clip node.
|
||||
*/
|
||||
virtual bool needsClipping() const;
|
||||
|
||||
QTransform transformation() const;
|
||||
QskIntervalF boundaries( Qt::Orientation ) const;
|
||||
|
||||
QRectF scaleRect() const;
|
||||
|
||||
const QskPlotView* view() const;
|
||||
|
||||
void markDirty();
|
||||
void resetDirty();
|
||||
bool isDirty() const;
|
||||
|
||||
virtual void updateNode( QSGNode* );
|
||||
virtual void transformationChanged( ChangeFlags );
|
||||
|
||||
Q_SIGNALS:
|
||||
void axisChanged();
|
||||
void zChanged( qreal );
|
||||
|
||||
private:
|
||||
QQuickItem* owningItem() const override final;
|
||||
void updatePlot();
|
||||
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( QskPlotItem::ChangeFlags )
|
||||
|
||||
inline void QskPlotItem::detach()
|
||||
{
|
||||
attach( nullptr );
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qmetaobject.h>
|
||||
|
||||
namespace QskPlot
|
||||
{
|
||||
Q_NAMESPACE
|
||||
|
||||
// for the moment only 2 axes
|
||||
enum Axis
|
||||
{
|
||||
XBottom = 0,
|
||||
YLeft = 1
|
||||
};
|
||||
Q_ENUM_NS( Axis )
|
||||
}
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotView.h"
|
||||
#include "QskPlotItem.h"
|
||||
|
||||
#include <QskTickmarks.h>
|
||||
#include <QskGraduation.h>
|
||||
#include <QskSkinlet.h>
|
||||
|
||||
#include <QskQuick.h>
|
||||
#include <QskBoxClipNode.h>
|
||||
#include <QskBoxBorderMetrics.h>
|
||||
|
||||
#include <qsgnode.h>
|
||||
#include <vector>
|
||||
|
||||
QSK_SUBCONTROL( QskPlotView, Panel )
|
||||
QSK_SUBCONTROL( QskPlotView, AxisScale )
|
||||
QSK_SUBCONTROL( QskPlotView, Canvas )
|
||||
|
||||
enum { AxisCount = 2 };
|
||||
|
||||
static inline bool qskIsXAxis( int axis )
|
||||
{
|
||||
return axis == QskPlot::XBottom;
|
||||
}
|
||||
|
||||
static inline QTransform qskScaleTransform( Qt::Orientation orientation,
|
||||
qreal s1, qreal s2, qreal p1, qreal p2 )
|
||||
{
|
||||
const auto ratio = ( p2 - p1 ) / ( s2 - s1 );
|
||||
|
||||
if ( orientation == Qt::Horizontal )
|
||||
{
|
||||
auto transform = QTransform::fromTranslate( -s1, 0.0 );
|
||||
transform *= QTransform::fromScale( ratio, 1.0 );
|
||||
transform *= QTransform::fromTranslate( p1, 0.0 );
|
||||
|
||||
return transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto transform = QTransform::fromTranslate( 0.0, -s1 );
|
||||
transform *= QTransform::fromScale( 1.0, -ratio );
|
||||
transform *= QTransform::fromTranslate( 0.0, p2 );
|
||||
|
||||
return transform;
|
||||
}
|
||||
}
|
||||
|
||||
class QskPlotView::PrivateData
|
||||
{
|
||||
public:
|
||||
|
||||
struct
|
||||
{
|
||||
QskIntervalF boundaries;
|
||||
QskTickmarks tickmarks;
|
||||
|
||||
bool boundariesDirty = true;
|
||||
bool ticksDirty = true;
|
||||
|
||||
} scales[ AxisCount ];
|
||||
|
||||
class ItemData
|
||||
{
|
||||
public:
|
||||
QskPlotItem* plotItem = nullptr;
|
||||
QSGTransformNode* node = nullptr;
|
||||
};
|
||||
|
||||
inline bool needsClipping() const
|
||||
{
|
||||
for ( const auto& data : itemData )
|
||||
{
|
||||
if ( data.plotItem->needsClipping() )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector< ItemData > itemData; // a flat map
|
||||
QVector< QSGNode* > orphanedNodes;
|
||||
|
||||
QRectF canvasRect;
|
||||
};
|
||||
|
||||
QskPlotView::QskPlotView( QQuickItem* parentItem )
|
||||
: Inherited( parentItem )
|
||||
, m_data( new PrivateData )
|
||||
{
|
||||
setBoundaries( QskPlot::XBottom, 0.0, 100.0 );
|
||||
setBoundaries( QskPlot::YLeft, 0.0, 100.0 );
|
||||
}
|
||||
|
||||
QskPlotView::~QskPlotView()
|
||||
{
|
||||
}
|
||||
|
||||
void QskPlotView::setBoundaries( QskPlot::Axis axis, qreal from, qreal to )
|
||||
{
|
||||
setBoundaries( axis, QskIntervalF( from, to ) );
|
||||
}
|
||||
|
||||
void QskPlotView::setBoundaries( QskPlot::Axis axis, const QskIntervalF& boundaries )
|
||||
{
|
||||
auto& sd = m_data->scales[ axis ];
|
||||
|
||||
if ( boundaries == sd.boundaries )
|
||||
return;
|
||||
|
||||
sd.boundaries = boundaries;
|
||||
sd.boundariesDirty = true;
|
||||
|
||||
const auto oldTickmarks = sd.tickmarks;
|
||||
|
||||
sd.tickmarks = QskGraduation::divideInterval(
|
||||
boundaries.lowerBound(), boundaries.upperBound(), 5, 5 );
|
||||
|
||||
if ( oldTickmarks != sd.tickmarks )
|
||||
sd.ticksDirty = true;
|
||||
|
||||
polish();
|
||||
update();
|
||||
}
|
||||
|
||||
QskIntervalF QskPlotView::boundaries( QskPlot::Axis axis ) const
|
||||
{
|
||||
return m_data->scales[ axis ].boundaries;
|
||||
}
|
||||
|
||||
QskTickmarks QskPlotView::tickmarks( QskPlot::Axis axis ) const
|
||||
{
|
||||
return m_data->scales[ axis ].tickmarks;
|
||||
}
|
||||
|
||||
QTransform QskPlotView::transformation( QskPlot::Axis xAxis, QskPlot::Axis yAxis ) const
|
||||
{
|
||||
const auto r = canvasRect();
|
||||
|
||||
const auto scales = m_data->scales;
|
||||
|
||||
const qreal x1 = scales[xAxis].boundaries.lowerBound();
|
||||
const qreal x2 = scales[xAxis].boundaries.upperBound();
|
||||
|
||||
const qreal y1 = scales[yAxis].boundaries.lowerBound();
|
||||
const qreal y2 = scales[yAxis].boundaries.upperBound();
|
||||
|
||||
auto transform = QTransform::fromTranslate( -x1, -y1 );
|
||||
|
||||
transform *= QTransform::fromScale(
|
||||
r.width() / ( x2 - x1 ), r.height() / ( y1 - y2 ) );
|
||||
|
||||
transform *= QTransform::fromTranslate( r.left(), r.bottom() );
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
QRectF QskPlotView::canvasRect() const
|
||||
{
|
||||
const auto r = subControlRect( Canvas );
|
||||
|
||||
const auto b = boxBorderMetricsHint( Canvas );
|
||||
return r.adjusted( b.left(), b.top(), -b.right(), -b.bottom() );
|
||||
}
|
||||
|
||||
QVariant QskPlotView::labelAt( QskPlot::Axis, qreal pos ) const
|
||||
{
|
||||
return QString::number( pos, 'g' );
|
||||
}
|
||||
|
||||
void QskPlotView::changeEvent( QEvent* event )
|
||||
{
|
||||
if ( event->type() == QEvent::StyleChange )
|
||||
{
|
||||
for ( auto& itemData : m_data->itemData )
|
||||
{
|
||||
auto plotItem = itemData.plotItem;
|
||||
|
||||
if ( plotItem->skinlet() == nullptr )
|
||||
plotItem->setSkinlet( nullptr ); // clearing the cached skinlet
|
||||
|
||||
plotItem->markDirty();
|
||||
}
|
||||
|
||||
polish();
|
||||
}
|
||||
}
|
||||
|
||||
void QskPlotView::geometryChange( const QRectF& newGeometry, const QRectF& oldGeometry )
|
||||
{
|
||||
Inherited::geometryChange( newGeometry, oldGeometry );
|
||||
polish();
|
||||
}
|
||||
|
||||
void QskPlotView::attachItem( QskPlotItem* plotItem )
|
||||
{
|
||||
const auto cmp = []( const qreal& z, const PrivateData::ItemData& data )
|
||||
{ return z < data.plotItem->z(); };
|
||||
|
||||
auto& itemData = m_data->itemData;
|
||||
|
||||
auto it = std::upper_bound( itemData.begin(), itemData.end(), plotItem->z(), cmp );
|
||||
itemData.insert( it, { plotItem, nullptr } );
|
||||
|
||||
plotItem->markDirty();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void QskPlotView::detachItem( QskPlotItem* plotItem )
|
||||
{
|
||||
auto& itemData = m_data->itemData;
|
||||
|
||||
for ( auto it = itemData.begin(); it != itemData.end(); ++it )
|
||||
{
|
||||
if ( it->plotItem == plotItem )
|
||||
{
|
||||
if ( it->node )
|
||||
m_data->orphanedNodes += it->node;
|
||||
|
||||
itemData.erase( it );
|
||||
update();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QskPlotView::updateResources()
|
||||
{
|
||||
using namespace QskPlot;
|
||||
using I = QskPlotItem;
|
||||
|
||||
/*
|
||||
updateResources is called from updatePolish.
|
||||
We should find a better name: TODO ...
|
||||
*/
|
||||
|
||||
I::ChangeFlags flags[ AxisCount ];
|
||||
|
||||
{
|
||||
bool canvasChanged = false;
|
||||
|
||||
{
|
||||
const auto canvasRect = this->canvasRect();
|
||||
if ( canvasRect != m_data->canvasRect )
|
||||
{
|
||||
m_data->canvasRect = canvasRect;
|
||||
canvasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
for ( int axis = 0; axis < AxisCount; axis++ )
|
||||
{
|
||||
I::ChangeFlags flag;
|
||||
|
||||
auto& scaleData = m_data->scales[ axis ];
|
||||
if ( canvasChanged )
|
||||
flag |= I::CanvasGeometryChanged;
|
||||
|
||||
if ( scaleData.boundariesDirty )
|
||||
flag |= qskIsXAxis( axis ) ? I::XBoundariesChanged : I::YBoundariesChanged;
|
||||
|
||||
if ( scaleData.ticksDirty )
|
||||
flag |= qskIsXAxis( axis ) ? I::XTickmarksChanged : I::YTickmarksChanged;
|
||||
|
||||
flags[axis] = flag;
|
||||
scaleData.boundariesDirty = scaleData.ticksDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( auto itemFlags = ( flags[0] | flags[1] ) )
|
||||
{
|
||||
for ( auto& itemData : m_data->itemData )
|
||||
itemData.plotItem->transformationChanged( itemFlags );
|
||||
}
|
||||
}
|
||||
|
||||
void QskPlotView::updateNode( QSGNode* node )
|
||||
{
|
||||
if ( !m_data->orphanedNodes.empty() )
|
||||
{
|
||||
for ( auto node : m_data->orphanedNodes )
|
||||
{
|
||||
if ( auto parentNode = node->parent() )
|
||||
parentNode->removeChildNode( node );
|
||||
|
||||
delete node;
|
||||
}
|
||||
|
||||
m_data->orphanedNodes.clear();
|
||||
}
|
||||
|
||||
// sorting items according to z TODO ...
|
||||
|
||||
auto oldItemsNode = node->lastChild();
|
||||
if ( oldItemsNode )
|
||||
node->removeChildNode( oldItemsNode );
|
||||
|
||||
/*
|
||||
the scene graph subtree might have been removed for situations
|
||||
like skin changes and the item node pointers are dangling pointers
|
||||
*/
|
||||
const bool danglingNodes = ( oldItemsNode == nullptr );
|
||||
|
||||
Inherited::updateNode( node );
|
||||
|
||||
auto itemsNode = oldItemsNode;
|
||||
|
||||
if ( m_data->needsClipping() )
|
||||
{
|
||||
if ( itemsNode == nullptr || itemsNode->type() != QSGNode::ClipNodeType )
|
||||
itemsNode = new QskBoxClipNode();
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( itemsNode == nullptr || itemsNode->type() == QSGNode::ClipNodeType )
|
||||
itemsNode = new QSGNode();
|
||||
}
|
||||
|
||||
if ( oldItemsNode && itemsNode != oldItemsNode )
|
||||
{
|
||||
oldItemsNode->reparentChildNodesTo( itemsNode );
|
||||
delete oldItemsNode;
|
||||
}
|
||||
|
||||
node->appendChildNode( itemsNode );
|
||||
|
||||
if ( itemsNode->type() == QSGNode::ClipNodeType )
|
||||
{
|
||||
/*
|
||||
updating the clip node:
|
||||
|
||||
By setting the clipNode above the geometry nodes of the plot items
|
||||
we only need on clip node for all plot items. However we exclude all
|
||||
nodes from scene graph batching. It might be better that each plot
|
||||
item has its own clip node. But we could share their clipping vertexes.
|
||||
TODO ...
|
||||
*/
|
||||
|
||||
itemsNode = QskSkinlet::updateBoxClipNode( this,
|
||||
itemsNode, subControlRect( Canvas ), Canvas );
|
||||
}
|
||||
|
||||
for ( auto& itemData : m_data->itemData )
|
||||
{
|
||||
if ( danglingNodes || itemData.node == nullptr )
|
||||
{
|
||||
itemData.node = new QSGTransformNode();
|
||||
itemsNode->appendChildNode( itemData.node );
|
||||
}
|
||||
|
||||
QMatrix4x4 matrix;
|
||||
|
||||
if ( itemData.plotItem->coordinateType() == QskPlotItem::PlotCoordinates )
|
||||
matrix = itemData.plotItem->transformation();
|
||||
|
||||
if ( matrix != itemData.node->matrix() )
|
||||
itemData.node->setMatrix( matrix );
|
||||
|
||||
if ( itemData.plotItem->isDirty() )
|
||||
itemData.plotItem->updateNode( itemData.node );
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_QskPlotView.cpp"
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "QskControl.h"
|
||||
#include "QskPlotNamespace.h"
|
||||
|
||||
class QskPlotItem;
|
||||
|
||||
class QskTickmarks;
|
||||
class QskIntervalF;
|
||||
class QTransform;
|
||||
|
||||
class QskPlotView : public QskControl
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QskControl;
|
||||
|
||||
public:
|
||||
QSK_SUBCONTROLS( Panel, AxisScale, Canvas )
|
||||
|
||||
QskPlotView( QQuickItem* parent = nullptr );
|
||||
~QskPlotView() override;
|
||||
|
||||
void setBoundaries( QskPlot::Axis, qreal, qreal );
|
||||
void setBoundaries( QskPlot::Axis, const QskIntervalF& );
|
||||
QskIntervalF boundaries( QskPlot::Axis axis ) const;
|
||||
|
||||
QskTickmarks tickmarks( QskPlot::Axis axis ) const;
|
||||
|
||||
// scales -> item coordinates
|
||||
QTransform transformation( QskPlot::Axis xAxis, QskPlot::Axis yAxis ) const;
|
||||
QRectF canvasRect() const;
|
||||
|
||||
virtual QVariant labelAt( QskPlot::Axis, qreal pos ) const;
|
||||
|
||||
protected:
|
||||
void geometryChange( const QRectF&, const QRectF& ) override;
|
||||
void updateNode( QSGNode* ) override;
|
||||
void updateResources() override;
|
||||
|
||||
void changeEvent( QEvent* ) override;
|
||||
|
||||
private:
|
||||
friend class QskPlotItem;
|
||||
|
||||
void attachItem( QskPlotItem* );
|
||||
void detachItem( QskPlotItem* );
|
||||
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskPlotViewSkinlet.h"
|
||||
#include "QskPlotView.h"
|
||||
|
||||
#include <QskScaleRenderer.h>
|
||||
#include <QskTickmarks.h>
|
||||
#include <QskTextColors.h>
|
||||
#include <QskFunctions.h>
|
||||
#include <QskSGNode.h>
|
||||
#include <QskBoxBorderMetrics.h>
|
||||
#include <QskMargins.h>
|
||||
#include <QskSkin.h>
|
||||
|
||||
#include <qfontmetrics.h>
|
||||
#include <qquickwindow.h>
|
||||
|
||||
static inline QskTextColors qskTextColors(
|
||||
const QskSkinnable* skinnable, QskAspect aspect )
|
||||
{
|
||||
using A = QskAspect;
|
||||
|
||||
QskSkinHintStatus status;
|
||||
|
||||
QskTextColors c;
|
||||
c.textColor = skinnable->color( aspect | A::TextColor, &status );
|
||||
|
||||
if ( status.aspect.subControl() != aspect.subControl() )
|
||||
{
|
||||
// using the same color as the one for the ticks
|
||||
c.textColor = skinnable->color( aspect );
|
||||
}
|
||||
|
||||
c.styleColor = skinnable->color( aspect | A::StyleColor );
|
||||
c.linkColor = skinnable->color( aspect | A::LinkColor );
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline QskAspect qskAxisAspect( QskPlot::Axis axis )
|
||||
{
|
||||
using Q = QskPlotView;
|
||||
using A = QskAspect;
|
||||
|
||||
return Q::AxisScale | ( ( axis == QskPlot::XBottom ) ? A::Bottom : A::Left );
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class ScaleRenderer : public QskScaleRenderer
|
||||
{
|
||||
using Inherited = QskScaleRenderer;
|
||||
|
||||
public:
|
||||
ScaleRenderer( const QskPlotView* view, QskPlot::Axis axis )
|
||||
: m_view( view )
|
||||
, m_axis( axis )
|
||||
{
|
||||
using Q = QskPlotView;
|
||||
using A = QskAspect;
|
||||
|
||||
setEdge( axis == QskPlot::XBottom ? Qt::BottomEdge : Qt::LeftEdge );
|
||||
|
||||
setBoundaries( view->boundaries( axis ) );
|
||||
setTickmarks( view->tickmarks( axis ) );
|
||||
|
||||
const auto aspect = Q::AxisScale
|
||||
| ( ( axis == QskPlot::XBottom ) ? A::Bottom : A::Left );
|
||||
|
||||
const auto flags = view->flagHint< QskScaleRenderer::Flags >(
|
||||
aspect | QskAspect::Style, QskScaleRenderer::Backbone );
|
||||
|
||||
setFlags( flags );
|
||||
|
||||
setTickColor( view->color( aspect ) );
|
||||
|
||||
const auto tickSize = view->strutSizeHint( aspect );
|
||||
setTickWidth( tickSize.width() );
|
||||
setTickLength( tickSize.height() );
|
||||
|
||||
setSpacing( view->spacingHint( aspect ) );
|
||||
|
||||
const auto fontRole = view->fontRoleHint( aspect );
|
||||
setFont( view->effectiveSkin()->font( fontRole ) );
|
||||
|
||||
setTextColors( qskTextColors( view, aspect ) );
|
||||
}
|
||||
|
||||
qreal labelOffset() const
|
||||
{
|
||||
qreal off = tickLength() + spacing();
|
||||
if ( flags() & CenteredTickmarks )
|
||||
off -= 0.5 * tickLength();
|
||||
|
||||
return off;
|
||||
}
|
||||
|
||||
QVariant labelAt( qreal pos ) const override
|
||||
{
|
||||
return m_view->labelAt( m_axis, pos );
|
||||
}
|
||||
|
||||
private:
|
||||
const QskPlotView* m_view;
|
||||
const QskPlot::Axis m_axis;
|
||||
};
|
||||
}
|
||||
|
||||
QskPlotViewSkinlet::QskPlotViewSkinlet( QskSkin* skin )
|
||||
: Inherited( skin )
|
||||
{
|
||||
setNodeRoles( { PanelRole, CanvasRole, AxisRole } );
|
||||
}
|
||||
|
||||
QskPlotViewSkinlet::~QskPlotViewSkinlet() = default;
|
||||
|
||||
QRectF QskPlotViewSkinlet::subControlRect( const QskSkinnable* skinnable,
|
||||
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
|
||||
{
|
||||
using Q = QskPlotView;
|
||||
|
||||
if ( subControl == Q::Panel )
|
||||
return contentsRect;
|
||||
|
||||
if ( subControl == Q::Canvas )
|
||||
{
|
||||
const auto view = static_cast< const QskPlotView* >( skinnable );
|
||||
|
||||
const auto rectX = sampleRect(
|
||||
skinnable, contentsRect, Q::AxisScale, QskPlot::XBottom );
|
||||
|
||||
const auto rectY = sampleRect(
|
||||
skinnable, contentsRect, Q::AxisScale, QskPlot::YLeft );
|
||||
|
||||
auto rect = view->subControlContentsRect( Q::Panel );
|
||||
|
||||
rect.setBottom( rectX.top() );
|
||||
rect.setLeft( rectY.right() );
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
return Inherited::subControlRect( skinnable, contentsRect, subControl );
|
||||
}
|
||||
|
||||
QSGNode* QskPlotViewSkinlet::updateSubNode(
|
||||
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
|
||||
{
|
||||
using Q = QskPlotView;
|
||||
|
||||
switch ( nodeRole )
|
||||
{
|
||||
case PanelRole:
|
||||
return updateBoxNode( skinnable, node, Q::Panel );
|
||||
|
||||
case CanvasRole:
|
||||
return updateBoxNode( skinnable, node, Q::Canvas );
|
||||
|
||||
case AxisRole:
|
||||
return updateSeriesNode( skinnable, Q::AxisScale, node );
|
||||
}
|
||||
|
||||
return Inherited::updateSubNode( skinnable, nodeRole, node );
|
||||
}
|
||||
|
||||
int QskPlotViewSkinlet::sampleCount(
|
||||
const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) const
|
||||
{
|
||||
using Q = QskPlotView;
|
||||
|
||||
if ( subControl == Q::AxisScale )
|
||||
return 2; // for the moment only 2 axes
|
||||
|
||||
return Inherited::sampleCount( skinnable, subControl );
|
||||
}
|
||||
|
||||
QRectF QskPlotViewSkinlet::sampleRect( const QskSkinnable* skinnable,
|
||||
const QRectF& contentsRect, QskAspect::Subcontrol subControl, int index ) const
|
||||
{
|
||||
if ( subControl == QskPlotView::AxisScale )
|
||||
{
|
||||
const auto axis = static_cast< QskPlot::Axis >( index );
|
||||
return axisRect( skinnable, axis );
|
||||
}
|
||||
|
||||
return Inherited::sampleRect( skinnable, contentsRect, subControl, index );
|
||||
}
|
||||
|
||||
QSGNode* QskPlotViewSkinlet::updateSampleNode( const QskSkinnable* skinnable,
|
||||
QskAspect::Subcontrol subControl, int index, QSGNode* node ) const
|
||||
{
|
||||
using Q = QskPlotView;
|
||||
|
||||
if ( subControl == Q::AxisScale )
|
||||
{
|
||||
const auto axis = static_cast< QskPlot::Axis >( index );
|
||||
return updateAxisNode( skinnable, axis, node );
|
||||
}
|
||||
|
||||
return Inherited::updateSampleNode( skinnable, subControl, index, node );
|
||||
}
|
||||
|
||||
QSizeF QskPlotViewSkinlet::sizeHint( const QskSkinnable* skinnable,
|
||||
Qt::SizeHint which, const QSizeF& constraint ) const
|
||||
{
|
||||
return Inherited::sizeHint( skinnable, which, constraint );
|
||||
}
|
||||
|
||||
QRectF QskPlotViewSkinlet::axisRect(
|
||||
const QskSkinnable* skinnable, QskPlot::Axis axis ) const
|
||||
{
|
||||
using Q = QskPlotView;
|
||||
using A = QskAspect;
|
||||
|
||||
const auto view = static_cast< const QskPlotView* >( skinnable );
|
||||
auto rect = view->subControlContentsRect( Q::Panel );
|
||||
|
||||
const QskMargins paddingLeft = view->paddingHint( Q::AxisScale | A::Left );
|
||||
const QskMargins paddingBottom = view->paddingHint( Q::AxisScale | A::Bottom );
|
||||
|
||||
qreal x0, y0;
|
||||
{
|
||||
const ScaleRenderer renderer( view, QskPlot::XBottom );
|
||||
const auto sz = renderer.boundingLabelSize();
|
||||
|
||||
y0 = rect.bottom() - sz.height()
|
||||
- renderer.labelOffset() - paddingBottom.height();
|
||||
}
|
||||
{
|
||||
const ScaleRenderer renderer( view, QskPlot::YLeft );
|
||||
const auto sz = renderer.boundingLabelSize();
|
||||
|
||||
x0 = rect.left() + sz.width()
|
||||
+ renderer.labelOffset() + paddingLeft.width();
|
||||
}
|
||||
|
||||
const auto canvasBorder = view->boxBorderMetricsHint( Q::Canvas );
|
||||
|
||||
if ( axis == QskPlot::XBottom )
|
||||
{
|
||||
rect.setTop( y0 );
|
||||
rect.setLeft( x0 + canvasBorder.left() );
|
||||
rect.setRight( rect.right() - canvasBorder.right() );
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.setRight( x0 );
|
||||
rect.setTop( rect.top() + canvasBorder.top() );
|
||||
rect.setBottom( y0 - canvasBorder.bottom() );
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
QSGNode* QskPlotViewSkinlet::updateAxisNode(
|
||||
const QskSkinnable* skinnable, QskPlot::Axis axis, QSGNode* node ) const
|
||||
{
|
||||
using Q = QskPlotView;
|
||||
|
||||
const auto view = static_cast< const QskPlotView* >( skinnable );
|
||||
|
||||
const auto axisRect = sampleRect( view, view->contentsRect(), Q::AxisScale, axis );
|
||||
const auto padding = view->paddingHint( qskAxisAspect( axis ) );
|
||||
|
||||
ScaleRenderer renderer( view, axis );
|
||||
if ( axis == QskPlot::XBottom )
|
||||
{
|
||||
renderer.setPosition( axisRect.top() + padding.top() );
|
||||
renderer.setRange( axisRect.left(), axisRect.right() );
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.setPosition( axisRect.right() - padding.right() );
|
||||
renderer.setRange( axisRect.top(), axisRect.bottom() );
|
||||
}
|
||||
|
||||
return renderer.updateNode( view, node );
|
||||
}
|
||||
|
||||
#include "moc_QskPlotViewSkinlet.cpp"
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QskSkinlet.h>
|
||||
#include <QskPlotNamespace.h>
|
||||
|
||||
class QskPlotViewSkinlet : public QskSkinlet
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
using Inherited = QskSkinlet;
|
||||
|
||||
public:
|
||||
enum NodeRole
|
||||
{
|
||||
PanelRole,
|
||||
CanvasRole,
|
||||
AxisRole
|
||||
};
|
||||
|
||||
Q_INVOKABLE QskPlotViewSkinlet( QskSkin* = nullptr );
|
||||
~QskPlotViewSkinlet() override;
|
||||
|
||||
QRectF subControlRect( const QskSkinnable*,
|
||||
const QRectF&, QskAspect::Subcontrol ) const override;
|
||||
|
||||
QSizeF sizeHint( const QskSkinnable*,
|
||||
Qt::SizeHint, const QSizeF& ) const override;
|
||||
|
||||
int sampleCount( const QskSkinnable*,
|
||||
QskAspect::Subcontrol ) const override final;
|
||||
|
||||
QRectF sampleRect( const QskSkinnable*,
|
||||
const QRectF&, QskAspect::Subcontrol, int index ) const override;
|
||||
|
||||
protected:
|
||||
QSGNode* updateSubNode( const QskSkinnable*,
|
||||
quint8 nodeRole, QSGNode* ) const override;
|
||||
|
||||
QSGNode* updateSampleNode( const QskSkinnable*,
|
||||
QskAspect::Subcontrol, int index, QSGNode* ) const override;
|
||||
|
||||
private:
|
||||
QRectF axisRect( const QskSkinnable*, QskPlot::Axis ) const;
|
||||
|
||||
QSGNode* updateAxisNode( const QskSkinnable*,
|
||||
QskPlot::Axis, QSGNode* ) const;
|
||||
};
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "Plot.h"
|
||||
|
||||
#include <QskWindow.h>
|
||||
#include <QskMainView.h>
|
||||
#include <QskLinearBox.h>
|
||||
#include <QskPushButton.h>
|
||||
#include <QskObjectCounter.h>
|
||||
|
||||
#include <SkinnyShortcut.h>
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
namespace
|
||||
{
|
||||
class TestPlot : public Plot
|
||||
{
|
||||
public:
|
||||
TestPlot( QQuickItem* parentItem = nullptr )
|
||||
: Plot( parentItem )
|
||||
{
|
||||
setMargins( 5 );
|
||||
|
||||
QVector< Sample > samples;
|
||||
|
||||
samples += { -50, 30, 60, 80 };
|
||||
samples += { -45, 35, 55, 85 };
|
||||
samples += { -40, 32, 60, 77 };
|
||||
samples += { -35, 38, 75, 66 };
|
||||
samples += { -30, 40, 65, 75 };
|
||||
samples += { -25, 48, 58, 82 };
|
||||
samples += { -20, 27, 62, 85 };
|
||||
samples += { -15, 32, 22, 70 };
|
||||
samples += { -10, 30, 24, 77 };
|
||||
samples += { -5, 20, 33, 70 };
|
||||
samples += { 0, 40, 60, 80 };
|
||||
|
||||
setSamples( samples );
|
||||
}
|
||||
};
|
||||
|
||||
class Header : public QskLinearBox
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Header( QQuickItem* parent = nullptr )
|
||||
: QskLinearBox( Qt::Horizontal, parent )
|
||||
{
|
||||
initSizePolicy( QskSizePolicy::Ignored, QskSizePolicy::Fixed );
|
||||
|
||||
setPanel( true );
|
||||
setPaddingHint( QskBox::Panel, 5 );
|
||||
|
||||
addStretch( 1 );
|
||||
|
||||
auto buttonLeft = new QskPushButton( "<", this );
|
||||
buttonLeft->setAutoRepeat( true );
|
||||
connect( buttonLeft, &QskPushButton::clicked,
|
||||
this, [this] { Q_EMIT shiftClicked( +1 ); } );
|
||||
|
||||
auto buttonRight = new QskPushButton( ">", this );
|
||||
buttonRight->setAutoRepeat( true );
|
||||
connect( buttonRight, &QskPushButton::clicked,
|
||||
this, [this] { Q_EMIT shiftClicked( -1 ); } );
|
||||
|
||||
auto buttonReset = new QskPushButton( "Reset", this );
|
||||
connect( buttonReset, &QskPushButton::clicked,
|
||||
this, &Header::resetClicked );
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void shiftClicked( int steps );
|
||||
void resetClicked();
|
||||
};
|
||||
|
||||
class MainView : public QskMainView
|
||||
{
|
||||
public:
|
||||
MainView( QQuickItem* parent = nullptr )
|
||||
: QskMainView( parent )
|
||||
{
|
||||
auto header = new Header( this );
|
||||
auto plot = new TestPlot();
|
||||
|
||||
connect( header, &Header::resetClicked,
|
||||
plot, &Plot::resetAxes );
|
||||
|
||||
connect( header, &Header::shiftClicked,
|
||||
plot, &Plot::shiftXAxis );
|
||||
|
||||
setHeader( header );
|
||||
setBody( plot );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
int main( int argc, char* argv[] )
|
||||
{
|
||||
#ifdef ITEM_STATISTICS
|
||||
QskObjectCounter counter( true );
|
||||
#endif
|
||||
|
||||
QGuiApplication app( argc, argv );
|
||||
|
||||
SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts );
|
||||
|
||||
QskWindow window;
|
||||
window.addItem( new MainView() );
|
||||
window.resize( 800, 600 );
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "main.moc"
|
||||
|
|
@ -99,6 +99,7 @@ list(APPEND SOURCES
|
|||
|
||||
list(APPEND HEADERS
|
||||
nodes/QskArcNode.h
|
||||
nodes/QskAxisScaleNode.h
|
||||
nodes/QskBasicLinesNode.h
|
||||
nodes/QskBoxNode.h
|
||||
nodes/QskBoxClipNode.h
|
||||
|
|
@ -126,7 +127,6 @@ list(APPEND HEADERS
|
|||
nodes/QskTextNode.h
|
||||
nodes/QskTextRenderer.h
|
||||
nodes/QskTextureRenderer.h
|
||||
nodes/QskTickmarksNode.h
|
||||
nodes/QskVertex.h
|
||||
)
|
||||
|
||||
|
|
@ -136,6 +136,7 @@ list(APPEND PRIVATE_HEADERS
|
|||
|
||||
list(APPEND SOURCES
|
||||
nodes/QskArcNode.cpp
|
||||
nodes/QskAxisScaleNode.cpp
|
||||
nodes/QskBasicLinesNode.cpp
|
||||
nodes/QskBoxNode.cpp
|
||||
nodes/QskBoxClipNode.cpp
|
||||
|
|
@ -163,7 +164,6 @@ list(APPEND SOURCES
|
|||
nodes/QskTextNode.cpp
|
||||
nodes/QskTextRenderer.cpp
|
||||
nodes/QskTextureRenderer.cpp
|
||||
nodes/QskTickmarksNode.cpp
|
||||
nodes/QskVertex.cpp
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,18 +7,15 @@
|
|||
#define QSK_GRADUATION_H
|
||||
|
||||
#include <QskGlobal.h>
|
||||
#include <qmetatype.h>
|
||||
|
||||
class QskTickmarks;
|
||||
|
||||
namespace QskGraduation
|
||||
{
|
||||
Q_NAMESPACE_EXPORT( QSK_EXPORT )
|
||||
|
||||
QskTickmarks divideInterval( qreal x1, qreal x2,
|
||||
QSK_EXPORT QskTickmarks divideInterval( qreal x1, qreal x2,
|
||||
int maxMajorSteps, int maxMinorSteps, qreal stepSize = 0.0 );
|
||||
|
||||
qreal stepSize( double length, int numSteps );
|
||||
QSK_EXPORT qreal stepSize( double length, int numSteps );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,207 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskAxisScaleNode.h"
|
||||
#include "QskTickmarks.h"
|
||||
#include "QskIntervalF.h"
|
||||
|
||||
#include <QTransform>
|
||||
|
||||
namespace
|
||||
{
|
||||
using Points = QSGGeometry::Point2D;
|
||||
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
inline Renderer( bool isHorizontal )
|
||||
: m_isHorizontal( isHorizontal )
|
||||
{
|
||||
}
|
||||
|
||||
inline Points* addBackbone( Points* points,
|
||||
qreal pos, qreal v1, qreal v2 ) const
|
||||
{
|
||||
if ( m_isHorizontal )
|
||||
setLine( points, v1, pos, v2, pos );
|
||||
else
|
||||
setLine( points, pos, v1, pos, v2 );
|
||||
|
||||
return points + 2;
|
||||
}
|
||||
|
||||
inline Points* addTickLine( Points* points,
|
||||
qreal pos, qreal tick, qreal tickLength ) const
|
||||
{
|
||||
if ( m_isHorizontal )
|
||||
setLine( points, tick, pos, tick, pos + tickLength );
|
||||
else
|
||||
setLine( points, pos, tick, pos + tickLength, tick );
|
||||
|
||||
return points + 2;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
inline void setLine( Points* points,
|
||||
qreal x1, qreal y1, qreal x2, qreal y2 ) const
|
||||
{
|
||||
points[ 0 ].set( x1, y1 );
|
||||
points[ 1 ].set( x2, y2 );
|
||||
}
|
||||
|
||||
const bool m_isHorizontal;
|
||||
};
|
||||
}
|
||||
|
||||
class QskAxisScaleNode::PrivateData
|
||||
{
|
||||
public:
|
||||
inline qreal map( qreal v ) const
|
||||
{
|
||||
if ( isHorizontal )
|
||||
return transform.dx() + transform.m11() * v;
|
||||
else
|
||||
return transform.dy() + transform.m22() * v;
|
||||
}
|
||||
|
||||
inline qreal length( QskTickmarks::TickType type ) const
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case QskTickmarks::MinorTick:
|
||||
return 0.7 * tickLength;
|
||||
|
||||
case QskTickmarks::MediumTick:
|
||||
return 0.85 * tickLength;
|
||||
|
||||
default:
|
||||
return tickLength;
|
||||
}
|
||||
}
|
||||
|
||||
inline qreal origin( qreal length ) const
|
||||
{
|
||||
switch( alignment )
|
||||
{
|
||||
case QskAxisScaleNode::Leading:
|
||||
return pos - length;
|
||||
|
||||
case QskAxisScaleNode::Centered:
|
||||
return pos - 0.5 * length;
|
||||
|
||||
default:
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
bool isHorizontal = true;
|
||||
qreal pos;
|
||||
|
||||
QskIntervalF backbone;
|
||||
QTransform transform;
|
||||
|
||||
QskAxisScaleNode::Alignment alignment = QskAxisScaleNode::Centered;
|
||||
qreal tickLength = 0.0;
|
||||
|
||||
QskHashValue hash = 0;
|
||||
|
||||
bool dirty = true;
|
||||
};
|
||||
|
||||
QskAxisScaleNode::QskAxisScaleNode()
|
||||
: m_data( new PrivateData() )
|
||||
{
|
||||
}
|
||||
|
||||
QskAxisScaleNode::~QskAxisScaleNode()
|
||||
{
|
||||
}
|
||||
|
||||
void QskAxisScaleNode::setAxis( Qt::Orientation orientation,
|
||||
qreal pos, const QTransform& transform )
|
||||
{
|
||||
const bool isHorizontal = ( orientation == Qt::Horizontal );
|
||||
|
||||
if( isHorizontal != m_data->isHorizontal
|
||||
|| pos != m_data->pos || transform != m_data->transform )
|
||||
{
|
||||
m_data->isHorizontal = isHorizontal;
|
||||
m_data->pos = pos;
|
||||
m_data->transform = transform;
|
||||
|
||||
m_data->dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void QskAxisScaleNode::setTickGeometry(
|
||||
Alignment alignment, qreal tickLength, qreal tickWidth )
|
||||
{
|
||||
setLineWidth( tickWidth );
|
||||
|
||||
if( tickLength != m_data->tickLength || alignment != m_data->alignment )
|
||||
{
|
||||
m_data->tickLength = tickLength;
|
||||
m_data->alignment = alignment;
|
||||
|
||||
m_data->dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void QskAxisScaleNode::update( const QskTickmarks& tickmarks,
|
||||
const QskIntervalF& backbone )
|
||||
{
|
||||
const auto hash = tickmarks.hash( 17435 );
|
||||
if ( m_data->hash != hash || m_data->backbone != backbone )
|
||||
{
|
||||
m_data->hash = hash;
|
||||
m_data->backbone = backbone;
|
||||
m_data->dirty = true;
|
||||
}
|
||||
|
||||
if( !m_data->dirty )
|
||||
return;
|
||||
|
||||
QSGGeometry::Point2D* points;
|
||||
|
||||
{
|
||||
auto lineCount = tickmarks.tickCount();
|
||||
if ( !backbone.isEmpty() )
|
||||
lineCount++;
|
||||
|
||||
geometry()->allocate( lineCount * 2 );
|
||||
points = geometry()->vertexDataAsPoint2D();
|
||||
}
|
||||
|
||||
const Renderer renderer( m_data->isHorizontal );
|
||||
|
||||
if ( !m_data->backbone.isEmpty() )
|
||||
{
|
||||
const auto v1 = m_data->map( backbone.lowerBound() );
|
||||
const auto v2 = m_data->map( backbone.upperBound() );
|
||||
|
||||
points = renderer.addBackbone( points, m_data->pos, v1, v2 );
|
||||
}
|
||||
|
||||
for( int i = QskTickmarks::MinorTick;
|
||||
i <= QskTickmarks::MajorTick; i++ )
|
||||
{
|
||||
const auto tickType = static_cast< QskTickmarks::TickType >( i );
|
||||
|
||||
const auto len = m_data->length( tickType );
|
||||
const auto origin = m_data->origin( len );
|
||||
|
||||
const auto ticks = tickmarks.ticks( tickType );
|
||||
for( auto tick : ticks )
|
||||
{
|
||||
tick = m_data->map( tick );
|
||||
points = renderer.addTickLine( points, origin, tick, len );
|
||||
}
|
||||
}
|
||||
|
||||
geometry()->markVertexDataDirty();
|
||||
markDirty( QSGNode::DirtyGeometry );
|
||||
m_data->dirty = false;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef QSK_AXIS_SCALE_NODE_H
|
||||
#define QSK_AXIS_SCALE_NODE_H
|
||||
|
||||
#include "QskGlobal.h"
|
||||
#include "QskBasicLinesNode.h"
|
||||
|
||||
#include <qnamespace.h>
|
||||
|
||||
class QRectF;
|
||||
class QskIntervalF;
|
||||
class QskTickmarks;
|
||||
|
||||
class QskAxisScaleNodePrivate;
|
||||
|
||||
class QSK_EXPORT QskAxisScaleNode : public QskBasicLinesNode
|
||||
{
|
||||
using Inherited = QskBasicLinesNode;
|
||||
|
||||
public:
|
||||
enum Alignment
|
||||
{
|
||||
Leading,
|
||||
Centered,
|
||||
Trailing
|
||||
};
|
||||
|
||||
QskAxisScaleNode();
|
||||
~QskAxisScaleNode() override;
|
||||
|
||||
void setAxis( Qt::Orientation, qreal pos, const QTransform& );
|
||||
void setTickGeometry( Alignment, qreal tickLength, qreal tickWidth );
|
||||
|
||||
void update( const QskTickmarks&, const QskIntervalF& );
|
||||
|
||||
private:
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -7,8 +7,7 @@
|
|||
#include "QskTickmarks.h"
|
||||
#include "QskSkinlet.h"
|
||||
#include "QskSGNode.h"
|
||||
#include "QskGraduationMetrics.h"
|
||||
#include "QskTickmarksNode.h"
|
||||
#include "QskAxisScaleNode.h"
|
||||
#include "QskTextOptions.h"
|
||||
#include "QskTextColors.h"
|
||||
#include "QskGraphic.h"
|
||||
|
|
@ -19,6 +18,31 @@
|
|||
|
||||
#include <qstring.h>
|
||||
#include <qfontmetrics.h>
|
||||
#include <qquickwindow.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
class ScaleMap
|
||||
{
|
||||
public:
|
||||
inline ScaleMap( bool isHorizontal, const QTransform& transform )
|
||||
: t( isHorizontal ? transform.dx() : transform.dy() )
|
||||
, f( isHorizontal ? transform.m11() : transform.m22() )
|
||||
{
|
||||
}
|
||||
|
||||
inline qreal map( qreal v ) const { return t + f * v; };
|
||||
|
||||
private:
|
||||
const qreal t;
|
||||
const qreal f;
|
||||
};
|
||||
}
|
||||
|
||||
static inline bool qskIsHorizontal( Qt::Edge edge )
|
||||
{
|
||||
return edge & ( Qt::TopEdge | Qt::BottomEdge );
|
||||
}
|
||||
|
||||
static QSGNode* qskRemoveTraillingNodes( QSGNode* node, QSGNode* childNode )
|
||||
{
|
||||
|
|
@ -26,44 +50,74 @@ static QSGNode* qskRemoveTraillingNodes( QSGNode* node, QSGNode* childNode )
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
static inline void qskInsertRemoveChild( QSGNode* parentNode,
|
||||
QSGNode* oldNode, QSGNode* newNode, bool append )
|
||||
static inline QTransform qskScaleTransform( Qt::Edge edge,
|
||||
const QskIntervalF& boundaries, const QskIntervalF& range )
|
||||
{
|
||||
if ( newNode == oldNode )
|
||||
return;
|
||||
using T = QTransform;
|
||||
|
||||
if ( oldNode )
|
||||
if ( qskIsHorizontal( edge ) )
|
||||
{
|
||||
parentNode->removeChildNode( oldNode );
|
||||
if ( oldNode->flags() & QSGNode::OwnedByParent )
|
||||
delete oldNode;
|
||||
auto transform = T::fromTranslate( -boundaries.lowerBound(), 0.0 );
|
||||
transform *= T::fromScale( range.length() / boundaries.length(), 1.0 );
|
||||
transform *= T::fromTranslate( range.lowerBound(), 0.0 );
|
||||
|
||||
return transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto transform = T::fromTranslate( 0.0, -boundaries.lowerBound() );
|
||||
transform *= T::fromScale( 1.0, -range.length() / boundaries.length() );
|
||||
transform *= T::fromTranslate( 0.0, range.upperBound() );
|
||||
|
||||
return transform;
|
||||
}
|
||||
}
|
||||
|
||||
static inline quint8 qskLabelNodeRole( const QVariant& label )
|
||||
{
|
||||
if ( !label.isNull() )
|
||||
{
|
||||
if ( label.canConvert< QString >() )
|
||||
return 1;
|
||||
|
||||
if ( label.canConvert< QskGraphic >() )
|
||||
return 2;
|
||||
}
|
||||
|
||||
if ( newNode )
|
||||
{
|
||||
if ( append )
|
||||
parentNode->appendChildNode( newNode );
|
||||
else
|
||||
parentNode->prependChildNode( newNode );
|
||||
}
|
||||
return QskSGNode::NoRole;
|
||||
}
|
||||
|
||||
class QskScaleRenderer::PrivateData
|
||||
{
|
||||
public:
|
||||
|
||||
// Coordinates related to the scales
|
||||
QskIntervalF boundaries;
|
||||
QskTickmarks tickmarks;
|
||||
|
||||
QColor tickColor = Qt::black;
|
||||
/*
|
||||
Item cooordinates. In case of an horizontal scale
|
||||
position is an y coordinate, while range corresponds to x coordinates
|
||||
( vertical: v.v )
|
||||
*/
|
||||
qreal position = 0.0;
|
||||
QskIntervalF range;
|
||||
|
||||
#if 1
|
||||
QColor tickColor = Qt::black; // rgb value ???
|
||||
#endif
|
||||
|
||||
qreal tickWidth = 1.0;
|
||||
qreal tickLength = 10.0;
|
||||
qreal spacing = 5.0;
|
||||
|
||||
QFont font;
|
||||
QskTextColors textColors;
|
||||
|
||||
QskColorFilter colorFilter;
|
||||
|
||||
Qt::Orientation orientation = Qt::Horizontal;
|
||||
Qt::Alignment alignment = Qt::AlignBottom | Qt::AlignRight;
|
||||
Qt::Edge edge = Qt::BottomEdge;
|
||||
QskScaleRenderer::Flags flags = ClampedLabels;
|
||||
};
|
||||
|
||||
QskScaleRenderer::QskScaleRenderer()
|
||||
|
|
@ -75,24 +129,32 @@ QskScaleRenderer::~QskScaleRenderer()
|
|||
{
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setOrientation( Qt::Orientation orientation )
|
||||
void QskScaleRenderer::setEdge( Qt::Edge edge )
|
||||
{
|
||||
m_data->orientation = orientation;
|
||||
m_data->edge = edge;
|
||||
}
|
||||
|
||||
Qt::Orientation QskScaleRenderer::orientation() const
|
||||
Qt::Edge QskScaleRenderer::edge() const
|
||||
{
|
||||
return m_data->orientation;
|
||||
return m_data->edge;
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setAlignment( Qt::Alignment alignment )
|
||||
void QskScaleRenderer::setFlag( Flag flag, bool on )
|
||||
{
|
||||
m_data->alignment = alignment;
|
||||
if ( on )
|
||||
m_data->flags |= flag;
|
||||
else
|
||||
m_data->flags &= ~flag;
|
||||
}
|
||||
|
||||
Qt::Alignment QskScaleRenderer::aligment() const
|
||||
void QskScaleRenderer::setFlags( Flags flags )
|
||||
{
|
||||
return m_data->alignment;
|
||||
m_data->flags = flags;
|
||||
}
|
||||
|
||||
QskScaleRenderer::Flags QskScaleRenderer::flags() const
|
||||
{
|
||||
return m_data->flags;
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setBoundaries( qreal lowerBound, qreal upperBound )
|
||||
|
|
@ -110,6 +172,31 @@ QskIntervalF QskScaleRenderer::boundaries() const
|
|||
return m_data->boundaries;
|
||||
}
|
||||
|
||||
qreal QskScaleRenderer::position() const
|
||||
{
|
||||
return m_data->position;
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setPosition( qreal pos )
|
||||
{
|
||||
m_data->position = pos;
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setRange( qreal from, qreal to )
|
||||
{
|
||||
setRange( QskIntervalF( from, to ) );
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setRange( const QskIntervalF& range )
|
||||
{
|
||||
m_data->range = range;
|
||||
}
|
||||
|
||||
QskIntervalF QskScaleRenderer::range() const
|
||||
{
|
||||
return m_data->range;
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setTickmarks( const QskTickmarks& tickmarks )
|
||||
{
|
||||
m_data->tickmarks = tickmarks;
|
||||
|
|
@ -120,6 +207,16 @@ const QskTickmarks& QskScaleRenderer::tickmarks() const
|
|||
return m_data->tickmarks;
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setSpacing( qreal spacing )
|
||||
{
|
||||
m_data->spacing = qMax( spacing, 0.0 );
|
||||
}
|
||||
|
||||
qreal QskScaleRenderer::spacing() const
|
||||
{
|
||||
return m_data->spacing;
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setTickColor( const QColor& color )
|
||||
{
|
||||
m_data->tickColor = color;
|
||||
|
|
@ -130,9 +227,19 @@ QColor QskScaleRenderer::tickColor() const
|
|||
return m_data->tickColor;
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setTickLength( qreal length )
|
||||
{
|
||||
m_data->tickLength = qMax( length, 0.0 );
|
||||
}
|
||||
|
||||
qreal QskScaleRenderer::tickLength() const
|
||||
{
|
||||
return m_data->tickLength;
|
||||
}
|
||||
|
||||
void QskScaleRenderer::setTickWidth( qreal width )
|
||||
{
|
||||
m_data->tickWidth = width;
|
||||
m_data->tickWidth = qMax( width, 0.0 );
|
||||
}
|
||||
|
||||
qreal QskScaleRenderer::tickWidth() const
|
||||
|
|
@ -170,79 +277,74 @@ const QskColorFilter& QskScaleRenderer::colorFilter() const
|
|||
return m_data->colorFilter;
|
||||
}
|
||||
|
||||
QSGNode* QskScaleRenderer::updateScaleNode(
|
||||
const QskSkinnable* skinnable, const QRectF& tickmarksRect,
|
||||
const QRectF& labelsRect, QSGNode* node )
|
||||
QSGNode* QskScaleRenderer::updateNode(
|
||||
const QskSkinnable* skinnable, QSGNode* node )
|
||||
{
|
||||
enum Role
|
||||
{
|
||||
Ticks = 1,
|
||||
Labels = 2
|
||||
};
|
||||
enum Role : quint8 { Ticks = 1, Labels = 2 };
|
||||
static const QVector< quint8 > roles = { Ticks, Labels };
|
||||
|
||||
const auto transform = qskScaleTransform(
|
||||
m_data->edge, m_data->boundaries, m_data->range );
|
||||
|
||||
if ( node == nullptr )
|
||||
node = new QSGNode();
|
||||
|
||||
for ( auto role : roles )
|
||||
{
|
||||
QSGNode* oldNode = QskSGNode::findChildNode( node, Ticks );
|
||||
QSGNode* newNode = nullptr;
|
||||
auto oldNode = QskSGNode::findChildNode( node, role );
|
||||
|
||||
if ( !tickmarksRect.isEmpty() )
|
||||
{
|
||||
newNode = updateTicksNode( skinnable, tickmarksRect, oldNode );
|
||||
if ( newNode )
|
||||
QskSGNode::setNodeRole( newNode, Ticks );
|
||||
}
|
||||
auto newNode = ( role == Ticks )
|
||||
? updateTicksNode( transform, oldNode )
|
||||
: updateLabelsNode( skinnable, transform, oldNode );
|
||||
|
||||
qskInsertRemoveChild( node, oldNode, newNode, false );
|
||||
}
|
||||
|
||||
{
|
||||
QSGNode* oldNode = QskSGNode::findChildNode( node, Labels );
|
||||
QSGNode* newNode = nullptr;
|
||||
|
||||
if ( !labelsRect.isEmpty() )
|
||||
{
|
||||
newNode = updateLabelsNode( skinnable, tickmarksRect, labelsRect, oldNode );
|
||||
if ( newNode )
|
||||
QskSGNode::setNodeRole( newNode, Labels );
|
||||
}
|
||||
|
||||
qskInsertRemoveChild( node, oldNode, newNode, true );
|
||||
QskSGNode::replaceChildNode( roles, role, node, oldNode, newNode );
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
QSGNode* QskScaleRenderer::updateTicksNode(
|
||||
const QskSkinnable*, const QRectF& rect, QSGNode* node ) const
|
||||
const QTransform& transform, QSGNode* node ) const
|
||||
{
|
||||
if ( rect.isEmpty() )
|
||||
return nullptr;
|
||||
QskIntervalF backbone;
|
||||
if ( m_data->flags & Backbone )
|
||||
backbone = m_data->boundaries;
|
||||
|
||||
auto ticksNode = static_cast< QskTickmarksNode* >( node );
|
||||
const auto orientation = qskIsHorizontal( m_data->edge )
|
||||
? Qt::Horizontal : Qt::Vertical;
|
||||
|
||||
if( ticksNode == nullptr )
|
||||
ticksNode = new QskTickmarksNode;
|
||||
auto alignment = QskAxisScaleNode::Centered;
|
||||
|
||||
#if 1
|
||||
const int tickWidth = qRound( m_data->tickWidth );
|
||||
#endif
|
||||
if ( !( m_data->flags & CenteredTickmarks ) )
|
||||
{
|
||||
switch( m_data->edge )
|
||||
{
|
||||
case Qt::LeftEdge:
|
||||
case Qt::TopEdge:
|
||||
alignment = QskAxisScaleNode::Leading;
|
||||
break;
|
||||
case Qt::BottomEdge:
|
||||
case Qt::RightEdge:
|
||||
alignment = QskAxisScaleNode::Trailing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ticksNode->update( m_data->tickColor, rect, m_data->boundaries,
|
||||
m_data->tickmarks, tickWidth, m_data->orientation,
|
||||
m_data->alignment, {});
|
||||
auto axisNode = QskSGNode::ensureNode< QskAxisScaleNode >( node );
|
||||
|
||||
return ticksNode;
|
||||
axisNode->setColor( m_data->tickColor );
|
||||
axisNode->setAxis( orientation, m_data->position, transform );
|
||||
axisNode->setTickGeometry( alignment, m_data->tickLength, m_data->tickWidth );
|
||||
axisNode->setPixelAlignment( Qt::Horizontal | Qt::Vertical );
|
||||
|
||||
axisNode->update( m_data->tickmarks, backbone );
|
||||
|
||||
return axisNode;
|
||||
}
|
||||
|
||||
QSGNode* QskScaleRenderer::updateLabelsNode(
|
||||
const QskSkinnable* skinnable, const QRectF& tickmarksRect,
|
||||
const QRectF& labelsRect, QSGNode* node ) const
|
||||
QSGNode* QskScaleRenderer::updateLabelsNode( const QskSkinnable* skinnable,
|
||||
const QTransform& transform, QSGNode* node ) const
|
||||
{
|
||||
if ( labelsRect.isEmpty() || tickmarksRect.isEmpty() )
|
||||
return nullptr;
|
||||
|
||||
const auto ticks = m_data->tickmarks.majorTicks();
|
||||
if ( ticks.isEmpty() )
|
||||
return nullptr;
|
||||
|
|
@ -252,158 +354,72 @@ QSGNode* QskScaleRenderer::updateLabelsNode(
|
|||
|
||||
const QFontMetricsF fm( m_data->font );
|
||||
|
||||
const qreal length = ( m_data->orientation == Qt::Horizontal )
|
||||
? tickmarksRect.width() : tickmarksRect.height();
|
||||
const qreal ratio = length / m_data->boundaries.length();
|
||||
|
||||
auto nextNode = node->firstChild();
|
||||
|
||||
QRectF labelRect;
|
||||
QRectF lastRect; // to skip overlapping label
|
||||
|
||||
for ( auto tick : ticks )
|
||||
{
|
||||
enum LabelNodeRole
|
||||
{
|
||||
TextNode = 1,
|
||||
GraphicNode = 2
|
||||
};
|
||||
|
||||
const auto label = labelAt( tick );
|
||||
if ( label.isNull() )
|
||||
continue;
|
||||
|
||||
const qreal tickPos = ratio * ( tick - m_data->boundaries.lowerBound() );
|
||||
const auto role = qskLabelNodeRole( label );
|
||||
|
||||
if ( nextNode && QskSGNode::nodeRole( nextNode ) != role )
|
||||
nextNode = qskRemoveTraillingNodes( node, nextNode );
|
||||
|
||||
QSizeF size;
|
||||
|
||||
if ( label.canConvert< QString >() )
|
||||
{
|
||||
auto text = label.toString();
|
||||
if ( text.isEmpty() )
|
||||
continue;
|
||||
|
||||
QRectF r;
|
||||
Qt::Alignment alignment;
|
||||
|
||||
if( m_data->orientation == Qt::Horizontal )
|
||||
{
|
||||
const auto w = qskHorizontalAdvance( fm, text );
|
||||
|
||||
auto pos = tickmarksRect.x() + tickPos - 0.5 * w;
|
||||
pos = qBound( labelsRect.left(), pos, labelsRect.right() - w );
|
||||
|
||||
r = QRectF( pos, labelsRect.y(), w, labelsRect.height() );
|
||||
|
||||
alignment = Qt::AlignLeft;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto h = fm.height();
|
||||
|
||||
auto pos = tickmarksRect.bottom() - ( tickPos + 0.5 * h );
|
||||
|
||||
/*
|
||||
when clipping the label we can expand the clip rectangle
|
||||
by the ascent/descent margins, as nothing gets painted there
|
||||
anyway.
|
||||
*/
|
||||
const qreal min = labelsRect.top() - ( h - fm.ascent() );
|
||||
const qreal max = labelsRect.bottom() + fm.descent();
|
||||
pos = qBound( min, pos, max );
|
||||
|
||||
r = QRectF( labelsRect.x(), pos, labelsRect.width(), h );
|
||||
|
||||
alignment = Qt::AlignRight;
|
||||
}
|
||||
|
||||
if ( nextNode && QskSGNode::nodeRole( nextNode ) != TextNode )
|
||||
{
|
||||
nextNode = qskRemoveTraillingNodes( node, nextNode );
|
||||
}
|
||||
|
||||
if ( !labelRect.isEmpty() && labelRect.intersects( r ) )
|
||||
{
|
||||
if ( tick != ticks.last() )
|
||||
{
|
||||
text = QString();
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( auto obsoleteNode = nextNode
|
||||
? nextNode->previousSibling() : node->lastChild() )
|
||||
{
|
||||
node->removeChildNode( obsoleteNode );
|
||||
if ( obsoleteNode->flags() & QSGNode::OwnedByParent )
|
||||
delete obsoleteNode;
|
||||
}
|
||||
|
||||
labelRect = r;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
labelRect = r;
|
||||
}
|
||||
|
||||
nextNode = QskSkinlet::updateTextNode( skinnable, nextNode,
|
||||
r, alignment, text, m_data->font, QskTextOptions(),
|
||||
m_data->textColors, Qsk::Normal );
|
||||
|
||||
if ( nextNode )
|
||||
{
|
||||
if ( nextNode->parent() != node )
|
||||
{
|
||||
QskSGNode::setNodeRole( nextNode, TextNode );
|
||||
node->appendChildNode( nextNode );
|
||||
}
|
||||
|
||||
nextNode = nextNode->nextSibling();
|
||||
}
|
||||
size = qskTextRenderSize( fm, label.toString() );
|
||||
}
|
||||
else if ( label.canConvert< QskGraphic >() )
|
||||
{
|
||||
const auto graphic = label.value< QskGraphic >();
|
||||
if ( graphic.isNull() )
|
||||
continue;
|
||||
|
||||
const auto h = fm.height();
|
||||
const auto w = graphic.widthForHeight( h );
|
||||
|
||||
Qt::Alignment alignment;
|
||||
|
||||
if( m_data->orientation == Qt::Horizontal )
|
||||
if ( !graphic.isNull() )
|
||||
{
|
||||
auto pos = tickmarksRect.x() + tickPos - 0.5 * w;
|
||||
pos = qBound( labelsRect.left(), pos, labelsRect.right() - w );
|
||||
|
||||
labelRect = QRectF( pos, labelsRect.y(), w, h );
|
||||
alignment = Qt::AlignHCenter | Qt::AlignBottom;
|
||||
size.rheight() = fm.height();
|
||||
size.rwidth() = graphic.widthForHeight( size.height() );
|
||||
}
|
||||
else
|
||||
{
|
||||
auto pos = tickmarksRect.bottom() - ( tickPos + 0.5 * h );
|
||||
pos = qBound( labelsRect.top(), pos, labelsRect.bottom() - h );
|
||||
}
|
||||
|
||||
labelRect = QRectF( labelsRect.right() - w, pos, w, h );
|
||||
alignment = Qt::AlignRight | Qt::AlignVCenter;
|
||||
if ( size.isEmpty() )
|
||||
continue;
|
||||
|
||||
const auto rect = labelRect( transform, tick, size );
|
||||
|
||||
if ( !lastRect.isEmpty() && lastRect.intersects( rect ) )
|
||||
{
|
||||
/*
|
||||
Label do overlap: in case it is the last tick we remove
|
||||
the precessor - otherwise we simply skip this one
|
||||
*/
|
||||
|
||||
if ( tick != ticks.last() )
|
||||
continue; // skip this label
|
||||
|
||||
if ( auto obsoleteNode = nextNode
|
||||
? nextNode->previousSibling() : node->lastChild() )
|
||||
{
|
||||
node->removeChildNode( obsoleteNode );
|
||||
if ( obsoleteNode->flags() & QSGNode::OwnedByParent )
|
||||
delete obsoleteNode;
|
||||
}
|
||||
}
|
||||
|
||||
nextNode = updateTickLabelNode( skinnable, nextNode, label, rect );
|
||||
|
||||
if ( nextNode)
|
||||
{
|
||||
lastRect = rect;
|
||||
|
||||
if ( nextNode->parent() != node )
|
||||
{
|
||||
QskSGNode::setNodeRole( nextNode, role );
|
||||
node->appendChildNode( nextNode );
|
||||
}
|
||||
|
||||
if ( nextNode && QskSGNode::nodeRole( nextNode ) != GraphicNode )
|
||||
{
|
||||
nextNode = qskRemoveTraillingNodes( node, nextNode );
|
||||
}
|
||||
|
||||
nextNode = QskSkinlet::updateGraphicNode(
|
||||
skinnable, nextNode, graphic, m_data->colorFilter, labelRect, alignment );
|
||||
|
||||
if ( nextNode )
|
||||
{
|
||||
if ( nextNode->parent() != node )
|
||||
{
|
||||
QskSGNode::setNodeRole( nextNode, GraphicNode );
|
||||
node->appendChildNode( nextNode );
|
||||
}
|
||||
|
||||
nextNode = nextNode->nextSibling();
|
||||
}
|
||||
nextNode = nextNode->nextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -417,40 +433,116 @@ QVariant QskScaleRenderer::labelAt( qreal pos ) const
|
|||
return QString::number( pos, 'g' );
|
||||
}
|
||||
|
||||
// should be cached
|
||||
QSizeF QskScaleRenderer::boundingLabelSize() const
|
||||
{
|
||||
QSizeF boundingSize( 0.0, 0.0 );
|
||||
|
||||
const auto ticks = m_data->tickmarks.majorTicks();
|
||||
if ( ticks.isEmpty() )
|
||||
return QSizeF( 0.0, 0.0 );
|
||||
return boundingSize;
|
||||
|
||||
const QFontMetricsF fm( m_data->font );
|
||||
|
||||
qreal maxWidth = 0.0;
|
||||
const qreal h = fm.height();
|
||||
|
||||
for ( auto tick : ticks )
|
||||
{
|
||||
qreal w = 0.0;
|
||||
|
||||
const auto label = labelAt( tick );
|
||||
if ( label.isNull() )
|
||||
continue;
|
||||
|
||||
if ( label.canConvert< QString >() )
|
||||
{
|
||||
w = qskHorizontalAdvance( fm, label.toString() );
|
||||
boundingSize = boundingSize.expandedTo(
|
||||
qskTextRenderSize( fm, label.toString() ) );
|
||||
}
|
||||
else if ( label.canConvert< QskGraphic >() )
|
||||
{
|
||||
const auto graphic = label.value< QskGraphic >();
|
||||
if ( !graphic.isNull() )
|
||||
{
|
||||
w = graphic.widthForHeight( h );
|
||||
const auto w = graphic.widthForHeight( h );
|
||||
boundingSize.setWidth( qMax( boundingSize.width(), w ) );
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth = qMax( w, maxWidth );
|
||||
}
|
||||
|
||||
return QSizeF( maxWidth, h );
|
||||
return boundingSize;
|
||||
}
|
||||
|
||||
QRectF QskScaleRenderer::labelRect(
|
||||
const QTransform& transform, qreal tick, const QSizeF& labelSize ) const
|
||||
{
|
||||
const auto isHorizontal = qskIsHorizontal( m_data->edge );
|
||||
|
||||
auto offset = m_data->tickLength + m_data->spacing;
|
||||
if ( m_data->flags & CenteredTickmarks )
|
||||
offset -= 0.5 * m_data->tickLength;
|
||||
|
||||
const bool clampLabels = m_data->flags & ClampedLabels;
|
||||
|
||||
const qreal w = labelSize.width();
|
||||
const qreal h = labelSize.height();
|
||||
|
||||
qreal x, y;
|
||||
|
||||
const ScaleMap map( isHorizontal, transform );
|
||||
|
||||
const auto tickPos = map.map( tick );
|
||||
|
||||
qreal min, max;
|
||||
if ( clampLabels )
|
||||
{
|
||||
min = map.map( m_data->boundaries.lowerBound() );
|
||||
max = map.map( m_data->boundaries.upperBound() );
|
||||
}
|
||||
|
||||
if( isHorizontal )
|
||||
{
|
||||
x = tickPos - 0.5 * w;
|
||||
|
||||
if ( clampLabels )
|
||||
x = qBound( min, x, max - w );
|
||||
|
||||
y = m_data->position + offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto tickPos = map.map( tick );
|
||||
y = tickPos - 0.5 * h;
|
||||
|
||||
if ( clampLabels )
|
||||
y = qBound( max, y, min - h );
|
||||
|
||||
x = m_data->position - offset - w;
|
||||
}
|
||||
|
||||
return QRectF( x, y, w, h );
|
||||
}
|
||||
|
||||
QSGNode* QskScaleRenderer::updateTickLabelNode( const QskSkinnable* skinnable,
|
||||
QSGNode* node, const QVariant& label, const QRectF& rect ) const
|
||||
{
|
||||
if ( label.canConvert< QString >() )
|
||||
{
|
||||
return QskSkinlet::updateTextNode( skinnable, node,
|
||||
rect, Qt::AlignCenter, label.toString(), m_data->font,
|
||||
QskTextOptions(), m_data->textColors, Qsk::Normal );
|
||||
}
|
||||
|
||||
if ( label.canConvert< QskGraphic >() )
|
||||
{
|
||||
const auto alignment = qskIsHorizontal( m_data->edge )
|
||||
? ( Qt::AlignHCenter | Qt::AlignBottom )
|
||||
: ( Qt::AlignRight | Qt::AlignVCenter );
|
||||
|
||||
return QskSkinlet::updateGraphicNode(
|
||||
skinnable, node, label.value< QskGraphic >(),
|
||||
m_data->colorFilter, rect, alignment );
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#include "moc_QskScaleRenderer.cpp"
|
||||
|
|
|
|||
|
|
@ -23,30 +23,61 @@ class QskColorFilter;
|
|||
class QSGNode;
|
||||
class QVariant;
|
||||
class QRectF;
|
||||
class QPointF;
|
||||
class QSizeF;
|
||||
class QTransform;
|
||||
|
||||
class QSK_EXPORT QskScaleRenderer
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
enum Flag
|
||||
{
|
||||
Backbone = 1 << 0,
|
||||
CenteredTickmarks = 1 << 1,
|
||||
ClampedLabels = 1 << 2
|
||||
};
|
||||
|
||||
Q_ENUM( Flag )
|
||||
Q_DECLARE_FLAGS( Flags, Flag )
|
||||
|
||||
QskScaleRenderer();
|
||||
virtual ~QskScaleRenderer();
|
||||
|
||||
void setOrientation( Qt::Orientation );
|
||||
Qt::Orientation orientation() const;
|
||||
void setEdge( Qt::Edge );
|
||||
Qt::Edge edge() const;
|
||||
|
||||
void setAlignment( Qt::Alignment );
|
||||
Qt::Alignment aligment() const;
|
||||
void setFlags( Flags );
|
||||
Flags flags() const;
|
||||
|
||||
void setFlag( Flag, bool );
|
||||
|
||||
// scale coordinates
|
||||
void setBoundaries( qreal lowerBound, qreal upperBound );
|
||||
void setBoundaries( const QskIntervalF& );
|
||||
QskIntervalF boundaries() const;
|
||||
|
||||
// item coordiates
|
||||
qreal position() const;
|
||||
void setPosition( qreal );
|
||||
|
||||
void setRange( qreal from, qreal to );
|
||||
void setRange( const QskIntervalF& );
|
||||
QskIntervalF range() const;
|
||||
|
||||
void setTickmarks( const QskTickmarks& );
|
||||
const QskTickmarks& tickmarks() const;
|
||||
|
||||
void setSpacing( qreal );
|
||||
qreal spacing() const;
|
||||
|
||||
void setTickColor( const QColor& );
|
||||
QColor tickColor() const;
|
||||
|
||||
void setTickLength( qreal );
|
||||
qreal tickLength() const;
|
||||
|
||||
void setTickWidth( qreal );
|
||||
qreal tickWidth() const;
|
||||
|
||||
|
|
@ -59,24 +90,29 @@ class QSK_EXPORT QskScaleRenderer
|
|||
void setColorFilter( const QskColorFilter& );
|
||||
const QskColorFilter& colorFilter() const;
|
||||
|
||||
QSGNode* updateScaleNode( const QskSkinnable*,
|
||||
const QRectF& tickmarksRect, const QRectF& labelsRect, QSGNode* );
|
||||
QSGNode* updateNode( const QskSkinnable*, QSGNode* );
|
||||
|
||||
virtual QVariant labelAt( qreal pos ) const;
|
||||
QSizeF boundingLabelSize() const;
|
||||
|
||||
virtual QSGNode* updateTicksNode(
|
||||
const QskSkinnable*, const QRectF&, QSGNode* ) const;
|
||||
protected:
|
||||
virtual QSGNode* updateTicksNode( const QTransform&, QSGNode* ) const;
|
||||
|
||||
virtual QSGNode* updateLabelsNode(
|
||||
const QskSkinnable*, const QRectF& ticksRect,
|
||||
const QRectF& labelsRect, QSGNode* node ) const;
|
||||
const QskSkinnable*, const QTransform&, QSGNode* ) const;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY( QskScaleRenderer )
|
||||
|
||||
QRectF labelRect( const QTransform&, qreal, const QSizeF& ) const;
|
||||
|
||||
QSGNode* updateTickLabelNode( const QskSkinnable*,
|
||||
QSGNode*, const QVariant&, const QRectF& ) const;
|
||||
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS( QskScaleRenderer::Flags )
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,126 +0,0 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskTickmarksNode.h"
|
||||
#include "QskTickmarks.h"
|
||||
#include "QskGraduationMetrics.h"
|
||||
|
||||
#include <qrect.h>
|
||||
#include <qhashfunctions.h>
|
||||
|
||||
QskTickmarksNode::QskTickmarksNode()
|
||||
{
|
||||
}
|
||||
|
||||
QskTickmarksNode::~QskTickmarksNode()
|
||||
{
|
||||
}
|
||||
|
||||
void QskTickmarksNode::update(
|
||||
const QColor& color, const QRectF& rect,
|
||||
const QskIntervalF& boundaries, const QskTickmarks& tickmarks,
|
||||
int lineWidth, Qt::Orientation orientation, Qt::Alignment alignment,
|
||||
const QskGraduationMetrics& graduationMetrics )
|
||||
{
|
||||
setLineWidth( lineWidth );
|
||||
|
||||
auto hash = tickmarks.hash( 17435 );
|
||||
hash = graduationMetrics.hash( hash );
|
||||
hash = qHashBits( &boundaries, sizeof( boundaries ), hash );
|
||||
hash = qHashBits( &rect, sizeof( rect ), hash );
|
||||
hash = qHash( orientation, hash );
|
||||
hash = qHash( alignment, hash );
|
||||
|
||||
if ( hash != m_hash )
|
||||
{
|
||||
m_hash = hash;
|
||||
|
||||
geometry()->allocate( tickmarks.tickCount() * 2 );
|
||||
auto vertexData = geometry()->vertexDataAsPoint2D();
|
||||
|
||||
const qreal min = boundaries.lowerBound();
|
||||
const qreal range = boundaries.length();
|
||||
|
||||
using TM = QskTickmarks;
|
||||
|
||||
for( int i = TM::MinorTick; i <= TM::MajorTick; i++ )
|
||||
{
|
||||
const auto tickType = static_cast< TM::TickType >( i );
|
||||
|
||||
const auto ticks = tickmarks.ticks( tickType );
|
||||
const float len = graduationMetrics.tickLength( tickType );
|
||||
|
||||
if ( orientation == Qt::Horizontal )
|
||||
{
|
||||
const qreal ratio = rect.width() / range;
|
||||
|
||||
for( const auto tick : ticks )
|
||||
{
|
||||
const auto x = rect.x() + ( tick - min ) * ratio;
|
||||
|
||||
qreal y1, y2;
|
||||
|
||||
if( alignment & Qt::AlignTop )
|
||||
{
|
||||
y1 = rect.top() + len;
|
||||
y2 = rect.top();
|
||||
}
|
||||
else if( alignment & Qt::AlignVCenter )
|
||||
{
|
||||
const auto offset = ( rect.height() - len ) / 2;
|
||||
y1 = rect.bottom() - offset;
|
||||
y2 = rect.top() + offset;
|
||||
}
|
||||
else // Bottom (default)
|
||||
{
|
||||
y1 = rect.bottom();
|
||||
y2 = rect.bottom() - len;
|
||||
}
|
||||
|
||||
vertexData[ 0 ].set( x, y1 );
|
||||
vertexData[ 1 ].set( x, y2 );
|
||||
vertexData += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const qreal ratio = rect.height() / range;
|
||||
|
||||
for( const auto tick : ticks )
|
||||
{
|
||||
const auto y = rect.bottom() - ( tick - min ) * ratio;
|
||||
|
||||
qreal x1, x2;
|
||||
|
||||
if( alignment & Qt::AlignLeft )
|
||||
{
|
||||
x1 = rect.left() + len;
|
||||
x2 = rect.left();
|
||||
}
|
||||
else if( alignment & Qt::AlignHCenter )
|
||||
{
|
||||
const auto offset = ( rect.width() - len ) / 2;
|
||||
x1 = rect.right() - offset;
|
||||
x2 = rect.left() + offset;
|
||||
}
|
||||
else // Right (default)
|
||||
{
|
||||
x1 = rect.right();
|
||||
x2 = rect.right() - len;
|
||||
}
|
||||
|
||||
vertexData[ 0 ].set( x1, y );
|
||||
vertexData[ 1 ].set( x2, y );
|
||||
vertexData += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
geometry()->markVertexDataDirty();
|
||||
markDirty( QSGNode::DirtyGeometry );
|
||||
}
|
||||
|
||||
setColor( color );
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef QSK_TICKMARKS_NODE_H
|
||||
#define QSK_TICKMARKS_NODE_H
|
||||
|
||||
#include "QskBasicLinesNode.h"
|
||||
|
||||
class QskIntervalF;
|
||||
class QskTickmarks;
|
||||
class QskGraduationMetrics;
|
||||
class QRectF;
|
||||
|
||||
class QSK_EXPORT QskTickmarksNode : public QskBasicLinesNode
|
||||
{
|
||||
public:
|
||||
QskTickmarksNode();
|
||||
~QskTickmarksNode() override;
|
||||
|
||||
void update(const QColor&, const QRectF&, const QskIntervalF&,
|
||||
const QskTickmarks&, int tickLineWidth, Qt::Orientation,
|
||||
Qt::Alignment, const QskGraduationMetrics& );
|
||||
|
||||
private:
|
||||
QskHashValue m_hash = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue