File indexing completed on 2024-12-15 04:02:28

0001 /*
0002  * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
0003  *
0004  * This file is part of the KD Chart library.
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #ifndef CARTESIANCOORDINATETRANSFORMATION_H
0010 #define CARTESIANCOORDINATETRANSFORMATION_H
0011 
0012 #include <QList>
0013 #include <QRectF>
0014 #include <QPointF>
0015 
0016 #include "KChartZoomParameters.h"
0017 
0018 #include <cmath>
0019 #include <limits>
0020 
0021 namespace KChart {
0022 
0023     // FIXME: if this struct is used more often, we need to make it a class
0024     // with proper accessor methods:
0025 
0026     /**
0027       * \internal
0028       */
0029     struct CoordinateTransformation {
0030 
0031         CoordinateTransformation()
0032             : axesCalcModeY( CartesianCoordinatePlane::Linear ),
0033               axesCalcModeX( CartesianCoordinatePlane::Linear ),
0034               isPositiveX( true ),
0035               isPositiveY( true )
0036         {}
0037 
0038         CartesianCoordinatePlane::AxesCalcMode axesCalcModeY;
0039         CartesianCoordinatePlane::AxesCalcMode axesCalcModeX;
0040 
0041         ZoomParameters zoom;
0042 
0043         QTransform transform;
0044         QTransform backTransform;
0045         // a logarithmic scale cannot cross zero, so we have to know which side we are on.
0046         bool isPositiveX;
0047         bool isPositiveY;
0048 
0049         qreal logTransform( qreal value, bool isPositiveRange ) const
0050         {
0051             if ( isPositiveRange ) {
0052                 return log10( value );
0053             } else  {
0054                 return -log10( -value );
0055             }
0056         }
0057 
0058         qreal logTransformBack( qreal value, bool wasPositive ) const
0059         {
0060             if ( wasPositive ) {
0061                 return pow( 10.0, value );
0062             } else {
0063                 return -pow( 10.0, -value );
0064             }
0065         }
0066 
0067         void updateTransform( const QRectF& constDataRect, const QRectF& screenRect )
0068         {
0069             QRectF dataRect = constDataRect;
0070             if ( axesCalcModeX == CartesianCoordinatePlane::Logarithmic ) {
0071                 // the data will be scaled by logTransform() later, so scale its bounds as well
0072                 isPositiveX = dataRect.left() >= 0.0;
0073                 dataRect.setLeft( logTransform( dataRect.left(), isPositiveX ) );
0074                 dataRect.setRight( logTransform( dataRect.right(), isPositiveX ) );
0075             }
0076             if ( axesCalcModeY == CartesianCoordinatePlane::Logarithmic ) {
0077                 isPositiveY = dataRect.top() >= 0.0;
0078                 dataRect.setTop( logTransform( dataRect.top(), isPositiveY  ) );
0079                 dataRect.setBottom( logTransform( dataRect.bottom(), isPositiveY ) );
0080             }
0081 
0082             transform.reset();
0083             // read the following transformation sequence from bottom to top(!)
0084             transform.translate( screenRect.left(), screenRect.bottom() );
0085             transform.scale( screenRect.width(), screenRect.height() );
0086 
0087             // TODO: mirror in case of "reverse" axes?
0088 
0089             // transform into screen space
0090             transform.translate( 0.5, -0.5 );
0091             transform.scale( zoom.xFactor, zoom.yFactor );
0092             transform.translate( -zoom.xCenter, 1.0 - zoom.yCenter );
0093             // zoom
0094             transform.scale( 1.0 / dataRect.width(), 1.0 / dataRect.height() );
0095             transform.translate( -dataRect.left(), -dataRect.bottom() );
0096             // transform into the unit square
0097 
0098             backTransform = transform.inverted();
0099         }
0100 
0101         // convert data space point to screen point
0102         inline QPointF translate( const QPointF& dataPoint ) const
0103         {
0104             QPointF data = dataPoint;
0105             if ( axesCalcModeX == CartesianCoordinatePlane::Logarithmic ) {
0106                 data.setX( logTransform( data.x(), isPositiveX  ) );
0107             }
0108             if ( axesCalcModeY == CartesianCoordinatePlane::Logarithmic ) {
0109                 data.setY( logTransform( data.y(), isPositiveY ) );
0110             }
0111 
0112             return transform.map( data );
0113         }
0114 
0115         // convert screen point to data space point
0116         inline const QPointF translateBack( const QPointF& screenPoint ) const
0117         {
0118             QPointF ret = backTransform.map( screenPoint );
0119             if ( axesCalcModeX == CartesianCoordinatePlane::Logarithmic ) {
0120                 ret.setX( logTransformBack( ret.x(), isPositiveX ) );
0121             }
0122             if ( axesCalcModeY == CartesianCoordinatePlane::Logarithmic ) {
0123                 ret.setY( logTransformBack( ret.y(), isPositiveY ) );
0124             }
0125             return ret;
0126         }
0127     };
0128 
0129     typedef QList<CoordinateTransformation> CoordinateTransformationList;
0130 
0131 }
0132 
0133 #endif