1

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;
monoceres
  • 4,722
  • 4
  • 38
  • 63

2 Answers2

2

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

Jens Borrisholt
  • 6,174
  • 1
  • 33
  • 67
  • Especially the code that doesn't work: Mouse pos to Bouble Object. – Jens Borrisholt Feb 02 '15 at 09:53
  • Added some code! Hopefully the problem is now clear. – monoceres Feb 02 '15 at 10:07
  • Hmm, would this really work? Wouldn't it suffer form the same problem because Inherited AddBubble will reshuffle internal implementation and return the same index several times? – monoceres Feb 02 '15 at 11:38
  • No it will not becaquse i store the internal index on the object it self. – Jens Borrisholt Feb 02 '15 at 11:43
  • If you can make it mail pelase give som realt data where it fails. – Jens Borrisholt Feb 02 '15 at 11:43
  • In my initial implementation I stored the internal index in a dictionary that mapped it to my index. However this fails because when you add several bubbles the previous bubble's index is no longer valid (it has moved). For example, when adding 10 bubbles I get the following indexes: "0,0,0,1,4,1,5,6,7,9". As you see, the internal list is reordered and indexes are reused, thus making "old" indexes useless. – monoceres Feb 02 '15 at 11:55
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/70040/discussion-between-jens-borrisholt-and-monoceres). – Jens Borrisholt Feb 02 '15 at 11:57
0

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]);
MBo
  • 77,366
  • 5
  • 53
  • 86
  • Heh, ugly, but I like it. If a cleaner solution doesn't show up, I'll use this. – monoceres Feb 02 '15 at 11:38
  • Hmm, is there any way to make the x value still show up on the x axis when using the label? I don't want to hide the axis completely.. – monoceres Feb 02 '15 at 12:49
  • An extremly ugly hack. – Jens Borrisholt Feb 02 '15 at 14:24
  • It is not a hack. It is like to Tag property using to store user data. Bubble has no Tag or another user-data property,and author isn't going to use auxillary bubble properties. Why not to make them work? – MBo Feb 02 '15 at 14:43
  • @MBo that's wrong, TBubbleSeries has Tag (integer) and TagObject (TObject) properties. – Narcís Calvet Feb 02 '15 at 15:35
  • @Narcís Calvet TBubbleSeries has tag, but every bubble is not an TObject (we know nothing about it's intrinsic structure without sources) – MBo Feb 02 '15 at 16:50
  • @MBo ok, yes, that's correct. I have added it as a feature request at Steema Software's bugzilla: http://bugs.teechart.net/show_bug.cgi?id=1111 – Narcís Calvet Feb 02 '15 at 17:49