4

Can you help me creating the area chart, where the part (top) of chart has other color? I would like to indicate that the values over any number value are critical.

This is a screenshot: enter image description here

I've created two area charts but as you can see, each chart has own left axis. The best solution would be coloring chart above particular values.

LU RD
  • 34,438
  • 5
  • 88
  • 296
  • Done that a couple of times by adding a custom chart background picture with color stripes. Works if the y-scale is fixed. – LU RD Apr 13 '12 at 23:15
  • This is a screenshot: http://imageshack.us/photo/my-images/84/58337625.png/ I've created two area charts but as you can see, each chart has own left axis. The best solution would be coloring chart above particular values. – user1332578 Apr 14 '12 at 08:06

1 Answers1

7

There is a tool called TSeriesBandTool which can do this with a little bit of work.

Add an extra series and make it identical as your series, but with a maximum of your limit.

This is how it looks in the Tools editor :

enter image description here

It will fill the difference between the two series with a color of your choice.

Update

Made a proof of concept, see image.

Added a third Series and a second TSeriesBandTool to fill the bottom part with a second color. I'm sure it's possible to do with less code, but this is good enough for a demonstration.

Update 2

Made some code refreshments.

Update 2.5

Now handles arbitrary X-values as well, not only integers.

enter image description here

Here is the code :

uses Series, TeeTools, TeeSeriesBandTool;

Procedure DrawLimitAreaChart(S1, S2, S3: TLineSeries; YLimit: Double;
  BT1, BT2: TSeriesBandTool; OutlineWidth: Integer;
  OutlineCL, TopCl, BottomCL: TColor);
Var
  i: Integer;
  iX: Double;
Begin
  S1.LinePen.Width := OutlineWidth;
  S1.Color := OutlineCL;
  S2.Color := OutlineCL;
  S3.Color := OutlineCL;
  for i := 0 to S1.Count - 1 do
    if (S1.YValue[i] > YLimit) then
    begin
      if (i > 0) and (S1.YValue[i - 1] < YLimit) then
      begin // Last point below limit
        iX := (S1.XValue[i] - S1.XValue[i - 1]) * (YLimit - S1.YValue[i - 1]) /
          (S1.YValue[i] - S1.YValue[i - 1]) + S1.XValue[i - 1];
        S2.AddXY(iX, YLimit);
        if (i < S1.Count - 1) then
          Continue;
      end;
      S2.AddXY(S1.XValue[i], YLimit); // Set to Ylimit
    end
    else // Below Ylimit
    begin
      if (i > 0) and (S1.YValue[i - 1] > YLimit) then
      begin // Last point above limit
        iX := (S1.XValue[i] - S1.XValue[i - 1]) * (YLimit - S1.YValue[i - 1]) /
          (S1.YValue[i] - S1.YValue[i - 1]) + S1.XValue[i - 1];
        S2.AddXY(iX, YLimit);
      end;
      S2.AddXY(S1.XValue[i], S1.YValue[i]);  // Same value
    end;

  for i := 0 to S2.Count - 1 do
  begin
    S3.AddXY(S2.XValue[i], 0.0);
  end;

  { - First TSeriesBandTool }
  BT1.Series := S1;
  BT1.Series2 := S2;
  BT1.Brush.BackColor := TopCl;
  BT1.DrawBehindSeries := True;
  BT1.Transparency := 50;

  { - Second TSeriesBandTool }
  BT2.Series := S2;
  BT2.Series2 := S3;
  BT2.Brush.BackColor := BottomCL;
  BT2.DrawBehindSeries := True;
  BT2.Transparency := 50;

End;

procedure TForm1.Button1Click(Sender: TObject);
const
  // Example data
  YVAL: array [0 .. 10] of Double = (10,40,45,20,48,5,47,47,47,47,30);
var
  i: Integer;
  YLimit: Double;
  S1, S2, S3: TLineSeries;
