Hello,
I have to combine volumepipe+bubble chart together. volumepipe = stages of projects, bubbles = project in current stage(radius = money costs).
*Is it possible? I was not successful in this task in design mode.bubbles were changing its position according many factors(radius, max y pos etc)
I tried to draw circles on canvas but i'm not able to determine area of one volumepipe segment.
How could I do that? something like this:
Thank you.
Lubos
Volumepipe + bubble chart - segment area
Re: Volumepipe + bubble chart - segment area
Hi Lubos,
You could draw the ellipses manually, but you'll have to calculate the polygons as TeeChart does internally.
Here it is a simple example:
You could draw the ellipses manually, but you'll have to calculate the polygons as TeeChart does internally.
Here it is a simple example:
Code: Select all
uses TeCanvas;
type TVolumePipeSeriesAccess=class(TVolumePipeSeries);
var Series1: TVolumePipeSeries;
Series2: TPointSeries;
procedure TForm1.FormCreate(Sender: TObject);
begin
Chart1.View3D:=false;
Series1:=Chart1.AddSeries(TVolumePipeSeries) as TVolumePipeSeries;
Series1.FillSampleValues();
Series2:=Chart1.AddSeries(TPointSeries) as TPointSeries;
Series2.FillSampleValues();
Chart1.OnAfterDraw:=Chart1AfterDraw;
end;
procedure TForm1.Chart1AfterDraw(Sender: TObject);
function GetMaxMarkHeight : Integer;
var t,
tmpLines : Integer;
begin
result:=0;
for t:=Series1.FirstValueIndex to Series1.LastValueIndex do
Begin
Series1.ParentChart.MultiLineTextWidth(TVolumePipeSeriesAccess(Series1).GetMarkText(t),tmpLines);
if tmpLines>result then
result:=tmpLines;
end;
result:=result*Chart1.Canvas.FontHeight;
end;
var BoundingPoints: TFourPoints;
i, xVal, lastX, yDisp: Integer;
lastYDisp, overallWidth: TCoordinate;
IDiff: Integer;
poly : TTrapeziumPoints;
IPolyList: Array of TTrapeziumPoints;
InnerRect: TRect;
tmp: TCoordinate;
tmpCone : Single;
mid1, mid2: TPoint;
begin
Chart1.Canvas.Brush.Style := bsClear;
InnerRect:=Chart1.ChartRect;
if Series1.Marks.Visible then
InnerRect.Top:=InnerRect.Top+GetMaxMarkHeight;
tmpCone:=(Series1.ConePercent div 2)*0.01;
With InnerRect do
Begin
BoundingPoints[0]:= TeePoint(Left+2,Top+2); //topleft
tmp:={$IFNDEF FMX}Round{$ENDIF}(Top+(Bottom-Top) * tmpCone);
BoundingPoints[1]:= TeePoint(Right-2, tmp); //topright
tmp:={$IFNDEF FMX}Round{$ENDIF}(Bottom-((Bottom-Top) * tmpCone));
BoundingPoints[2]:= TeePoint(Right-2, tmp); //bottomright
BoundingPoints[3]:= TeePoint(Left+2,Bottom-2); //bottomleft
end;
IDiff:= BoundingPoints[1].Y-BoundingPoints[0].Y;
overallWidth:=BoundingPoints[1].X-BoundingPoints[0].X;
IPolyList:=nil;
lastX:=BoundingPoints[0].X;
lastYDisp:=0;
if overallWidth<>0 then
for i:=0 to Series1.Count-1 do
if not Series1.IsNull(i) then
Begin
xVal:=TVolumePipeSeriesAccess(Series1).CalcSegment(i,Series1.YValues[i])+BoundingPoints[0].X; //add left displacement
yDisp:=Round((((xVal-BoundingPoints[0].X)/overallWidth)*IDiff));
poly[0]:=TeePoint(xVal,BoundingPoints[3].Y-yDisp); //right bottom
poly[1]:=TeePoint(xVal,BoundingPoints[0].Y+yDisp); //right top
poly[2]:=TeePoint(lastX,BoundingPoints[0].Y+lastYDisp); //left top
poly[3]:=TeePoint(lastX,BoundingPoints[3].Y-lastYDisp); //left bottom
SetLength(IPolyList,Length(IPolyList)+1);
IPolyList[Length(IPolyList)-1]:=poly;
lastYDisp:=yDisp;
mid1:=TeePoint(poly[2].X + ((poly[1].X - poly[2].X) div 2), poly[2].Y + ((poly[1].Y - poly[2].Y) div 2));
mid2:=TeePoint(poly[3].X + ((poly[0].X - poly[3].X) div 2), poly[3].Y + ((poly[0].Y - poly[3].Y) div 2));
with Series1.ParentChart.Canvas do
Begin
AssignVisiblePen(Series1.Pen);
Pen.Color:=Series1.ValueColor[i];
Ellipse(Rect(poly[2].X, mid1.Y, poly[0].X, mid2.Y));
end;
lastX:=xVal;
end;
end;
Best Regards,
Yeray Alonso Development & Support Steema Software Av. Montilivi 33, 17003 Girona, Catalonia (SP) | |
Please read our Bug Fixing Policy |
Re: Volumepipe + bubble chart - segment area
hi,
thank you very much for that example.
Sorry to say it but i'm not concerned on that ellipse.
I still cant figure out how to combine volumepipe and bubble chart together.
example of data :
6 project stages = 6 stages of volumepipe chart.
bubble chart data- could be in more active stages
Project1 -1,2
Project2 - 4
Project3 -3,5
Project4 -1,3
Project5 -4
Project6 -6
Project7 -1,3,4
Project8 -2
Project9 -2
Project10-2,3
I tried to add bubble serie on afterdraw event when I get the segment top left corner(from your example)- as addnullxy and then one bubble to the middle of segment. (x,y value form mid1,mid2 point).
I was not successfull. bubble was not in middle.
Please advice.
Thank you
thank you very much for that example.
Sorry to say it but i'm not concerned on that ellipse.
I still cant figure out how to combine volumepipe and bubble chart together.
example of data :
6 project stages = 6 stages of volumepipe chart.
bubble chart data- could be in more active stages
Project1 -1,2
Project2 - 4
Project3 -3,5
Project4 -1,3
Project5 -4
Project6 -6
Project7 -1,3,4
Project8 -2
Project9 -2
Project10-2,3
I tried to add bubble serie on afterdraw event when I get the segment top left corner(from your example)- as addnullxy and then one bubble to the middle of segment. (x,y value form mid1,mid2 point).
I was not successfull. bubble was not in middle.
Please advice.
Thank you
Re: Volumepipe + bubble chart - segment area
Hello,
Could you please arrange a simple example project we can run as-is to reproduce the problem here?
Thanks in advance.
Could you please arrange a simple example project we can run as-is to reproduce the problem here?
Thanks in advance.
Best Regards,
Yeray Alonso Development & Support Steema Software Av. Montilivi 33, 17003 Girona, Catalonia (SP) | |
Please read our Bug Fixing Policy |
Re: Volumepipe + bubble chart - segment area
Code: Select all
type
TVolumePipeSeriesAccess=class(TVolumePipeSeries);
TArrayBounds= Array of TTrapeziumPoints;
TpProj=^TrProj;
TrProj=record
name:string;
activestage:string;
end;
TForm1 = class(TForm)
Chart1: TChart;
procedure FormCreate(Sender: TObject);
procedure Chart1AfterDraw(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
pProj:TpProj;
listProj:Tlist;
bFilled:boolean;
procedure fillprojdata(n:integer);
function GetMaxMarkHeight : Integer;
function Calculatebounds:TArrayBounds;
procedure drawbubles(xArBounds:TArrayBounds);
public
{ Public declarations }
end;
var
Form1: TForm1;
Series1: TVolumePipeSeries;
Series2: TBubbleSeries;
implementation
{$R *.dfm}
function TForm1.GetMaxMarkHeight : Integer;
var t, tmpLines : Integer;
begin
result:=0;
for t:=Series1.FirstValueIndex to Series1.LastValueIndex do
Begin
Series1.ParentChart.MultiLineTextWidth(TVolumePipeSeriesAccess(Series1).GetMarkText(t),tmpLines);
if tmpLines>result then
result:=tmpLines;
end;
result:=result*Chart1.Canvas.FontHeight;
end;
procedure TForm1.fillprojdata(n:integer);
const sdata='01234';
var i,j,fpos,len:integer;
sub:string;
begin
randomize;
listProj:=Tlist.Create;
for I := 0 to n do begin
New(pProj);
fpos:=random(5);
len:=random(length(sdata)-fpos)+1;
pProj.name:='project '+inttostr(i+1);
pProj.activestage:='';
sub:=copy(sData,fpos,len);
for j:=1 to length(sub) do begin
pProj.activestage:=pProj.activestage+sub[j]+',';
end;
pProj.activestage:=copy(pProj.activestage,1,length(pProj.activestage)-1);
listproj.Add(pProj);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Chart1.View3D:=false;
Series1:=Chart1.AddSeries(TVolumePipeSeries) as TVolumePipeSeries;
series1.ShowInLegend:=false;
//five stages
Series1.Add(100,'stage1');
Series1.Add(100,'stage2');
Series1.Add(100,'stage3');
Series1.Add(100,'stage4');
Series1.Add(100,'stage5');
Chart1.OnAfterDraw:=Chart1AfterDraw;
fillprojdata(19);
bFilled:=false;
end;
procedure TForm1.FormDestroy(Sender: TObject);
var i:integer;
begin
for i:=0 to listProj.Count-1 do begin
pProj:=listproj[i];
dispose(pProj);
end;
end;
function TForm1.Calculatebounds:TArrayBounds;
var BoundingPoints: TFourPoints;
i,j, xVal, lastX, yDisp: Integer;
lastYDisp, overallWidth: TCoordinate;
IDiff: Integer;
poly : TTrapeziumPoints;
IPolyList: Array of TTrapeziumPoints;
InnerRect: TRect;
tmp: TCoordinate;
tmpCone : Single;
mid1, mid2: TPoint;
circleCenter:Tpoint;
begin
InnerRect:=Chart1.ChartRect;
if Series1.Marks.Visible then
InnerRect.Top:=InnerRect.Top+GetMaxMarkHeight;
tmpCone:=(Series1.ConePercent div 2)*0.01;//koeficient zmensenia,stupanie pyramidy
With InnerRect do
Begin
BoundingPoints[0]:= TeePoint(Left+2,Top+2); //topleft
tmp:=Round(Top+(Bottom-Top) * tmpCone);
BoundingPoints[1]:= TeePoint(Right-2, tmp); //topright
tmp:=Round(Bottom-((Bottom-Top) * tmpCone));
BoundingPoints[2]:= TeePoint(Right-2, tmp); //bottomright
BoundingPoints[3]:= TeePoint(Left+2,Bottom-2); //bottomleft
end;
IDiff:= BoundingPoints[1].Y-BoundingPoints[0].Y;
overallWidth:=BoundingPoints[1].X-BoundingPoints[0].X;
IPolyList:=nil;
lastX:=BoundingPoints[0].X;
lastYDisp:=0;
if overallWidth<>0 then
for i:=0 to Series1.Count-1 do
if not Series1.IsNull(i) then
Begin
xVal:=TVolumePipeSeriesAccess(Series1).CalcSegment(i,Series1.YValues[i])+BoundingPoints[0].X;
//add left displacement
yDisp:=Round((((xVal-BoundingPoints[0].X)/overallWidth)*IDiff));
poly[0]:=TeePoint(xVal,BoundingPoints[3].Y-yDisp); //right bottom
poly[1]:=TeePoint(xVal,BoundingPoints[0].Y+yDisp); //right top
poly[2]:=TeePoint(lastX,BoundingPoints[0].Y+lastYDisp); //left top
poly[3]:=TeePoint(lastX,BoundingPoints[3].Y-lastYDisp); //left bottom
SetLength(IPolyList,Length(IPolyList)+1);
IPolyList[Length(IPolyList)-1]:=poly;
lastYDisp:=yDisp;
lastX:=xVal;
end;
result := TArrayBounds(IPolyList);
end;
procedure TForm1.Chart1AfterDraw(Sender: TObject);
begin
if not bFilled then
drawbubles( Calculatebounds);
end;
procedure TForm1.drawbubles(xArBounds: TArrayBounds);
var poly : TTrapeziumPoints;
newp, mid1, mid2: TPoint;
x,y,i,nProj: Integer;
seriesX: TBubbleSeries;
slist:Tstringlist;
begin
randomize;
// poly[0] //right top
// poly[1] //right bottom
// poly[2]//left bottom
// poly[3]//left top
for i:= Chart1.SeriesList.Count-1 downto 0 do begin
if chart1.Series[i] is TBubbleSeries then
chart1.Series[i].Free;
end;
slist:=TStringList.Create;
slist.StrictDelimiter:=true;
slist.Delimiter:=',';
for nproj := 0 to listproj.Count-1 do begin
pProj:=listproj[nproj];
seriesX:=TBubbleSeries.Create(Chart1);
seriesX.ParentChart := Chart1;
seriesX.title := pProj^.name;
seriesX.ColorEachPoint:=false;
slist.Clear;
slist.DelimitedText:=pproj^.activestage;
for i:=0 to slist.Count-1 do begin
poly:= xArBounds[strToInt(slist[i])];
y:=poly[1].y+Random(poly[0].y-poly[1].y);
x:=random(poly[1].X-poly[2].x)+poly[2].X;
seriesX.AddBubble(x,y ,random(10));
//mid1 := TeePoint(poly[2].X + ((poly[1].X - poly[2].X) div 2), poly[2].Y + ((poly[1].Y - poly[2].Y) div 2));
//mid2 := TeePoint(poly[3].X + ((poly[0].X - poly[3].X) div 2), poly[3].Y + ((poly[0].Y - poly[3].Y) div 2));
//newp:= Teepoint(mid2.X,(mid1.y-mid2.y)div 2);
//seriesX.AddBubble(newp.x,newp.y ,5);
//seriesX.AddNullXY(poly[2].x,poly[2].Y);
end;
end;
slist.Free;
bFilled:=true;
end;
end.
Re: Volumepipe + bubble chart - segment area
Hello,
I see in your code you are populating a TBubbleSeries at OnAfterDraw event. I understand you need the chart to be drawn to calculate the positions of the bubbles relative to the TVolumePipeSeries segments, but it would be more appropriate to force a chart repaint (Chart1.Draw) at OnCreate and calculate the positions after it.
The OnAfterDraw event is appropriate to manually draw shapes and lines (Chart1.Canvas.Line, Chart1.Canvas.Ellipse, etc) but not for populating series in general.
On the other hand, note the polygons you are calculating are using pixel positions while adding values to a series expects to take axis values. However, since the TVolumePipeSeries doesn't use the regular axes, the usual functions to convert from pixels to values can't be used to populate the TBubbleSeries.
I would try to follow this:
- Enhance your TrProj structure to also store color, x, y and radius.
- Create the TVolumePipeSeries at OnCreate, as you already do.
- Create as many TBubbleSeries (with title and color but without points) as TrProj you'll have later. This is just to populate the legend.
- Force a repaint (Chart1.Draw). Now the chart has the final size (note the legend shrinks the chart).
- Calculate the polygons for the segments.
- Populate your TrProj structure with the desired data, still at OnCreate.
- At OnAfterDraw, loop for your TrProj structure as you do and manually draw your ellipses. Ie:
Note that this way you are storing absolute pixel positions to your pProj structure and, if you resize the chart the TVolumePipeSeries will be resized with it but not your ellipses. So if your chart is resized you should recalculate the polygons for the TVolumePipeSeries segments, repopulate your TrProj structure and repaint.
I see in your code you are populating a TBubbleSeries at OnAfterDraw event. I understand you need the chart to be drawn to calculate the positions of the bubbles relative to the TVolumePipeSeries segments, but it would be more appropriate to force a chart repaint (Chart1.Draw) at OnCreate and calculate the positions after it.
The OnAfterDraw event is appropriate to manually draw shapes and lines (Chart1.Canvas.Line, Chart1.Canvas.Ellipse, etc) but not for populating series in general.
On the other hand, note the polygons you are calculating are using pixel positions while adding values to a series expects to take axis values. However, since the TVolumePipeSeries doesn't use the regular axes, the usual functions to convert from pixels to values can't be used to populate the TBubbleSeries.
I would try to follow this:
- Enhance your TrProj structure to also store color, x, y and radius.
- Create the TVolumePipeSeries at OnCreate, as you already do.
- Create as many TBubbleSeries (with title and color but without points) as TrProj you'll have later. This is just to populate the legend.
- Force a repaint (Chart1.Draw). Now the chart has the final size (note the legend shrinks the chart).
- Calculate the polygons for the segments.
- Populate your TrProj structure with the desired data, still at OnCreate.
- At OnAfterDraw, loop for your TrProj structure as you do and manually draw your ellipses. Ie:
Code: Select all
Chart1.Canvas.Brush.Color:=pProj.color;
Chart1.Canvas.Ellipse(Rect(pProj.x-pProj.r, pProj.y-pProj.r, pProj.x+pProj.r, pProj.y+pProj.r));
Best Regards,
Yeray Alonso Development & Support Steema Software Av. Montilivi 33, 17003 Girona, Catalonia (SP) | |
Please read our Bug Fixing Policy |