TAChart documentation

From Lazarus wiki
Jump to: navigation, search

English (en) | Українська (uk)

Contents

Overview

TAChart is a package for drawing graphs, charts and other diagrams. It is comparable in features, but not specifically compatible, with Delphi TeeChart package. One substantial difference is that some features (e.g. data sources and axis transformations) are implemented via separate components instead of just properties. This leads to increased flexibility and opportunity for code re-use, but at the cost of some additional API complexity.

This document provides comprehensive but concise overview of TAChart concepts, features and components. For step-by-step introduction, see TAChart tutorial: Getting started and other tutorials.

The documentation describes TAChart package in the latest development version of Lazarus. Some features may not be available in the official release yet. It is still recommended to use the latest version of this document because it contains not only new features, but fixes and improvements to old descriptions too.

Nevertheless, you can look at the the old version of this document to see a state approximately corresponding to Lazarus 1.0 release.

Series

Series are the central part of TAChart. Most of the series represent data taken from data sources in graphical ways, such as lines or bars.

Constant line series

This is the simplest series type, representing "infinite" vertical or horizontal line. It can be used as "central axis" in function graphs or as a draggable marker.

Also, by setting Active=true, Pen.Style=psClear and UseBounds=true and appropriate AxisIndexX, it becomes an "axis extender", making sure that given Position will always by included in the axis range.

Basic series

Basic series are most often used, and include line, bar and area series. All basic series can be "stacked" by using multi-valued source. Also, all basic series fully support rotation and 3-D drawing.

Line series

TLineSeries can be used to draw a given set of points, optionally marking them with shapes and connecting with lines.

You can get a "stepped" look by setting LineType property to ltStepXY or ltStepYX.

Fast lines

Some charting packages include special "fast line" series to quickly draw line series from extremely large datasets (10000+ points). Instead, TAChart contains an optimized fast path inside standard line series code, achieving comparable drawing speed. Line series will be drawn very fast if all of the following are true:

  • There are no marks.
  • There are no pointers.
  • LineType is not ltFromOrigin.

Some operating systems/widgetsets may additionally require that LinePen.Style=psSolid and LinePen.Width=1.

Additional speedups will be available if Source.Sorted=true.

You can measure line speed drawing on your platform with the "line" demo.

Bar series

TBarSeries represents data as a set or bars, extending from ZeroLevel to data points.

You can control bar width with BarWidthPercent property. Note that the it is measured relative to the neighboring bars. If the X values are not equidistant, bars will have varying width. To prevent that, set BarWidthStyle=bwPercentMin.

You can draw multiple bar series side-by-side by using BarOffsetPercent property. Use multiple y values to create stacked bar series.

Area series

TAreaSeries represents data as a polygon extending from the data points to either ZeroLevel line or infinitely down (if UseZeroLevel=false).

You can get a "stepped" look by setting ConnectType property.

Multi-value series

Multi-value series require multi-valued data source, and use additional Y values as extra parameters to draw complex shapes.

Bubble series

TBubbleSeries represent data as a circles of variable radius centered at data points. This series requires source with YCount of at least 2, and uses first additional Y value as radius.

OverrideColor property allows to set color of either interior or edge of each bubble individually.

Box-and-whiskers series

TBoxAndWhiskerSeries represents data as rectangles with a medium line and two T-like shape protruding in both directions. Although in statistics box-and whiskers plot is supposed to be based on specific data quartiles, TAChart does not enforce this, allowing users to draw arbitrary plots.

With some effort, box-and whiskers series may be used to represent other charts different in meaning, but similar in appearance, such as Gantt diagram or Candlestick chart.

This series requires source with YCount of at least 5, and uses Y values as follows:

Index Usage
0 Lower whisker
1 Lower box bound
2 Medium line
3 Upper box bound
4 Upper whisker

Open-high-low-close series

TOpenHighLowCloseSeries represent data as a vertical lines with two ticks, as described here.

It usually requires YCount of at least 4, and uses Y values as follows:

Property Default Usage
YIndexLow 0 Lower point of line
YIndexOpen 1 Left-facing tick position
YIndexClose 2 Right-facing tick position
YIndexHigh 3 Upper point of line

Note that although Y values are supposed to be ordered ascending along the table above, the series does not enforce this and will draw any supplied data.

Radial series

Radial series ignore axis transformations. You can see examples of radial series in the "radial" demo.

Pie series

TPieSeries draws pie charts.

For each data point, pie series interprets Y value as a relative size of the slice, and X value as a distance of splice from the center of the pie (only if Exploded property is true). Slice colors can be set in data items, or taken from the hard-coded list.

Pie radius can be either set manually by FixedRadius property, or calculated automatically so that the whole series, including all labels, exactly fits the parent chart.

Slice colors are determined by either data items' Color field or hard-coded palette. Slices contours are drawn using EdgePen property.

There are several options for label positioning, controlled by MarkPositions property:

  • pmpAround -- marks are drawn outside the pie, on the continuation or radius vector for each slice
  • pmpInside -- marks are drawn inside each slice
  • pmpLeftRight -- marks are drawn directly to the left or to the right of slice

If RotateLabels is true, each label is additionally rotated so that (if LabelFont.Orientation=0) it is parallel to the radius vector of its slice.

If the Y value of a data item is set to NaN, the item is skipped. If the X value is set to NaN, the item is not drawn, but the space for it is still reserved. This allows drawing of "partial" pie diagrams.

Pie charts have limited support for 3-d drawing.

Polar series

TPolarSeries represents data as points in polar coordinates.

The origin of the polar coordinate system is defined in graph coordinates by OriginX and OriginY properties.

For each data point, X value is interpreted as an angle in radians and Y value -- as a distance from the center.

If CloseCircle = true, the last point is connected with the first one.

User-drawn series

Provides OnDraw and OnGetBounds events to allow arbitrary custom drawing on the TChart. Note that using TChart.Canvas directly is highly discouraged an will often not work as expected.

Functional series

