/****************************************************************************** * 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 #include #include #include 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"