1
votes

I'm using TeeChart with Delphi XE5 and utilizing the BubbleSeries component to show X/Y/Radius bubbles in a chart.

I'm building the chart using an list of objects that I have, calculating X/Y/Radius values for these objects on the fly and inserting them using the TBubbleSeries.AddBubble method.

The problem is when I want to perform some action on the objects when the corresponding bubble is hovered/clicked/etc. I use the TChartSeries.Clicked method to find out which bubble is clicked, but the index I get returned is only usable for finding out the xy/radius values of the bubble, not which object originated it.

Maybe I'm missing something simple, because this seems to be something that any charting library should handle easily. I tried using the returned index from AddBubble method, but this index is only valid until another call to AddBubble is performed, at which point, the internal list seems to be re-ordered.

Edit: Was asked for some code, here it is!

procedure TBubbleReportForm.ChartMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  Index: Integer;
  Device: TDevice;
begin

  Index := BubbleSeries.Clicked(X,Y);

  if Index = -1 then
  begin
    BubbleChart.ShowHint := False;
    Exit;
  end;

  // This does not work as indexing does seems to correspond to the order which the bubbles was added.
  Device := FDevices[Index];


  BubbleChart.Hint := Device.Name;
  BubbleChart.ShowHint := True;

end;



procedure TBubbleReportForm.FormCreate(Sender: TObject);
var
  Device: TDevice;
begin


  BubbleChart.OnMouseMove := ChartMouseMove;

  // FDevices is of TObjectList type.
  for Device in FDevices do
  begin

    BubbleSeries.AddBubble(Device.CalculateXVal,Device.CalculateYVal,Device.CalculateRadius);

  end;

end;
2
Are you using label and color bubble properties? - MBo
I'm not using label and all bubbles have the same styling (set form the form designer on the series). - monoceres

2 Answers

2
votes

I would use a a Generic TObjectList. Or an descendant og a TObjectList.

First Iimpelment your BoubleObject, and a list of them. In the following example I've just used a dummy implementation:

unit BubbleU;

interface

uses

  System.Generics.Collections, System.SysUtils, Vcl.Graphics;

{$M+}

type
  TBubble = class
  private
    FX: Double;
    FRadius: Double;
    FY: Double;
    FLabelText: String;
    FColor: TColor;
    FIndex: Integer;
    FChartIndex: Integer;
    procedure SetChartIndex(const Value: Integer);
  protected
    procedure DoCalculation;
  public
    constructor Create(aIndex: Integer); reintroduce;
  published
    property X: Double read FX;
    property Y: Double read FY;
    property Radius: Double read FRadius;
    property LabelText: String read FLabelText;
    property Color: TColor read FColor;
    property ChartIndex: Integer read FChartIndex write SetChartIndex;
  end;

  TBubbleList = class(TObjectList<TBubble>)
  public
    function ElementFormChartIndex(ChartIndex: Integer): TBubble; overload;
  end;

implementation

{ TBubble }

constructor TBubble.Create(aIndex: Integer);
begin
  inherited Create;
  FIndex := aIndex;
  DoCalculation;
end;

procedure TBubble.DoCalculation;
begin
  FX := FIndex;
  FY := FIndex;
  FRadius := 1;
  FColor := clRed;
  FLabelText := 'Index: ' + FIndex.ToString;
end;

procedure TBubble.SetChartIndex(const Value: Integer);
begin
  FChartIndex := Value;
end;

{ TBubbleList }

function TBubbleList.ElementFormChartIndex(ChartIndex: Integer): TBubble;
var
  Element : TBubble;
begin
  for Element in Self do
    if Element.FChartIndex = ChartIndex then
      Exit(element);

  Exit(nil);
end;
end.

Next Extend your TBubbleSeries

unit BubbleSeriesExtention;

interface

uses
  System.Classes, System.SysUtils,
  VclTee.BubbleCh,
  BubbleU;

type
  TBubbleSeries = class(VclTee.BubbleCh.TBubbleSeries)
  strict private
    FBoubleList: TBubbleList;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function AddBubble(aBubble: TBubble): Integer; reintroduce;
  published
    property BoubleList : TBubbleList read FBoubleList;
  end;

implementation

{ TBubbleSeries }

function TBubbleSeries.AddBubble(aBubble: TBubble): Integer;
begin
  aBubble.ChartIndex := Inherited AddBubble(aBubble.X, aBubble.Y, aBubble.Radius, aBubble.LabelText, aBubble.Color);
  FBoubleList.Add(aBubble);
  Result := aBubble.ChartIndex;
end;

constructor TBubbleSeries.Create(AOwner: TComponent);
begin
  inherited;
  FBoubleList := TBubbleList.Create(True);
end;

destructor TBubbleSeries.Destroy;
begin
  FreeAndNil(FBoubleList);
  inherited;
end;

end.

Finally Use it in your from:

Add BubbleSeriesExtention toh the uses list AFTER VclTee.BubbleCh

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VclTee.TeeGDIPlus, VclTee.TeEngine,
  VclTee.Series, VclTee.BubbleCh, Vcl.ExtCtrls, VclTee.TeeProcs, VclTee.Chart,

  BubbleU, BubbleSeriesExtention;

And use it:

type
  TForm4 = class(TForm)
    Chart1: TChart;
    BubbleSeries: TBubbleSeries;
    procedure FormCreate(Sender: TObject);
    procedure Chart1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form4: TForm4;

implementation

{$R *.dfm}

procedure TForm4.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  Index: Integer;
  Bouble: TBubble;

begin
  Index := BubbleSeries.Clicked(X, Y);

  if index < 0 then
    exit;

  Bouble := BubbleSeries.BoubleList.ElementFormChartIndex(Index);
  Caption := Bouble.LabelText;
end;

procedure TForm4.FormCreate(Sender: TObject);
var
  i: Integer;
begin
  //Add dummy data
  for i := 0 to 9 do
    BubbleSeries.AddBubble(TBubble.Create(i));
end;

end.

this solution has this advantage that you have acces to your Object all the time and when your BubbleSeries are destroyes so is your objects for calculating elements in it. and gives you a kind of garbage collection

0
votes

You can exploit unused AXLabel argument like this:

for DevIndex := 0 to DeviceCount - 1 do begin
    Device := FDevices[DevIndex]; 
    BubbleSeries.AddBubble(Device.CalculateXVal,Device.CalculateYVal, Device.CalculateRadius, IntToStr(DevIndex));
end; 

// to avoid labels' text ox X-Axis:
Chart1.BottomAxis.LabelStyle := talValue; 

  //in Clicked:
   DeviceIndex := StrToInt(BubbleSeries.Labels[Index]);