Functional series are recommended way to draw functional plots as opposed to, for example, pre-calculating function data and using line series. They provide scale-independent controls of smoothness vs drawing speed.

You can see examples of functional series in "func" demo.

Function series

TFuncSeries represents a one-dimensional function defined by OnCalculate event as a line.

The function is calculated for each Step pixels of the image, so you can use this property to increase either "smoothness" or drawing speed.

Extent property may be used to set both x and y extents for the purpose of calculating full extent of the chart. While drawing, however, this property is ignored, and the function will be displayed according to the current extent.

DomainExclusions property allows to exclude some intervals from the function domain. Function series correctly draws discontinuity points set by DomainExclusions. Currently, DomainExclusions can only be set at run-time by calling AddRange or AddPoint procedures.

To draw a function around the excluded interval, series must choose a point near the interval endpoint. DomainExclusions.Epsilon property controls the distance between endpoint and last drawn point. It may be increased for infinity-type discontinuities, and decreased to model open excluded intervals.

B-spline series

TBSplineSeries draws B-spline of given Degree using De Boor's algorithm.

Spline segments shorter then Step pixels are represented by straight lines.

Cubic spline series

TCubicSplineSeries draws cubic spline using standard Numlib package from the FPC.

The spline function is calculated for each Step pixels of the image, so you can use this property to increase "smoothness" or drawing speed.

Data source must contain at least 4 points and have strictly increasing X coordinate.

If there are too few points, and csoDrawFewPoints option is set, line will be drawn instead of spline using BadDataPen.

If X values are unordered and csoDrawUnorderedX option is set, spline will be drawn ignoring offending points using BadDataPen.

csoExtrapolateLeft and csoExtrapolateRight options enable natural extrapolation to the left and to the right correspondingly.

Fit series

TFitSeries performs least squares fitting using standard Numlib package from the FPC.

Fitting function is selected via FitEquation property:

  • fePolynomial: y = b0 + b1x + b2x2 + … + bnxn, where value of n is controlled by ParamCount property.
  • feLinear: y = a + bx
  • feExp: y = a * ebx
  • fePower: y = a * xb

Fitting range is usually defined by the series extent, but can be manually set via FitRange property. Fitting function is drawn over the entire axis unless DrawFitRangeOnly = true.

EquationText method returns an IEquationText interface, which allows to construct a string representing the fit equation. You can override names of x and y variables, numeric format, and, finally, obtain a string from Get function. Note that a transformation from IEquationText to String is defined, so usually there is no need to call Get explicitly.

Legend.Format property of fit series supports additional format argument ('%2:s') pointing to the default equation text.

OnFitComplete event is called after the fitting equation is found, but before the drawing starts.

After a change to TFitSeries parameters, the equation will be recalculated on the next drawing of the series. To recalculate it immediately, call ExecFit procedure. Use State property to check the validity of current equation.

Color map series

TColorMapSeries represent 2-dimensional function defined by OnCalculate event as a field of pixels, with color depending on function value.

The field is drawn as a set of rectangles of size StepX by StepY pixels. The function is called once for each rectangle.

Color values are defined by ColorSource, which must be sorted. For each data point is interpreted as having X value correspond to Color value. If the actual value falls between color levels, it can be either linearly interpolated (if Interpolate=true) or rounded down to the nearest level.

When Legend.Multiplicity=lmPoint, color map series will display color levels in the legend.

Sources

Data can get into a chart from various sources. They are implemented as a set of components derived from TCustomChartSource.

To assign a source to a series, you can set the Source property. If the property is left unassigned, the series will use its own built-in list source. Methods like AddXY are delegated to the current series source. Note that the list source is the only editable source, so after you assign, for example, a random chart source to the series, a call to AddXY will raise an exception.

Each data item has the following fields: X, Y, YList, Color, Text.

Sorted sources

If it is known that X values of the source are ascending, some additional optimizations like binary search become possible. So all sources have IsSorted property which helps determine that.

Multi-valued sources

Sources can contain multiple Y values for each X value. These values are stored in the YList field of the source data item. The number of Y values is determined by the YCount property. Note that the first Y value is stored in Y field anyway, so YCount=3 means that values are stored in Y, YList[0] and YList[1].

Additional values may be used by various series -- for example, stacked bars or bubble charts.

Skipping source items

It is possible to instruct TAChart to skip drawing some source items without removing them from the source. This may be useful for both optimization and user interface reasons.

To skip the item, assign NaN to either X value or one of the Y values. The exact effect of skipping depends on the series, but usually setting X to NaN means skipping the entire item, while setting Y or some element of YList means skipping only this Y value, while still drawing others.

Also note that if you set only one coordinate to NaN, the other will still take part in extent calculation.

You can see examples of skipping items in the "nan" demo.

List source

TListChartSource is a basic chart source, storing chart data inside itself. As such, you can use Add and Delete functions to change source data.

The Item property returns a pointer directly to the underlying storage, so you can modify item fields directly. However, doing this will not automatically update the chart and will also invalidate some internal state of list source. It is recommended:

  • To change a single item, use Set{Color|Text|XValue|YValue|YList} procedures.
  • To change many items in a time-sensitive code, call BeginUpdate, then modify items directly, then call EndUpdate.

The source also has DataPoints property to allow setting data at design time. This property is a TStringList, with each line representing a data point. Line consists of X, Y, optional YList, Color and Text values separated by | (vertical bar) character. Note that DataPoints property is designed primarily for sample and demo code. It is very inefficient, and you should not use it to add data points from the code.

You can control X value sorting by setting the Sorted property. Note when Sorted is set to true, list source sorts the data and keeps it sorted after insertion of new points. If inserted points are not sorted, this may result in quadratic running time. You should either set Sorted to true only after insertion, or pre-sort your data to avoid this.

Random source

TRandomChartSource source generates random data in the given range and is intended mostly to use in demos. You can also use it as design-time replacement for you actual data source. This will let you see and change the look of your chart without having to run the application.

Each random source uses its own independent random number generator to guarantee stability of its values.

