Ternary Graph Assigning the Weighting parameter

TeeChart VCL for Borland/CodeGear/Embarcadero RAD Studio, Delphi and C++ Builder.
Errol
Newbie
Newbie
Posts: 35
Joined: Mon Jul 02, 2018 12:00 am

Ternary Graph Assigning the Weighting parameter

Post by Errol » Sun Mar 24, 2019 6:06 am

I am having continuing problems assigning the Weighting parameter in a ternary graph, as follows:
1. If I do not assign this parameter, the graph does not draw.
2. If I assign it to the Weight field from my input memory table (derived from a csv file), the Weighting value is set to zero, and the Y field is filled with the Weight values.
3. If I assign the parameter to the same field as the Radius parameter, the Weighting is still zero, but at least the Y field is filled to the correct values.
I have attached a simple example project that shows these effects. I would be grateful if you could indicate what I am doing wrong here.
Many thanks
Errol
Attachments
TernaryGraph_Weight.zip
(8.82 KiB) Downloaded 877 times

Yeray
Site Admin
Site Admin
Posts: 9514
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Ternary Graph Assigning the Weighting parameter

Post by Yeray » Mon Mar 25, 2019 3:32 pm

As mentioned here this project seems to be using components we don't have here (TkbmMemTable, TkbmCSVStreamFormat).
If you still find problems with it, could you please arrange example projects using the minimum code to reproduce them and without using 3rd party libraries?
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Errol
Newbie
Newbie
Posts: 35
Joined: Mon Jul 02, 2018 12:00 am

Re: Ternary Graph Assigning the Weighting parameter

Post by Errol » Wed Mar 27, 2019 11:10 am

Good evening Yeray
Thank you for your response. As I use kbmMemTables with line series and bar series without any problems, I do not believe that the kbmMemTable is the cause of the difficulties I am having with Ternary Series. Rather, I feel it is because i am not correctly assigning the various axes and other ternary series parameters to the kbmMemTable fields.
Rather than rewriting the example project ot get the data from the csv file to a Ternary Series by some other method, and still failing because I am assigning the parameters incorrectly, I would appreciate it if you could write a simple example project that uses the csv file in the TernaryGraph_Weight example project as data input, and show how the X, Y, Z axes and the Radius and Weight parameters are assigned.
I look forward to receiving such a project, and appreciate all your asistance in this matter.
Best regards
Errol

Yeray
Site Admin
Site Admin
Posts: 9514
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Ternary Graph Assigning the Weighting parameter

Post by Yeray » Thu Apr 04, 2019 7:36 am

Hello Errol,

Sorry for the delay here.
Find attached the project modified to use the TSeriesTextSource instead of the kbmMemTables. It seems to work without problems:
TernaryGraph_Weight.zip
(3.01 KiB) Downloaded 904 times
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Errol
Newbie
Newbie
Posts: 29
Joined: Mon Jul 08, 2019 12:00 am

Re: Ternary Graph Assigning the Weighting parameter

Post by Errol » Tue Aug 27, 2019 6:16 am

Good afternoon

After a long break, I am back again, still trying to get ternary graphs to work.

I am writing to express my disappointment in the implementation of Ternary Graphs. We use ternary graphs to display the relationship between three data values in exactly the same way as we use regular XY graphs to display relationships between two data values. For XY graphs, we place a Chart or DBChart on a form and then send series of xy pairs to it, and have developed substantial code to handle symbols, colours, connecting lines and so on. We can easily set the axis scales to “zoom in” to show a small part of the chart in cases where there is dense data and have good control over the legend.

However, when using a ternary graph, this functionality does not seem to be available. Each series places a new ternary chart on the form, inside the previous chart, and each of which has fixed axes (0 to 100%). I understand that I can use an event to draw each chart on top of the previous chart, which is fine, but an additional complication. The legend seems to have limited functionality, and there is a Weight parameter which I still do not understand.

