Delphi - How to create a stacked bar series during runtime? - delphi

I would like to create 2 series that stack upon each other using the Teechart series in Delphi during runtime.
Essentially I want to have 2 series, each with 2 entries, or data points, and the corresponding data points, i.o.w series1 datapoint1 and series 2 datapoint 1, should stack upon each other to form a single
bar.
I have tried to look for a procedure or property to change to no avail.

Minimal example:
var
S1, S2: TBarSeries;
begin
S1 := TBarSeries(Chart1.AddSeries(TBarSeries));
S2 := TBarSeries(Chart1.AddSeries(TBarSeries));
S1.MultiBar := mbStacked;
S2.MultiBar := mbStacked;
//S1.StackGroup := 0;
//S2.StackGroup := 0; //same group if few groups will be used
S1.Add(3);
S1.Add(1);
S2.Add(2);
S2.Add(4);

Related

Tcxtreelist overlap text over columns Devexpress

I have a populated tcxtreelist with all root nodes(any node which is set to level 0) being a parent to more desired information. The issue that I have is that the text is clipped by columns used for the children nodes.
I want to be able to have the text from the root nodes(LSite.Name) to span the length of all the columns, basically I want to remove the clipping. However I want the the children nodes to work as default.
How is this done?
Edit: Code added
LSite : TSiteList;
i : Integer;
LNode : TcxTreeListNode;
// variables used are above
for i := 0 to FSiteList.Count - 1 do
begin
LSite := TSiteList(FSiteList.Items[i]);then
LNode := TreeTypes.Add;
LNode.Values[0] := LSite.Name;
LNode.Data := LSite; // this is is location name built up from a database which populates FSiteList(Object list)
end
This is how the root node is populated. Thousands of records with some information used elsewhere but with regards to the list all I care about is the name fitting across all the columns.
Children nodes:
LOffer : TOfferList;
i : Integer;
LNode, LSiteNode : TcxTreeListNode;
begin
LSiteNode := TreeTypes.FocusedNode;
for i := 0 to FOfferList.Count - 1 do
begin
LOffer := TOfferList(FOfferList.Items[i]);
LNode := LSiteNode.AddChild;
LNode.Values[1] := Trim(FOfferList.Items[i].InstName + ' ' + FOfferList.Items[i].SoftName);
LNode.Values[2] := Trim(FOfferList.Items[i].SerialNumber + ' ' + FOfferList.Items[i].ActivateNum);
LNode.Values[3] := Trim(FOfferList.Items[i].OfferNum);
LNode.Data := LOffer;
end;
end;
children nodes are only populated when a site is clicked. This will then display all offers which exists for said site. Again offerlist is an objectlist
I have had a play around with the custom draw event but I have no idea what options to look at.
Test at custom drawing - OnCustomDrawDataCell:
AViewInfo.EditViewInfo.TextColor := clBlack;
AViewInfo.BoundsRect.Width := 200;
//AViewInfo.ContentRect.Width := 200;
AViewInfo.EditViewInfo.Paint(ACanvas);
ADone := True;
All this seems to do is bunch all the nodes into the far left of the grid and group all the columns ontop of eachother, whilst still not increasing the width of the root node text columns.
TLDR:
I want to be able to have text from column[0] to not clip but instead either continue into other columns or to overlay other columns. For a tcxtreelist which has been populated by a database and objectlists.
Edit 2:
Added image with a visual example of how it looks now
As you can see the 'Company' is getting clipped, I would like that root node to just continue displaying its text across all the columns if needs be. This way I can better sort out the width of other columns to try and get the desired view.

Draw lines between two known points