User-defined source

This source may be used if you already have your data in memory, but in a format different from the data items used in TAChart. With user-defined source you can access your data directly instead of first moving it all into a list source. In some cases this may improve performance or reduce memory consumption. You can of course also generate, filter or modify data with the user-defined source.

The number of data items in the source is determined by PointsNumber property. Items themselves must be returned by the OnGetChartDataItem event handler. You should call Reset method to notify the chart about changes in the data. (Other sources detect changes and perform notification automatically).

Note that if the Sorted property is set to true, it is the responsibility of the event handler to provide actually sorted data.

Database source

TDbChartSource takes data directly from a database. It is contained in a separate unit to avoid introducing a db-aware component dependency into every project using TAChart.

The following properties contain database field names for data item fields:

Property Access method
FieldX AsFloat
FieldY AsFloat
FieldColor AsInteger
FieldText AsString

If FieldX property is empty, RecNo is used instead. NULL values in coordinate fields are translated into NaNs.

To get multi-valued source, set FieldY property to a comma-separated list of field names. Note that YCount will be set automatically -- trying to set it by hand will raise an exception.

Calculated source

TCalculatedChartSource is the source used for manipulating data taken from the Origin source. This source performs transformations in the following order:

  • Y reordering -- Y values of multi-valued source can be duplicated, removed or exchanged according to ReorderYList property, which is a comma-separated list of original Y value indexes. Step skipped if ReorderYList is empty.
  • Accumulation -- replaces each item's Y values by a function of the neighboring values.
  • Percentage -- replace each Y value by the percentage of total of all Y values for that item. Useful for drawing "stacked percentage" bar and area charts. Step skipped if the Percentage property is false.

Accumulation is controlled by several properties:

AccumulationRange controls number of items to accumulate, counting the current item, so AccumulationRange = 1 disables accumulation. AccumulationRange = 0 is interpreted as "infinite" range, i.e. request to accumulate over all the available data. This is mostly useful in conjunction with camSum method to produce cumulative sums.

Note that the actual number of items may be lower for the points near the beginning or end of the source.

AccumulationDirection:

  • cadBackward -- use previous values from the source,
  • cadForward -- use next values from the source,
  • cadCenter -- use both previous and next values for a total number of up to 2 * AccumulationRange - 1.

AccumulationMethod:

  • camNone -- skip accumulation step,
  • camSum -- sum of the last AccumulationRange items,
  • camAverage -- average the last AccumulationRange items,
  • camDerivative -- finite differences derivative, calculated using the last AccumulationRange items. Note that the calculation method assumes equidistant X values, and may loose accuracy if the X distance varies substantially.
  • camSmoothDerivative -- smoothed finite differences derivative, more robust against random measurement errors in the data.

Interval source

TIntervalChartSource can supply arbitrarily many points in a given interval, controlled by various properties of the source. This source is the default built-in source for axis marks. If you want to set the same axis interval parameters for several axises, you can assign a single TIntervalChartSource component the Marks.Source of each of those axises.

Date-time interval source

TDateTimeIntervalChartSource is similar to the TIntervalChartSource, but provides marks formatted as date/time values. This source automatically selects appropriate calendar interval (such as week or hour) depending on the axis scale.

Note that X values of provided data items are TDateTime values, and Label values contain formatted date-time strings. If you want to use TDateTimeIntervalChartSource as the source of axis marks, you should probably set Marks.Format or Marks.Style properties to make use of provided labels.

If the DateTimeFormat property is set, it is used to format all labels. Formatting is performed with standard SysUtils.FormatDateTime function. If DateTimeFormat is empty, format is chosen automatically based on scale.

TDateTimeIntervalChartSource is defined in the TAIntervalSources unit.

Source optimization notes

Primary data source API allows random access. Nevertheless, many sources, in particular random, database and calculated, may exhibit quadratic or worse behavior if actually accessed randomly. TAChart itself takes care to only use sequential access (although it may require several passes). Sources optimize sequential access by using internal state. User code should be careful not to reset this state during chart drawing from event handlers or custom series code.

A notable exception is the list source, which is guaranteed to provide fast random access. It may be used to cache slow sources with the help of CopyFrom procedure.

Also note that the pointer returned by GetItem function may point to the internal buffer which will be overwritten by the next call to GetItem. Again the list source does not have this limitation.

Coordinates and axises

TAChart uses four coordinate systems:

  • Axis coordinates (known in some other applications as object coordinates) -- this is the "raw" coordinate values obtained from the data. As the name implies, axis coordinates are interpreted in terms of specific axis -- the same coordinate value may have different meaning depending on the axis it is applied to.
  • Graph coordinates (aka world coordinates) are converted from the axis coordinates using axis transformation, such as logarithmic scale. Graph coordinates are common for all objects in the chart.
  • Image coordinates (aka screen coordinates) are converted from graph coordinates based on the chart viewport. This transformation is always linear and can be influenced by chart tools such as zooming and panning.
  • Device coordinates are usually equal to screen coordinates, but may be adjusted to the drawing back-end to accommodate different physical resolutions (DPI values). See, for example, printer drawer.

You can add or remove an arbitrary number of axises by editing AxisList property. By default, chart have two axises: one horizontal and one vertical. They are accessible via BottomAxis and LeftAxis properties. Note that those properties are aliases to AxisList[0] and AxisList[1], so if you remove those default axises, accessing BottomAxis and LeftAxis will return nil.

Visually, axis consists of the axis line (drawn by AxisPen), grid lines (drawn by GridPen), ticks, marks and arrow.

Each axis is drawn inside its own rectangle, determined by the size of mark labels and ticks. By assigning several axis the same positive Group number, you can have them share the same rectangular area. Grouped axises can be used to achieve "panes" look, when several series are drawn on different portions of the same graph.

Axises of the same alignment, but different groups are stacked alongside each other. You can use Margin property to control spacing between such axises.

Axis transformations

Axis transformations are grouped in the TChartAxisTransformations component. It contains a list of transformations which are applied in the order given. (For example, performing scale before and after logarithm will yield different results).

