iup-stack/iup/srcplot/iupPlot.cpp

608 lines
16 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;
}
}
/************************************************************************************************/
iupPlot::iupPlot(Ihandle* _ih, int inDefaultFontStyle, int inDefaultFontSize)
:ih(_ih), mCurrentDataSet(-1), mRedraw(true), mDataSetListCount(0), mCrossHairH(false), mCrossHairV(false),
mGrid(true), mGridMinor(false), mViewportSquare(false), mScaleEqual(false), mHighlightMode(IUP_PLOT_HIGHLIGHT_NONE),
mDefaultFontSize(inDefaultFontSize), mDefaultFontStyle(inDefaultFontStyle), mScreenTolerance(5),
mAxisX(inDefaultFontStyle, inDefaultFontSize), mAxisY(inDefaultFontStyle, inDefaultFontSize),
mCrossHairX(0), mCrossHairY(0), mShowSelectionBand(false), mDataSetListMax(20), mDataSetClipping(IUP_PLOT_CLIPAREA)
{
mDataSetList = (iupPlotDataSet**)malloc(sizeof(iupPlotDataSet*)* mDataSetListMax); /* use malloc because we will use realloc */
memset(mDataSetList, 0, sizeof(iupPlotDataSet*)* mDataSetListMax);
}
iupPlot::~iupPlot()
{
RemoveAllDataSets();
free(mDataSetList); /* use free because we used malloc */
}
void iupPlot::SetViewport(int x, int y, int w, int h)
{
mViewportBack.mX = x;
mViewportBack.mY = y;
mViewportBack.mWidth = w;
mViewportBack.mHeight = h;
if (mViewportSquare && w != h)
{
/* take the smallest length */
if (w > h)
{
mViewport.mX = x + (w - h) / 2;
mViewport.mY = y;
mViewport.mWidth = h;
mViewport.mHeight = h;
}
else
{
mViewport.mX = x;
mViewport.mY = y + (h - w) / 2;
mViewport.mWidth = w;
mViewport.mHeight = w;
}
}
else
{
mViewport.mX = x;
mViewport.mY = y;
mViewport.mWidth = w;
mViewport.mHeight = h;
}
mRedraw = true;
}
void iupPlot::SetFont(cdCanvas* canvas, int inFontStyle, int inFontSize) const
{
if (inFontStyle == -1) inFontStyle = mDefaultFontStyle;
if (inFontSize == 0) inFontSize = mDefaultFontSize;
cdCanvasFont(canvas, NULL, inFontStyle, inFontSize);
}
void iupPlot::UpdateMultibarCount()
{
int i, count = 0, index = 0;
for (i = 0; i < mDataSetListCount; i++)
{
if (mDataSetList[i]->mMode == IUP_PLOT_MULTIBAR)
count++;
}
for (i = 0; i < mDataSetListCount; i++)
{
if (mDataSetList[i]->mMode == IUP_PLOT_MULTIBAR)
{
mDataSetList[i]->mMultibarCount = count;
mDataSetList[i]->mMultibarIndex = index;
index++;
}
else
{
mDataSetList[i]->mMultibarCount = 0;
mDataSetList[i]->mMultibarIndex = 0;
}
}
}
static long iPlotGetDefaultColor(int index)
{
switch (index)
{
case 0: return cdEncodeColor(255, 0, 0);
case 1: return cdEncodeColor(0, 255, 0);
case 2: return cdEncodeColor(0, 0, 255);
case 3: return cdEncodeColor(0, 255, 255);
case 4: return cdEncodeColor(255, 0, 255);
case 5: return cdEncodeColor(255, 255, 0);
case 6: return cdEncodeColor(128, 0, 0);
case 7: return cdEncodeColor(0, 128, 0);
case 8: return cdEncodeColor(0, 0, 128);
case 9: return cdEncodeColor(0, 128, 128);
case 10: return cdEncodeColor(128, 0, 128);
case 11: return cdEncodeColor(128, 128, 0);
default: return cdEncodeColor(0, 0, 0); // the last must be always black
}
}
long iupPlot::GetNextDataSetColor() const
{
int def_color = 0, i = 0;
long theColor;
do
{
theColor = iPlotGetDefaultColor(def_color);
for (i = 0; i < mDataSetListCount; i++)
{
// already used, get another
long theDataSetColor = cdEncodeAlpha(mDataSetList[i]->mColor, 255);
if (theDataSetColor == theColor)
break;
}
// not found, use it
if (i == mDataSetListCount)
break;
def_color++;
} while (def_color < 12);
return theColor;
}
void iupPlot::AddDataSet(iupPlotDataSet* inDataSet)
{
if (mDataSetListCount >= mDataSetListMax)
{
int old_max = mDataSetListMax;
mDataSetListMax += 20;
mDataSetList = (iupPlotDataSet**)realloc(mDataSetList, sizeof(iupPlotDataSet*)* mDataSetListMax);
memset(mDataSetList + old_max, 0, sizeof(iupPlotDataSet*)* (mDataSetListMax - old_max));
}
if (mDataSetListCount < mDataSetListMax)
{
long theColor = GetNextDataSetColor();
mCurrentDataSet = mDataSetListCount;
mDataSetListCount++;
char theLegend[30];
sprintf(theLegend, "plot %d", mCurrentDataSet);
mDataSetList[mCurrentDataSet] = inDataSet;
inDataSet->SetName(theLegend);
inDataSet->mColor = theColor;
}
}
void iupPlot::RemoveDataSet(int inIndex)
{
if (mCurrentDataSet == mDataSetListCount - 1)
mCurrentDataSet--;
delete mDataSetList[inIndex];
for (int i = inIndex; i < mDataSetListCount; i++)
mDataSetList[i] = mDataSetList[i + 1];
mDataSetList[mDataSetListCount - 1] = NULL;
mDataSetListCount--;
}
int iupPlot::FindDataSet(const char* inName) const
{
for (int ds = 0; ds < mDataSetListCount; ds++)
{
if (iupStrEqualNoCase(mDataSetList[ds]->GetName(), inName))
return ds;
}
return -1;
}
void iupPlot::RemoveAllDataSets()
{
for (int ds = 0; ds < mDataSetListCount; ds++)
{
delete mDataSetList[ds];
}
mDataSetListCount = 0;
}
void iupPlot::ClearHighlight()
{
for (int ds = 0; ds < mDataSetListCount; ds++)
{
iupPlotDataSet* dataset = mDataSetList[ds];
dataset->mHighlightedCurve = false;
dataset->mHighlightedSample = -1;
}
}
bool iupPlot::FindDataSetSample(double inScreenX, double inScreenY, int &outIndex, const char* &outName, int &outSampleIndex, double &outX, double &outY, const char* &outStrX) const
{
if (!mAxisX.mTrafo || !mAxisY.mTrafo)
return false;
/* search for datasets in the inverse order they are drawn */
for (int ds = mDataSetListCount - 1; ds >= 0; ds--)
{
iupPlotDataSet* dataset = mDataSetList[ds];
if (dataset->FindSample(mAxisX.mTrafo, mAxisY.mTrafo, inScreenX, inScreenY, mScreenTolerance, outSampleIndex, outX, outY))
{
const iupPlotData *theXData = dataset->GetDataX();
if (theXData->IsString())
{
const iupPlotDataString *theStringXData = (const iupPlotDataString *)(theXData);
outStrX = theStringXData->GetSampleString(outSampleIndex);
}
else
outStrX = NULL;
outIndex = ds;
outName = dataset->GetName();
return true;
}
}
return false;
}
bool iupPlot::FindDataSetSegment(double inScreenX, double inScreenY, int &outIndex, const char* &outName, int &outSampleIndex1, double &outX1, double &outY1, int &outSampleIndex2, double &outX2, double &outY2) const
{
if (!mAxisX.mTrafo || !mAxisY.mTrafo)
return false;
/* search for datasets in the inverse order they are drawn */
for (int ds = mDataSetListCount - 1; ds >= 0; ds--)
{
iupPlotDataSet* dataset = mDataSetList[ds];
// only for modes that have lines connecting the samples.
if (dataset->mMode != IUP_PLOT_LINE &&
dataset->mMode != IUP_PLOT_MARKLINE &&
dataset->mMode != IUP_PLOT_AREA &&
dataset->mMode != IUP_PLOT_ERRORBAR)
continue;
if (dataset->FindSegment(mAxisX.mTrafo, mAxisY.mTrafo, inScreenX, inScreenY, mScreenTolerance, outSampleIndex1, outSampleIndex2, outX1, outY1, outX2, outY2))
{
outIndex = ds;
outName = dataset->GetName();
return true;
}
}
return false;
}
void iupPlot::SelectDataSetSamples(double inMinX, double inMaxX, double inMinY, double inMaxY)
{
bool theChanged = false;
iPlotCheckMinMax(inMinX, inMaxX);
iPlotCheckMinMax(inMinY, inMaxY);
IFniiddi select_cb = (IFniiddi)IupGetCallback(ih, "SELECT_CB");
if (select_cb)
{
Icallback cb = IupGetCallback(ih, "SELECTBEGIN_CB");
if (cb && cb(ih) == IUP_IGNORE)
return;
}
for (int ds = 0; ds < mDataSetListCount; ds++)
{
iupPlotDataSet* dataset = mDataSetList[ds];
iupPlotSampleNotify theNotify = { ih, ds, select_cb };
if (dataset->SelectSamples(inMinX, inMaxX, inMinY, inMaxY, &theNotify))
theChanged = true;
}
if (select_cb)
{
Icallback cb = IupGetCallback(ih, "SELECTEND_CB");
if (cb)
return;
}
if (theChanged)
mRedraw = true;
}
void iupPlot::ClearDataSetSelection()
{
bool theChanged = false;
IFniiddi select_cb = (IFniiddi)IupGetCallback(ih, "SELECT_CB");
if (select_cb)
{
Icallback cb = IupGetCallback(ih, "SELECTBEGIN_CB");
if (cb && cb(ih) == IUP_IGNORE)
return;
}
for (int ds = 0; ds < mDataSetListCount; ds++)
{
iupPlotDataSet* dataset = mDataSetList[ds];
iupPlotSampleNotify theNotify = { ih, ds, select_cb };
if (dataset->ClearSelection(&theNotify))
theChanged = true;
}
if (select_cb)
{
Icallback cb = IupGetCallback(ih, "SELECTEND_CB");
if (cb)
return;
}
if (theChanged)
mRedraw = true;
}
void iupPlot::DeleteSelectedDataSetSamples()
{
bool theChanged = false;
IFniiddi delete_cb = (IFniiddi)IupGetCallback(ih, "DELETE_CB");
if (delete_cb)
{
Icallback cb = IupGetCallback(ih, "DELETEBEGIN_CB");
if (cb && cb(ih) == IUP_IGNORE)
return;
}
for (int ds = 0; ds < mDataSetListCount; ds++)
{
iupPlotDataSet* dataset = mDataSetList[ds];
iupPlotSampleNotify theNotify = { ih, ds, delete_cb };
if (dataset->DeleteSelectedSamples(&theNotify))
theChanged = true;
}
if (delete_cb)
{
Icallback cb = IupGetCallback(ih, "DELETEEND_CB");
if (cb)
return;
}
if (theChanged)
mRedraw = true;
}
void iupPlot::ConfigureAxis()
{
mAxisX.Init();
mAxisY.Init();
if (mAxisX.mLogScale)
mAxisY.mPosition = IUP_PLOT_START; // change at the other axis
else
{
if (mDataSetListCount > 0)
{
const iupPlotData *theXData = mDataSetList[0]->GetDataX(); // The first dataset will define the named tick usage
if (theXData->IsString())
{
const iupPlotDataString *theStringXData = (const iupPlotDataString *)(theXData);
mAxisX.SetNamedTickIter(theStringXData);
}
}
}
if (mAxisY.mLogScale)
mAxisX.mPosition = IUP_PLOT_START; // change at the other axis
}
iupPlotDataSet* iupPlot::HasPie() const
{
for (int ds = 0; ds < mDataSetListCount; ds++)
{
iupPlotDataSet* dataset = mDataSetList[ds];
if (dataset->mMode == IUP_PLOT_PIE)
return dataset;
}
return NULL;
}
void iupPlot::DataSetClipArea(cdCanvas* canvas, int xmin, int xmax, int ymin, int ymax) const
{
if (mDataSetClipping == IUP_PLOT_CLIPAREAOFFSET)
{
if (!mAxisY.HasZoom())
{
int yoff = (ymax - ymin) / 50; // 2%
if (yoff < 10) yoff = 10;
ymin -= yoff;
ymax += yoff;
}
if (!mAxisX.HasZoom())
{
int xoff = (xmax - xmin) / 50; // 2%
if (xoff < 10) xoff = 10;
xmin -= xoff;
xmax += xoff;
}
}
if (mDataSetClipping != IUP_PLOT_CLIPNONE)
cdCanvasClipArea(canvas, xmin, xmax, ymin, ymax);
}
bool iupPlot::PrepareRender(cdCanvas* canvas)
{
cdCanvasNativeFont(canvas, IupGetAttribute(ih, "FONT"));
ConfigureAxis();
if (!CalculateAxisRange())
return false;
if (!CheckRange(mAxisX))
return false;
if (!CheckRange(mAxisY))
return false;
CalculateTitlePos();
// Must be before calculate margins
CalculateTickSize(canvas, mAxisX.mTick);
CalculateTickSize(canvas, mAxisY.mTick);
CalculateMargins(canvas);
return true;
}
bool iupPlot::Render(cdCanvas* canvas)
{
if (!mRedraw)
return true;
// draw entire plot viewport
DrawBackground(canvas);
// Shift the drawing area to the plot viewport
cdCanvasOrigin(canvas, mViewport.mX, mViewport.mY);
// There are no additional transformations set in the CD canvas,
// all transformations are done here.
cdCanvasClip(canvas, CD_CLIPAREA);
// Draw axis and grid restricted only by the viewport
cdCanvasClipArea(canvas, 0, mViewport.mWidth - 1, 0, mViewport.mHeight - 1);
if (!mDataSetListCount)
return true;
cdCanvasNativeFont(canvas, IupGetAttribute(ih, "FONT"));
iupPlotRect theDataSetArea; /* Viewport - Margin (size only, no need for viewport offset) */
theDataSetArea.mX = mBack.mMargin.mLeft + mBack.mHorizPadding;
theDataSetArea.mY = mBack.mMargin.mBottom + mBack.mVertPadding;
theDataSetArea.mWidth = mViewport.mWidth - mBack.mMargin.mLeft - mBack.mMargin.mRight - 2 * mBack.mHorizPadding;
theDataSetArea.mHeight = mViewport.mHeight - mBack.mMargin.mTop - mBack.mMargin.mBottom - 2 * mBack.mVertPadding;
if (!CalculateTickSpacing(theDataSetArea, canvas))
return false;
if (!CalculateXTransformation(theDataSetArea))
return false;
if (!CalculateYTransformation(theDataSetArea))
return false;
IFnC pre_cb = (IFnC)IupGetCallback(ih, "PREDRAW_CB");
if (pre_cb)
pre_cb(ih, canvas);
if (mBack.GetImage())
DrawBackgroundImage(canvas);
if (!mGrid.DrawX(mAxisX.mTickIter, mAxisX.mTrafo, theDataSetArea, canvas))
return false;
if (mGrid.mShowX)
mGridMinor.DrawX(mAxisX.mTickIter, mAxisX.mTrafo, theDataSetArea, canvas);
if (!mGrid.DrawY(mAxisY.mTickIter, mAxisY.mTrafo, theDataSetArea, canvas))
return false;
if (mGrid.mShowY)
mGridMinor.DrawY(mAxisY.mTickIter, mAxisY.mTrafo, theDataSetArea, canvas);
if (!mAxisX.DrawX(theDataSetArea, canvas, mAxisY, ih))
return false;
if (!mAxisY.DrawY(theDataSetArea, canvas, mAxisX, ih))
return false;
if (mBox.mShow)
mBox.Draw(theDataSetArea, canvas);
// draw the datasets restricted to the dataset area with options
DataSetClipArea(canvas, theDataSetArea.mX, theDataSetArea.mX + theDataSetArea.mWidth - 1, theDataSetArea.mY, theDataSetArea.mY + theDataSetArea.mHeight - 1);
IFniiddi drawsample_cb = (IFniiddi)IupGetCallback(ih, "DRAWSAMPLE_CB");
iupPlotDataSet* pie_dataset = HasPie();
for (int ds = 0; ds < mDataSetListCount; ds++)
{
iupPlotDataSet* dataset = mDataSetList[ds];
iupPlotSampleNotify theNotify = { ih, ds, drawsample_cb };
if (pie_dataset)
{
if (dataset != pie_dataset)
continue;
else
dataset->DrawDataPie(mAxisX.mTrafo, mAxisY.mTrafo, canvas, &theNotify, mAxisY, mBack.mColor);
}
dataset->DrawData(mAxisX.mTrafo, mAxisY.mTrafo, canvas, &theNotify);
}
// draw the legend, crosshair and selection restricted to the dataset area
cdCanvasClipArea(canvas, theDataSetArea.mX, theDataSetArea.mX + theDataSetArea.mWidth - 1, theDataSetArea.mY, theDataSetArea.mY + theDataSetArea.mHeight - 1);
if (mCrossHairH)
DrawCrossHairH(theDataSetArea, canvas);
else if (mCrossHairV)
DrawCrossHairV(theDataSetArea, canvas);
if (mShowSelectionBand)
{
if (mSelectionBand.mX < theDataSetArea.mX)
{
mSelectionBand.mWidth = mSelectionBand.mX + mSelectionBand.mWidth - theDataSetArea.mX;
mSelectionBand.mX = theDataSetArea.mX;
}
if (mSelectionBand.mY < theDataSetArea.mY)
{
mSelectionBand.mHeight = mSelectionBand.mY + mSelectionBand.mHeight - theDataSetArea.mY;
mSelectionBand.mY = theDataSetArea.mY;
}
if (mSelectionBand.mX + mSelectionBand.mWidth > theDataSetArea.mX + theDataSetArea.mWidth)
mSelectionBand.mWidth = theDataSetArea.mX + theDataSetArea.mWidth - mSelectionBand.mX;
if (mSelectionBand.mY + mSelectionBand.mHeight > theDataSetArea.mY + theDataSetArea.mHeight)
mSelectionBand.mHeight = theDataSetArea.mY + theDataSetArea.mHeight - mSelectionBand.mY;
mBox.Draw(mSelectionBand, canvas);
}
IFnC post_cb = (IFnC)IupGetCallback(ih, "POSTDRAW_CB");
if (post_cb)
post_cb(ih, canvas);
if (pie_dataset)
DrawSampleColorLegend(pie_dataset, theDataSetArea, canvas, mLegend.mPos);
else if (!DrawLegend(theDataSetArea, canvas, mLegend.mPos))
return false;
// Draw title restricted only by the viewport
cdCanvasClipArea(canvas, 0, mViewport.mWidth - 1, 0, mViewport.mHeight - 1);
DrawTitle(canvas);
if (!IupGetInt(ih, "ACTIVE"))
DrawInactive(canvas);
mRedraw = false;
return true;
}