iup-stack/iup/srcplot/iupPlotAxis.cpp

783 lines
19 KiB
C++
Raw Permalink Normal View History

2023-02-20 16:44:45 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "iupPlot.h"
static inline void iPlotCheckMinMax(double &inoutMin, double &inoutMax)
{
if (inoutMin > inoutMax)
{
double theTmp = inoutMin;
inoutMin = inoutMax;
inoutMax = theTmp;
}
}
static int iPlotCountDigit(int inNum)
{
int theCount = 0;
while (inNum != 0)
{
inNum = inNum / 10;
theCount++;
}
if (theCount == 0) theCount = 1;
return theCount;
}
static int iPlotEstimateNumberCharCount(bool inFormatAuto, const char* inFormatString, double inMin, double inMax)
{
int thePrecision = 0;
while (*inFormatString)
{
if (*inFormatString == '.')
break;
inFormatString++;
}
if (*inFormatString == '.')
{
inFormatString++;
iupStrToInt(inFormatString, &thePrecision);
}
if (inFormatAuto)
{
int theMinPrecision = iupPlotCalcPrecision(inMin);
int theMaxPrecision = iupPlotCalcPrecision(inMax);
if (theMinPrecision > theMaxPrecision)
thePrecision = iupPlotMax(thePrecision, theMinPrecision);
else
thePrecision = iupPlotMax(thePrecision, theMaxPrecision);
}
int theMin = iupPlotRound(inMin);
int theMax = iupPlotRound(inMax);
int theNumDigitMin = iPlotCountDigit(theMin);
int theNumDigitMax = iPlotCountDigit(theMax);
if (theNumDigitMin > theNumDigitMax)
thePrecision += theNumDigitMin;
else
thePrecision += theNumDigitMax;
thePrecision += 3; // sign, decimal symbol, exp
return thePrecision;
}
static bool iPlotGetTickFormat(Ihandle* ih, IFnssds formatticknumber_cb, char* inBuf, const char *inFormatString, double inValue)
{
char* decimal_symbol = IupGetGlobal("DEFAULTDECIMALSYMBOL");
if (formatticknumber_cb)
{
int ret = formatticknumber_cb(ih, inBuf, (char*)inFormatString, inValue, decimal_symbol);
if (ret == IUP_IGNORE)
return false;
else if (ret == IUP_CONTINUE)
iupStrPrintfDoubleLocale(inBuf, inFormatString, inValue, decimal_symbol);
}
else
iupStrPrintfDoubleLocale(inBuf, inFormatString, inValue, decimal_symbol);
return true;
}
/************************************************************************************************/
double iupPlotTrafoLinear::Transform(double inValue) const
{
return inValue * mSlope + mOffset;
}
double iupPlotTrafoLinear::TransformBack(double inValue) const
{
if (mSlope != 0)
return (inValue - mOffset) / mSlope;
else
return 0;
}
bool iupPlotTrafoLinear::Calculate(int inBegin, int inEnd, const iupPlotAxis& inAxis)
{
double theDataRange = inAxis.mMax - inAxis.mMin;
if (theDataRange < kFloatSmall)
return false;
double theMin = inAxis.mMin;
if (inAxis.mDiscrete)
{
theDataRange++;
theMin -= 0.5;
}
double theTargetRange = inEnd - inBegin;
double theScale = theTargetRange / theDataRange;
if (!inAxis.mReverse)
mOffset = inBegin - theMin * theScale;
else
mOffset = inEnd + theMin * theScale;
mSlope = theScale;
if (inAxis.mReverse)
mSlope *= -1;
return true;
}
/************************************************************************************************/
double iupPlotTrafoLog::Transform(double inValue) const
{
if (inValue < kLogMinClipValue) inValue = kLogMinClipValue;
return iupPlotLog(inValue, mBase)*mSlope + mOffset;
}
double iupPlotTrafoLog::TransformBack(double inValue) const
{
if (mSlope != 0)
return iupPlotExp((inValue - mOffset) / mSlope, mBase);
else
return 0;
}
bool iupPlotTrafoLog::Calculate(int inBegin, int inEnd, const iupPlotAxis& inAxis)
{
double theBase = inAxis.mLogBase;
double theDataRange = iupPlotLog(inAxis.mMax, theBase) - iupPlotLog(inAxis.mMin, theBase);
if (theDataRange < kFloatSmall)
return false;
double theTargetRange = inEnd - inBegin;
double theScale = theTargetRange / theDataRange;
if (!inAxis.mReverse)
mOffset = inBegin - iupPlotLog(inAxis.mMin, theBase) * theScale;
else
mOffset = inEnd + iupPlotLog(inAxis.mMin, theBase) * theScale;
mBase = theBase;
mSlope = theScale;
if (inAxis.mReverse)
mSlope *= -1;
return true;
}
/************************************************************************************************/
void iupPlotAxis::Init()
{
if (mLogScale)
{
mTickIter = &mLogTickIter;
mTrafo = &mLogTrafo;
}
else
{
mTickIter = &mLinTickIter;
mTrafo = &mLinTrafo;
}
mTickIter->SetAxis(this);
if (mPosition == IUP_PLOT_START)
mReverseTicksLabel = false;
if (mPosition == IUP_PLOT_END)
mReverseTicksLabel = true;
}
void iupPlotAxis::GetTickNumberSize(cdCanvas* canvas, int *outWitdh, int *outHeight) const
{
int theTickFontWidth, theTickFontHeight;
SetFont(canvas, mTick.mFontStyle, mTick.mFontSize);
cdCanvasGetTextSize(canvas, "1234567890.", &theTickFontWidth, &theTickFontHeight);
theTickFontWidth /= 11;
if (outHeight) *outHeight = theTickFontHeight;
if (outWitdh) *outWitdh = theTickFontWidth * iPlotEstimateNumberCharCount(mTick.mFormatAuto, mTick.mFormatString, mHasZoom? mNoZoomMin: mMin, mHasZoom? mNoZoomMax: mMax);
}
void iupPlotAxis::SetNamedTickIter(const iupPlotDataString *inStringData)
{
mTickIter = &mNamedTickIter;
mTickIter->SetAxis(this);
mNamedTickIter.SetStringList(inStringData);
}
void iupPlotAxis::CheckZoomOutLimit(double inRange)
{
if (mMin < mNoZoomMin)
{
mMin = mNoZoomMin;
mMax = mMin + inRange;
if (mMax > mNoZoomMax)
mMax = mNoZoomMax;
}
if (mMax > mNoZoomMax)
{
mMax = mNoZoomMax;
mMin = mMax - inRange;
if (mMin < mNoZoomMin)
mMin = mNoZoomMin;
}
}
void iupPlotAxis::InitZoom()
{
if (!mHasZoom)
{
// Save NoZoom state values
mNoZoomMin = mMin;
mNoZoomMax = mMax;
mNoZoomAutoScaleMin = mAutoScaleMin;
mNoZoomAutoScaleMax = mAutoScaleMax;
mHasZoom = true;
// disable AutoScale
mAutoScaleMin = false;
mAutoScaleMax = false;
}
}
bool iupPlotAxis::ResetZoom()
{
if (mHasZoom)
{
mHasZoom = false;
// Restore NoZoom state values
mMin = mNoZoomMin;
mMax = mNoZoomMax;
mAutoScaleMin = mNoZoomAutoScaleMin;
mAutoScaleMax = mNoZoomAutoScaleMax;
return true;
}
return false;
}
bool iupPlotAxis::ZoomOut(double inCenter)
{
if (inCenter < mMin || inCenter > mMax)
return false;
if (!mHasZoom)
return false;
double theRange = mMax - mMin;
double theNewRange = theRange * 1.1; // 10%
double theFactor = (inCenter - mMin) / theRange;
double theOffset = (theNewRange - theRange);
mMin -= theOffset*theFactor;
mMax += theOffset*(1.0 - theFactor);
CheckZoomOutLimit(theNewRange);
if (mMin == mNoZoomMin && mMax == mNoZoomMax)
ResetZoom();
return true;
}
bool iupPlotAxis::ZoomIn(double inCenter)
{
if (inCenter < mMin || inCenter > mMax)
return false;
InitZoom();
double theRange = mMax - mMin;
double theNewRange = theRange * 0.9; // 10%
double theFactor = (inCenter - mMin) / theRange;
double theOffset = (theRange - theNewRange);
mMin += theOffset*theFactor;
mMax -= theOffset*(1.0 - theFactor);
// Check limits only in ZoomOut and Pan
return true;
}
bool iupPlotAxis::ZoomTo(double inMin, double inMax)
{
InitZoom();
iPlotCheckMinMax(inMin, inMax);
if (inMin > mNoZoomMax || inMax < mNoZoomMin)
return false;
if (inMin < mNoZoomMin) inMin = mNoZoomMin;
if (inMax > mNoZoomMax) inMax = mNoZoomMax;
mMin = inMin;
mMax = inMax;
if (mMin == mNoZoomMin && mMax == mNoZoomMax)
ResetZoom();
return true;
}
bool iupPlotAxis::Pan(double inOffset)
{
if (!mHasZoom)
return false;
double theRange = mMax - mMin;
mMin = mPanMin - inOffset;
mMax = mMin + theRange;
CheckZoomOutLimit(theRange);
return true;
}
bool iupPlotAxis::Scroll(double inDelta, bool inFullPage)
{
if (!mHasZoom)
return false;
double theRange = mMax - mMin;
double thePage;
if (inFullPage)
thePage = theRange;
else
thePage = theRange / 10.0;
double theOffset = thePage * inDelta;
mMin += theOffset;
mMax += theOffset;
CheckZoomOutLimit(theRange);
return true;
}
bool iupPlotAxis::ScrollTo(double inMin)
{
if (inMin < mNoZoomMin || inMin > mNoZoomMax)
return false;
if (!mHasZoom)
return false;
double theRange = mMax - mMin;
mMin = inMin;
mMax = mMin + theRange;
CheckZoomOutLimit(theRange);
return true;
}
/************************************************************************************************/
static inline void iPlotDrawRotatedText(cdCanvas* canvas, double inX, double inY, double inDegrees, int inAlignment, const char *inString)
{
cdCanvasTextAlignment(canvas, inAlignment);
double theOldOrientation = cdCanvasTextOrientation(canvas, inDegrees);
cdfCanvasText(canvas, inX, inY, inString);
cdCanvasTextOrientation(canvas, theOldOrientation);
}
static void iPlotFillArrowI(cdCanvas* canvas, int inX1, int inY1, int inX2, int inY2, int inX3, int inY3)
{
cdCanvasBegin(canvas, CD_FILL);
cdCanvasVertex(canvas, inX1, inY1);
cdCanvasVertex(canvas, inX2, inY2);
cdCanvasVertex(canvas, inX3, inY3);
cdCanvasEnd(canvas);
}
static void iPlotDrawArrow(cdCanvas* canvas, double inX, double inY, int inVertical, int inDirection, int inSize)
{
int theX = iupPlotRound(inX);
int theY = iupPlotRound(inY);
int theSizeDir = iupPlotRound(inSize * 0.7);
if (inVertical)
{
cdfCanvasLine(canvas, inX, inY, inX, inY + inDirection*inSize); // complement the axis
int theY1 = iupPlotRound(inY + inDirection*inSize);
int theY2 = theY1 - inDirection*theSizeDir;
iPlotFillArrowI(canvas, theX, theY1,
theX - theSizeDir, theY2,
theX + theSizeDir, theY2);
}
else
{
cdfCanvasLine(canvas, inX, inY, inX + inDirection*inSize, inY);
int theX1 = iupPlotRound(inX + inDirection*inSize);
int theX2 = theX1 - inDirection*theSizeDir;
iPlotFillArrowI(canvas, theX1, theY,
theX2, theY - theSizeDir,
theX2, theY + theSizeDir);
}
}
void iupPlotAxis::SetFont(cdCanvas* canvas, int inFontStyle, int inFontSize) const
{
if (inFontStyle == -1) inFontStyle = mDefaultFontStyle;
if (inFontSize == 0) inFontSize = mDefaultFontSize;
cdCanvasFont(canvas, NULL, inFontStyle, inFontSize);
}
int iupPlotAxis::GetTickNumberHeight(cdCanvas* canvas) const
{
int height;
if (mTick.mRotateNumber)
{
int theXTickNumberWidth;
GetTickNumberSize(canvas, &theXTickNumberWidth, NULL);
height = theXTickNumberWidth;
}
else
{
int theXTickNumberHeight;
GetTickNumberSize(canvas, NULL, &theXTickNumberHeight);
height = theXTickNumberHeight;
}
return height;
}
int iupPlotAxis::GetTickNumberWidth(cdCanvas* canvas) const
{
int width;
if (mTick.mRotateNumber)
{
int theYTickNumberHeight;
GetTickNumberSize(canvas, NULL, &theYTickNumberHeight);
width = theYTickNumberHeight;
}
else
{
int theYTickNumberWidth;
GetTickNumberSize(canvas, &theYTickNumberWidth, NULL);
width = theYTickNumberWidth;
}
return width;
}
int iupPlotAxis::GetArrowSize() const
{
return mTick.mMinorSize + 2; // to avoid too small sizes
}
/*****************************************************************************/
double iupPlotAxisX::GetScreenYOriginX(const iupPlotAxis& inAxisY) const
{
double theTargetY = 0;
if (mPosition != IUP_PLOT_CROSSORIGIN)
{
if (mPosition == IUP_PLOT_START)
{
if (inAxisY.mReverse)
theTargetY = inAxisY.mMax;
else
theTargetY = inAxisY.mMin;
}
else
{
if (inAxisY.mReverse)
theTargetY = inAxisY.mMin;
else
theTargetY = inAxisY.mMax;
}
}
if (inAxisY.mDiscrete)
theTargetY -= 0.5;
return inAxisY.mTrafo->Transform(theTargetY);
}
bool iupPlotAxisX::DrawX(const iupPlotRect &inRect, cdCanvas* canvas, const iupPlotAxis& inAxisY, Ihandle* ih) const
{
if (!mShow)
return true;
cdCanvasSetForeground(canvas, mColor);
iupPlotDrawSetLineStyle(canvas, CD_CONTINUOUS, mLineWidth);
double theScreenY = GetScreenYOriginX(inAxisY);
double theScreenX1 = inRect.mX;
double theScreenX2 = theScreenX1 + inRect.mWidth;
cdfCanvasLine(canvas, theScreenX1, theScreenY, theScreenX2, theScreenY);
if (mShowArrow)
{
if (!mReverse)
iPlotDrawArrow(canvas, theScreenX2, theScreenY, 0, 1, GetArrowSize());
else
iPlotDrawArrow(canvas, theScreenX1, theScreenY, 0, -1, GetArrowSize());
}
if (mTick.mShow)
{
if (!mTickIter->Init())
return false;
double theX;
bool theIsMajorTick;
char theFormatString[30];
strcpy(theFormatString, mTick.mFormatString);
IFnssds formatticknumber_cb = (IFnssds)IupGetCallback(ih, "XTICKFORMATNUMBER_CB");
if (mTick.mShowNumber)
SetFont(canvas, mTick.mFontStyle, mTick.mFontSize);
while (mTickIter->GetNextTick(theX, theIsMajorTick, theFormatString))
DrawXTick(theX, theScreenY, theIsMajorTick, theFormatString, canvas, ih, formatticknumber_cb);
int theTickSpace = mTick.mMajorSize; // skip major tick
if (mTick.mShowNumber)
theTickSpace += GetTickNumberHeight(canvas) + mTick.mMinorSize; // Use minor size as spacing
if (mReverseTicksLabel)
theScreenY += theTickSpace;
else
theScreenY -= theTickSpace;
}
if (GetLabel())
{
SetFont(canvas, mFontStyle, mFontSize);
int theLabelSpacing = mLabelSpacing;
if (mLabelSpacing == -1)
{
int theXFontHeight;
cdCanvasGetFontDim(canvas, NULL, &theXFontHeight, NULL, NULL);
theLabelSpacing = theXFontHeight / 10; // default spacing
}
if (mReverseTicksLabel)
theScreenY += theLabelSpacing;
else
theScreenY -= theLabelSpacing;
if (mLabelCentered)
{
double theScreenX = theScreenX1 + inRect.mWidth / 2;
iupPlotDrawText(canvas, theScreenX, theScreenY, mReverseTicksLabel? CD_SOUTH: CD_NORTH, GetLabel());
}
else
{
double theScreenX = theScreenX2;
iupPlotDrawText(canvas, theScreenX, theScreenY, mReverseTicksLabel? CD_SOUTH_EAST: CD_NORTH_EAST, GetLabel());
}
}
return true;
}
void iupPlotAxisX::DrawXTick(double inX, double inScreenY, bool inMajor, const char*inFormatString, cdCanvas* canvas, Ihandle* ih, IFnssds formatticknumber_cb) const
{
int theTickSize;
double theScreenX = mTrafo->Transform(inX);
if (inMajor)
{
theTickSize = mTick.mMajorSize;
if (mTick.mShowNumber)
{
char theBuf[128];
if (iPlotGetTickFormat(ih, formatticknumber_cb, theBuf, inFormatString, inX))
{
double theScreenY;
if (mReverseTicksLabel)
theScreenY = inScreenY + theTickSize + mTick.mMinorSize; // Use minor size as spacing
else
theScreenY = inScreenY - theTickSize - mTick.mMinorSize; // Use minor size as spacing
// SetFont called in DrawX
if (mTick.mRotateNumber)
iPlotDrawRotatedText(canvas, theScreenX, theScreenY, mTick.mRotateNumberAngle, mReverseTicksLabel ? CD_WEST : CD_EAST, theBuf);
else
iupPlotDrawText(canvas, theScreenX, theScreenY, mReverseTicksLabel ? CD_SOUTH : CD_NORTH, theBuf);
}
}
}
else
theTickSize = mTick.mMinorSize;
if (mReverseTicksLabel)
cdfCanvasLine(canvas, theScreenX, inScreenY, theScreenX, inScreenY + theTickSize);
else
cdfCanvasLine(canvas, theScreenX, inScreenY, theScreenX, inScreenY - theTickSize);
}
/*****************************************************************************/
double iupPlotAxisY::GetScreenXOriginY(const iupPlotAxis& inAxisX) const
{
double theTargetX = 0;
if (mPosition != IUP_PLOT_CROSSORIGIN)
{
if (mPosition == IUP_PLOT_START)
{
if (inAxisX.mReverse)
theTargetX = inAxisX.mMax;
else
theTargetX = inAxisX.mMin;
}
else
{
if (inAxisX.mReverse)
theTargetX = inAxisX.mMin;
else
theTargetX = inAxisX.mMax;
}
}
if (inAxisX.mDiscrete)
theTargetX -= 0.5;
return inAxisX.mTrafo->Transform(theTargetX);
}
bool iupPlotAxisY::DrawY(const iupPlotRect &inRect, cdCanvas* canvas, const iupPlotAxis& inAxisX, Ihandle* ih) const
{
if (!mShow)
return true;
cdCanvasSetForeground(canvas, mColor);
iupPlotDrawSetLineStyle(canvas, CD_CONTINUOUS, mLineWidth);
double theScreenX = GetScreenXOriginY(inAxisX);
double theScreenY1 = inRect.mY;
double theScreenY2 = theScreenY1 + inRect.mHeight;
cdfCanvasLine(canvas, theScreenX, theScreenY1, theScreenX, theScreenY2);
if (mShowArrow)
{
if (!mReverse)
iPlotDrawArrow(canvas, theScreenX, theScreenY2, 1, 1, GetArrowSize());
else
iPlotDrawArrow(canvas, theScreenX, theScreenY1, 1, -1, GetArrowSize());
}
if (mTick.mShow)
{
if (!mTickIter->Init())
return false;
double theY;
bool theIsMajorTick;
char theFormatString[30];
strcpy(theFormatString, mTick.mFormatString);
IFnssds formatticknumber_cb = (IFnssds)IupGetCallback(ih, "YTICKFORMATNUMBER_CB");
if (mTick.mShowNumber)
SetFont(canvas, mTick.mFontStyle, mTick.mFontSize);
while (mTickIter->GetNextTick(theY, theIsMajorTick, theFormatString))
DrawYTick(theY, theScreenX, theIsMajorTick, theFormatString, canvas, ih, formatticknumber_cb);
int theTickSpace = mTick.mMajorSize; // skip major tick
if (mTick.mShowNumber)
theTickSpace += GetTickNumberWidth(canvas) + mTick.mMinorSize; // Use minor size as spacing
if (mReverseTicksLabel)
theScreenX += theTickSpace;
else
theScreenX -= theTickSpace;
}
if (GetLabel())
{
SetFont(canvas, mFontStyle, mFontSize);
int theLabelSpacing = mLabelSpacing;
if (mLabelSpacing == -1)
{
int theYFontHeight;
cdCanvasGetFontDim(canvas, NULL, &theYFontHeight, NULL, NULL);
theLabelSpacing = theYFontHeight / 10; // default spacing
}
if (mReverseTicksLabel)
theScreenX += theLabelSpacing;
else
theScreenX -= theLabelSpacing;
if (mLabelCentered)
{
double theScreenY = theScreenY1 + inRect.mHeight / 2;
iPlotDrawRotatedText(canvas, theScreenX, theScreenY, 90, mReverseTicksLabel? CD_NORTH: CD_SOUTH, GetLabel());
}
else
{
double theScreenY = theScreenY2;
iPlotDrawRotatedText(canvas, theScreenX, theScreenY, 90, mReverseTicksLabel? CD_NORTH_EAST: CD_SOUTH_EAST, GetLabel());
}
}
return true;
}
void iupPlotAxisY::DrawYTick(double inY, double inScreenX, bool inMajor, const char* inFormatString, cdCanvas* canvas, Ihandle* ih, IFnssds formatticknumber_cb) const
{
int theTickSize;
double theScreenY = mTrafo->Transform(inY);
if (inMajor)
{
theTickSize = mTick.mMajorSize;
if (mTick.mShowNumber)
{
char theBuf[128];
if (iPlotGetTickFormat(ih, formatticknumber_cb, theBuf, inFormatString, inY))
{
double theScreenX;
if (mReverseTicksLabel)
theScreenX = inScreenX + theTickSize + mTick.mMinorSize; // Use minor size as spacing
else
theScreenX = inScreenX - theTickSize - mTick.mMinorSize; // Use minor size as spacing
// SetFont called in DrawX
if (mTick.mRotateNumber)
iPlotDrawRotatedText(canvas, theScreenX, theScreenY, mTick.mRotateNumberAngle, mReverseTicksLabel ? CD_NORTH : CD_SOUTH, theBuf);
else
iupPlotDrawText(canvas, theScreenX, theScreenY, mReverseTicksLabel ? CD_WEST : CD_EAST, theBuf);
}
}
}
else
theTickSize = mTick.mMinorSize;
if (mReverseTicksLabel)
cdfCanvasLine(canvas, inScreenX, theScreenY, inScreenX + theTickSize, theScreenY);
else
cdfCanvasLine(canvas, inScreenX, theScreenY, inScreenX - theTickSize, theScreenY);
}