Lazarus

Programming => Graphics and Multimedia => TAChart => Topic started by: apeoperaio on December 17, 2014, 01:59:41 pm

Title: matrix on a colormap
Post by: apeoperaio on December 17, 2014, 01:59:41 pm
I would like to represent a matrix on a chart using the colormapserie (since I suppose it is the right choice). I have a matrix with values between -1 and 1. I would like to visualize it using a chart and getting something similar to:
http://i.stack.imgur.com/3ZaFq.png

Is it possible?
Title: Re: matrix on a colormap
Post by: wp on December 17, 2014, 04:05:54 pm
Certainly. Have a look at the tutorial http://wiki.lazarus.freepascal.org/TAChart_Tutorial:_ColorMapSeries,_Zooming
Title: Re: matrix on a colormap
Post by: wp on December 18, 2014, 04:01:20 pm
I found some time and could assemble a little demo which shows what you want by means of dummy data. In addition to the tutorial it also has an owner-drawn gradient legend.

Please be aware that there is quite a lot of adjustment of properties; therefore, you should not only look at the pas file. Feel free to ask here again if you should have difficulties in putting everything together.
Title: Re: matrix on a colormap
Post by: apeoperaio on December 20, 2014, 05:04:40 pm
wow, thanks.
I will have a look after holidays!
Title: Re: matrix on a colormap
Post by: apeoperaio on January 13, 2015, 02:29:42 pm
ok. I checked it. I am doing some tests but I have an additional question.
How can I show userdegined hint? For example if I would like to show an hint with the label of the x and y azis and the z value how can I do it?
I didn't find any event that can be used to set a user defined hint..
Title: Re: matrix on a colormap
Post by: wp on January 13, 2015, 04:02:18 pm
Since the colormap series covers the entire axis range it is easiest to use the mouse coordinates in the MouseMove event, convert them from pixels to graph coordinates, lookup the x/y texts and calculate the function according to the x/y coordinates.

In above example, add a statusbar and the following OnMouseMove event handler:
Code: [Select]
procedure TForm1.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  P: TDoublePoint;
  ix, iy: Integer;
  z: Double;
  s: String;
begin
  // Mouse outside chart axis area?
  if (X < Chart1.ClipRect.Left) or (Y > Chart1.ClipRect.Bottom) then
    s := ''
  else begin
    // Inside!
    // Convert mouse coordinates to graph coordinates
    P := Chart1.ImageToGraph(Point(X,Y)); 
    // get index to axis labels array
    ix := trunc(P.X+0.5);   
    iy := trunc(P.Y+0.5);
    if (ix >= 0) and (ix <= High(DataNames)) and (iy >= 0) and (iy <= High(DataNames))  // make sure not to be out of range
    then begin
      // calculate function value
      Chart1ColorMapSeries1Calculate(P.X, P.Y, z); 
      // Create hint string
      s := Format('x = %s, y = %s: z = %f', [DataNames[ix], DataNames[iy], z]);
    end;
  end;
  // Show hint string in status bar.
  Statusbar1.SimpleText := s;
end;
Title: Re: matrix on a colormap
Post by: apeoperaio on January 14, 2015, 03:45:20 pm
thanks. I implemented the chart. I was not able to directly use the hint, since if I use the hint instead of the statusbar the hints areno updated when I move the mouse. Btw it is ok to use an additional control instead of the hint.
I have another question, I would like to avoid zooming or moving the graph showing the part of the graph with no values (the white part of the graph). Is it possible to set this kind of constraints?
Title: Re: matrix on a colormap
Post by: wp on January 14, 2015, 06:03:13 pm
See the attached demo for a version which shows a hint window at the mouse position when you press the left mouse button.

