charts code added: will become Qsk classes at some point
This commit is contained in:
parent
2d6b7b3f46
commit
207ba079a1
@ -5,6 +5,7 @@ add_subdirectory(gradients)
|
||||
add_subdirectory(invoker)
|
||||
add_subdirectory(shadows)
|
||||
add_subdirectory(shapes)
|
||||
add_subdirectory(charts)
|
||||
|
||||
if (BUILD_INPUTCONTEXT)
|
||||
add_subdirectory(inputpanel)
|
||||
|
12
playground/charts/CMakeLists.txt
Normal file
12
playground/charts/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
############################################################################
|
||||
# QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
# This file may be used under the terms of the 3-clause BSD License
|
||||
############################################################################
|
||||
|
||||
qsk_add_example(charts
|
||||
ChartSample.h ChartSample.cpp
|
||||
CircularChartSkinlet.h CircularChartSkinlet.cpp
|
||||
StackedChart.h StackedChart.cpp
|
||||
CircularChart.h CircularChart.cpp
|
||||
ChartView.h ChartView.cpp
|
||||
main.cpp )
|
35
playground/charts/ChartSample.cpp
Normal file
35
playground/charts/ChartSample.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "ChartSample.h"
|
||||
|
||||
static void qskRegisterChartSample()
|
||||
{
|
||||
qRegisterMetaType< ChartSample >();
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
QMetaType::registerEqualsComparator< ChartSample >();
|
||||
#endif
|
||||
}
|
||||
|
||||
Q_CONSTRUCTOR_FUNCTION( qskRegisterChartSample )
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
|
||||
#include <qdebug.h>
|
||||
|
||||
QDebug operator<<( QDebug debug, const ChartSample& sample )
|
||||
{
|
||||
QDebugStateSaver saver( debug );
|
||||
debug.nospace();
|
||||
|
||||
debug << "ChartSample" << "( " << sample.title() << ": "
|
||||
<< sample.value() << sample.gradient() << " )";
|
||||
return debug;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#include "moc_ChartSample.cpp"
|
110
playground/charts/ChartSample.h
Normal file
110
playground/charts/ChartSample.h
Normal file
@ -0,0 +1,110 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QskGlobal.h>
|
||||
#include <QskGradient.h>
|
||||
#include <qstring.h>
|
||||
#include <qmetatype.h>
|
||||
|
||||
class ChartSample
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY( QString title READ title WRITE setTitle )
|
||||
Q_PROPERTY( qreal value READ value WRITE setValue )
|
||||
Q_PROPERTY( QskGradient gradient READ gradient WRITE setGradient )
|
||||
|
||||
public:
|
||||
ChartSample() noexcept = default;
|
||||
ChartSample( const QString&, qreal, const QskGradient& ) noexcept;
|
||||
|
||||
void setSample( const QString&, qreal, const QskGradient& ) noexcept;
|
||||
|
||||
bool operator==( const ChartSample& ) const noexcept;
|
||||
bool operator!=( const ChartSample& ) const noexcept;
|
||||
|
||||
QString title() const noexcept;
|
||||
void setTitle( const QString& ) noexcept;
|
||||
|
||||
qreal value() const noexcept;
|
||||
void setValue( qreal ) noexcept;
|
||||
|
||||
QskGradient gradient() const noexcept;
|
||||
void setGradient( const QskGradient& ) noexcept;
|
||||
|
||||
private:
|
||||
QString m_title;
|
||||
qreal m_value = 0.0;
|
||||
QskGradient m_gradient;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE( ChartSample )
|
||||
|
||||
inline ChartSample::ChartSample( const QString& title, qreal value,
|
||||
const QskGradient& gradient ) noexcept
|
||||
: m_title( title )
|
||||
, m_value( value )
|
||||
, m_gradient( gradient )
|
||||
{
|
||||
}
|
||||
|
||||
inline bool ChartSample::operator==( const ChartSample& other ) const noexcept
|
||||
{
|
||||
return ( m_value == other.m_value )
|
||||
&& ( m_title == other.m_title )
|
||||
&& ( m_gradient == other.m_gradient );
|
||||
}
|
||||
|
||||
inline bool ChartSample::operator!=( const ChartSample& other ) const noexcept
|
||||
{
|
||||
return ( !( *this == other ) );
|
||||
}
|
||||
|
||||
inline QString ChartSample::title() const noexcept
|
||||
{
|
||||
return m_title;
|
||||
}
|
||||
|
||||
inline void ChartSample::setTitle( const QString& title ) noexcept
|
||||
{
|
||||
m_title = title;
|
||||
}
|
||||
|
||||
inline qreal ChartSample::value() const noexcept
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
inline void ChartSample::setValue( qreal value ) noexcept
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
inline QskGradient ChartSample::gradient() const noexcept
|
||||
{
|
||||
return m_gradient;
|
||||
}
|
||||
|
||||
inline void ChartSample::setGradient( const QskGradient& gradient ) noexcept
|
||||
{
|
||||
m_gradient = gradient;
|
||||
}
|
||||
|
||||
inline void ChartSample::setSample(
|
||||
const QString& title, qreal value, const QskGradient& gradient ) noexcept
|
||||
{
|
||||
m_title = title;
|
||||
m_value = value;
|
||||
m_gradient = gradient;
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
|
||||
class QDebug;
|
||||
QSK_EXPORT QDebug operator<<( QDebug, const ChartSample& );
|
||||
|
||||
#endif
|
229
playground/charts/ChartView.cpp
Normal file
229
playground/charts/ChartView.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "ChartView.h"
|
||||
#include "ChartSample.h"
|
||||
#include "CircularChart.h"
|
||||
|
||||
#include <QskArcMetrics.h>
|
||||
#include <QskGraphic.h>
|
||||
#include <QskRgbValue.h>
|
||||
|
||||
#include <QskBox.h>
|
||||
#include <QskLinearBox.h>
|
||||
#include <QskGridBox.h>
|
||||
#include <QskTextLabel.h>
|
||||
#include <QskGraphicLabel.h>
|
||||
#include <QskSlider.h>
|
||||
|
||||
#include <qpainter.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
class ChartBox : public QskControl
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ChartBox( CircularChart* chart, QQuickItem* parent = nullptr )
|
||||
: QskControl( parent )
|
||||
, m_chart( chart )
|
||||
{
|
||||
m_chart->setParentItem( this );
|
||||
if ( m_chart->parent() == nullptr )
|
||||
m_chart->setParent( this );
|
||||
|
||||
setPolishOnResize( true );
|
||||
setMargins( 10 );
|
||||
}
|
||||
|
||||
QskArcMetrics arcMetrics() const
|
||||
{
|
||||
return m_chart->arcMetrics();
|
||||
}
|
||||
|
||||
public Q_SLOTS:
|
||||
void setThickness( qreal thickness )
|
||||
{
|
||||
m_chart->setArcThickness( thickness, Qt::RelativeSize );
|
||||
}
|
||||
|
||||
void setStartAngle( qreal degrees )
|
||||
{
|
||||
auto metrics = m_chart->arcMetrics();
|
||||
metrics.setStartAngle( degrees );
|
||||
m_chart->setArcMetrics( metrics );
|
||||
}
|
||||
|
||||
void setSpanAngle( qreal degrees )
|
||||
{
|
||||
auto metrics = m_chart->arcMetrics();
|
||||
metrics.setSpanAngle( degrees );
|
||||
m_chart->setArcMetrics( metrics );
|
||||
|
||||
polish();
|
||||
}
|
||||
|
||||
protected:
|
||||
void updateLayout() override
|
||||
{
|
||||
const auto r = layoutRect();
|
||||
|
||||
m_chart->setArcDiameters( r.size() );
|
||||
|
||||
const auto align = Qt::AlignTop | Qt::AlignHCenter;
|
||||
m_chart->setGeometry( qskAlignedRectF( r, m_chart->sizeHint(), align ) );
|
||||
}
|
||||
|
||||
private:
|
||||
CircularChart* m_chart = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class SliderBox : public QskLinearBox
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SliderBox( const QString& label, qreal min, qreal max, qreal value )
|
||||
{
|
||||
auto textLabel = new QskTextLabel( label, this );
|
||||
textLabel->setSizePolicy( Qt::Horizontal, QskSizePolicy::Fixed );
|
||||
|
||||
auto slider = new QskSlider( this );
|
||||
slider->setBoundaries( min, max );
|
||||
slider->setValue( value );
|
||||
slider->setStepSize( 1.0 );
|
||||
slider->setPageSize( 10.0 );
|
||||
|
||||
connect( slider, &QskSlider::valueChanged,
|
||||
this, &SliderBox::valueChanged );
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void valueChanged( qreal );
|
||||
};
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class ControlPanel : public QskGridBox
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ControlPanel( const QskArcMetrics& metrics, QQuickItem* parent = nullptr )
|
||||
: QskGridBox( parent )
|
||||
{
|
||||
setPanel( true );
|
||||
setPaddingHint( QskBox::Panel, 20 );
|
||||
setSpacing( 10 );
|
||||
|
||||
auto sliderStart = new SliderBox( "Angle", 0.0, 360.0, metrics.startAngle() );
|
||||
auto sliderSpan = new SliderBox( "Span", -360.0, 360.0, metrics.spanAngle() );
|
||||
auto sliderExtent = new SliderBox( "Extent", 10.0, 100.0, metrics.thickness() );
|
||||
|
||||
connect( sliderStart, &SliderBox::valueChanged,
|
||||
this, &ControlPanel::startAngleChanged );
|
||||
|
||||
connect( sliderSpan, &SliderBox::valueChanged,
|
||||
this, &ControlPanel::spanAngleChanged );
|
||||
|
||||
connect( sliderExtent, &SliderBox::valueChanged,
|
||||
this, &ControlPanel::thicknessChanged );
|
||||
|
||||
addItem( sliderStart, 0, 0 );
|
||||
addItem( sliderExtent, 0, 1 );
|
||||
addItem( sliderSpan, 1, 0, 1, 2 );
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void thicknessChanged( qreal );
|
||||
void startAngleChanged( qreal );
|
||||
void spanAngleChanged( qreal );
|
||||
};
|
||||
|
||||
class Legend : public QskGridBox
|
||||
{
|
||||
public:
|
||||
Legend( QQuickItem* parent = nullptr )
|
||||
: QskGridBox( parent )
|
||||
{
|
||||
setMargins( 10 );
|
||||
setLayoutAlignmentHint( Qt::AlignLeft | Qt::AlignTop );
|
||||
}
|
||||
|
||||
void setSamples( const QVector< ChartSample >& samples )
|
||||
{
|
||||
clear( true );
|
||||
|
||||
for ( const auto& sample : samples )
|
||||
{
|
||||
// using QskBox instead TODO ...
|
||||
auto iconLabel = new QskGraphicLabel( graphic( sample.gradient() ) );
|
||||
iconLabel->setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed );
|
||||
|
||||
auto textLabel = new QskTextLabel( sample.title() );
|
||||
textLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
|
||||
|
||||
const auto row = rowCount();
|
||||
|
||||
addItem( iconLabel, row, 0 );
|
||||
addItem( textLabel, row, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QskGraphic graphic( const QskGradient& gradient ) const
|
||||
{
|
||||
QskGraphic identifier;
|
||||
|
||||
QPainter painter( &identifier );
|
||||
painter.setPen( QPen( QskRgb::toTransparent( Qt::black, 100 ), 1 ) );
|
||||
painter.setBrush( gradient.toQGradient() );
|
||||
|
||||
QLinearGradient qGradient;
|
||||
qGradient.setStops( qskToQGradientStops( gradient.stops() ) );
|
||||
painter.setBrush( qGradient );
|
||||
|
||||
painter.drawRect( 0, 0, 20, 20 );
|
||||
painter.end();
|
||||
|
||||
return identifier;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
ChartView::ChartView( CircularChart* chart, QQuickItem* parent )
|
||||
: QskMainView( parent )
|
||||
{
|
||||
auto hBox = new QskLinearBox( Qt::Horizontal );
|
||||
|
||||
auto chartBox = new ChartBox( chart, hBox );
|
||||
|
||||
auto legend = new Legend( hBox );
|
||||
legend->setSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed );
|
||||
legend->setSamples( chart->series() );
|
||||
|
||||
auto controlPanel = new ControlPanel( chartBox->arcMetrics() );
|
||||
controlPanel->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed );
|
||||
|
||||
connect( controlPanel, &ControlPanel::thicknessChanged,
|
||||
chartBox, &ChartBox::setThickness );
|
||||
|
||||
connect( controlPanel, &ControlPanel::startAngleChanged,
|
||||
chartBox, &ChartBox::setStartAngle );
|
||||
|
||||
connect( controlPanel, &ControlPanel::spanAngleChanged,
|
||||
chartBox, &ChartBox::setSpanAngle );
|
||||
|
||||
setHeader( controlPanel );
|
||||
setBody( hBox );
|
||||
}
|
||||
|
||||
#include "ChartView.moc"
|
16
playground/charts/ChartView.h
Normal file
16
playground/charts/ChartView.h
Normal file
@ -0,0 +1,16 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QskMainView.h>
|
||||
|
||||
class CircularChart;
|
||||
|
||||
class ChartView : public QskMainView
|
||||
{
|
||||
public:
|
||||
ChartView( CircularChart*, QQuickItem* parent = nullptr );
|
||||
};
|
132
playground/charts/CircularChart.cpp
Normal file
132
playground/charts/CircularChart.cpp
Normal file
@ -0,0 +1,132 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "CircularChart.h"
|
||||
#include "CircularChartSkinlet.h"
|
||||
#include "ChartSample.h"
|
||||
|
||||
#include <QskArcMetrics.h>
|
||||
#include <QskRgbValue.h>
|
||||
#include <QskBoxBorderMetrics.h>
|
||||
|
||||
QSK_SUBCONTROL( CircularChart, Panel )
|
||||
QSK_SUBCONTROL( CircularChart, Arc )
|
||||
QSK_SUBCONTROL( CircularChart, Segment )
|
||||
QSK_SUBCONTROL( CircularChart, SegmentLabel )
|
||||
|
||||
CircularChart::CircularChart( QQuickItem* parentItem )
|
||||
: StackedChart( parentItem )
|
||||
{
|
||||
#if 1
|
||||
/*
|
||||
This code has to go to the skins, but for the moment
|
||||
we do a local customization
|
||||
*/
|
||||
setGradientHint( Panel, QskGradient() );
|
||||
setBoxBorderMetricsHint( Panel, 0 );
|
||||
|
||||
setArcMetricsHint( Arc, { 90.0, -360.0, 100.0, Qt::RelativeSize } );
|
||||
|
||||
setGradientHint( Arc, QskRgb::toTransparent( QskRgb::LightGray, 100 ) );
|
||||
setColor( Arc | QskAspect::Border, QskRgb::LightGray );
|
||||
setMetric( Arc | QskAspect::Border, 1.0 );
|
||||
|
||||
setColor( Segment | QskAspect::Border, QskRgb::Black );
|
||||
setMetric( Segment | QskAspect::Border, 1.0 );
|
||||
|
||||
auto skinlet = new CircularChartSkinlet();
|
||||
skinlet->setOwnedBySkinnable( true );
|
||||
|
||||
setSkinlet( skinlet );
|
||||
#endif
|
||||
}
|
||||
|
||||
CircularChart::~CircularChart()
|
||||
{
|
||||
}
|
||||
|
||||
QSizeF CircularChart::arcDiameters() const
|
||||
{
|
||||
return strutSizeHint( Arc );
|
||||
}
|
||||
|
||||
void CircularChart::setArcDiameter( qreal diameter )
|
||||
{
|
||||
setArcDiameters( QSizeF( diameter, diameter ) );
|
||||
}
|
||||
|
||||
void CircularChart::setArcDiameters( qreal diameterX, qreal diameterY )
|
||||
{
|
||||
setArcDiameters( QSizeF( diameterX, diameterY ) );
|
||||
}
|
||||
|
||||
void CircularChart::setArcDiameters( const QSizeF& diameters )
|
||||
{
|
||||
if ( setStrutSizeHint( Arc, diameters ) )
|
||||
{
|
||||
resetImplicitSize();
|
||||
update();
|
||||
|
||||
Q_EMIT arcDiametersChanged( diameters );
|
||||
}
|
||||
}
|
||||
|
||||
void CircularChart::resetArcDiameters()
|
||||
{
|
||||
if ( resetStrutSizeHint( Arc ) )
|
||||
{
|
||||
resetImplicitSize();
|
||||
update();
|
||||
|
||||
Q_EMIT arcDiametersChanged( arcDiameters() );
|
||||
}
|
||||
}
|
||||
|
||||
QskArcMetrics CircularChart::arcMetrics() const
|
||||
{
|
||||
return arcMetricsHint( Arc );
|
||||
}
|
||||
|
||||
void CircularChart::setArcMetrics( const QskArcMetrics& metrics )
|
||||
{
|
||||
if ( setArcMetricsHint( Arc, metrics ) )
|
||||
{
|
||||
resetImplicitSize();
|
||||
update();
|
||||
|
||||
Q_EMIT arcMetricsChanged( metrics );
|
||||
}
|
||||
}
|
||||
|
||||
void CircularChart::resetArcMetrics()
|
||||
{
|
||||
if ( resetArcMetricsHint( Arc ) )
|
||||
{
|
||||
resetImplicitSize();
|
||||
update();
|
||||
|
||||
Q_EMIT arcMetricsChanged( arcMetrics() );
|
||||
}
|
||||
}
|
||||
|
||||
void CircularChart::setArcThickness( qreal width, Qt::SizeMode sizeMode )
|
||||
{
|
||||
auto metrics = arcMetricsHint( Arc );
|
||||
metrics.setThickness( width );
|
||||
metrics.setSizeMode( sizeMode );
|
||||
|
||||
setArcMetrics( metrics );
|
||||
}
|
||||
|
||||
void CircularChart::setArcAngles( qreal startAngle, qreal spanAngle )
|
||||
{
|
||||
auto metrics = arcMetricsHint( Arc );
|
||||
metrics.setStartAngle( startAngle );
|
||||
metrics.setSpanAngle( spanAngle );
|
||||
|
||||
setArcMetrics( metrics );
|
||||
}
|
||||
|
||||
#include "moc_CircularChart.cpp"
|
57
playground/charts/CircularChart.h
Normal file
57
playground/charts/CircularChart.h
Normal file
@ -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 "StackedChart.h"
|
||||
|
||||
class QskArcMetrics;
|
||||
|
||||
/*
|
||||
Pie/Donut chart
|
||||
|
||||
The default setting organizes the values clockwise in a
|
||||
full circle starting at 90°.
|
||||
|
||||
A chart where the thickness of the arc is smaller than the radius
|
||||
is usually referred as "donut" ( or "doughnut" ) chart while it is
|
||||
called a "pie" chart otherwise.
|
||||
*/
|
||||
class CircularChart : public StackedChart
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY( QskArcMetrics arcMetrics READ arcMetrics
|
||||
WRITE setArcMetrics RESET resetArcMetrics NOTIFY arcMetricsChanged )
|
||||
|
||||
Q_PROPERTY( QSizeF arcDiameters READ arcDiameters
|
||||
WRITE setArcDiameters RESET resetArcDiameters NOTIFY arcDiametersChanged )
|
||||
|
||||
using Inherited = StackedChart;
|
||||
|
||||
public:
|
||||
QSK_SUBCONTROLS( Panel, Arc, Segment, SegmentLabel )
|
||||
|
||||
CircularChart( QQuickItem* parent = nullptr );
|
||||
~CircularChart() override;
|
||||
|
||||
QskArcMetrics arcMetrics() const;
|
||||
void resetArcMetrics();
|
||||
|
||||
QSizeF arcDiameters() const;
|
||||
void setArcDiameters( qreal width, qreal height );
|
||||
void resetArcDiameters();
|
||||
|
||||
public Q_SLOTS:
|
||||
void setArcDiameters( const QSizeF& );
|
||||
void setArcDiameter( qreal size );
|
||||
void setArcThickness( qreal width, Qt::SizeMode = Qt::RelativeSize );
|
||||
void setArcMetrics( const QskArcMetrics& );
|
||||
void setArcAngles( qreal startAngle, qreal spanAngle );
|
||||
|
||||
Q_SIGNALS:
|
||||
void arcMetricsChanged( const QskArcMetrics& );
|
||||
void arcDiametersChanged( const QSizeF& );
|
||||
};
|
454
playground/charts/CircularChartSkinlet.cpp
Normal file
454
playground/charts/CircularChartSkinlet.cpp
Normal file
@ -0,0 +1,454 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "CircularChartSkinlet.h"
|
||||
#include "CircularChart.h"
|
||||
#include "ChartSample.h"
|
||||
|
||||
#include <QskIntervalF.h>
|
||||
#include <QskArcMetrics.h>
|
||||
#include <QskArcNode.h>
|
||||
|
||||
#include <qpainterpath.h>
|
||||
#include <qmath.h>
|
||||
|
||||
#define PAINTED_NODE 0
|
||||
|
||||
#if PAINTED_NODE
|
||||
|
||||
/*
|
||||
This is a fallback implementation for a user who is using an outdated
|
||||
version of QSkinny where only shaders for linear gradients are available.
|
||||
*/
|
||||
#include <QskPaintedNode.h>
|
||||
#include <qpainter.h>
|
||||
#include <qpainterpath.h>
|
||||
#include <qpen.h>
|
||||
#include <qbrush.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
class PaintedArcNode : public QskPaintedNode
|
||||
{
|
||||
public:
|
||||
void setArcData( const QRectF&, const QskArcMetrics&,
|
||||
qreal, const QColor&, const QskGradient&, QQuickWindow* );
|
||||
|
||||
protected:
|
||||
void paint( QPainter*, const QSize&, const void* nodeData ) override;
|
||||
QskHashValue hash( const void* nodeData ) const override;
|
||||
|
||||
private:
|
||||
QskHashValue arcHash( const QRectF&, const QskArcMetrics&,
|
||||
qreal, const QColor&, const QskGradient& ) const;
|
||||
|
||||
QBrush fillBrush( const QskGradient&, const QRectF&, qreal, qreal ) const;
|
||||
|
||||
struct ArcData
|
||||
{
|
||||
QPointF translation;
|
||||
QPen pen;
|
||||
QBrush brush;
|
||||
QPainterPath path;
|
||||
|
||||
QskHashValue hash;
|
||||
};
|
||||
};
|
||||
|
||||
void PaintedArcNode::setArcData(
|
||||
const QRectF& rect, const QskArcMetrics& metrics,
|
||||
qreal borderWidth, const QColor& borderColor,
|
||||
const QskGradient& gradient, QQuickWindow* window )
|
||||
{
|
||||
const auto hash = arcHash( rect, metrics, borderWidth, borderColor, gradient );
|
||||
|
||||
const auto brush = fillBrush( gradient, rect,
|
||||
metrics.startAngle(), metrics.spanAngle() );
|
||||
|
||||
QPen pen( borderColor, borderWidth );
|
||||
if ( borderWidth <= 0.0 )
|
||||
pen.setStyle( Qt::NoPen );
|
||||
|
||||
const auto path = metrics.painterPath( rect );
|
||||
const auto r = path.controlPointRect();
|
||||
|
||||
const ArcData arcData { r.topLeft(), pen, brush, path, hash };
|
||||
update( window, r, QSizeF(), &arcData );
|
||||
}
|
||||
|
||||
void PaintedArcNode::paint( QPainter* painter, const QSize&, const void* nodeData )
|
||||
{
|
||||
const auto arcData = reinterpret_cast< const ArcData* >( nodeData );
|
||||
|
||||
painter->setRenderHint( QPainter::Antialiasing, true );
|
||||
painter->translate( -arcData->translation );
|
||||
painter->setPen( arcData->pen );
|
||||
painter->setBrush( arcData->brush );
|
||||
painter->drawPath( arcData->path );
|
||||
}
|
||||
|
||||
QskHashValue PaintedArcNode::hash( const void* nodeData ) const
|
||||
{
|
||||
const auto arcData = reinterpret_cast< const ArcData* >( nodeData );
|
||||
return arcData->hash;
|
||||
}
|
||||
|
||||
QBrush PaintedArcNode::fillBrush( const QskGradient& gradient,
|
||||
const QRectF& rect, qreal startAngle, qreal spanAngle ) const
|
||||
{
|
||||
const auto stops = gradient.stops();
|
||||
|
||||
QskGradientStops scaledStops;
|
||||
scaledStops.reserve( gradient.stops().size() );
|
||||
|
||||
const auto ratio = qAbs( spanAngle ) / 360.0;
|
||||
|
||||
if ( spanAngle > 0.0 )
|
||||
{
|
||||
for ( auto it = stops.cbegin(); it != stops.cend(); ++it )
|
||||
scaledStops += { ratio* it->position(), it->color() };
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( auto it = stops.crbegin(); it != stops.crend(); ++it )
|
||||
scaledStops += { 1.0 - ratio * it->position(), it->color() };
|
||||
}
|
||||
|
||||
QConicalGradient qGradient( QPointF(), startAngle );
|
||||
qGradient.setStops( qskToQGradientStops( scaledStops ) );
|
||||
|
||||
const qreal sz = qMax( rect.width(), rect.height() );
|
||||
const qreal sx = rect.width() / sz;
|
||||
const qreal sy = rect.height() / sz;
|
||||
|
||||
QTransform t;
|
||||
t.scale( sx, sy );
|
||||
t.translate( rect.center().x() / sx, rect.center().y() / sy );
|
||||
|
||||
QBrush brush( qGradient );
|
||||
brush.setTransform( t );
|
||||
|
||||
return brush;
|
||||
}
|
||||
|
||||
inline QskHashValue PaintedArcNode::arcHash(
|
||||
const QRectF& rect, const QskArcMetrics& metrics, qreal borderWidth,
|
||||
const QColor& borderColor, const QskGradient& gradient ) const
|
||||
{
|
||||
auto hash = metrics.hash( 6753 );
|
||||
hash = qHashBits( &rect, sizeof( rect ), hash );
|
||||
hash = qHash( borderWidth, hash );
|
||||
hash = qHash( borderColor.rgba(), hash );
|
||||
hash = gradient.hash( hash );
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // PAINTED_NODE
|
||||
|
||||
namespace
|
||||
{
|
||||
inline QskArcMetrics segmentMetrics(
|
||||
const QskSkinnable* skinnable, int index )
|
||||
{
|
||||
auto metrics = skinnable->arcMetricsHint( CircularChart::Arc );
|
||||
|
||||
const auto chart = static_cast< const CircularChart* >( skinnable );
|
||||
|
||||
const auto span = chart->stackedInterval( index );
|
||||
const auto total = chart->effectiveTotalValue();
|
||||
|
||||
const auto a1 = metrics.angleAtRatio( span.lowerBound() / total );
|
||||
const auto a2 = metrics.angleAtRatio( span.upperBound() / total );
|
||||
|
||||
metrics.setStartAngle( a1 );
|
||||
metrics.setSpanAngle( a2 - a1 );
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
QRectF closedArcRect( const QskSkinnable* skinnable )
|
||||
{
|
||||
using Q = CircularChart;
|
||||
|
||||
const auto chart = static_cast< const CircularChart* >( skinnable );
|
||||
const auto r = chart->subControlContentsRect( Q::Arc );
|
||||
|
||||
const auto metrics = skinnable->arcMetricsHint( Q::Arc );
|
||||
if ( metrics.isClosed() )
|
||||
return r;
|
||||
|
||||
const auto size = skinnable->strutSizeHint( CircularChart::Arc );
|
||||
|
||||
auto pos = r.topLeft();
|
||||
|
||||
{
|
||||
const QRectF ellipseRect( 0.0, 0.0, size.width(), size.height() );
|
||||
pos -= metrics.boundingRect( ellipseRect ).topLeft();
|
||||
}
|
||||
|
||||
return QRectF( pos.x(), pos.y(), size.width(), size.height() );
|
||||
}
|
||||
|
||||
static inline QRectF textRect( qreal x, qreal y )
|
||||
{
|
||||
const qreal sz = 1000.0; // something big enough to avoid wrapping
|
||||
return QRectF( x - 0.5 * sz, y - 0.5 * sz, sz, sz );
|
||||
}
|
||||
}
|
||||
|
||||
class CircularChartSkinlet::PrivateData
|
||||
{
|
||||
public:
|
||||
// caching the geometry of the rect for drawing all arc segments
|
||||
mutable QRectF closedArcRect;
|
||||
};
|
||||
|
||||
CircularChartSkinlet::CircularChartSkinlet( QskSkin* skin )
|
||||
: Inherited( skin )
|
||||
, m_data( new PrivateData() )
|
||||
{
|
||||
setNodeRoles( { PanelRole, ArcRole, SegmentRole, SegmentLabelRole } );
|
||||
}
|
||||
|
||||
CircularChartSkinlet::~CircularChartSkinlet() = default;
|
||||
|
||||
void CircularChartSkinlet::updateNode(
|
||||
QskSkinnable* skinnable, QSGNode* node ) const
|
||||
{
|
||||
m_data->closedArcRect = ::closedArcRect( skinnable );
|
||||
Inherited::updateNode( skinnable, node );
|
||||
}
|
||||
|
||||
QRectF CircularChartSkinlet::subControlRect( const QskSkinnable* skinnable,
|
||||
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
|
||||
{
|
||||
using Q = CircularChart;
|
||||
|
||||
if ( subControl == Q::Panel )
|
||||
return contentsRect;
|
||||
|
||||
if ( subControl == Q::Arc )
|
||||
{
|
||||
const auto chart = static_cast< const CircularChart* >( skinnable );
|
||||
return chart->subControlContentsRect( Q::Panel );
|
||||
}
|
||||
|
||||
return Inherited::subControlRect( skinnable, contentsRect, subControl );
|
||||
}
|
||||
|
||||
QSGNode* CircularChartSkinlet::updateSubNode(
|
||||
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
|
||||
{
|
||||
using Q = CircularChart;
|
||||
|
||||
switch ( nodeRole )
|
||||
{
|
||||
case PanelRole:
|
||||
return updateBoxNode( skinnable, node, Q::Panel );
|
||||
|
||||
case ArcRole:
|
||||
{
|
||||
const auto aspect = Q::Arc | QskAspect::Border;
|
||||
|
||||
const auto borderColor = skinnable->color( aspect );
|
||||
const auto borderWidth = skinnable->metric( aspect );
|
||||
|
||||
return updateArcSegmentNode( skinnable, node, borderWidth, borderColor,
|
||||
skinnable->gradientHint( Q::Arc ), skinnable->arcMetricsHint( Q::Arc ) );
|
||||
}
|
||||
|
||||
case SegmentRole:
|
||||
return updateSeriesNode( skinnable, Q::Segment, node );
|
||||
|
||||
case SegmentLabelRole:
|
||||
return updateSeriesNode( skinnable, Q::SegmentLabel, node );
|
||||
}
|
||||
|
||||
return Inherited::updateSubNode( skinnable, nodeRole, node );
|
||||
}
|
||||
|
||||
int CircularChartSkinlet::sampleCount(
|
||||
const QskSkinnable* skinnable, QskAspect::Subcontrol subControl ) const
|
||||
{
|
||||
using Q = CircularChart;
|
||||
|
||||
if ( subControl == Q::Segment || subControl == Q::SegmentLabel )
|
||||
{
|
||||
const auto chart = static_cast< const CircularChart* >( skinnable );
|
||||
return chart->series().count();
|
||||
}
|
||||
|
||||
return Inherited::sampleCount( skinnable, subControl );
|
||||
}
|
||||
|
||||
QRectF CircularChartSkinlet::sampleRect( const QskSkinnable* skinnable,
|
||||
const QRectF& contentsRect, QskAspect::Subcontrol subControl, int index ) const
|
||||
{
|
||||
// See https://github.com/uwerat/qskinny/issues/307
|
||||
return Inherited::sampleRect( skinnable, contentsRect, subControl, index );
|
||||
}
|
||||
|
||||
int CircularChartSkinlet::sampleIndexAt(
|
||||
const QskSkinnable* skinnable, const QRectF& contentsRect,
|
||||
QskAspect::Subcontrol subControl, const QPointF& pos ) const
|
||||
{
|
||||
// See https://github.com/uwerat/qskinny/issues/307
|
||||
return Inherited::sampleIndexAt( skinnable, contentsRect, subControl, pos );
|
||||
}
|
||||
|
||||
QSGNode* CircularChartSkinlet::updateSampleNode( const QskSkinnable* skinnable,
|
||||
QskAspect::Subcontrol subControl, int index, QSGNode* node ) const
|
||||
{
|
||||
using Q = CircularChart;
|
||||
|
||||
const auto chart = static_cast< const CircularChart* >( skinnable );
|
||||
|
||||
if ( subControl == Q::Segment )
|
||||
{
|
||||
const auto aspect = Q::Segment | QskAspect::Border;
|
||||
|
||||
const auto borderColor = skinnable->color( aspect );
|
||||
const auto borderWidth = skinnable->metric( aspect );
|
||||
|
||||
return updateArcSegmentNode( skinnable, node, borderWidth, borderColor,
|
||||
chart->gradientAt( index ), ::segmentMetrics( skinnable, index ) );
|
||||
}
|
||||
|
||||
if ( subControl == Q::SegmentLabel )
|
||||
{
|
||||
return updateArcLabelNode( skinnable, node,
|
||||
chart->labelAt( index ), ::segmentMetrics( skinnable, index ) );
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QSGNode* CircularChartSkinlet::updateArcSegmentNode(
|
||||
const QskSkinnable* skinnable,
|
||||
QSGNode* node, qreal borderWidth, const QColor& borderColor,
|
||||
const QskGradient& gradient, const QskArcMetrics& metrics ) const
|
||||
{
|
||||
auto fillGradient = gradient;
|
||||
|
||||
if ( fillGradient.type() == QskGradient::Stops )
|
||||
{
|
||||
fillGradient.setStretchMode( QskGradient::StretchToSize );
|
||||
fillGradient.setConicDirection( 0.5, 0.5,
|
||||
metrics.startAngle(), metrics.spanAngle() );
|
||||
}
|
||||
|
||||
#if PAINTED_NODE
|
||||
auto arcNode = static_cast< PaintedArcNode* >( node );
|
||||
if ( arcNode == nullptr )
|
||||
arcNode = new PaintedArcNode();
|
||||
|
||||
const auto chart = static_cast< const CircularChart* >( skinnable );
|
||||
|
||||
arcNode->setArcData( m_data->closedArcRect, metrics,
|
||||
borderWidth, borderColor, fillGradient, chart->window() );
|
||||
#else
|
||||
Q_UNUSED( skinnable )
|
||||
|
||||
auto arcNode = static_cast< QskArcNode* >( node );
|
||||
if ( arcNode == nullptr )
|
||||
arcNode = new QskArcNode();
|
||||
|
||||
arcNode->setArcData( m_data->closedArcRect, metrics,
|
||||
borderWidth, borderColor, fillGradient );
|
||||
#endif
|
||||
|
||||
return arcNode;
|
||||
}
|
||||
|
||||
QSGNode* CircularChartSkinlet::updateArcLabelNode(
|
||||
const QskSkinnable* skinnable, QSGNode* node,
|
||||
const QString& text, const QskArcMetrics& metrics ) const
|
||||
{
|
||||
/*
|
||||
possible improvements:
|
||||
|
||||
- rotating the labels
|
||||
- elide/wrap modes
|
||||
*/
|
||||
const auto& r = m_data->closedArcRect;
|
||||
const auto sz = qMin( r.width(), r.height() );
|
||||
|
||||
if ( text.isEmpty() || sz <= 0.0 )
|
||||
return nullptr;
|
||||
|
||||
const auto m = metrics.toAbsolute( 0.5 * sz );
|
||||
|
||||
const auto pos = 0.5; // [0.0, 1.0] -> distance from the inner border
|
||||
const auto dt = pos * ( 1.0 - m.thickness() / sz );
|
||||
|
||||
const qreal radians = qDegreesToRadians( m.startAngle() + 0.5 * m.spanAngle() );
|
||||
const auto c = r.center();
|
||||
|
||||
const qreal x = c.x () + dt * r.width() * qFastCos( radians );
|
||||
const qreal y = c.y() - dt * r.height() * qFastSin( radians );
|
||||
|
||||
return updateTextNode( skinnable, node,
|
||||
textRect( x, y ), Qt::AlignCenter, text, CircularChart::SegmentLabel );
|
||||
}
|
||||
|
||||
QSizeF CircularChartSkinlet::sizeHint( const QskSkinnable* skinnable,
|
||||
Qt::SizeHint which, const QSizeF& constraint ) const
|
||||
{
|
||||
using Q = CircularChart;
|
||||
|
||||
if ( which != Qt::PreferredSize )
|
||||
return QSizeF();
|
||||
|
||||
const auto sz = skinnable->strutSizeHint( CircularChart::Arc );
|
||||
|
||||
auto metrics = skinnable->arcMetricsHint( Q::Arc );
|
||||
|
||||
QSizeF hint;
|
||||
|
||||
if ( metrics.isClosed() )
|
||||
{
|
||||
// We only support constrained hints for closed arcs
|
||||
|
||||
if ( constraint.width() >= 0.0 || constraint.height() >= 0.0 )
|
||||
{
|
||||
const qreal ratio = sz.isEmpty() ? 1.0 : sz.width() / sz.height();
|
||||
|
||||
if ( constraint.width() >= 0.0 )
|
||||
{
|
||||
hint = skinnable->innerBoxSize(
|
||||
Q::Panel, QSizeF( constraint.width(), constraint.width() ) );
|
||||
|
||||
hint.setHeight( hint.width() / ratio );
|
||||
hint = skinnable->outerBoxSize( Q::Panel, hint );
|
||||
|
||||
return QSizeF( -1.0, hint.height() );
|
||||
}
|
||||
else
|
||||
{
|
||||
hint = skinnable->innerBoxSize(
|
||||
Q::Panel, QSizeF( constraint.height(), constraint.height() ) );
|
||||
|
||||
hint.setWidth( hint.height() * ratio );
|
||||
hint = skinnable->outerBoxSize( Q::Panel, hint );
|
||||
|
||||
return QSizeF( hint.width(), -1.0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( constraint.width() >= 0.0 || constraint.height() >= 0.0 )
|
||||
return QSizeF();
|
||||
|
||||
metrics = metrics.toAbsolute( 0.5 * sz.width(), 0.5 * sz.height() );
|
||||
|
||||
hint = metrics.boundingSize( sz );
|
||||
hint = skinnable->outerBoxSize( Q::Panel, hint );
|
||||
|
||||
return hint;
|
||||
}
|
||||
|
||||
#include "moc_CircularChartSkinlet.cpp"
|
61
playground/charts/CircularChartSkinlet.h
Normal file
61
playground/charts/CircularChartSkinlet.h
Normal file
@ -0,0 +1,61 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QskSkinlet.h>
|
||||
#include <memory>
|
||||
|
||||
class CircularChartSkinlet : public QskSkinlet
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
using Inherited = QskSkinlet;
|
||||
|
||||
public:
|
||||
enum NodeRole
|
||||
{
|
||||
PanelRole,
|
||||
ArcRole,
|
||||
SegmentRole,
|
||||
SegmentLabelRole
|
||||
};
|
||||
|
||||
Q_INVOKABLE CircularChartSkinlet( QskSkin* = nullptr );
|
||||
~CircularChartSkinlet() override;
|
||||
|
||||
void updateNode( QskSkinnable*, QSGNode* ) const 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;
|
||||
|
||||
QRectF sampleRect( const QskSkinnable*,
|
||||
const QRectF&, QskAspect::Subcontrol, int index ) const override;
|
||||
|
||||
int sampleIndexAt( const QskSkinnable*,
|
||||
const QRectF&, QskAspect::Subcontrol, const QPointF& ) 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* updateArcSegmentNode( const QskSkinnable*, QSGNode*,
|
||||
qreal, const QColor&, const QskGradient&, const QskArcMetrics& ) const;
|
||||
|
||||
QSGNode* updateArcLabelNode( const QskSkinnable*, QSGNode*,
|
||||
const QString&, const QskArcMetrics& ) const;
|
||||
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
112
playground/charts/StackedChart.cpp
Normal file
112
playground/charts/StackedChart.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "StackedChart.h"
|
||||
#include "ChartSample.h"
|
||||
|
||||
#include <QskIntervalF.h>
|
||||
|
||||
class StackedChart::PrivateData
|
||||
{
|
||||
public:
|
||||
qreal totalValue = 0.0;
|
||||
|
||||
QVector< ChartSample > samples;
|
||||
QVector< qreal > cumulatedValues;
|
||||
};
|
||||
|
||||
StackedChart::StackedChart( QQuickItem* parentItem )
|
||||
: QskControl( parentItem )
|
||||
, m_data( new PrivateData() )
|
||||
{
|
||||
}
|
||||
|
||||
StackedChart::~StackedChart()
|
||||
{
|
||||
}
|
||||
|
||||
void StackedChart::setTotalValue( qreal value )
|
||||
{
|
||||
if ( value < 0.0 )
|
||||
value = 0.0;
|
||||
|
||||
if ( value != m_data->totalValue )
|
||||
{
|
||||
m_data->totalValue = value;
|
||||
update();
|
||||
|
||||
Q_EMIT totalValueChanged( m_data->totalValue );
|
||||
}
|
||||
}
|
||||
|
||||
qreal StackedChart::totalValue() const
|
||||
{
|
||||
return m_data->totalValue;
|
||||
}
|
||||
|
||||
qreal StackedChart::effectiveTotalValue() const
|
||||
{
|
||||
qreal cummulated = 0.0;
|
||||
|
||||
if ( !m_data->samples.isEmpty() )
|
||||
{
|
||||
cummulated = m_data->cumulatedValues.last()
|
||||
+ m_data->samples.last().value();
|
||||
}
|
||||
|
||||
return qMax( m_data->totalValue, cummulated );
|
||||
}
|
||||
|
||||
QskIntervalF StackedChart::stackedInterval( int index ) const
|
||||
{
|
||||
QskIntervalF interval;
|
||||
|
||||
if ( index >= 0 && index < m_data->samples.size() )
|
||||
{
|
||||
interval.setLowerBound( m_data->cumulatedValues[index] );
|
||||
interval.setWidth( m_data->samples[index].value() );
|
||||
}
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
void StackedChart::setSeries( const QVector< ChartSample >& samples )
|
||||
{
|
||||
m_data->samples = samples;
|
||||
|
||||
{
|
||||
// caching the cumulated values
|
||||
|
||||
m_data->cumulatedValues.reserve( samples.size() );
|
||||
|
||||
qreal total = 0.0;
|
||||
for ( const auto& sample : samples )
|
||||
{
|
||||
m_data->cumulatedValues += total;
|
||||
total += sample.value();
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
Q_EMIT seriesChanged();
|
||||
}
|
||||
|
||||
QVector< ChartSample > StackedChart::series() const
|
||||
{
|
||||
return m_data->samples;
|
||||
}
|
||||
|
||||
QString StackedChart::labelAt( int index ) const
|
||||
{
|
||||
return m_data->samples.value( index ).title();
|
||||
}
|
||||
|
||||
QskGradient StackedChart::gradientAt( int index ) const
|
||||
{
|
||||
return m_data->samples.value( index ).gradient();
|
||||
}
|
||||
|
||||
#include "moc_StackedChart.cpp"
|
60
playground/charts/StackedChart.h
Normal file
60
playground/charts/StackedChart.h
Normal file
@ -0,0 +1,60 @@
|
||||
/******************************************************************************
|
||||
* 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 <qvector.h>
|
||||
#include <qnamespace.h>
|
||||
|
||||
class ChartSample;
|
||||
class QskIntervalF;
|
||||
class QString;
|
||||
|
||||
/*
|
||||
Examples for charts, that do stack their values:
|
||||
|
||||
- pie charts
|
||||
- donut charts
|
||||
- stacked bar charts
|
||||
- ...
|
||||
*/
|
||||
class StackedChart : public QskControl
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using Inherited = QskControl;
|
||||
|
||||
Q_PROPERTY( QVector< ChartSample > series READ series
|
||||
WRITE setSeries NOTIFY seriesChanged )
|
||||
|
||||
Q_PROPERTY( qreal totalValue READ totalValue
|
||||
WRITE setTotalValue NOTIFY totalValueChanged )
|
||||
|
||||
public:
|
||||
StackedChart( QQuickItem* parent = nullptr );
|
||||
~StackedChart() override;
|
||||
|
||||
void setTotalValue( qreal );
|
||||
qreal totalValue() const;
|
||||
|
||||
QskIntervalF stackedInterval( int index ) const;
|
||||
|
||||
qreal effectiveTotalValue() const;
|
||||
|
||||
void setSeries( const QVector< ChartSample >& );
|
||||
QVector< ChartSample > series() const;
|
||||
|
||||
virtual QskGradient gradientAt( int ) const;
|
||||
virtual QString labelAt( int ) const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void totalValueChanged( qreal );
|
||||
void seriesChanged();
|
||||
|
||||
private:
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
79
playground/charts/main.cpp
Normal file
79
playground/charts/main.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* This file may be used under the terms of the 3-clause BSD License
|
||||
*****************************************************************************/
|
||||
|
||||
#include "CircularChart.h"
|
||||
#include "ChartSample.h"
|
||||
#include "ChartView.h"
|
||||
|
||||
#include <QskWindow.h>
|
||||
#include <QskFocusIndicator.h>
|
||||
#include <QskObjectCounter.h>
|
||||
#include <QskRgbValue.h>
|
||||
|
||||
#include <SkinnyShortcut.h>
|
||||
#include <QGuiApplication>
|
||||
|
||||
namespace
|
||||
{
|
||||
class DistroChart : public CircularChart
|
||||
{
|
||||
using Inherited = CircularChart;
|
||||
|
||||
public:
|
||||
DistroChart( QQuickItem* parent = nullptr )
|
||||
: CircularChart( parent )
|
||||
{
|
||||
setArcDiameters( 400, 300 );
|
||||
setArcThickness( 50, Qt::RelativeSize );
|
||||
|
||||
/*
|
||||
DistroWatch Page Hit Ranking, Last 12 months: 2023/04/04
|
||||
https://distrowatch.com/dwres.php?resource=popularity
|
||||
*/
|
||||
|
||||
QVector< ChartSample > hits;
|
||||
hits += { "MX Linux", 2738, QGradient::WinterNeva };
|
||||
hits += { "EndeavourOS", 2355, QGradient::StrongBliss };
|
||||
hits += { "Mint", 2062, 0xff86be43 };
|
||||
hits += { "Manjaro", 1474, QGradient::SandStrike };
|
||||
|
||||
setSeries( hits );
|
||||
|
||||
#if 1
|
||||
// only to demonstrate the effect of setTotalValue
|
||||
const auto totalValue = 1.1 * effectiveTotalValue();
|
||||
setTotalValue( totalValue );
|
||||
#endif
|
||||
}
|
||||
|
||||
QString labelAt( int index ) const override
|
||||
{
|
||||
const auto value = series().value( index ).value();
|
||||
const auto ratio = value / effectiveTotalValue();
|
||||
|
||||
const QLocale l;
|
||||
return l.toString( ratio * 100.0, 'f', 1 ) + l.percent();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 ChartView( new DistroChart() ) );
|
||||
window.addItem( new QskFocusIndicator() );
|
||||
window.resize( 600, 500 );
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user