Using Delphi XE3 and GMLib 1.2.4. Should be basic question. How to draw basic non-linked lines between two given points. I currently have GMMap displayed on a WebBrowser and have GMPolyline component. Using known values for both lat-lon pairs. Just need help plotting line between the two. Using this to plot lines of bearing. This is what I have so far:
procedure TMainGMForm.ButtonPlotLineClick(Sender: TObject);
var
CurLat,CurLon,DisLat,DisLon: Double;
P1,P2: TLatLng;
begin
CurLat := StrToFloat(EditLat.Text);
CurLon := StrToFloat(EditLon.Text);
DisLat := StrToFloat(EditLat2.Text);
DisLon := StrToFloat(EditLon2.Text);
P1 := TLatLng.Create(CurLat,CurLon);
Inc(PointIndex);
P2 := TLatLng.Create(DisLat,DisLon);
Inc(PointIndex);
//what goes here to plot a line between these two points?
//
FreeAndNil(P1);
FreeAndNil(P2);
end;
You need to add a TPolyline into your TGMPolyline, something like this
var
Poly: TPolyline;
begin
Poly := TPolyline(GMPolyline1.Add);
and add the two points into the LinePoints array
Poly.AddLinePoint(CurLat, CurLon);
Poly.AddLinePoint(DisLat, Double);

Simple Delphi DBcharting

So, the problem I'm having is that I'm displaying two bars on the graph for each student, I just want one of them. They're the correct height though, so that's good.
This is my Delphi source code;
strlstField := TStringList.Create();
ADOQGetResults.SQL.clear;
ADOQGetResults.SQL.Add(
'SELECT Results.StudentID, SUM(Results.Rawmark) as TRM, StudentInfo.Fname '+
'FROM (StudentInfo INNER JOIN Results ON StudentInfo.StudentID = Results.StudentID) '+
'WHERE (((StudentInfo.StudentID)=Results.StudentID)) AND Results.TestID =12 '+
'GROUP BY StudentInfo.Fname, Results.StudentID'
);
ADOQGetResults.Active := True;
ADOQGetResults.Open;
DBChart1.Title.Text.Clear;
DBChart1.Title.Text.Add('Class leaderboard');
DBChart1.Title.Font.Size := 15;
DBChart1.LeftAxis.Title.Font.Size := 12;
DBChart1.LeftAxis.Title.Caption := 'Total marks';
DBChart1.BottomAxis.Title.Font.Size := 12;
DBChart1.BottomAxis.Title.Caption := 'Student';
//Charting Series
//To Remove Old Series
for intCnt := DBChart1.SeriesCount -1 downto 0 do
DBChart1.Series[intCnt].Free;
//To Add New Series
for intCnt := 1 to ADOQGetResults.FieldCount - 1 do
begin
strlstField.Add(ADOQGetResults.FieldList[intCnt].FieldName);
DBChart1.AddSeries(TBarSeries.Create(nil));
end;
//To set source for Series
for intCnt:= 0 to DBChart1.SeriesCount -1 do
begin
with DBChart1 do begin
Series[intCnt].Clear;
Series[intCnt].Title := strlstField[intCnt];
Series[intCnt].ParentChart := DBChart1;
Series[intCnt].DataSource := ADOQGetResults;
Series[intCnt].XLabelsSource := 'Fname';
Series[intCnt].YValues.ValueSource := 'TRM';
end;
end;
I've been trying to work-out whats going wrong all day, so if anyone can help at all I'd be very grateful!
Here is what the graph looks like right now;
http://oi48.tinypic.com/6qelba.jpg
Why are you looping over EVERY FIELD in the result (you return 3 fields in your query) and adding one series PER field in the result? It's almost like you think that the field count equals your row count or something. Secondly I would venture to guess that something in your query plus your data (that we can't see) could result in you getting more rows in your query result than you were expecting.
Why are you destroying and re-adding series when your query always returns 3 fields, 1 field is not charted, 1 field is the series label source and 1 field is the series value source? Just statically create one series at designtime in your dfm and forget all this crazy runtime stuff. Have you tried double clicking dbchart and adding ONE BarChart series there?
This works and is much less code. You don't need to open a dataset twice, by the way. Note that I'm using the DBDEMOS.mdb database that comes with Delphi here so that everyone can play along. Add a db chart and at DESIGNTIME add ONE barchart series to it. Configure as desired. Use this code. dataset below is a TADODataset.
-
dataset.CommandText := 'select EmpNo,FirstName,Salary from employee';
dataset.Active := True;
DBChart1.Title.Text.Clear;
DBChart1.Title.Text.Add('Class leaderboard');
DBChart1.Title.Font.Size := 15;
DBChart1.LeftAxis.Title.Font.Size := 12;
DBChart1.LeftAxis.Title.Caption := 'Total marks';
DBChart1.BottomAxis.Title.Font.Size := 12;
DBChart1.BottomAxis.Title.Caption := 'Student';
if DBChart1.SeriesCount<1 then
begin
raise Exception.Create('Add series to your chart in the dfm ONCE.');
end;
//To set source for Series
with DBChart1 do begin
Series[0].Title := 'Test';
Series[0].DataSource := dataset;
Series[0].XLabelsSource := 'FirstName';
Series[0].YValues.ValueSource := 'Salary';
end;
Note that this is still more code than you absolutely have to write. You could do most of this if not all in dfm (form designer).