I would have preferred a structure more similar to a regular XY chart, that could be placed on the form and then use the equivalent of your standard line series, but requiring the definition of XValues, YValues and ZValues. If this was the case, then we could use much of our existing code to create ternary graphs. However, I presume that you have progressed too far in your development of ternary graphs to change to a form that is more compatible with regular XY graphs.

I have downloaded the TernaryGraph_Weight.zip attachment of 4 April 2019, and have got it to work - thanks. However, I do not seem to be able to alter the radius or the colours of the symbols. Also, I want to treat the data in the attached csv file as three separate series based on the name of the well, each series with a separate symbol, but with different colours for each data point. Also, I would like the option to draw a line between the data points of each series, in date order.

Any help you can give me would be greatly appreciated, or point me in the direction of the members of the TernarySeries component (i.e. the properties and methods).

I look forward to hearing from you soon.

Best regards

Errol

Yeray
Site Admin
Site Admin
Posts: 9514
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Ternary Graph Assigning the Weighting parameter

Post by Yeray » Fri Aug 30, 2019 2:18 pm

Hello Errol,
Errol wrote:
Tue Aug 27, 2019 6:16 am
After a long break, I am back again, still trying to get ternary graphs to work.
Welcome back!
Errol wrote:
Tue Aug 27, 2019 6:16 am
I am writing to express my disappointment in the implementation of Ternary Graphs.
I'm sorry to hear that. The Ternary series is a quite particular series that draws elements (like axes) in it's own way so it doesn't receive improvements and fixes made in other parts of the chart as other series do.
If you can help us to identify and report with detail any issue you may find with it, it would help us to improve this specific series.
Errol wrote:
Tue Aug 27, 2019 6:16 am
We use ternary graphs to display the relationship between three data values in exactly the same way as we use regular XY graphs to display relationships between two data values. For XY graphs, we place a Chart or DBChart on a form and then send series of xy pairs to it, and have developed substantial code to handle symbols, colours, connecting lines and so on. We can easily set the axis scales to “zoom in” to show a small part of the chart in cases where there is dense data and have good control over the legend.
Ok, thanks for the explanation.
Errol wrote:
Tue Aug 27, 2019 6:16 am
However, when using a ternary graph, this functionality does not seem to be available. Each series places a new ternary chart on the form, inside the previous chart, and each of which has fixed axes (0 to 100%). I understand that I can use an event to draw each chart on top of the previous chart, which is fine, but an additional complication.
I've added these issues to the public tracker:
http://bugs.teechart.net/show_bug.cgi?id=2231
http://bugs.teechart.net/show_bug.cgi?id=2232
Errol wrote:
Tue Aug 27, 2019 6:16 am
The legend seems to have limited functionality
I'm not sure to see this limit. Could you please explain with more detail what are you missing?
Errol wrote:
Tue Aug 27, 2019 6:16 am
there is a Weight parameter which I still do not understand.
The weight list is used to fill the colors of the points in range and palette styles. The point with the lowest weight is drawn with the end color in the palette, the point with the highest weight value is drawn with the start color in the palette, and the rest of points are drawn with a color calculated by extrapolation.
Errol wrote:
Tue Aug 27, 2019 6:16 am
I would have preferred a structure more similar to a regular XY chart, that could be placed on the form and then use the equivalent of your standard line series, but requiring the definition of XValues, YValues and ZValues. If this was the case, then we could use much of our existing code to create ternary graphs. However, I presume that you have progressed too far in your development of ternary graphs to change to a form that is more compatible with regular XY graphs.
Indeed there are some functionalities that will be difficult to port to the Ternary series, but the majority relate to the axes. As far as I see, the rest is inherited from TCustom3DPaletteSeries which means it has the same base than other 3D series like TPoint3DSeries, TSurfaceSeries, TMapSeries,... which require XYZ.
Errol wrote:
Tue Aug 27, 2019 6:16 am
I have downloaded the TernaryGraph_Weight.zip attachment of 4 April 2019, and have got it to work - thanks. However, I do not seem to be able to alter the radius or the colours of the symbols. Also, I want to treat the data in the attached csv file as three separate series based on the name of the well, each series with a separate symbol, but with different colours for each data point. Also, I would like the option to draw a line between the data points of each series, in date order.
I'll get into this and reply you here when I'll end my tests.
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Yeray
Site Admin
Site Admin
Posts: 9514
Joined: Tue Dec 05, 2006 12:00 am
Location: Girona, Catalonia
Contact:

