MVP Logo

Материализуя идеи

Не поднимешься в горы — не узнаешь высоты неба; не спустишься в бездну — не узнаешь толщи земли.

Requirements and pre-conditions:

  1. Chart series may contain different point types:
    1. First, let's call them 'automatic' - which may have been acquired by some logger process, in ordered, evenly spaced moments in time.
    2. Other type is 'manual', - meaning, that thesee may be acquired as a result of explicit user-to-device iteration during the same measurement session, as an automatic ones. These manual points usually have an arbitrary distributed x, and sometimes even y values.
  2. Chart series should draw automatic and manual points differently, while allowing these points to be iterated as a members of the same sequence.

Solution:
I derived my series class from TCustomSeries, overriding DrawAllValues smethod, and adding some properties to add distinct style to the manual points.
An extra event handler allows to plug-in function to differentiate between automatic and manual points in user-specific way.

Declaration:

class EsCustomSeries;

// Custom series class - adaptive painting of series with a small number of pints +
// way to distinguish 'manual' points and draw them separately
//
typedef bool __fastcall (__closure *onCheckManualPointMarkT)(const EsCustomSeries* sender, int valIdx, const UnicodeString& mark);

class EsCustomSeries : public TCustomSeries
{
public:
  __fastcall EsCustomSeries(TComponent* owner);

  __property TSeriesPointerStyle manualPointerStyle = {read=m_manualPointerStyle,write=manualPointerStyleSet};
  __property TAlphaColor manualPointerColor = {read=m_manualPointerColor,write=manualPointerColorSet};
  __property onCheckManualPointMarkT onCheckManualPointMark = {read=m_onCheckManualPointMark,write=m_onCheckManualPointMark};

protected:
  bool isManualPointMark(int valIdx, const UnicodeString& txt) const;

  void __fastcall manualPointerStyleSet(TSeriesPointerStyle ps);
  void __fastcall manualPointerColorSet(TAlphaColor clr);

  virtual void __fastcall DrawAllValues() override;

protected:
  onCheckManualPointMarkT m_onCheckManualPointMark;
  TSeriesPointerStyle m_manualPointerStyle;
  TAlphaColor m_manualPointerColor;
};

 The main job is done in custom DrawAllValues

__fastcall EsCustomSeries::EsCustomSeries(TComponent* owner) :
TCustomSeries(owner),
m_onCheckManualPointMark(nullptr),
m_manualPointerStyle(TSeriesPointerStyle::psNothing),
m_manualPointerColor(claNull)
{}
//---------------------------------------------------------------------------

void __fastcall EsCustomSeries::manualPointerStyleSet(TSeriesPointerStyle ps)
{
  if( m_manualPointerStyle != ps )
  {
    m_manualPointerStyle = ps;
    Repaint();
  }
}
//---------------------------------------------------------------------------

void __fastcall EsCustomSeries::manualPointerColorSet(TAlphaColor clr)
{
  if( m_manualPointerColor != clr )
  {
    m_manualPointerColor = clr;
    Repaint();
  }
}
//---------------------------------------------------------------------------

bool EsCustomSeries::isManualPointMark(int valIdx, const UnicodeString& txt) const
{
  if( m_onCheckManualPointMark )
    return m_onCheckManualPointMark(this, valIdx, txt);

  return false;
}
//---------------------------------------------------------------------------