For transformations to have an effect, you should:

  • Make sure Enabled property is true for all transformations.
  • Assign transformations component to Transformations property of at least one axis.
  • Assign AxisIndexX and/or AxisIndexY properties of the series to the appropriate axis index.

Note that by default, AxisIndexX and AxisIndexY have a special value of -1, which means "ignore axis transformations". Also note that if you add or remove axises, the indexes may change. You can rotate the series by assigning both AxisIndexX to vertical axis and AxisIndexY to the horizontal axis.

Linear and logarithmic transformation

Those are simple arithmetic transformations.

Auto-scaling transformation

To display several independently scaled series, assign them to two or more axises and apply TAutoScaleAxisTransform to each axis. See "axistransf" demo, page "Linear", checkbox "Auto scale".

By using MinValue and MaxValue properties you can control the in graph coordinates of the auto-scaled series. For example, by setting one transformation to a range from 0 to 1, and another to a range from 1 to 2, you will confine all the series using the first transformation to the upper half of the chart, and all the series using the second transformation to the lower half (assuming there are no unassigned series left).

Cumulative normal distribution transformation

Use TCumulNormDistrAxisTransform to set cumulative normal distribution as an axis transformation. This may be useful in statistical charting.

Note that this transformation result is in range from 0 to 1. It is recommended to restrict axis range accordingly.

See "axistransf" demo, page "Normal distribution" for an example.

User-defined transformation

You can create you own transformation either by inheriting from TAxisTransform, or, if you prefer "visual" programming, by using TUserDefinedAxisTransform. In either case there are two basic requirements:

  • AxisToGraph and GraphToAxis functions should be defined everywhere in data range and inverse of each other (for example, avoid now only dividing, but also multiplying by zero).
  • Functions should be monotonic.

Date and time axises

Using date/time values for axis marks is a common requirement. Proper way to do this depends on the exact nature of date/time data:

  • If the data is actual TDateTime values, use it as an X coordinate in points, assign TDateTimeIntervalChartSource to the Marks.Source of the corresponding axis, and change Marks.Style to smsLabel. TDateTimeIntervalChartSource provides automatic labeling depending on the scale in wide range -- from centuries to milliseconds.
  • If the data is in physical units, but outside TDateTime values range, such as astronomical or micro-electronics timings, use it as a normal X coordinate with custom Marks.Format.
  • If the data is in calendar units, such as months and years, which is common for financial data, you have several options:
    • If date units are "equidistant" when interpreted as numbers (for example, simple year numbers), assign the same data source to both series and axis marks, then use custom Marks.Format, Axis.OnMarkToText event or Text field of the data items to format dates per your requirements.
    • If date units are not "equidistant" (for example, numbers in YYYYMM format or even date strings), use surrogate X coordinate (usually, simply a point index) instead and display dates using methods described above.
    • Convert coordinates to TDateTime values beforehand, then use TDateTimeIntervalChartSource as described above.

Axis ranges

Axis range is measured in axis units and determines the extent of series attached to this axis. Normally axis range is equal to the union of all series extents, but may be overridden with Range property.

Axis marks are displayed inside the axis marks range, which is determined as intersection of:

  • Logical extent (converted to axis coordinates)
  • Combined extent of all data series of this axis, if Marks.AtDataOnly = true
  • Marks.Range property.

Axis intervals

Axis marks are located along the axis at equal intervals chosen by the chart. The choice of intervals can be influenced via Intervals property. This property has a few subproperties, which are applied in the following order:

  • Options property contains a set of flags controlling usage of other parameters. If the flag is not in the set, corresponding parameter is ignored.
  • NiceSteps is a string containing a sequence of "step multipliers" -- floating point values in the range from 0 to 1, excluding 0. If this property is applied, the axis step will be a power of ten multiplied by one of the provided values. If several multipliers can be used, the leftmost one will be chosen. Multipliers are separated by the vertical bar (|) character.
  • If NiceSteps is ignored or TAChart fails to find appropriate step, the axis range will be divided into equal intervals without regard to the number of decimal digits in the representation of mark values. In this case, it is recommended to reduce the number of visible digits in Marks.Format.
  • MinLength and MaxLength properties set the limits of interval length in image units (usually pixels).
  • Count property is the desired number of axis marks. Among all mark steps passing the previous tests, TChart chooses the one which gives the number of marks nearest to the Count. If Count property is ignored, or there are several steps with equal number of marks, the longest step is chosen.
  • Tolerance property sets the maximum distance in image units by which the mark may be moved in order to reduce the length of its decimal representation. This helps to avoid a lot of meaningless digits in mark labels when previous steps have for some reason failed to generate "nice" marks. Note that this stage ignores all previous restrictions, so large Tolerance values may result in significant distortions. Setting Tolerance = 1 is often sufficient.

Note that if you set chart source manually, Intervals property may apply only partially or not apply at all. For example, list source is unable to guarantee maximum interval length, since it has only a finite number of points. See also interval chart source.

Axis position

By default, axis is aligned to the side of the chart corresponding to Alignment property. You can position axis differently by setting Position and PositionUnits properties.

The following values of PositionUnits property are accepted:

  • cuPercent -- percentage of the clipping rectangle.
  • cuGraph -- absolute position in graph coordinates.
  • cuPixels -- position in screen units relative to the default coordinate.

Note 1: If the axis is position is changed from the default (Position = 0 and PositionUnits = cuPercent), it will be excluded from margins calculation, so it will appear to overlap the series.

Note 2: The Alignment affects not only the position of the axis itself, but also of the corresponding labels. So, if two axises have both Position = 50 and PositionUnits = cuPercent, but one has Alignment = calLeft and another Alignment = calRight, they will diaplay a common axis line with labels on defferent sides.

See "Position" page of the "axis" demo for an example.

Extents and margins

Extents

Chart extent is a rectangle in graph coordinates.