Re: Ternary Graph Assigning the Weighting parameter

Post by Yeray » Tue Sep 03, 2019 6:16 am

Hello again,
Errol wrote:
Tue Aug 27, 2019 6:16 am
alter the radius
You could use OnGetPointerStyle to use the radius with any pointer style. Ie:

Code: Select all

type TTernarySeriesAccess=class(TTernarySeries);

function TTernarySeriesForm.SeriesGetPointerStyle(Sender: TChartSeries; ValueIndex: Integer): TSeriesPointerStyle;
begin
  TTernarySeriesAccess(Sender).PreparePointer(ValueIndex);
  result:=TTernarySeries(Sender).Pointer.Style;
end;
Errol wrote:
Tue Aug 27, 2019 6:16 am
the colours of the symbols.
The Ternary series, as the other derivates of TCustom3DPaletteSeries, uses ColorRange by default and this uses the Weight as explained my last reply above.
If you set UseColorRange to false then you'll be able to individually set the colors to each point in your series.
Errol wrote:
Tue Aug 27, 2019 6:16 am
I want to treat the data in the attached csv file as three separate series based on the name of the well, each series with a separate symbol, but with different colours for each data point.
I can think on multiple alternatives to do this:
- We could add Filters to the TSeriesTextSource (#2233)
- You could split your .csv into 3 different files
- You could load your .csv into a TFileStream, looping it and manually adding the data to each series
- You could remove the unwanted points after adding them using OnAfterAdd method (I used this approach in the attached project).
Errol wrote:
Tue Aug 27, 2019 6:16 am
Also, I would like the option to draw a line between the data points of each series, in date order.
If you have the points in your .csv already sorted, you can just loop them and draw the lines as follows:

Code: Select all

procedure TTernarySeriesForm.Chart1AfterDraw(Sender: TObject);
var i, j: Integer;
begin
  for i:=0 to 2 do
  with TTernarySeries(Chart1[i]) do
  begin
    Chart1.Canvas.MoveTo(CalcXPos(0), CalcYPos(0));
    for j:=1 to Count-1 do
    begin
      Chart1.Canvas.LineTo(CalcXPos(j), CalcYPos(j));
    end;
  end;
end;
If you don't have the points sorted, you'll have to manually sort them using a temporal array.
Project1_2019-09-03_08-15-50.png
Project1_2019-09-03_08-15-50.png (32.32 KiB) Viewed 28931 times
TernaryGraph_Weight.zip
(3.49 KiB) Downloaded 898 times
Best Regards,
ImageYeray Alonso
Development & Support
Steema Software
Av. Montilivi 33, 17003 Girona, Catalonia (SP)
Image Image Image Image Image Image Please read our Bug Fixing Policy

Errol
Newbie
Newbie
Posts: 29
Joined: Mon Jul 08, 2019 12:00 am

Re: Ternary Graph Assigning the Weighting parameter

Post by Errol » Tue Sep 10, 2019 4:32 am

Good afternoon Yeray
Thank you for your detailed reply - sorry for the delay in replying.
I am still having problems getting a ternary graph to draw. I have attached the code I use. When I run the program, selecting a single series, I just get a blank chart with a title, but without axes, vertex titles, etc.
This code works correctly if I change it to a TLineSeries instead of a TTernarySeries, so this means the data is available. Of course, I have to remove the references to ZValues, VertexTitles and UseColourRange, and change other references to TTernarySeries to TLineSeries elsewhere in the code.
I would appreciate it if you could suggest what I need to do to show the ternary chart and the data.
Thanks and regards
Errol

Code: Select all

procedure TQSCollection.InsertTernarySeries(aFieldName, aCalcField: array of string; dID: integer);
var
  sTitle: string;
  iIndex: integer;

begin

  sTitle := self.Owner.fMultiGraphList.Strings[dID];

  iIndex := 0;

  if (SeriesListT.IndexOf(sTitle) = -1) then
  begin
    SeriesListT.AddObject(sTitle, TTernarySeries.Create(Owner));
    iIndex := SeriesListT.IndexOf(sTitle);

    with TTernarySeries(SeriesListT.Objects[iIndex]) as TTernarySeries do
    begin
      IniFile := fIniFile;
      ParentChart := self.Owner.Chart;
      DataSource := TDataset(self.Owner.fMultiGraphList.Objects[dID]);
      Name := GetValidName(sTitle);

      UseColorRange := True;
      VertexTitles[0][0] := aCalcField[0];
      VertexTitles[1][0] := aCalcField[1];
      VertexTitles[2][0] := aCalcField[2];

      YValues.ValueSource := aCalcField[0];
      XValues.ValueSource := aCalcField[1];
      ZValues.ValueSource := aCalcField[2];
      XValues.Order := loNone;
      YValues.Order := loNone;
      ZValues.Order := loNone;

      Pointer.Style:=psCircle;
      Pointer.Transparency:=0;
      Pointer.Visible := True;
      ShowInLegend := False;
      UseColorRange := False;

    end;
  end
  else
    DeleteSeries(SeriesListT.IndexOf(sTitle));
end;

Errol
Newbie
Newbie
Posts: 29
Joined: Mon Jul 08, 2019 12:00 am

Re: Ternary Graph Assigning the Weighting parameter

Post by Errol » Thu Sep 12, 2019 11:02 pm

Good morning

I am still having great difficulty in producing a ternary chart by code. I have attempted to implement the various events of the Ternary Chart test program in case I am missing some important steps, but to no avail. I acknowledge that I am using components that you do not use, but I still feel that you should be able to pinpoint what I am doing wrong.

I use the following code to implement a graph using a simple line series. This works correctly, showing that the data is correctly obtained from the memory table.

Code: Select all

 
 procedure TQSCollection.InsertTernarySeries(aFieldName, aCalcField: array of string; dID: integer);
var
  sTitle,A,B,C: string;
  iIndex, iData: integer;

begin
  sTitle := self.Owner.fMultiGraphList.Strings[dID];
  iIndex := 0;
  if (SeriesListT.IndexOf(sTitle) = -1) then
  begin
    SeriesListT.AddObject(sTitle, TLineSeries.Create(Owner));
    iIndex := SeriesListT.IndexOf(sTitle);

    with TLineSeries(SeriesListT.Objects[iIndex]) do
    begin
      IniFile := fIniFile;
      ParentChart := self.Owner.Chart;
      DataSource := TDataset(self.Owner.fMultiGraphList.Objects[dID]);
      Name := GetValidName(sTitle);

      YValues.ValueSource := aCalcField[0];
      XValues.ValueSource := aCalcField[1];
      XValues.Order := loNone;
      YValues.Order := loNone;
      self.Owner.Chart.Axes.Left.Automatic := True;
      self.Owner.Chart.Axes.Bottom.Automatic := True;
      self.Owner.Chart.Axes.Bottom.Labels := True;

      self.Owner.Chart.Axes.Left.Title.Text := aCalcField[0];
      self.Owner.Chart.Axes.Bottom.Title.Text := aCalcField[1];
      Pointer.Style:=psCircle;
      Pointer.Visible := True;
      ShowInLegend := True;

      self.Owner.Chart.Legend.TextStyle := ltsPlain;
      self.Owner.Chart.Legend.Title.Visible:=true;
    end;
  end
  else
    DeleteSeries(SeriesListT.IndexOf(sTitle));
end;


The results are shown on the accompanying graphic.
LineSeries.jpg
LineSeries.jpg (66.07 KiB) Viewed 28808 times
However, when I change the code to use a ternary series, as shown below, I just get a blank screen. (Note: I also change other references from TLineSeries to TTernarySeries in two other places in the code, dealing with refreshing and clearing the ternary series.) I have also implemented the Chart.OnAfterDraw and Chart.OnBeforeDrawAxes events as suggested in the test program, also shown in the code. However, OnAfterDraw fails on CalcXPos(i) with a List index out of bounds(0) error.

Code: Select all

procedure TQSCollection.InsertTernarySeries(aFieldName, aCalcField: array of string; dID: integer);
var
  sTitle,A,B,C: string;
  iIndex, iData: integer;
  T: TTernarySeries;

begin
  sTitle := self.Owner.fMultiGraphList.Strings[dID];
  iIndex := 0;
  T := TTernarySeries(self.Owner.Chart.AddSeries(TTernarySeries));
  if (SeriesListT.IndexOf(sTitle) = -1) then
  begin
    SeriesListT.AddObject(sTitle, T);
    iIndex := SeriesListT.IndexOf(sTitle);
    with T as TTernarySeries do
    begin
      IniFile := fIniFile;
      ParentChart := self.Owner.Chart;
      DataSource := TDataset(self.Owner.fMultiGraphList.Objects[dID]);
      Name := GetValidName(sTitle);
      if dID > 0 then
      begin
        VertexTitle.Hide;
        self.Owner.Chart.Axes.Hide;
        BeforeDrawValues:=BeforeDrawSecond;
      end
      else
      begin
        self.Owner.Chart.Visible := True;
        self.Owner.chart.Axes.Visible:=True;
        BeforeDrawValues:=BeforeDrawFirst;
      end;
      OnAfterAdd := SeriesAfterAdd;
      UseColorRange := True;
      VertexTitles[0][0] := aCalcField[0];
      VertexTitles[1][0] := aCalcField[1];
      VertexTitles[2][0] := aCalcField[2];

      XValues.ValueSource := aCalcField[0];
      YValues.ValueSource := aCalcField[1];
      ZValues.ValueSource := aCalcField[2];
      XValues.Order := loNone;
      YValues.Order := loNone;
      ZValues.Order := loNone;
      Active := True;

      Pointer.Style:=psCircle;
      Pointer.Transparency:=0;
      Pointer.Visible := True;
      ShowInLegend := False;
      UseColorRange := False;

      self.Owner.Chart.Legend.TextStyle := ltsPlain;
      self.Owner.Chart.Legend.Title.TextAlignment := taLeftJustify;
      self.Owner.Chart.Legend.Width :=100;
      self.Owner.Chart.Legend.Title.Visible:=true;
      self.Owner.Chart.Walls.Back.Hide;
    end;
  end
  else
    DeleteSeries(SeriesListT.IndexOf(sTitle));
 end;
 
 procedure TPBQuickGraph.SetupTernaryChart(AIndex: integer);
begin
  Chart.OnAfterDraw := TernaryChartAfterDraw;
  Chart.OnBeforeDrawAxes := TernaryChartBeforeDrawAxes;
  fTernaryCollection.ClearAllTernarySeries;
end;
 
 procedure TPBQuickGraph.TernaryChartAfterDraw(Sender: TObject);
var i, j: Integer;
begin
  for i:=0 to fMultiGraphlist.Count - 1 do
  with TTernarySeries(Chart[i]) do
  begin
    Chart.Canvas.MoveTo(CalcXPos(i), CalcYPos(i));
    for j:=1 to Count-1 do
    begin
      Chart.Canvas.LineTo(CalcXPos(j), CalcYPos(j));
    end;
  end;
end;
I and my programmer have spent considerable time on this project, and I feel there is some essential information that we do not have.

Furthermore, I still do not understand why Steema did not use a simple X-Y graph with the Y-axis at a 60°angle, and with the ZValues parameter activated. Then if there was a procedure to calculate A% and B% from A, B and C and plot A% on the Y-axis and B% on the left axis, the results would be the same. Of course it would be necessary to draw the B-axis on the right, and overwrite the bottom axis with the reverse C axis, but that can be easily done. This is the approach of most scientific graphing applications.

To show that this approach is correct, I have attached the results of the test program. If you consider the position of the donut symbol (coordinates: Na/1000 = 30, Sqrt(Mg) = 10), this symbol would plot at exactly the same point if the Sqrt(Mg) axis ran from 0 to 100 on the bottom axis. As far as I can see, using a 3DGraph to plot ternary data is unnecessary, and a simple X-Y plot with a 60° Y-axis would suffice.
TernaryPlot.jpg
TernaryPlot.jpg (48.86 KiB) Viewed 28808 times
I am at the end of my tether. I guess there is some simple way to get ternary graphs to work, but I haven't managed to find it.

I look forward to some guidance here.

Best regards

Errol

Marc
Site Admin
Site Admin
Posts: 1209
Joined: Thu Oct 16, 2003 4:00 am
Location: Girona
Contact:

Re: Ternary Graph Assigning the Weighting parameter

Post by Marc » Mon Sep 16, 2019 12:19 pm

Hello Errol,

The way the Ternary Series was devised was to use 5 components:
X,Y,Z values and Radius and Weight.

X,Y and Z position the point as a measure of proportionality of each of the three values. Radius decides the visible radius of the point and Weight uses a colour scale.

This code then plots four points, one equidistant from each axis (centered) and the other offset proportionally as per their values:

Code: Select all

procedure TForm13.Button1Click(Sender: TObject);
begin
  Series1.AddBubbleXYZWeight(10,10,10,5,1,clTeeColor);

  Series1.AddBubbleXYZWeight(30,20,10,15,2,clTeeColor);
  Series1.AddBubbleXYZWeight(20,10,30,25,3,clTeeColor);
  Series1.AddBubbleXYZWeight(10,30,20,35,4,clTeeColor);

  Series1.UsePalette := True;
  Series1.TernaryStyle := tsBubble;
  Series1.Marks.Style := smsPointIndex;
end;
output:
Ternary.png
Ternary.png (47.42 KiB) Viewed 28762 times
TernarySeries won't share the chart with other series types so they need to be Active:=False;

Your code sample has a degree of complexity in which I don't clearly see yet where the obstacle might be but I can try and mimic some of the steps to populate the Series to see if it becomes clearer to me where the problem may lie.

Regards,
Marc Meumann
Steema Support

Marc
Site Admin
Site Admin
Posts: 1209
Joined: Thu Oct 16, 2003 4:00 am
Location: Girona
Contact:

Re: Ternary Graph Assigning the Weighting parameter

Post by Marc » Mon Sep 16, 2019 12:30 pm

As a footnote on this, looking more closely at your comments, as I see that the goal is to make the Ternary plot from two variables, there may be a possibility of adding an overload to facilitate that. We'll take a look.

Regards,
Marc

Marc
Site Admin
Site Admin
Posts: 1209
Joined: Thu Oct 16, 2003 4:00 am
Location: Girona
Contact:

Re: Ternary Graph Assigning the Weighting parameter

Post by Marc » Mon Sep 16, 2019 3:03 pm

Hello Errol,

Re.
Furthermore, I still do not understand why Steema did not use a simple X-Y graph with the Y-axis at a 60°angle, and with the ZValues parameter activated.

TeeChart's Ternary is based on a 3 value plot:
https://en.wikipedia.org/wiki/Ternary_p ... rnary_plot

Re.
Then if there was a procedure to calculate A% and B% from A, B and C and plot A% on the Y-axis and B% on the left axis, the results would be the same. Of course it would be necessary to draw the B-axis on the right, and overwrite the bottom axis with the reverse C axis, but that can be easily done.

Please note:
c must be equal to K − a − b
(where k is 1.0 or 100%)

The plot you describe seems somewhat different, common only perhaps, in having 60º axes. If there is a standard that defines the % of A and B without C then we'll need to know it and can work with it.

Re.
This is the approach of most scientific graphing applications.
Once we have the definition we'll be happy to have a go. Do you have any examples/references we can use? We assume that the third variable is not relevant and what you're interested in is the % relationship between A and B (X and Y).

With thanks.
Regards,
Marc
Steema Support

Errol
Newbie
Newbie
Posts: 29
Joined: Mon Jul 08, 2019 12:00 am

Re: Ternary Graph Assigning the Weighting parameter

Post by Errol » Mon Sep 16, 2019 8:58 pm

Hi Marc
Thank you for your reply. A ternary graph requires three values to calculate relative percentages, but only two values need to be plotted on a ternary graph as the third value is automatically 100 - A - B (working in percentages) as you state. If you use a regular XY chart with with the Y value at 60°and fixed ranges of 0 to 100 on each axis, and plot A on the Y axis on a line parallel with the X-axis and B on the X-axis on a line parallel with the y-axis, then the point is in exactly the same position as you obtain in your formulation.
Early work by Giggenbach, a geochemist in the geothermal industry, used this technique. If you search for "Giggenbach ternary diagrams", you will see many images, some with Mg plotted on the bottom axis going from left to right, and others with Mg on the right axis going from top to bottom. The positions of the points in either format are identical - the 10% line can either be projected from the bottom axis at a 6o° angle or from the right axis at -60°. (Note that Giggenbach used the regular X-Y plot initially as the graphing tools he had available had the option to draw the y-axis at an angle other than 90°).
Therefore, a ternary graph formulation using regular line, point and fastline series but with a 60° Y-axis and with a special A-B-C axis format, would be much more compatible with regular graphing used elsewhere.

However, I have another question. In the test program (TernaryGraph_Weight.zip) provided earlier in this thread, the code is shown below. In this code, I do not understand how Chart1[ i ] is created. When I try to emulate this code, I cannot create an "apparent" array of charts, which I presume is why I am not getting any output.

I look forward to your comments.

Best regards

Errol

Code: Select all

procedure TTernarySeriesForm.FormCreate(Sender: TObject);
var
  i: integer;
  
begin
  inherited;

  FormatSettings.DecimalSeparator:='.';

  for i:=0 to 2 do
  begin
    with TTernarySeries(Chart1.AddSeries(TTernarySeries)) do
    begin
      case i of
        0: begin
             Title:='AT-303';
             Pointer.Style:=psDonut;
           end;

        1: begin
             Title:='AT-505';
             Pointer.Style:=psTriangle;
           end;
        2: begin
             Title:='AT-602';
             Pointer.Style:=psDownTriangle;
           end;
      end;

      if i>0 then
      begin
        VertexTitle.Hide;
        Chart1.Axes.Hide;
        BeforeDrawValues:=BeforeDrawSecond;
      end
      else
        BeforeDrawValues:=BeforeDrawFirst;

      OnAfterAdd:=SeriesAfterAdd;
      DataSource := SeriesTextSource1;

      with SeriesTextSource1 do
      begin
        HeaderLines:=1;
        FileName := 'Na-Mg-K.csv';
        FieldSeparator:=',';
        Fields.Clear;

        AddField('X',3);
        AddField('Y',4);
        AddField('Z',5);
        AddField('Radius',6);
        AddField('Weighting',7);
        AddField('Text',8);

        Load;
      end;

      UseColorRange:=true;
      VertexTitles[0][0] := 'Na/1000' ;
      VertexTitles[1][0] := 'Sqrt(Mg)' ;
      VertexTitles[2][0] := 'K/100' ;

      Pointer.Transparency:=50;
      ShowInLegend:=False;

      UseColorRange:=False;

      OnGetPointerStyle:=SeriesGetPointerStyle;
    end;
  end;

  for i:=0 to 2 do
  begin
    with TPointSeries(Chart1.AddSeries(TPointSeries)) do
    begin
      Title:=Chart1[i].Title;
      Pointer.Style:=TTernarySeries(Chart1[i]).Pointer.Style;
    end;
  end;

  Chart1.Legend.Title.TextAlignment := taLeftJustify;
  Chart1.Legend.Width :=30;
  Chart1.Legend.Title.Visible:=true;
  Chart1.Walls.Back.Hide;
end;


Marc
Site Admin
Site Admin
Posts: 1209
Joined: Thu Oct 16, 2003 4:00 am
Location: Girona
Contact:

Re: Ternary Graph Assigning the Weighting parameter

Post by Marc » Tue Sep 17, 2019 4:27 pm

Hello Errol,

Re.
However, I have another question. In the test program (TernaryGraph_Weight.zip) provided earlier in this thread, the code is shown below. In this code, I do not understand how Chart1[ i ] is created. When I try to emulate this code, I cannot create an "apparent" array of charts, which I presume is why I am not getting any output.
Firstly, your project has highlighted a bug between SeriesTextSrc and the Ternary Series. I noticed it when looking at the Y values reported in the data section of the Editor. Y is coming up as the Weighting value. That's because (the bug) SeriesTextSrc uses what's called the MandatoryValueList for its second value. For most Series Types Y is mandatory (ie a chart could be created with x as a sequential index but Y needs to have some significance). For the case of Ternary X,Y,Z are all treated as equals and it's Weight that shows the true value difference and that was chosen to be Mandatory. That's being erroneously picked up by SeriesTextSource.

I put in a temporary fix for your project which generates the Chart image attached. Notice that Ternary points have moved respectively. We'll revise how best to apply this fix for inclusion with the next release update.
Ternary2.png
Ternary2.png (54.83 KiB) Viewed 28738 times
That doesn't answer your question though.

Your test project loads via SeriesTextSource to several Series. Have you considered loading this as one Series?

eg. ... you can still use the SeriesTextSource instead of the i loop.

Code: Select all

procedure TForm12.Button1Click(Sender: TObject);

  function GetLabel(Idx : Integer) : String;
  Begin
    if idx <2 then
      result:='1st'
    else if idx < 4 then
      result:='2nd'
    else result:='3rd';
  End;

begin
  Series1.Clear;

  for i:=0 to 5 do
  Begin
    //some random data
    Series1.AddBubbleXYZWeight(Random(30),Random(20),Random(10),4 + Random(4),i,clTeeColor);
    Series1.Labels[Series1.Count-1] := GetLabel(Series1.Count-1);
  End;

  Series1.UsePalette := True;
  Series1.TernaryStyle := tsBubble;
  Series1.Marks.Style := smsPointIndex;

  Chart1.Legend.MaxNumRows := Chart1.SeriesCount;

  Chart1.Legend.LegendStyle := lsValues;
end;
 
function TForm12.Series1GetPointerStyle(Sender: TChartSeries;
  ValueIndex: Integer): TSeriesPointerStyle;
begin
    //some code to control pointer type
    if ValueIndex <2 then  
      result:=psDonut
    else if ValueIndex < 4 then
      result:=psTriangle
    else result:=psDownTriangle;
end;
There'd be a slight extra setting the Legend pointers and max Legend rows, I'll see if a get a moment to build a small example (based on TeeChart featuredemo:"Legend:Symbol OnDraw").

We'll look at Giggenbach, thanks.

Regards,
Marc
Steema Support

Marc
Site Admin
Site Admin
Posts: 1209
Joined: Thu Oct 16, 2003 4:00 am
Location: Girona
Contact:

Re: Ternary Graph Assigning the Weighting parameter

Post by Marc » Wed Sep 18, 2019 3:50 pm

Hello Errol,

Taking up your lead; to simplify things a little we're internalising code to enable multiple Ternary Series in one Chart without the need for custom code in the form.

Regards,
Marc
Steema Support

Post Reply