void __fastcall EsCustomSeries::DrawAllValues()
{
  TCanvas3D* canvas = ParentChart->Canvas;
  if( !canvas )
    return;

  std::unique_ptr<TTeeBlend> tmpBlend;
  if( Transparency > 0)
    tmpBlend.reset(
      canvas->BeginBlending(
        TeeZeroRect,
        Transparency
      )
    );

  int tmpFirst = FirstDisplayedIndex();

  if( tmpFirst > 0 )
    --tmpFirst;

  bool tmpFraction = false;
  double tmpFractionValue = 0;

  int tmpLast;
  int cnt;
  if( DisplayLast == 1 )
    tmpLast = LastDisplayedIndex();
  else
  {
    cnt = Count();
    tmpLast = std::min(
      cnt-1,
      static_cast<int>(DisplayLast*cnt)
    );

    if( tmpLast < tmpFirst )
      tmpLast = tmpFirst;
    else if( tmpLast < cnt-1 )
      tmpFractionValue = (DisplayLast*cnt) - static_cast<int>(DisplayLast*cnt);

    tmpFraction = tmpFractionValue > 0;
  }

  cnt = tmpLast-tmpFirst+1;

  std::vector<TPointF> linePoints;
  std::vector<int> lineIdxs;
  std::vector<TPointF> markPoints;

  linePoints.reserve(cnt);
  lineIdxs.reserve(cnt + (tmpFraction ? 1 : 0) );

  markPoints.reserve(
    std::max(
      1,
      cnt/2
    )
  );

  if( tmpFraction )
    ++tmpLast;

  for(int idx = tmpFirst; idx <= tmpLast; ++idx)
  {
    const TPointF& point = {
      static_cast<float>(CalcXPos(idx)),
      static_cast<float>(CalcYPos(idx))
    };

    const UnicodeString& markTxt = ValueMarkText[idx];

    if( isManualPointMark(idx, markTxt) )
    {
      markPoints.push_back(
        point
      );
      continue;
    }

    linePoints.push_back(
      point
    );
    lineIdxs.push_back(
      idx
    );
  }

  if( tmpFraction )
    --tmpLast;

  try
  {
    canvas->AssignVisiblePenColor(
      LinePen,
      LinePen->Color
    );

    if( linePoints.size() > 2 )
    {
      canvas->Polyline(
        &linePoints[0],
        linePoints.size()-1
      );
    }
    else if(
      Pointer->Visible &&
      Pointer->Style != TSeriesPointerStyle::psNothing
    )
    {
      for( size_t idx = 0; idx < linePoints.size(); ++idx )
      {
        const TPointF& point = linePoints[idx];

        Pointer->DrawPointer(
          canvas,
          false,
          point.X,
          point.Y,
          Pointer->VertSize,
          Pointer->HorizSize,
          ValueColor[
            lineIdxs[idx]
          ],
          Pointer->Style
        );
      }
    }

    if(
      Pointer->Visible &&
      manualPointerStyle != TSeriesPointerStyle::psNothing &&
      manualPointerColor != claNull &&
      !markPoints.empty()
    )
    {
      canvas->AssignVisiblePenColor(
        LinePen,
        manualPointerColor
      );

      for( auto const& point : markPoints )
      {
        Pointer->DrawPointer(
          canvas,
          false,
          point.X,
          point.Y,
          Pointer->VertSize,
          Pointer->HorizSize,
          manualPointerColor,
          manualPointerStyle
        );
      }
    }
  }
  __finally
  {
    if( tmpBlend )
      canvas->EndBlending(
        tmpBlend.get()
      );
  }
}
//---------------------------------------------------------------------------

 The resulting code draws automatic points as Pointers, if there are a few points (< 3) in automatic sequence.

The manual points are drawn separately from the automatic sequence, as pointer of specifc style and color.

The user code defines and plug-in handler to sort-off automatic and manual points from the series being drawn.
A small example code is below:
Create and add custom series to chart:

  m_ser0 = new EsCustomSeries(chart_);
  m_ser0->LinePen->Color = claNavy;
  m_ser0->LinePen->Width = 2;
  m_ser0->Pointer->Visible = true;
  m_ser0->Pointer->Size = 10;
  m_ser0->Pointer->Style = TSeriesPointerStyle::psCircle;
  m_ser0->manualPointerStyle = TSeriesPointerStyle::psDiamond;
  m_ser0->manualPointerColor = claRed;
  m_ser0->onCheckManualPointMark = onCheckManualPointMark;

  m_ser0->ParentChart = chart_;

 Fill series with random values, specifying some of them as manual ones using ValueMarkText series property

  m_ser0->BeginUpdate();
  m_ser0->FillSampleValues(
    ed_->Value
  );

  // Fake manual points
  int cnt = m_ser0->Count();
  int div = cnt/3;
  if( div < 1 )
    div = 1;

  for(int idx = 0; idx < cnt-1; ++idx)
  {
    if( idx % ((idx % div) ? (idx % div) : 4)  == 3 )
      m_ser0->ValueMarkText[idx] = "manual";
  }

  m_ser0->EndUpdate();

 Implement the simplest manual point identification handler

bool __fastcall TForm1::onCheckManualPointMark(const EsCustomSeries* sender, int valIdx, const UnicodeString& mark)
{
  return "manual" == mark;
}
//---------------------------------------------------------------------------

 The result is as follows:

100-points series with some points marked manual2-points 'automatic' point series drawn as markers, not polyline

Яндекс.Метрика

Сейчас 320 гостей и ни одного зарегистрированного пользователя на сайте

14.12.2018  ©2018 - ExactSoft - All rights reserved