begin
  Chart1.SeriesList.Clear;
  S1 := TLineSeries.Create(Self); // Contains real data
  Chart1.AddSeries(S1);
  S2 := TLineSeries.Create(Self); // Data below Ylimit
  Chart1.AddSeries(S2);
  S3 := TLineSeries.Create(Self); // baseline data
  Chart1.AddSeries(S3);
  for i := 0 to 10 do
    S1.AddXY(i, YVAL[i]);
  YLimit := S1.MaxYValue * 0.75;
  DrawLimitAreaChart(S1, S2, S3, YLimit, ChartTool1, ChartTool2, 2, clBlack,
    TColor($0024FF){clOrangeRed}, TColor($FF4D4D){clNeonBlue});

end;

So what the code does :

Fills in the X,Y data into series[0]. Calls DrawLimitAreaChart procedure where a new series is calculated from series[0] such that no part is above the Ylimit. Finally a third series is applied as a baseline to the second series. The three curves together with the two TSeriesBandTools now forms a two color region area chart.

Update 3

As per request here is a chart example with the X axis as time. The code for doing this is in my comment below.

enter image description here

LU RD
  • 34,438
  • 5
  • 88
  • 296
  • Thank you master :) How can I feed chart by data in time interval ? The procedure would be nice: procedure AddChartValue(ATime: TDateTime; AValue: integer); AddChartValue('12:00:00', 30); AddChartValue('12:00:10', 40); AddChartValue('12:00:20', 20); ... ... AddChartValue('12:01:50', 20); .... .... Of course I have to set the max "x" value to show max for example 60 seconds, more data will lower clarity the chart. – user1332578 Apr 15 '12 at 19:43
  • I have an answer for you (please remove your other answer to the question as Ken pointed out). Set `S1.XValues.DateTime := True;` and `Chart1.BottomAxis.DateTimeFormat := 'nn:ss';` or any other format of your liking. Add X values as TDateTime format, i.e `S1.AddXY(Now + XVAL[i]/(24*60*60), YVAL[i]);`. – LU RD Apr 15 '12 at 19:58
  • I hope it is working for you. Don't forget to accept the answer, see [faq#howtoask](http://stackoverflow.com/faq#howtoask). – LU RD Apr 16 '12 at 12:02
  • I tried to add an items manually procedure TfrmQueueMonitor.Button1Click(Sender: TObject); var i: Integer; YLimit: Double; begin dbChart.Series[0].AddXY(StrToTime('12:00:10'), 50); dbChart.Series[0].AddXY(StrToTime('12:00:20'), 20); dbChart.Series[0].AddXY(StrToTime('12:00:30'), 80); YLimit := 30; DrawLimitAreaChart(dbChart.Series[0] as TLineSeries, dbChart.Series[1] as TLineSeries, dbChart.Series[2] as TLineSeries, YLimit, ChartTool1, ChartTool2, 2, clBlack, TColor($0024FF){clOrangeRed}, TColor($FF4D4D){clNeonBlue}); end; but it displays 12 xvalues, why? – user1332578 Apr 20 '12 at 15:21
  • Are you sure the series are empty before entering Button1Click ? Otherwise call `dbChart.Series[0].Clear; dbChart.Series[1].Clear; dbChart.Series[2].Clear;` the first thing you do in Button1Click. – LU RD Apr 20 '12 at 21:55
  • Yes, the series were empty. I do not have any other code except the procedure DrawLimitAreaChart and button1click. If I understood correctly the AddXY(x,y) adds x values in x-axis and y values in y axis, so when I used '12' in the x parameter the 12 items has been created. Maybe i have to use AddY(FormatDateTime('hh:mm:ss', now), 55) ? – user1332578 Apr 21 '12 at 13:59
  • Did you try my example (with the code for the time-scale X_axis included) ? I don't see that in your example above. Did you try clearing the Series ? The `Series[0].AddXY( StrToTime('12:00:00'),50);` is correct, you are entering a TDateTime value plus an Y-value of 50. – LU RD Apr 21 '12 at 14:32