There are several extents defined by TChart:

  • Full extent -- usually determined automatically as the area encompassing all the data from series and axis ranges. Returned by GetFullExtent function.
  • Fixed extent -- determined by TChart.Extent property. May override full extent calculation partially or fully.
  • LogicalExtent -- the extent requested by user to be seen on chart image. Writing to this property if the official way to change chart extent by the external code. For example, LogicalExtent := GetFullExtent is (almost) equivalent to calling ZoomFull procedure.
  • CurrentExtent -- the extent actually displayed to the user. May differ from the LogicalExtent due to the need to reserve space for series marks, inner chart margins etc.

Extent limits

By default, TAChart allows arbitrary extents. However, for both usability and speed reasons it may be desirable to limit extent size. For example, setting the lower bound of the size may disallow extreme zoom levels, while setting the upper bound may force the user to only see a part of the very long series at any given time.

Extent can be limited with ExtentSizeLimit property. Sub-properties MinX, MinY, UseMinX and UseMinY control lower bounds of the extent, while sub-properties MaxX, MaxY, UseMaxX and UseMaxY control upper bounds.

Extent limits are expressed in graph units.

Setting Proportional = true will enforce the current extent to have the shape of the plot area. This may be useful for math plots which require same scale on both axises.

Linked extents

Using TChartExtentLink component, you can ensure that logical extents of several charts enumerated by LinkedCharts property always stay the same.

This is useful for simulating multi-pane chart layout. See "panes" demo for an example.

Margins

Margin is a distance reserved around the edges of rectangular region. Margins are measured in image units (usually pixels). Chart itself have two margins:

  • Internal (Margins property) -- applied after the axises drawing. Are also influenced by series marks and series themselves.
  • External (MarginsExternal property) -- applied before the axises drawing. Are also influenced by axis marks and arrows.

Other chart elements, such as legend, title, footer and labels, also have margins.

Optimization notes

Calculation of CurrentExtent and actual margins is non-trivial iterative process (see TChart.PrepareAxis code for details). Although usually fast, in complex cases it can require multiple passes through chart sources.

Tools

Chart tools define reaction of the chart to various user actions, primarily mouse movements and clicks. You can see examples of tools usage in "tools" demo.

Tools are grouped in TChartToolset component, which should be assigned to chart's Toolset property. Same toolset can be used in several charts.

If Toolset is unassigned, for compatibility reasons built-in toolset consisting of drag zoom and reticule tools is used.

Each user action, tools in the toolset are processed in order, and for each tool:

  • If Enabled=false, the tool is ignored.
  • If Shift is not equal to the current shift state, the tool is ignored.
  • Tool is requested to process the action.
  • If the tool signals that the action is handled, processing is stopped, otherwise it continues to the next tool. This way, tools with the same Shift value may be differentiated based on special activation conditions. For example, some drag tools may be configured to not activate on a simple click, leaving click event available for other actions.
  • Finally, if no suitable tool is found, chart's event handler is called. Note that this means that chart event handlers will only work with all tools disabled. Generally it is recommended to use tools for interactivity, chart events are left mostly for compatibility.

In your application you can create, for example, a toolbar with each button enabling corresponding tool in the toolset and disabling all others. Alternatively, by assigning different Shift values, you can enable several tools at once.

Some tools publish EscapeCancels property which, if set to true, cancels the tool operation if the user pressed Escape key.

Keyboard handling in tools

Besides mouse events, some tools may react on key presses -- for example, crosshair tool with Shift = [ssCtrl] will display crosshairs when the Control key is pressed, without mouse buttons. Unfortunately, the chart control must be focused to receive keyboard events. This means that after the user interacted with other controls on the same form, chart stops reacting on keyboard-only events.

To prevent this, you can either call Chart.SetFocus method by hand, or set Chart.AutoFocus = true, which will make a chart grab the focus when the mouse moves over it.

Drawing mode

Some tools, such as drag zoom or crosshair, display moving shapes over the chart with the mouse movement.

There are two ways to display those shapes: either simply draw them over the chart, fully redrawing the chart upon each mouse movement, or use pmXor pen mode to draw and erase the shape directly from the MouseMove event handler. The former method allows to use arbitrary pen color and style, but the latter is much more efficient. Additionally, some widgetset ignore all drawing outside the Paint event, so the latter method will not work at all.

Display method is controlled by DrawingMode property with the following values:

  • tdmXor -- use XOR method;
  • tdmNormal -- use full chart redraw;
  • tdmDefault -- use XOR method on widgetsets where it is known to work (Windows and Gtk) and full redraw on others.

Extent tools

Extent tools modify chart's logical extent.

Zooming tools can be animated by setting AnimationSteps to the value greater then 1 and AnimationInterval (in milliseconds).

Panning tools can be restricted to the chart extent on all or some directions by using LimitToExtent property.

Zoom drag tool

TZoomDragTool allows user to zoom in by drawing rectangle with the mouse. The rectangle then becomes the new logical extent.

Restoration of the zooming to the full extent can be achieved by several actions, controlled by RestoreExtentOn property:

  • zreDragTopLeft, zreDragTopRight, zreDragBottomLeft, zreDragBottomRight -- dragging in the specified direction,
  • zreClick -- clicking without dragging,
  • zreDifferentDrag -- dragging in the direction different from the one used in the previous action.

By default, RestoreExtentOn = [zreDragTopLeft, zreDragTopRight, zreDragBottomLeft, zreClick] which means that either clicking or drawing a rectangle in any direction except top-left to bottom-right restores zooming to the full extent.

To get behavior compatible with earlier versions of TeeChart, specify RestoreExtentOn = [zreDragTopLeft, zreDragTopRight, zreDragBottomLeft].

To get behavior compatible with newser versions of TeeChart, specify RestoreExtentOn = [zreDifferentDrag].

RatioLimit property lets you restrict zooming to one of the coordinates, or require it to keep original proportions.

Zoom click tool

TZoomClickTool allows user to zoom in or out clicking on the chart with the mouse. ZoomFactor is the multiplier of scale applied by the tool, so factors below 1 represent zoom out, and factors above represent zoom in.

