252 lines
7.8 KiB
C++
252 lines
7.8 KiB
C++
/******************************************************************************
|
|
* Copyright (C) 2021 Edelhirsch Software GmbH
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*****************************************************************************/
|
|
|
|
#include "DiagramSkinlet.h"
|
|
#include "Diagram.h"
|
|
#include "nodes/DiagramDataNode.h"
|
|
#include "nodes/DiagramSegmentsNode.h"
|
|
|
|
#include <QskBoxBorderColors.h>
|
|
#include <QskBoxBorderMetrics.h>
|
|
#include <QskBoxRectangleNode.h>
|
|
#include <QskBoxShapeMetrics.h>
|
|
|
|
namespace
|
|
{
|
|
QskAspect::Subcontrol subcontrolForIndex( Diagram::Type type, int i )
|
|
{
|
|
QskAspect::Subcontrol subcontrols[] = {
|
|
Diagram::ChartLine1, Diagram::ChartLine2, Diagram::ChartLine3,
|
|
Diagram::ChartArea1, Diagram::ChartArea2, Diagram::ChartArea3,
|
|
Diagram::ChartBar1, Diagram::ChartBar2, Diagram::ChartBar3 };
|
|
|
|
int row;
|
|
|
|
switch( type )
|
|
{
|
|
case Diagram::Line:
|
|
row = 0;
|
|
break;
|
|
|
|
case Diagram::Area:
|
|
row = 1;
|
|
break;
|
|
|
|
case Diagram::Bar:
|
|
row = 2;
|
|
break;
|
|
}
|
|
|
|
return subcontrols[ row * 3 + i ];
|
|
}
|
|
}
|
|
|
|
DiagramSkinlet::DiagramSkinlet( QskSkin* skin )
|
|
: QskSkinlet( skin )
|
|
{
|
|
setNodeRoles( { ChartRole, SeparatorRole } );
|
|
}
|
|
|
|
DiagramSkinlet::~DiagramSkinlet()
|
|
{
|
|
}
|
|
|
|
QRectF DiagramSkinlet::subControlRect(
|
|
const QskSkinnable* skinnable, const QRectF& contentsRect,
|
|
QskAspect::Subcontrol subControl ) const
|
|
{
|
|
if( subControl == Diagram::Chart )
|
|
{
|
|
return contentsRect;
|
|
}
|
|
|
|
return Inherited::subControlRect( skinnable, contentsRect, subControl );
|
|
}
|
|
|
|
QSGNode* DiagramSkinlet::updateSubNode(
|
|
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
|
|
{
|
|
const auto discharge = static_cast< const Diagram* >( skinnable );
|
|
|
|
switch( nodeRole )
|
|
{
|
|
case ChartRole:
|
|
{
|
|
return updateChartNode( discharge, node );
|
|
}
|
|
|
|
case SeparatorRole:
|
|
{
|
|
return updateSeparatorNode( discharge, node );
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
QSGNode* DiagramSkinlet::updateChartNode( const Diagram* diagram, QSGNode* node ) const
|
|
{
|
|
if( node == nullptr )
|
|
{
|
|
node = new DiagramDataNode;
|
|
}
|
|
|
|
using Q = Diagram;
|
|
|
|
const QRectF rect = diagram->subControlRect( Q::Chart );
|
|
const qreal yMax = diagram->yMax();
|
|
|
|
for( int i = 0; i < diagram->dataPoints().size(); ++i )
|
|
{
|
|
QSGNode* chartNode;
|
|
|
|
if( node->childCount() > i )
|
|
{
|
|
chartNode = node->childAtIndex( i );
|
|
}
|
|
else
|
|
{
|
|
chartNode = new QSGNode;
|
|
node->appendChildNode( chartNode );
|
|
}
|
|
|
|
const QVector< QPointF > dataPoints = diagram->dataPoints().at( i );
|
|
int nodeIndex = 0;
|
|
QskAspect::Subcontrol lineSubcontrol = subcontrolForIndex( Diagram::Line, i );
|
|
QskAspect::Subcontrol areaSubcontrol = subcontrolForIndex( Diagram::Area, i );
|
|
QskAspect::Subcontrol barSubcontrol = subcontrolForIndex( Diagram::Bar, i );
|
|
|
|
const auto lineWidth = diagram->metric( lineSubcontrol | QskAspect::Size );
|
|
|
|
for( const auto type : { Diagram::Line, Diagram::Area, Diagram::Bar } )
|
|
{
|
|
if( diagram->typesAt( i ) & type )
|
|
{
|
|
QColor color;
|
|
|
|
if( type == Diagram::Bar )
|
|
{
|
|
QSGNode* barsNode;
|
|
|
|
if( chartNode->childCount() > nodeIndex )
|
|
{
|
|
barsNode = chartNode->childAtIndex( nodeIndex );
|
|
}
|
|
else
|
|
{
|
|
barsNode = new QSGNode;
|
|
chartNode->appendChildNode( barsNode );
|
|
}
|
|
|
|
for( int k = 0; k < dataPoints.size(); ++k )
|
|
{
|
|
QskBoxRectangleNode* barNode;
|
|
|
|
if( barsNode->childCount() > k )
|
|
{
|
|
barNode = static_cast< QskBoxRectangleNode* >(
|
|
barsNode->childAtIndex( k ) );
|
|
}
|
|
else
|
|
{
|
|
barNode = new QskBoxRectangleNode;
|
|
barsNode->appendChildNode( barNode );
|
|
}
|
|
|
|
const auto& dataPoint = dataPoints.at( k );
|
|
const auto size = diagram->strutSizeHint( barSubcontrol );
|
|
const qreal xMin = dataPoints.at( 0 ).x();
|
|
const qreal xMax = dataPoints.at( dataPoints.count() - 1 ).x();
|
|
const qreal spacing = 3;
|
|
const qreal sectionWidth = rect.width() / dataPoints.count();
|
|
const qreal sectionOffset = ( sectionWidth
|
|
- ( diagram->dataPoints().count() * size.width()
|
|
+ ( ( diagram->dataPoints().count() - 1 ) * spacing ) ) ) / 2;
|
|
const qreal x = ( ( dataPoint.x() - xMin ) / ( xMax - xMin + 1 ) ) * rect.width()
|
|
+ sectionOffset + i * ( size.width() + spacing );
|
|
const qreal fraction = ( dataPoint.y() / yMax ) * rect.height();
|
|
const qreal y = rect.height() - fraction;
|
|
|
|
QRectF barRect( x, y, size.width(), fraction );
|
|
color = diagram->color( barSubcontrol );
|
|
|
|
const auto shape = diagram->boxShapeHint( barSubcontrol );
|
|
barNode->updateFilling( barRect, shape, color );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DiagramDataNode* dataPointNode;
|
|
|
|
if( chartNode->childCount() > nodeIndex )
|
|
{
|
|
dataPointNode = static_cast< DiagramDataNode* >(
|
|
chartNode->childAtIndex( nodeIndex ) );
|
|
}
|
|
else
|
|
{
|
|
dataPointNode = new DiagramDataNode;
|
|
chartNode->appendChildNode( dataPointNode );
|
|
}
|
|
|
|
DiagramDataNode::Type nodeType;
|
|
|
|
if( type == Diagram::Line )
|
|
{
|
|
nodeType = DiagramDataNode::Line;
|
|
color = diagram->color( lineSubcontrol );
|
|
}
|
|
else
|
|
{
|
|
nodeType = DiagramDataNode::Area;
|
|
color = diagram->color( areaSubcontrol );
|
|
}
|
|
|
|
dataPointNode->update( rect, nodeType, color,
|
|
dataPoints, yMax, false, lineWidth );
|
|
}
|
|
nodeIndex++;
|
|
}
|
|
}
|
|
|
|
while( nodeIndex < chartNode->childCount() )
|
|
{
|
|
chartNode->removeChildNode( chartNode->childAtIndex( nodeIndex++ ) );
|
|
}
|
|
}
|
|
|
|
// ### also check for superfluous nodes here
|
|
|
|
return node;
|
|
}
|
|
|
|
QSGNode* DiagramSkinlet::updateSeparatorNode( const Diagram* diagram, QSGNode* node ) const
|
|
{
|
|
const int xGridLines = diagram->xGridLines();
|
|
|
|
if( xGridLines <= 0 )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
auto* separatorNode = static_cast< DiagramSegmentsNode* >( node );
|
|
|
|
if( separatorNode == nullptr )
|
|
{
|
|
separatorNode = new DiagramSegmentsNode;
|
|
}
|
|
|
|
using Q = Diagram;
|
|
const QRectF rect = diagram->subControlRect( Q::Chart );
|
|
const QColor color = diagram->color( Q::Segments );
|
|
const QVector< QVector< QPointF > > dataPoints = diagram->dataPoints();
|
|
|
|
separatorNode->update( rect, color, dataPoints, xGridLines );
|
|
|
|
return separatorNode;
|
|
}
|
|
|
|
#include "moc_DiagramSkinlet.cpp"
|