Changing steps in a graph made with cxGrid

I have a graph made with cxGrid from DevExpress and on the X axis I have a date
But when there is a lot of data in the graph these dates are cut to just 2 or 4 digits
How can I change it so the X axis only show text at every 5 or 10 values?
You should implement paging into your application. You can do that by overriding OnDataChanged and OnFilterRecord of your grid's ChartView.DataController:
aChartView.DataController.OnDataChanged := cvChartDataControllerDataChanged;
aChartView.DataController.OnFilterRecord := cvChartDataControllerFilterRecord;
The point is to use OnFilterRecord to display just a limited amount of records at a time. That makes your chart presentable, otherwise you get too many data points. The most important one is OnFilterRecord. Here's an example:
procedure TSomeGrid.cvChartDataControllerFilterRecord(ADataController: TcxCustomDataController; ARecordIndex: Integer; var Accept: Boolean);
begin
// inspect the number of all records
FNoOfRecords := ADataController.RecordCount;
//FStartRecordNo and FEndRecordNo are relative to the FCurrentPageNo
//calculated elsewhere OnDataChanged
if FCurrentPageNo > 0 then
Accept := (ARecordIndex >= FStartRecordNo) and (ARecordIndex <= FEndRecordNo)
else
Accept := ARecordIndex < FMaxChartRecords;
end;

How to add up the integer values in a label in delphi

I am currently having problems with screating a scoreboard in Delphi.
I have a series of forms which are individual questions.
If the questions are answered correctly, then the score is 1. Otherwise the score is -1.
On my scoreboard at the moment, I have 12 labels and 11 of them contain the score for each of the forms.
What I would like to do is add up the numbers in each of the labels and output the final score into the 12th label.
Is there a way of doing this?
Any help will be greatly appreciated.
You should use the UI purely for displaying your values.
For working with your data you should use appropriate data structures: array, lists, etc.
Example using an Array:
var
Scores[0..10]: Integer;
Sum: Integer;
procedure CollectData;
var
i: Integer;
begin
Scores[0] := ...;
//...
Scores[10] := ...;
Sum := 0;
for i := Low(Scores) to High(Scores) do
Sum := Sum + Scores[i];
end;
procedure DisplayData;
begin
Label1.Caption := IntToStr(Scores[0]);
//...
Label11.Caption := IntToStr(Scores[10]);
Label12.Caption := IntToStr(Sum);
end;
Neat solution: Keep scores as integers in Integer fields
Not so neat solution:
SumLabel.Caption := IntToStr( StrToIntDef( Label1.Caption, 0 ) + StrToIntDef( Label2.Caption, 0 ) + ... );
Although I think #DR's answer is spot-on, and #Ritsaert's is helpful, here's another option.
Your label components will have a 'TAG' property - you can use this for your own purposes and in your case I'd just set the TAG property at the same time as you set the Caption.
The advantage behind this is that you can format your caption to contain more than a simple number (if you wish), and also you are just summing up tags (which are already integers and don't need you to do the extra work behind a StrToIntDef call). Really, you're following #DR's point about keeping values out of the GUI (in a sense), you're using a storage field in each label instead.
eg;
when setting a score;-
Label1.Caption:=Format('%d point',[FScore]);
Label1.Tag:=FScore;
and when summing them;-
FSum:=Label1.Tag + Label2.Tag + Label3.Tag (etc)

Resources