ZoomRatio allows to set non-proportional zoom by indicating ratio of X to Y scale. The X zoom factor is given by the property ZoomFactor, the Y zoom factor is the product ZoomFactor*ZoomRatio. In particular, to zoom only horizontally, the Y zoom factor must be 1, i.e. ZoomFactor*ZoomRatio = 1 or ZoomRatio = 1/ZoomFactor. For example, when your ZoomFactor for the X axis is 1.1, then ZoomRatio must be 1/1.1 = 0.90909090909. Similarly, to zoom only vertically, the X factor must be 1, i.e. ZoomFactor = 1, and you can use ZoomRatio alone to determine the Y zoom factor. And finally, in the normal case, when X and Y should zoom in the same ratio, keep ZoomRatio at 1 and determine the overall zoom factor by the ZoomFactor alone.

If FixedPoint is true, the location of the mouse click is used as a fixed point for zooming, otherwise chart image center is used instead.

Zoom mouse-wheel tool

TZoomMouseWheelTool allows user to zoom in and out with the mouse wheel. Its properties are identical to the zoom click tool.

Chart is scaled by ZoomFactor when the user scrolls the mouse wheel up, and by 1 / ZoomFactor when the user scrolls the mouse wheel down.

Pan drag tool

TPanDragTool allows user to move logical extent by dragging mouse in the directions indicated by Directions property.

Use MinDragRadius property to distinguish dragging from clicking.

Pan click tool

TPanClickTool allows user to move logical extent by clicking inside Margins pixels from the corresponding border of the chart image.

The panning offset is determined by the distance from the edge of the chart (the nearer to the edge, the greater). Setting Interval in milliseconds will allow to continue panning with the given interval until the mouse button is up.

Pan mouse wheel tool

TPanMouseWheelTool allows user to move by logical extent by scrolling the mouse wheel.

Extent is moved in WheelUpDirection when the wheel is scrolled up and in the opposite direction when the wheel is scrolled down.

Movement speed is controlled by Step property.

Data tools

Data tools are linked to specific data series via AffectedSeries property, which is a string of comma-separated series indexes. Note that indexes may change if you add or remove series.

When the data tool is activated, it determines the nearest point of the affected series which is located inside of the GrabRadius (in pixels).

For large series, efficiency can be improved by using sorted source, small GrabRadius and DistanceMode<>cdmOnlyY .

Data point drag tool

TDataPointDragTool allows user to change data values by dragging the data point. Requires series' data source to be a list source.

You can use OnDrag and OnDragStart events to limit drag direction or grab area.

See "dragdrop" demo for an example.

Data point click tool

TDataPointClickTool allows you to assign OnPointClick event handler, which will be called when the user clicks on the data point.

Data point hint tool

TDataPointHintTool displays hint when the user moves the mouse over the data point. The hint is either equal to the point label (if UseDefaultHintText=true) or determined by calling OnHint event handler.

By default, this tool displays a separate hint window, independent from the application hint. To use a single hint window per application, you may set UseApplicationHint=true.

Note that application-level hint does not work in combination with modifier keys and mouse buttons, so the hint is displayed only if no buttons are pressed (i.e. Shift property must be empty when UseApplicationHint=true).

Data point crosshair tool

TDataPointCrosshairTool displays a cross-hair centered on the data point.

Data point distance tool

TDataPointDistanceTool allows to measure and display a distance between two points on the chart.

Reticule tool

Reticule tool is deprecated. Use TDataPointCrosshairTool instead.

User defined tool

To add you own tool, either inherit from TUserDefinedTool or use it directly, assigning one or more On{After,Before}{KeyDown,KeyUp,MouseDown,MouseMove,MouseUp,MouseWheelDown,MouseWheelUp} event handlers.

Call Handled method of the tool to indicate that no further processing of the event should be done.

Decorative elements

Title and footer

Chart title and footer are multi-line texts appearing above and below the chart correspondingly. They support various rotations and alignments.

Legend

Chart legend is a table with each item containing an icon and a text line. Grid lines may be controlled by GridHorizontal and GridVertical properties.

Legend supports various alignments and can be located inside the chart or on the sidebar. Legend can be displayed in two or more columns by setting ColumnCount property. Setting ColumnCount to a very large value effectively creates "horizontal" legend.

Legend items are generated based on chart series which have both Active and ShowInLegend set to true. Depending on Legend.Multiplicity series can produce a single item or one item per point.

Legend items can be grouped together under sub-headers. Sub-headers are taken from GroupNames property, and each series can use Legend.GroupIndex property to indicate its group.

Legend items are sorted as following:

  • By group index, items without group (GroupIndex=-1) going first to avoid confusion with the last group.
  • By Order property, items without explicit order (Order=-1) going last.
  • By creation order of series.
  • For multiple items per series, by generation order (for standard series it is the order of points).

Sorted items are then added to the legend table in either by rows or by columns depending on the ItemFillOrder property.

Legend item text

The text of a legend item is generated by the corresponding series based on the Legend.Format property. This property is used as a first argument for the SysUtils.Format function, with the second argument containing following data items:

  • For per-series multiplicity:
    • 0: Series Title
    • 1: Series Index
  • For per-point multiplicity:

User-defined legend items

Arbitrary legend items can be generated by overriding OnCreate and OnDraw events of series Legend property.

Note that user-defined item count is controlled solely by UserItemsCount property and does not depend on Multiplicity. Also note that GroupIndex and Order properties are actually per-item, so you can totally override entire legend from a single (perhaps fictive) series.

Arrows

Arrowheads can be drawn at the end of axis lines, constant lines, mark links, etc. It is controlled by TChartAxis.Arrow, TConstantLine.Arrow, TChartMarks.Arrow correspondingly.

Arrowhead shape is determined by Width, Length and BaseLength properties, for example:

  • Thin wedge: BaseLength = 0
  • Triangle: BaseLength = Length
  • Rhombus: BaseLength = 2 * Length
  • Inverted Triangle: BaseLength > 0, Length = 0