The demo makes use of the ChartTools that come along with TAChart. There are several prebuilt tools, even a TDataPointHintTool,but this one does not work here since it is for standard plots y(x), but you have a 3D-plot z(x,y). But the UserDefinedChartTool is very flexible and provides all the events needed.
Using a dedicated ChartToolSet automatically disabled the built-in toolset which is responsible for zooming and panning. (If, in another application, you only want to disable zooming/panning without adding a toolset simply set the chart's AllowZoom to false - it automatically kills the built-in panning as well).

If you want to learn more about ChartTools read the tutorials http://wiki.lazarus.freepascal.org/TAChart_Tutorial:_Chart_Tools (http://wiki.lazarus.freepascal.org/TAChart_Tutorial:_Chart_Tools) and http://wiki.lazarus.freepascal.org/TAChart_Tutorial:_ColorMapSeries,_Zooming (http://wiki.lazarus.freepascal.org/TAChart_Tutorial:_ColorMapSeries,_Zooming).
Title: Re: matrix on a colormap
Post by: apeoperaio on January 15, 2015, 12:25:15 pm
I am still analyzing your first demo Chart_ColorMap.zip and I get an error if I increase the number of data (e.g. NUM_DATA = 120). The error is raised on line 698 tacustomsource.pas:
        if not IsNan(X) and (X >= AXMin) then break;

Additionally I have two more question related to Chart_ColorMap demo
1. I didn't understand the lines:
Code: [Select]
  // Populate color source (you can add more colors...)
  with ColorSource do begin
    Add(FMin, FMin, '', clBlue);                                // Data minimum --> blue
    Add(FMin+0.3*(FMax-FMin), FMin+0.3*(FMax-FMin), '', clRed); // 0.3 of range --> red
    Add(FMax, FMax, '', clYellow);                              // Data maximum --> yellow
  end;

How they works? How a gradient is introduced? What it means that there is a  0.3 of range?

2. Additionally I don't understand how the extent work:
Code: [Select]
  with Chart1ColorMapSeries1.Extent do begin
    XMin := -0.5;
    XMax := NUM_DATA-0.5;
    YMin := -0.5;
    YMax := NUM_DATA-0.5;
    UseXMin := true;
    UseXMax := true;
    UseYMin := true;
    UseYMax := true;
  end;
If I well understand XMax will be set to 11.5 if numdata is 12.
If I use the same approach in my application I got an error since in the Chart1ColorMapSeries1Calculate event the matrix limits are not respected, it tries to get an element that is not present in the matrix. e.g. if AY = 11.5 and I add 0.5 I got 12 that is a not valid index. I didn't get this error in your demo but in my application. I cannot understand why..

Code: [Select]
{ Event handler which returns the z value of each data point lying at (AX,AY)
  in the plane }
procedure TForm1.Chart1ColorMapSeries1Calculate(const AX, AY: Double; out
  AZ: Double);
begin
  // Lookup data in matrix
  // The shift by 0.5 is for centering the color boxes around the ticks.
  AZ := DataValues[trunc(AX+0.5), trunc(AY+0.5)];
end;
Title: Re: matrix on a colormap
Post by: wp on January 15, 2015, 01:55:57 pm
Quote
I get an error if I increase the number of data (e.g. NUM_DATA = 120)
Yes, I can confirm. After some debugging I found that this is due to accessing the data array beyond its highest element. Please modify the OnCalculate event as follows:
Code: [Select]
{ Event handler which returns the z value of each data point lying at (AX,AY)
  in the plane }
procedure TForm1.Chart1ColorMapSeries1Calculate(const AX, AY: Double; out
  AZ: Double);
var
  indexX, indexY: Integer;
begin
  // Lookup data in matrix
  // The shift by 0.5 is for centering the color boxes around the ticks.
  // Make sure that we do not go beyond the highest index.
  indexX := Min(NUM_DATA-1, trunc(AX+0.5));
  indexY := Min(NUM_DATA-1, trunc(AY+0.5));
  AZ := DataValues[indexX, indexY];
end;

This modification returns valid data if, due to rounding errors, the series requests a z value beyond the data array limits.

Maybe the 0.5 offset requires some more explanation:

TColorMapSeries is a "function" series, this means that it gets its data from a function calculated for every point in the x/y plane. This is what the OnCalculate event does: It gets the coordinates of the data point for which a function value z is needed and calculates the function value.

In your case, the x values are the indexes in the 2-dimensional DataValues array. But the TColorMapSeries does not need z values only at integers, but also at any value in between. Mathematically speaking, it requires a "step function". Therefore I chop off all decimals from the x/y coordinates, and this is the array index, as simple as that. In this way the colormap series gets that tiled look. But without anything else the axis tick marks would be located at the lower left edge of each tile. Typically they should be in the center of each tile. Therefore, I am adding 0.5 (= half a tile) to the x/y coordinates. In this way the first tile extends between -0.5 and +0.5: the x coordinate of the very left point is -0.5, in the vent handler we add 0.5 to get 0.0, i.e. the data index is 0. This is true also for all points before they reach 0.5. Therefore the x data range of the series starts at -0.5. The same with y. And the last data point, after shifting by -0.5, is at NUM_DATA-0.5. These values are to be assigned to the Extent of the series.

Your question 1 on the ColorSource: The ColorMapSeries has a built-in color interpolation procedure (ColorByValue()). It needs pivots defined by the ColorSource. Each pivot is defined by a value-color pair. Because interpolation has to be made for both axes a value has to be provided for x and y (first and second parameter fo the Add method of the ColorSource):
Code: [Select]
  ColorSource.Add(value, value, '', color_of_value);

Suppose you want a two-color gradient: the data minimum corresponds to the firstcolor, the maximum to the last color. Intermediate colors are calculated by the series using linear interpolation of the rgb components. If you want a three-color gradient you have to assign a third color to an intermediate data value. In the demo I used the value which is at 0.3 of the distance between max and Min, closer to the min. If I had selected 0.5 the point would be exactly in the middle. 0.7 would be closer to the max value.
TinyPortal © 2005-2018