Blog
All Blog Posts | Next Post | Previous Post
The Next Evolution of Charting in Delphi: Data Binding
Today
Intro
Charts that pull live data from a database are far more useful than charts that are fed hardcoded arrays. With TMS FNC Chart, the TTMSFNCChartDatabaseAdapter bridges your dataset and your chart in just a few steps: point it at a DataSource, mark which field is the X label, configure each series as it arrives, and activate it. From that point, every dataset column becomes a chart series automatically.
Watch The Video
Watch the companion walkthrough on YouTube:
Quick Highlights
Drop a TTMSFNCChartDatabaseAdapter on the form, assign its
Source.DataSource, and setActive := Trueto have the chart populated automatically from a dataset.Use the OnSetFieldValueType event to mark the string field as
cvtXLabel, so it drives the X-axis labels instead of being treated as a numeric value.Use the OnFieldsToSeries event to configure each series as it is created from a dataset field, keeping series setup in one reusable procedure.
Connect a TTMSFNCDataGrid to the same
DataSourceto show the raw data and the chart side by side with no extra code.
The Problem With Manual Point Loading
The previous videos in this series populated the chart by calling AddPoint in a loop. That works well for static or computed data, but it breaks down as soon as the data lives in a database. You would have to iterate the dataset yourself, match fields to series, handle the label column separately, and re-run the whole loop whenever the data changes. The database adapter exists to replace all of that with a declarative connection.
The component model stays the same. There is still a TTMSFNCChart on the form, still a series collection, and still the same per-series properties for chart type, markers, and Y range. The difference is that the adapter creates and fills the series from the dataset fields rather than from code you write manually.
Highlight: The database adapter does not replace the series model. It drives it. Everything you already know about configuring a series still applies, just moved into an event rather than a loop.
Building the Dataset
For this demo, the data is a TClientDataSet built at runtime: a Month string field and three numeric fields, Electronics, Clothing, and Food, each holding 12 monthly revenue values. The same structure would apply to any real dataset coming from a database, a REST API response marshalled into a client dataset, or a query result.
procedure TFormDataBinding.BuildDataSet;
var
I: Integer;
begin
ClientDataSet1.FieldDefs.Add('Month', ftString, 20);
ClientDataSet1.FieldDefs.Add('Electronics', ftFloat);
ClientDataSet1.FieldDefs.Add('Clothing', ftFloat);
ClientDataSet1.FieldDefs.Add('Food', ftFloat);
ClientDataSet1.CreateDataSet;
for I := 0 to 11 do
begin
ClientDataSet1.Append;
ClientDataSet1.FieldByName('Month').AsString := MonthNames[I];
ClientDataSet1.FieldByName('Electronics').AsFloat := ElectronicsData[I];
ClientDataSet1.FieldByName('Clothing').AsFloat := ClothingData[I];
ClientDataSet1.FieldByName('Food').AsFloat := FoodData[I];
ClientDataSet1.Post;
end;
ClientDataSet1.FieldByName('Month').ReadOnly := True;
ClientDataSet1.First;
end;
Making the Month field read-only is a small but useful detail. It signals to the grid adapter that the field is a label column and prevents accidental edits to what is essentially a key.
Activating the Adapter
The connection is a two-step process: assign the DataSource and set Active := True. The adapter then reads the fields, creates one series per numeric column, fires OnSetFieldValueType for each field so you can classify it, fires OnFieldsToSeries for each series so you can configure it, and fills the points. Disconnecting is the reverse: set Active := False, clear the DataSource reference, and clear the chart.
procedure TFormDataBinding.BtnConnectClick(Sender: TObject);
begin
if not TMSFNCChartDatabaseAdapter1.Active then
begin
if not ClientDataSet1.Active then
begin
BuildDataSet;
ConfigureChart;
end;
TMSFNCChartDatabaseAdapter1.Source.DataSource := DataSource1;
TMSFNCChartDatabaseAdapter1.Active := True;
BtnConnect.Text := 'Disconnect';
end
else
begin
TMSFNCChartDatabaseAdapter1.Active := False;
TMSFNCChartDatabaseAdapter1.Source.DataSource := nil;
TMSFNCChart1.Clear;
ConfigureChart;
BtnConnect.Text := 'Connect';
end;
end;
Notice that ConfigureChart runs before activation, not after. The chart shell, title, legend, axis positions, and margins are set up independently of the data. The adapter then fills in the series on top of that already-configured shell.
Classifying Fields With OnSetFieldValueType
The adapter cannot know which field is a label and which fields are numeric values without some guidance. That is what OnSetFieldValueType is for. It fires once per field. For any field whose name matches the label column, set AValueType to cvtXLabel. Every other field defaults to a Y value and becomes a series.
procedure TFormDataBinding.TMSFNCChartDatabaseAdapter1SetFieldValueType(
Sender: TObject; AFieldName: string; AField: TField;
var AValueType: TTMSFNCChartPointValueType);
begin
if AFieldName = 'Month' then
AValueType := cvtXLabel;
end;
That single line is the only place where the data model is described to the chart. If your dataset has a date field for the X axis instead of a string, the same event applies: match the field name and assign the appropriate value type.
Highlight: OnSetFieldValueType is the one place where data semantics are communicated to the chart. Keep it simple: match the field name and set the type. Everything else follows automatically.
Configuring Each Series With OnFieldsToSeries
Once the adapter knows which fields are Y values, it creates a series for each one and fires OnFieldsToSeries. This is where series-level configuration belongs: chart type, mode, Y range behavior, axis positions, marker visibility, stroke width, and label visibility. A shared ConfigureSerie procedure handles all of that and is called from the event, passing the series and a flag indicating whether it is the first one in the collection.
procedure TFormDataBinding.TMSFNCChartDatabaseAdapter1FieldsToSeries( Sender: TObject; AFields: TFields; ASeries: TTMSFNCChartSerie); begin ConfigureSerie(ASeries, ASeries.Index = 0); end;
The first-series flag matters because only the first series should render the X and Y axis labels. Subsequent series share the same axis but should not repeat the tick marks and titles, otherwise the axes draw on top of each other.
procedure TFormDataBinding.ConfigureSerie(ASerie: TTMSFNCChartSerie;
AIsFirst: Boolean);
begin
ASerie.Mode := smStatistical;
ASerie.AutoXRange := arEnabled;
ASerie.AutoYRange := arCommonZeroBased;
ASerie.MaxYOffsetPercentage := 10;
ASerie.XValues.AutoUnits := False;
ASerie.XValues.MajorUnit := 1;
ASerie.XValues.Title.Text := 'Month';
ASerie.YValues.Title.Text := 'Revenue (K)';
ASerie.XGrid.Visible := False;
ASerie.YGrid.Visible := True;
ASerie.Labels.Visible := False; // 36 labels (12 months x 3 series) overlap
if AIsFirst then
begin
ASerie.XValues.Positions := [xpBottom];
ASerie.YValues.Positions := [ypLeft];
end
else
begin
ASerie.XValues.Positions := [];
ASerie.YValues.Positions := [];
end;
case FChartMode of
cmBar:
begin
ASerie.ChartType := ctBar;
ASerie.Bar.Width := 36;
ASerie.Bar.Spacing := 10;
ASerie.Markers.Visible := False;
ASerie.Stroke.Width := 1;
ASerie.XValues.Angle := -55;
end;
cmLine:
begin
ASerie.ChartType := ctXYLine;
ASerie.Markers.Visible := True;
ASerie.Markers.Width := 8;
ASerie.Markers.Height := 8;
ASerie.Stroke.Width := 3;
ASerie.XValues.Angle := -45;
end;
end;
end;
A few things are worth noting here. arCommonZeroBased keeps all three series on a shared Y scale that starts at zero, which is essential for a fair comparison between categories. smStatistical distributes points as categories rather than as mathematically scaled X coordinates. And Labels.Visible := False is a deliberate choice: with 12 months across 3 series, 36 labels would overlap and make the chart unreadable.
Conclusion
Database binding in TMS FNC Chart follows a clear pattern: configure the chart shell once, let the TTMSFNCChartDatabaseAdapter create and fill the series, use OnSetFieldValueType to mark the label column, and use OnFieldsToSeries to apply per-series styling as each one arrives. After that, switching chart types is a property pass over the existing series, and a side-by-side grid is just a second adapter pointing at the same DataSource. The result is a chart that stays connected to real data with very little glue code between them.
Pieter Scheldeman
Related Blog Posts
-
TMS FNC Chart 4.0
-
The Next Evolution of Charting in Delphi: Getting Started
-
The Next Evolution of Charting in Delphi: Data Import & Export
-
The Next Evolution of Charting in Delphi: Look & Feel
-
The Next Evolution of Charting in Delphi: Data Binding
This blog post has not received any comments yet.
All Blog Posts | Next Post | Previous Post