Transparency

Transparency property of the series represents a level of transparency from 0 (fully opaque) to 255 (fully transparent -- i.e. invisible).

Compatibility note: FPVectorial and TFPCanvas drawers currently do not support transparency because of limitations of the underlying libraries. Printer and WMF drawers can not support transparency in principle.

Optimization note: Transparency support in the LCL is rudimentary, so current TCanvas drawer implementation may be slow with many transparent series and large charts. To improve efficiency, it is recommended to use as few different transparency levels as possible and to not interleave series with different transparencies. Alternatively, use BGRABitmap as back-end, since it has faster transparency implementation.

Marks

Marks annotate certain points of the chart. These points can be defined by a series or an axis. Typically, mark consists of some graphical element (such as an axis tick) and a text label. However, either of these elements can be omitted.

You can see examples in the "labels" demo.

Mark labels

The label text can be enclosed in a box, controlled by LabelBrush and Frame properties. The text itself is created based on the data source items, with the help of Format property. This property is used as a first argument for the SysUtils.Format function, with the second argument containing following data items:

  • 0: Y
  • 1: Y as a percentage of the Y total
  • 2: Text
  • 3: Y total
  • 4: X

Where "Y total" is the sum of all Y values for this source. Note that not all sources supply all the items above.

Some pre-defined formats can be set via the Style property.

If the source is mutli-valued, YIndex property determines which Y value is used. If YIndex = -1, a separate label is displayed for each Y value, which is useful for stacked series.

The text is rendered using the LabelFont. Note in particular that TAChart supports arbitrary font Orientation.

Mark labels are usually included in margins calculation to guarantee that all labels fit in the viewport. This behavior can be turned off by setting AutoMargins property to false.

Multi-line marks

If the mark text contains LineEnding character sequence, it is split into several lines. Lines of different length are aligned according to Alignment property.

Mark positions and attachment

Mark position relative to the marked point is determined by the marks owner (series or axis).

Common mark properties include Distance, which measures the distance from the origin point to the attachment point in image units, and Attachment, which controls whether the attachment point is considered to be in the center or at the edge of the label box.

Additionally, basic chart series have MarkPositions property, specifying the direction of labels' offsets relative to series data points as following:

  • lmpOutside -- away from zero level
  • lmpPositive -- positive direction of series' Y axis
  • lmpNegative -- negative direction of series' Y axis
  • lmpInside -- towards zero level

Drawers

For low-level drawing routines, TAChart uses special set of classes implementing IChartDrawer interface. This allows such features as printing charts and exporting them to SVG format.

These classes are called drawing back-ends or drawers for short.

TCanvas drawer

TCanvasDrawer is the default drawer used to display chart on TCanvas. This includes screen and various raster image formats. The image produced by this drawer is used as a reference when developing and debugging other back-ends.

TFPCanvas drawer

TFPCanvasDrawer is similar to TCanvas drawer, but based on TFPCanvas, which is TCanvas analog implemented in FCL instead of LCL.

See "nogui" demo for an example.

Although TFPCanvas and, correspondingly, TFPCanvasDrawer have limited implementation of some TAChart features, their important advantage is a possibility of compiling the application with nogui widgetset. In particular, it can be used by Web applications to generate raster chart images without requirement to install X/Gtk/Qt on the server.

SVG drawer

TSVGDrawer produces text stream with the image of the chart in SVG format. Similarly to TFPCanvas drawer, it is independent of LCL and can be used in Web applications to generate vector charts in nogui widgetset.

See "save" demo for an example.

For this drawer, image unit is an SVG canvas unit instead of a pixel.

Note that due to the nature of SVG, there is no way to measure font dimensions, so they are approximated crudely. This may result in problems like label text not fitting in the mark rectangle, especially in browsers like Firefox that do not support textLength attribute.

OpenGL drawer

TOpenGLDrawer draws chart on the current OpenGL context. It is suitable to be used in games and other OpenGL-only applications.

OpenGL drawer expects, but does not set by itself, an orthogonal projection. See "opengl" demo for an example.

Note that, like in OpenGL itself, TOpenGLDrawer font support is extremely limited.

Printer drawer

TPrinterDrawer draws chart on the printer canvas. It does not flush the page.

Although printer canvas is a descendant of TCanvas, and so printing can be done using the default drawer, TPrinterDrawer does proper re-scaling of image coordinates according to printer vs screen DPI.

You can use this drawer to export chart to PDF format using one of the available PDF writer products.

See "print" demo for an example.

Note that this drawer is located in a separate TAChartPrint package.

AggPas drawer

TAggPasDrawer draws chart using AggPas library.

AggPas offers high-speed antialiased drawing and is included in Lazarus sources. Unfortunately, the library is currently not maintained and there are some limitations in TAChart support.

Note that this drawer is located in a separate TAChartAggPas package.

BGRABitmap drawer

TBGRADrawer draws chart using BGRABitmap library.

BGRABitmap is recently created and actively developed graphics library, offering, in particular, anti-aliasing and rich selection of gradients.

Currently BGRABitmap supports all TAChart features, but is somewhat slower then other drawing methods.

Note that this drawer is located in a separate TAChartBGRA package, which depends on external bgrabitmappack package.

FPVectorial drawer

TFPVectorialDrawer draws chart using fpvectorial library. FPVectorial offers exporting to various vector formats, including SVG, PDF, CorelDraw and even instructions for metal cutting machines.

It currently has some limitations in TAChart support, but is actively developed.

Note that this drawer is located in a separate TAChartFPVectorial package, which depends on fpvectorialpkg package.

WMF drawer

TWindowsMetafileDrawer draws chart into a Windows Metafile.

It uses WinAPI directly, and so will only work on Windows. In the future, WMF support may be added to fpvectorial, which will provide cross-platform alternative to this package.

Navigation

Chart navigation consists of two parts: moving logical extent around without changing zoom factor, and visualizing the logical extent's position and size relative to the full extent.

