Search code examples
delphiteechart

Teechart, automatic recalculation of a custom Mark Area due to title changes


Delphi 10.1, VCL, with embedded Teechart.

I've an Area Series with a Mark moved by code to a custom position. When the Mark title content is changed, the Yellow background is not adjusted (Auto Sized) to the new Mark content. I've a work around for that, but it has flickers and it is not elegant. I'm looking for an idea how to do it better.

In details: I put three buttons on the chart, one to move the Mark location, the 2nd button to add a second content line to Mark title. The 3rd button is my work around to get a proper size. The Series Creation:

procedure TForm2.FormCreate(Sender: TObject);
begin
  Chart1.View3D := false;
  Chart1.Axes.Bottom.SetMinMax(0,5);

  with Chart1.AddSeries(tAreaSeries) as tAreaSeries do
    begin
      AddXY(1, 10);                  // Two points AreaSeries
      AddXY(4, 10);
      Marks[1].Visible     := false; // Hide the other Mark, the default is true
      Marks.Visible        := true;  // Global Visibility for all Markers
      Chart1[0].Marks[0].Text.Text := 'First-line';
    end;
end;

Pressing Move Mark button code:

procedure TForm2.btnMoveMarkClick(Sender: TObject);
begin
  Chart1[0].Marks.Positions[0].Custom := true;
  Chart1[0].Marks.Positions[0].Offset(point(50,70));
//  Chart1[0].Marks.Positions[0].LeftTop := point(150,200);  // It is moving the Mark but not drawing the line to Series point
  Chart1.Repaint; // It doesn't work without this Repaint
end;

Will generate the following screen: enter image description here Now, pressing the 2nd button to change Mark Title content as follow:

procedure TForm2.btnChangeMarkContentClick(Sender: TObject);
begin
  Chart1[0].Marks[0].Text.Text := 'First-line'+#13+'Second-line';
end;

As you can see, the Yellow background size was not changed: enter image description here

My brut-force work around is to delete the custom position, this will resize the Mark, then reposition the Mark again as follow:

procedure TForm2.btnResizeMarkClick(Sender: TObject);
var
  LastPoint: tpoint;
begin
  LastPoint := Chart1[0].Marks.Positions[0].LeftTop;
  Chart1[0].Marks.Positions.Automatic(0);
  Chart1.Repaint;

  Chart1[0].Marks.Positions[0].Custom := true;
  Chart1.Repaint;
//  Chart1[0].Marks[0].MoveTo(LastPoint); // It doesn't work - Why?
  Chart1[0].Marks.Positions[0].LeftTop := LastPoint; // Better to use Offset
  Chart1.Repaint;
end;

It is doing the job, but with flicker due to Mark movement, as follow: enter image description here

Thanks for any hint how to resize the Mark without delete its custom position which cause the flicker. Reron


Solution

  • Yeray solved the main problem. In addition, the Arrows should be adjusted too as follow:

    type
      tCustomTextShapeAccess = class(tCustomTextShape); // Yeray: tCustomTextShapeAccess class to get access to the protected CalcBounds method
    
    const
      tcaTopLeft = 0;
      tcaArrowTo = 1;
    
    procedure TeeChart_ResizeCustomMark(aChart: tChart; aSeriesInx, aMarkInx, aAnchor: integer);
    // Resize Custom Mark area shape. It is required after Title text modification
    // aAnchor: tcaTopLeft(0), tcaArrowTo(1); Choose which point to keep
    var
      aSeries: tChartSeries;
      aMark  : tMarksItem;
      aMarkPosision: tSeriesMarkPosition;
    begin
      // Assignments for more readable code
      aSeries       := aChart[aSeriesInx];
      aMark         := aChart[aSeriesInx].Marks[aMarkInx];
      aMarkPosision := aSeries.Marks.Positions[aMarkInx];
    
      // Bounds Calculation of the new Mark. Yeray solution.
      tCustomTextShapeAccess(aMark).CalcBounds(aChart); // Yeray: tCustomTextShapeAccess class to get access to the protected CalcBounds method
      aMarkPosision.Height := aMark.Height;
      aMarkPosision.Width  := aMark.Width;
    
      // Set Mark position based on aAnchor
      case aAnchor of
        tcaTopLeft: // Keep LeftTop point. Set new ArrowTo point.
          begin
            aMarkPosision.ArrowTo.X := aMarkPosision.LeftTop.X + (aMarkPosision.Width div 2);
            if aSeries.CalcYPos(aMarkInx) > aMarkPosision.ArrowTo.Y then // Mark above Series point
              aMarkPosision.ArrowTo.Y := aMarkPosision.LeftTop.Y + aMarkPosision.Height
            else
              aMarkPosision.ArrowTo.Y := aMarkPosision.LeftTop.Y;
          end;
        else        // Set ArrowTo point. Set a New LeftTop point.
          begin
            aMarkPosision.LeftTop.X := aMarkPosision.ArrowTo.X - (aMarkPosision.Width div 2);
            if aSeries.CalcYPos(aMarkInx) > aMarkPosision.ArrowTo.Y then // Mark above Series point
              aMarkPosision.LeftTop.Y := aMarkPosision.ArrowTo.Y - (aMarkPosision.Height -1)
            else                                                         // Mark below Series point
              aMarkPosision.ArrowTo.Y := aMarkPosision.LeftTop.Y;
          end;
      end; // case
    
      aChart.Repaint;
    end;