Moving extent (but not visualizing it) is possible by using panning tools.

You can see examples in the "navigation" demo.

Scroll bars

TChartNavScrollBar is a TCustomScrollBar descendant with additional Chart property referencing the chart. TChartNavScrollBar synchronizes its position with chart extent in both directions. If the logical extent is equal to or larger than the full extent, navigation scroll bar does nothing.

Setting AutoPageSize = true lets TChartNavScrollBar to pick page size proportional to the logical extent.

Note that Min and Max properties are not changed automatically. It is recommended to set Min = 0 and Max to some fairly large integer value to avoid rounding issues.

Also note that TChartNavScrollBar does not automatically align or attach itself to a chart, so it can be arbitrarily positioned on the form.

Navigation panel

TChartNavPanel component displays logical and full extent of an assigned chart as differently colored rectangles, allowing user to drag the logical extent rectangle if AllowDragNavigation = true.

If MiniMap = true, the panel additionally displays the chart series.

TChartNavPanel can have arbitrary size, but it is recommended to keep height to width proportion the same as in the assigned chart. Setting Proportional = true will enforce the same proportions even if the above condition is not met, at the cost of some wasted space on the panel.

Additional components

Legend panel

Chart listbox

Chart image list

TeeChart compatibility

TeeChart is a standard set of charting components used by Delphi. Although TAChart does not have a goal to to be compatible with TeeChart, basic feature set and names are very similar, so simple examples may work in both libraries equivalently.

More complex features -- in particular, multi-value series, series sources, multiple axises -- are implemented differently from TeeChart. This is by design and will not change. See comparison page for more detailed info.

To assist porting of TeeChart code to the TAChart, you can use TAChartTeeChart unit. It contains class helpers adding or emulating some TeeChart-specific properties and methods.

Note that it is NOT recommended to use this unit for normal development, since the emulation is only intended to simulate the subset of features implemented in TeeChart, and may not be reliable when used in conjunction with the full set of TAChart features.

Technical details

Drawing order

Chart drawing consists of three stages:

  1. Preparation. At this stage various internal data structures are initialized.
  2. Measurement. At this stage chart calculates the sizes of all elements, and optimizes them for best presentation. Optimization may require several iterations, so measurement stage is often the heaviest one both in terms of implementation complexity and running time.
  3. Drawing. At this stage actual chart image is displayed.

There also exists an ordering among various chart elements:

  1. Background (using TChart.Color property).
  2. Back-wall (using TChart.BackColor property).
  3. Series and axises according to ZPosition property.
    1. For each series and axis, graphic elements are drawn before marks. Note that this protects marks against hiding by the axis/series they belong to, but not by other axises/series.
  4. Legend.
  5. Tools.

Note that ZPosition works for both 2-D and 3-D charts, so you can overlay series and axises in arbitrary order.

Coding style

Historically FPC, Lazarus and LCL sources contain a mix of coding styles, with the general rule being "be consistent with the surrounding code".

However, since TAChart has many fewer contributors, it is feasible to adopt and maintain a consistent style across all TAChart code. If you want to contribute to TAChart, please format your code accordingly. Also remember that any coding style may be violated in certain situations when the reason if good enough, but please explain that reason if you do so.

Spaces

  • No double spaces anywhere.
  • Spaces after: operations, comma, semicolon, assignment, closing parenthesis in expressions (not in function calls).
  • Spaces before: operations, assignment, opening parenthesis in expressions (not in function calls).

Lines

  • No double empty lines anywhere.
  • Empty lines between procedures, classes, unit sections. Rare empty lines inside procedure bodies to separate logical blocks.
  • Line length below 80 characters, with rare exceptions.
  • Single statement per line, except if ... then {exit|break|continue} and some rare cases of mass assignment.
  • If the line is too long, line breaks may be inserted after at the following symbols, in order of decreasing priority: keywords, opening parenthesis, opening square bracket, semicolon, comma, operation.

Indentation and blocks

  • Always two spaces, both for blocks and continuation lines.
  • begin on the same line as the control statement, end aligned with the control statement.
  • end always alone on the line. In particular, write end else begin on two lines.
  • Put then or do on a separate line to separate a complex condition from the statement body:
 while
   long condition or
   another long condition
 do
   loop body
  • Use begin/end only when necessary (i. e. not for single statements).

Comments

  • Put a license header at the beginning of every file.
  • Single-line comments everywhere except the license header and auto-generated class headers in implementation section.
  • Use only full sentences in comments, starting with a capital letter and ending with a full stop.
  • Comments should be placed before commented code, except in rare cases where a comment fits at the end of the same line.
  • Comments should only include information not evident from the source. In particular, choose meaningful procedure and argument names in preference to adding comments describing their usage.

Names

  • Constants use ALL_CAPS_WITH_UNDERSCORE, everything else use CamelCase.
  • Local variables start with a lower-case letter, everything else starts with an upper-case letter, even when FPC library disagrees (e.g. ``Math``, not ``math``).
  • Class fields start with 'F', arguments start with 'A', types start with 'T', TAChart units start with 'TA'.

Variable declarations

  • Initialize when possible.
  • Local variables after nested procedures unless used by them.
  • Group variables by type.

Classes and methods

  • Methods are grouped per-class, methods inside class are sorted alphabetically both in interface and implementation. There should be zero Code Observer warnings about 'Unsorted members'.
  • If there is a need to group methods by topic, use visibility specifiers as topic separators. In particular, group overridden methods separately from the newly introduced ones.
  • Use strict private/strict protected visibility where possible.

Hints and warnings

  • Code should compile with zero hints and warnings.
  • Silence any "unused parameter" warning with the ``Unused`` procedure from the TAChartUtils unit.

Control flow

  • Use exit or raise to abort method execution in case of violated pre-condition.
  • Use enumerators where possible.
  • Use with carefully, and only where its use significantly saves code size and in the minimal possible range.
  • Limit all procedures to 50-60 lines, and use nested procedures liberally.
Personal tools