5592 lines
184 KiB
ObjectPascal
5592 lines
184 KiB
ObjectPascal
{-----------------------------------------------------------------------------
|
|
JvChart - TJvChart Component - 2007 Public
|
|
|
|
The contents of this file are subject to the Mozilla Public License
|
|
Version 1.1 (the "License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL/MPL-1.1.html
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
|
|
the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is: JvChart.PAS, released on 2003-09-30.
|
|
|
|
The Initial Developers of the Original Code are
|
|
Warren Postma (TJvChart which was originally based on TAABGraph)
|
|
Mårten Henrichson (TAABGraph)
|
|
|
|
Contributor(s):
|
|
Warren Postma (warrenpstma att hotmail dott com)
|
|
Mårten Henrichson/AABSoft (no email known)
|
|
|
|
Contains some code which is
|
|
(C) 1996-1998 AABsoft and Mårten Henrichson
|
|
|
|
The rest is
|
|
(C) 2003 Warren Postma
|
|
warren.postma att sympatico dott ca
|
|
warrenpstma att hotmail dott com
|
|
|
|
Last Modified:
|
|
|
|
2003-09-30 - (WP) - Alpha-Initial checkin of new component, into JVCL3 CVS tree.
|
|
2004-02-26 - (WP) - Pre-JVCL3.0-Has been substantially jedified, also new
|
|
properties/events, and some renaming has occurred. See
|
|
cvs logs. NEW: OnChartClick event.
|
|
RENAME: Options.ThickLineWidth -> Options.PenLineWidth
|
|
RENAME: Values -> ValueIndex
|
|
2004-04-10 - (WP) - Much improved Charting! Beta-Quality in most places.
|
|
Significant property reorganization and renaming.
|
|
Primary and Secondary Y (vertical) Axis support.
|
|
2004-07-06 - (WP)- Added events OnYAxisClick (Left margin click),
|
|
OnXAxisClick (Bottom margin), OnAltYAxisClick (Right margin)
|
|
and OnTitleClick (Top Margin).
|
|
|
|
2005-01-14 - (WP) - Floating Chart Markers added. Major changes to painting
|
|
code to allow canvas as a parameter. This is in preparation
|
|
for fixing up the printing code to allow printing to work
|
|
once again, and because the floating objects require us to
|
|
draw the chart into a bitmap, and then decorate the bitmap
|
|
dynamically with the floating objects, different
|
|
canvases are used to paint the bitmap, and to paint the
|
|
floating layer on top, thus the need for the changes.
|
|
|
|
2005-04-15 - (WP) - Changed internal Data storage from Array of Array to
|
|
simple single-dimension array. This has many benefits,
|
|
and at least one drawback. The benefit is that much
|
|
larger sets of pens/data values can be accomodated.
|
|
The drawback is that you can't add a pen to an already displayed
|
|
chart without regenerating all the data points in it.
|
|
If you change the pen count you now MUST clear the data,
|
|
and re-plot the chart. Any attempt to add a pen,
|
|
write the new pen, and then plot without rewriting the other
|
|
values will cause data to be displayed in a corrupt fashion.
|
|
I think this obscure limitation is better to live with than
|
|
the many alternatives. The problem I have is that the chart
|
|
supports extremely large data sets, and clearing the data
|
|
MULTIPLE times would be a huge performance penalty in
|
|
any application. You can enable the OLD mode, with it's
|
|
limitations, by defining TJVCHART_ARRAY_OF_ARRAY.
|
|
|
|
2007-04-26 - (WP) - Merged upstream changes.
|
|
- Added gradients (TJvChartGradientBar)
|
|
- Added new way of doing date/time markers (Options.XAxisDateTimeMode),
|
|
GraphXAxisLegend, and JvChart.Data properties StartDateTime, EndDateTime.
|
|
- Added vertical markers (AddVerticalBar)
|
|
- Added horizontal bars (TJvChartHorizontalBar) ClearHorizontalBars
|
|
- Added FloatingMarkerCount.
|
|
- New property in jvfloatingMarker: CaptionColor
|
|
- Added DeleteFloatingMarkerObj
|
|
- Graphical glitches/chart-display-bug-fixes.
|
|
2007-04-27 - (WP) - Fixes
|
|
- Calls only JclMath.IsNaN, not Math.IsNaN, which doesn't
|
|
exist on older Delphi/BCB versions.
|
|
- Added CopyFloatingMarkers (thought I did that yesterday but missed it)
|
|
|
|
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
|
|
located at http://jvcl.sourceforge.net
|
|
|
|
-----------------------------------------------------------------------------}
|
|
// $Id: JvChart.pas 11242 2007-03-29 07:25:03Z marquardt $
|
|
|
|
unit JvChart;
|
|
|
|
{$I jvcl.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFDEF UNITVERSIONING}
|
|
JclUnitVersioning,
|
|
{$ENDIF UNITVERSIONING}
|
|
Windows, Messages, Classes,
|
|
{$IFDEF HAS_UNIT_TYPES}
|
|
Types,
|
|
{$ENDIF HAS_UNIT_TYPES}
|
|
Graphics, Controls, Contnrs,
|
|
JvComponent;
|
|
|
|
const
|
|
JvChartVersion = 300; // ie, version 3.00
|
|
|
|
JvDefaultHintColor = TColor($00DDFBFA);
|
|
JvDefaultAvgLineColor = TColor($00EEDDDD);
|
|
JvDefaultDivisionLineColor = clLtGray; //NEW!
|
|
JvDefaultShadowColor = clLtGray; //NEW!
|
|
|
|
JvDefaultYLegends = 20;
|
|
MaxShowXValueInLegends = 10;
|
|
|
|
// Special indices to GetPenColor(Index)
|
|
jvChartAverageLineColorIndex = -6;
|
|
jvChartDivisionLineColorIndex = -5;
|
|
jvChartShadowColorIndex = -4;
|
|
jvChartAxisColorIndex = -3;
|
|
jvChartHintColorIndex = -2;
|
|
jvChartPaperColorIndex = -1;
|
|
|
|
JvChartDefaultMarkerSize = 3;
|
|
|
|
type
|
|
{ CHART TYPES }
|
|
TJvChartKind =
|
|
(
|
|
ckChartNone, // Blank graph.
|
|
ckChartLine, // default type. Line and Marker plots.
|
|
ckChartBar,
|
|
ckChartStackedBar,
|
|
ckChartBarAverage,
|
|
ckChartStackedBarAverage,
|
|
ckChartPieChart,
|
|
//ckChartLineWithMarkers, // obsolete. use ckChartLine, and set PenMarkerKind to cmDiamond.
|
|
ckChartMarkers,
|
|
ckChartDeltaAverage
|
|
);
|
|
|
|
// ckChartLine can have a different pen type for each pen:
|
|
|
|
TJvChartPenMarkerKind = (pmkNone, pmkDiamond, pmkCircle, pmkSquare, pmkCross);
|
|
|
|
TJvChartGradientDirection = (grNone, grUp, grDown, grLeft, grRight); // WP
|
|
|
|
TJvChartLegend = (clChartLegendNone, clChartLegendRight, clChartLegendBelow);
|
|
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
TJvChartDataArray = array of array of Double;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
|
|
TJvChart = class;
|
|
|
|
// TJvChartFloatingMarker.Caption position enumerator:
|
|
TJvChartCaptionPosition =
|
|
(
|
|
cpMarker, // put right where the marker is
|
|
cpXAxisBottom, // put below the symbol marker, in the bottom margin
|
|
cpXAxisTop, // put above the symbol marker, in the top margin
|
|
cpTitleArea
|
|
);
|
|
|
|
TJvChartFloatingMarker = class(TPersistent) // was from TObject
|
|
private
|
|
FOwner: TJvChart;
|
|
procedure SetCaptionColor(const Value: TColor); // Which chart does it belong to?
|
|
protected
|
|
FRawXPosition: Integer; // raw pixel-based X position.
|
|
FRawYPosition: Integer; // raw pixel-based Y position.
|
|
FDragging: Boolean; // drag in progress!
|
|
FVisible: Boolean; // Make chart marker object visible or invisible.
|
|
FIndex: Integer; // Which marker is this?
|
|
FTag: Integer; // User assignable Integer like TComponent.Tag
|
|
FMarker: TJvChartPenMarkerKind; // What symbol to plot at this position?
|
|
FMarkerColor: TColor; // Marker color.
|
|
FXPosition: Integer; // Plot at same X co-ordinates as Data Sample X.
|
|
FYPosition: Double; // Plot at Y height as data
|
|
FXDraggable: Boolean; // Can marker be dragged horizontally?
|
|
FXDragMin: Integer; // Minimum X Position that we can drag to.
|
|
FXDragMax: Integer; // Maximum X Position that we can drag to.
|
|
FYDraggable: Boolean; // Can marker be dragged vertically?
|
|
//FYPositionToPen:Integer; // YPosition copied from Pen Values. (-1=disable feature, 0=first pen,1=second pen,...)
|
|
FLineToMarker: Integer; // If -1 then none. Otherwise, index of another marker object
|
|
FLineVertical: Boolean; // If true, then this object plots a vertical divider line.
|
|
FLineStyle: TPenStyle; // Line style (solid,dashed,etc)
|
|
FLineColor: TColor; // Line color.
|
|
FLineWidth: Integer;
|
|
FCaption: string; // Caption to print above the marker, or if no marker, then just this text is plotted.
|
|
FCaptionColor: TColor; // New 2007 - WP - Color of floating marker caption
|
|
FCaptionPosition: TJvChartCaptionPosition;
|
|
FCaptionBoxed: Boolean; // Marker caption can have a box around it to make it more readable for some uses.
|
|
|
|
//FCaptionBorderStyle:TPenStyle; // Style of border around caption, or psClear if no border.
|
|
//FCaptionBorderColor:TColor; //
|
|
|
|
//PROTECTED CONSTRUCTOR: Only TJvChart should create a new marker object:
|
|
constructor Create(Owner: TJvChart);
|
|
|
|
procedure SetCaption(ACaption: string);
|
|
procedure SetXPosition(XPos: Integer); // should invalidate the chart (FOwner) if changed.
|
|
procedure SetYPosition(YPos: Double); // should invalidate the chart (FOwner) if changed.
|
|
|
|
procedure SetVisible(AVisible: Boolean);
|
|
public
|
|
property Index: Integer read FIndex;
|
|
|
|
procedure Assign(Source: TPersistent); override; // Should be able to copy from one floating marker to another
|
|
|
|
property Marker: TJvChartPenMarkerKind read FMarker write FMarker;
|
|
property MarkerColor: TColor read FMarkerColor write FMarkerColor; // Marker color.
|
|
property Visible: Boolean read FVisible write FVisible; // Make chart marker object visible or invisible.
|
|
property XPosition: Integer read FXPosition write SetXPosition;
|
|
property YPosition: Double read FYPosition write SetYPosition;
|
|
property XDraggable: Boolean read FXDraggable write FXDraggable;
|
|
property XDragMin: Integer read FXDragMin write FXDragMin;
|
|
property XDragMax: Integer read FXDragMax write FXDragMax;
|
|
property YDraggable: Boolean read FYDraggable write FYDraggable;
|
|
//property YPositionToPen :Integer read FYPositionToPen write FYPositionToPen;
|
|
property LineToMarker: Integer read FLineToMarker write FLineToMarker;
|
|
property LineVertical: Boolean read FLineVertical write FLineVertical;
|
|
property LineStyle: TPenStyle read FLineStyle write FLineStyle;
|
|
property LineColor: TColor read FLineColor write FLineColor;
|
|
property LineWidth: Integer read FLineWidth write FLineWidth;
|
|
property Caption: string read FCaption write FCaption;
|
|
property CaptionColor: TColor read FCaptionColor write SetCaptionColor;
|
|
property CaptionPosition: TJvChartCaptionPosition read FCaptionPosition write FCaptionPosition;
|
|
property CaptionBoxed: Boolean read FCaptionBoxed write FCaptionBoxed;
|
|
|
|
property Tag: Integer read FTag write FTag; // User assignable Integer like TComponent.Tag
|
|
|
|
//property CaptionBorderStyle :TPenStyle read FCaptionBorderStyle write FCaptionBorderStyle;
|
|
//property CaptionBorderColor :TColor read FCaptionBorderColor write FCaptionBorderColor;
|
|
end;
|
|
|
|
TJvChartGradientBar = class(TObject) // NEW 2007
|
|
private
|
|
FOwner: TJvChart; // Which chart does it belongs to?
|
|
FVisible: Boolean;
|
|
//FRawRect: TRect; // raw pixel-based X position.
|
|
FYTop, FYBottom: Double;
|
|
FColor: TColor;
|
|
FIndex: Integer;
|
|
FGradDirection: TJvChartGradientDirection;
|
|
FGradColor: TColor;
|
|
protected
|
|
constructor Create(Owner: TJvChart);
|
|
procedure SetVisible(AVisible: Boolean);
|
|
procedure SetColor(AColor: TColor);
|
|
procedure SetGradientColor(AColor: TColor);
|
|
procedure SetGradientType(AType: TJvChartGradientDirection);
|
|
public
|
|
property Visible: boolean read FVisible write SetVisible;
|
|
property YTop: Double read FYTop write FYTop;
|
|
property YBottom: Double read FYBottom write FYBottom;
|
|
property Color: TColor read FColor write SetColor;
|
|
property GradDirection: TJvChartGradientDirection read FGradDirection write FGradDirection;
|
|
property GradColor: TColor read FGradColor write FGradColor;
|
|
end;
|
|
|
|
TJvChartHorizontalBar = class(TJvChartGradientBar) // NEW 2007
|
|
private
|
|
FYTop: Double;
|
|
FYBottom: Double;
|
|
protected
|
|
constructor Create(Owner: TJvChart);
|
|
public
|
|
property YTop: Double read FYTop write FYTop;
|
|
property YBottom: Double read FYBottom write FYBottom;
|
|
end;
|
|
|
|
TJvChartVerticalBar = class(TJvChartGradientBar) // NEW 2007
|
|
private
|
|
FXLeft: Integer;
|
|
FXRight: Integer;
|
|
protected
|
|
constructor Create(Owner: TJvChart);
|
|
public
|
|
property XLeft: Integer read FXLeft write FXLeft;
|
|
property XRight: Integer read FXRight write FXRight;
|
|
end;
|
|
|
|
{ TJvChartData : Holds NxN array of Reals, Resizes automatically within preset
|
|
limits. Provides a functionality mix of dynamic memory use, but with
|
|
a memory cap, so we don't thrash the system or leak forever. -WAP.}
|
|
TJvChartData = class(TObject)
|
|
private
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
FData: TJvChartDataArray;
|
|
{$ELSE}
|
|
FData: array of Double;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
|
|
FClearToValue: Double; // Typically either 0.0 or NaN
|
|
FTimeStamp: array of TDateTime; // Time-series as a TDateTime
|
|
// Dynamic array of dynamic array of Double.
|
|
// is empty until data is stored in them.
|
|
// *** Order of indexing: FData[ValueIndex,Pen] ***
|
|
FDataAlloc: Integer; // Last Allocated Value.
|
|
FValueCount: Integer; // Number of sample indices used
|
|
FPenCount: Integer; // can't be changed without erasing all data!
|
|
|
|
FStartDateTime: TDateTime; // needed for DateTime mode, and datetime axis labels! NEW 2007
|
|
FEndDateTime: TDateTime;
|
|
procedure SetEndDateTime(const Value: TDateTime);
|
|
procedure SetStartDateTime(const Value: TDateTime);
|
|
// needed for DateTime mode, and datetime axis labels! NEW 2007
|
|
protected
|
|
procedure Grow(Pen, ValueIndex: Integer);
|
|
//GetValue/SetValue resizer, also throws exception if Pen,ValueIndex is negative or just way too big.
|
|
function GetValue(Pen, ValueIndex: Integer): Double;
|
|
procedure SetValue(Pen, ValueIndex: Integer; NewValue: Double);
|
|
function GetTimestamp(ValueIndex: Integer): TDateTime;
|
|
procedure SetTimestamp(ValueIndex: Integer; AValue: TDateTime);
|
|
public
|
|
constructor Create;
|
|
{$IFNDEF CLR}
|
|
destructor Destroy; override;
|
|
{$ENDIF !CLR}
|
|
|
|
procedure PreGrow(Pen, ValueIndex: Integer); // Advanced users. Allocate a large batch of memory in advance.
|
|
|
|
function DebugStr(ValueIndex: Integer): string; // dump all pens for particular valueindex, as string.
|
|
|
|
procedure Clear; // Resets All Data to zero.
|
|
procedure ClearPenValues; // Clears all pen values to NaN but does not reset pen definitions etc.
|
|
procedure Scroll;
|
|
property Value[Pen, ValueIndex: Integer]: Double read GetValue write SetValue; default;
|
|
property Timestamp[ValueIndex: Integer]: TDateTime read GetTimestamp write SetTimestamp;
|
|
property ValueCount: Integer read FValueCount write FValueCount;
|
|
property ClearToValue: Double read FClearToValue write FClearToValue; // Typically either 0.0 or NaN. default 0.0
|
|
|
|
// NEW 2007 [for DateTimeMode XAxis Labels]
|
|
property StartDateTime: TDateTime read FStartDateTime write SetStartDateTime;
|
|
// needed for DateTime mode, and datetime axis labels! NEW 2007
|
|
property EndDateTime: TDateTime read FEndDateTime write SetEndDateTime;
|
|
// needed for DateTime mode, and datetime axis labels! NEW 2007
|
|
end;
|
|
|
|
TJvChartPaintEvent = procedure(Sender: TJvChart; ACanvas: TCanvas) of object;
|
|
TJvChartEvent = procedure(Sender: TJvChart) of object;
|
|
TJvChartFloatingMarkerDragEvent = procedure(Sender: TJvChart; FloatingMarker: TJvChartFloatingMarker) of object; {NEW}
|
|
|
|
TJvChartClickEvent = procedure(Sender: TJvChart;
|
|
Button: TMouseButton; { left/right mouse click?}
|
|
Shift: TShiftState; { keyboard shift state?}
|
|
X, Y: Integer; { mouse position}
|
|
ChartValueIndex: Integer; { what value in the chart? }
|
|
ChartPenIndex: Integer; { what pen was clicked? }
|
|
{ User modifiable return values for custom hinting! }
|
|
var ShowHint, HintFirstLineBold: Boolean; HintStrs: TStrings) of object; {NEW}
|
|
|
|
TJvChartOptions = class;
|
|
|
|
{ There are TWO Y Axis per graph, optionally:
|
|
Chart.Options.PenAxis[I] -and-
|
|
Chart.Options.SecondaryYAxisOne
|
|
The primary one is displayed along the left side, and the one
|
|
for the right side is only displayed if you need it.
|
|
Properties for each side are grouped by the
|
|
TJvChartYAxisOptions persistent-properties-object.
|
|
}
|
|
TJvChartYAxisOptions = class(TPersistent)
|
|
private
|
|
FOwner: TJvChartOptions;
|
|
FActive: Boolean; // One or more pens use this Y Axis.
|
|
protected
|
|
FYMax: Double; // Y Scale value at the top left hand side of chart.
|
|
FYMin: Double; // Y Scale value at the bottom left hand side of the chart (default 0)
|
|
FYGap: Double; // Number of values per Y scale division
|
|
FYGap1: Double; // Gap multiplication factor for value scaling.
|
|
FMarkerValueDecimals: Integer; // Decimal places on marker-values (only applies to Marker Pens with Values)
|
|
FYDivisions: Integer; // Number of vertical divisions in the chart. (default 10)
|
|
FMaxYDivisions: Integer;
|
|
FMinYDivisions: Integer;
|
|
FYLegendDecimalPlaces: Integer;
|
|
FYLegends: TStringList;
|
|
FDefaultYLegends: Integer; // Number of default Y legends.
|
|
FYPixelGap: Double;
|
|
procedure SetYMax(NewYMax: Double);
|
|
procedure SetYMin(NewYMin: Double);
|
|
// procedure SetYGap(newYgap: Double);
|
|
function GetYLegends: TStrings;
|
|
procedure SetYLegends(Value: TStrings);
|
|
procedure SetYDivisions(AValue: Integer);
|
|
public
|
|
constructor Create(Owner: TJvChartOptions); virtual;
|
|
destructor Destroy; override;
|
|
procedure Normalize;
|
|
procedure Clear;
|
|
// runtime only properties
|
|
property YPixelGap: Double read FYPixelGap write FYPixelGap;
|
|
property Active: Boolean read FActive;
|
|
property YGap: Double read FYGap;
|
|
property YGap1: Double read FYGap1; // Gap multiplication factor for value scaling.
|
|
property YLegends: TStrings read GetYLegends write SetYLegends; { Y Axis Legends as Strings }
|
|
published
|
|
property YMax: Double read FYMax write SetYMax;
|
|
property YMin: Double read FYMin write SetYMin;
|
|
property YDivisions: Integer read FYDivisions write SetYDivisions default 10;
|
|
// Number of vertical divisions in the chart
|
|
// YDivisions->YDivisions
|
|
property MaxYDivisions: Integer read FMaxYDivisions write FMaxYDivisions default 20;
|
|
property MinYDivisions: Integer read FMinYDivisions write FMinYDivisions default 5;
|
|
property MarkerValueDecimals: Integer read FMarkerValueDecimals write FMarkerValueDecimals default -1;
|
|
// Decimal places on marker-values (only applies to Marker Pens with Values)
|
|
property YLegendDecimalPlaces: Integer read FYLegendDecimalPlaces write FYLegendDecimalPlaces;
|
|
property DefaultYLegends: Integer read FDefaultYLegends write FDefaultYLegends default JvDefaultYLegends;
|
|
end;
|
|
|
|
TJvChartOptions = class(TPersistent)
|
|
private
|
|
FOwner: TJvChart;
|
|
{accessors}
|
|
function GetAverageValue(Index: Integer): Double;
|
|
procedure SetAverageValue(Index: Integer; AValue: Double);
|
|
function GetPenColor(Index: Integer): TColor;
|
|
procedure SetPenColor(Index: Integer; AColor: TColor);
|
|
function GetPenStyle(Index: Integer): TPenStyle;
|
|
procedure SetPenStyle(Index: Integer; APenStyle: TPenStyle);
|
|
function GetPenMarkerKind(Index: Integer): TJvChartPenMarkerKind;
|
|
procedure SetPenMarkerKind(Index: Integer; AMarkKind: TJvChartPenMarkerKind);
|
|
procedure SetXStartOffset(Offset: Integer);
|
|
function GetPenSecondaryAxisFlag(Index: Integer): Boolean;
|
|
procedure SetPenSecondaryAxisFlag(Index: Integer; NewValue: Boolean);
|
|
function GetPenValueLabels(Index: Integer): Boolean;
|
|
procedure SetPenValueLabels(Index: Integer; NewValue: Boolean);
|
|
procedure SetPenCount(Count: Integer);
|
|
procedure SetChartKind(AKind: TJvChartKind);
|
|
// TStrings<->TStringList transmogrifiers
|
|
function GetPenLegends: TStrings;
|
|
procedure SetPenLegends(Value: TStrings);
|
|
function GetPenUnit: TStrings;
|
|
procedure SetPenUnit(Value: TStrings);
|
|
function GetXLegends: TStrings;
|
|
procedure SetXLegends(Value: TStrings);
|
|
procedure SetHeaderFont(AFont: TFont);
|
|
procedure SetLegendFont(AFont: TFont);
|
|
procedure SetAxisFont(AFont: TFont);
|
|
procedure SetPaperColor(AColor: TColor);
|
|
procedure SetPrimaryYAxis(AssignFrom: TJvChartYAxisOptions);
|
|
procedure SetSecondaryYAxis(AssignFrom: TJvChartYAxisOptions);
|
|
// Each pen can be associated with either the primary or secondary axis,
|
|
// this function decides which axis to return depending on the pen configuration:
|
|
function GetPenAxis(Index: Integer): TJvChartYAxisOptions;
|
|
procedure SetXAxisDateTimeDivision(const Value: Double);
|
|
protected
|
|
FChartKind: TJvChartKind; // default JvChartLine
|
|
{runtime pixel spacing multipliers}
|
|
FXPixelGap: Double;
|
|
{Fonts}
|
|
FHeaderFont: TFont;
|
|
FLegendFont: TFont;
|
|
FAxisFont: TFont;
|
|
FTitle: string;
|
|
FNoDataMessage: string;
|
|
FYAxisHeader: string;
|
|
FYAxisDivisionMarkers: Boolean; // Do you want grid-paper look?
|
|
FXAxisDivisionMarkers: Boolean; // Do you want grid-paper look?
|
|
FXAxisHeader: string;
|
|
|
|
FXLegends: TStringList; // Text labels.
|
|
FXLegendMaxTextWidth: Integer; // runtime: display width (pixels) of widest string in FXLegends[1:X].
|
|
FXAxisValuesPerDivision: Integer;
|
|
// Number of Values (aka samples) in each vertical dotted lines that are divisision marker.
|
|
FXAxisLegendSkipBy: Integer; //1=print every X axis label, 2=every other, and so on. default=1
|
|
FXLegendHoriz: Integer; // Horizontally oriented GraphXAxisLegend ends at this X Point.
|
|
FXAxisLabelAlignment: TAlignment; // New: Text alignment for X axis labels. Default is left alignment.
|
|
FXAxisDateTimeMode: Boolean;
|
|
// False=use custom text labels, True=Use Date/Time Stamps as X axis labels. [REWORKED LOGIC IN 2007!]
|
|
FXAxisDateTimeDivision: Double; // NEW 2007 : What is the nominal date/time division (1.0=day, 1.0/24=1 hour)
|
|
FXAxisDateTimeFormat: string; // Usually a short date-time label, hh:nn:ss is good.
|
|
FDateTimeFormat: string;
|
|
// Usually a long date-time label, ISO standard yyyy-mm-dd hh:nn:ss is fine, as is Windows locale defaults.
|
|
FXValueCount: Integer;
|
|
// Number of pens:
|
|
FPenCount: Integer;
|
|
// Per-pen array/list properties
|
|
FPenColors: array of TColor;
|
|
FPenStyles: array of TPenStyle; // solid, dotted
|
|
FPenMarkerKind: array of TJvChartPenMarkerKind;
|
|
FPenSecondaryAxisFlag: array of Boolean; // False=Primary Y Axis, True=Secondary Y Axis.
|
|
FPenValueLabels: array of Boolean;
|
|
FPenLegends: TStringList;
|
|
FPenUnit: TStringList;
|
|
FAverageValue: array of Double; // Used in averaging chart types only.
|
|
FPrimaryYAxis: TJvChartYAxisOptions;
|
|
FSecondaryYAxis: TJvChartYAxisOptions;
|
|
FXGap: Double; // Number of pixels per X scale unit.
|
|
FXOrigin: Integer; {which value corresponds to Origin}
|
|
FYOrigin: Integer; // Vertical (Y) Position of the Origin point, and X axis.
|
|
FXStartOffset: Longint; {margin} // Horizontal (X) Position of the Origin point, and Y Axis
|
|
FYStartOffset: Longint; // height of the top margin above the charting area.
|
|
FXEnd: Longint; { From top left of control, add XEnd to find where the right margin starts }
|
|
FYEnd: Longint; { from top left of control, add YEnd to find where the below-the bottom margin starts }
|
|
FMarkerSize: Integer; { marker size. previously called PointSize which sounded like a Font attribute. }
|
|
{ more design time }
|
|
FLegendWidth: Integer;
|
|
FLegendRowCount: Integer; // Number of lines of text in legend.
|
|
FAutoUpdateGraph: Boolean;
|
|
FMouseEdit: Boolean;
|
|
FMouseDragObjects: Boolean; // Can mouse drag floating objects?
|
|
FMouseInfo: Boolean;
|
|
FLegend: TJvChartLegend; // was FShowLegend, now Legend=clChartLegendRight
|
|
FPenLineWidth: Integer;
|
|
FAxisLineWidth: Integer;
|
|
|
|
//COLORS:
|
|
FPaperColor: TColor;
|
|
FDivisionLineColor: TColor; // NEW! Division line
|
|
FShadowColor: TColor; // NEW! Shadow color
|
|
FAxisLineColor: TColor; // Color of box around chart plot area.
|
|
FHintColor: TColor; // Hint box color
|
|
FAverageLineColor: TColor; // Pen color for Charts with auto-average lines.
|
|
FCursorColor: TColor; // Sample indicator Cursor color
|
|
|
|
FCursorStyle: TPenStyle; // Cursor style.
|
|
FGradientColor: TColor; // new 2007
|
|
FGradientDirection: TJvChartGradientDirection; // new 2007
|
|
|
|
// INTERNALS :NEW STUFF IN 2007
|
|
FXAxisDateTimeFirstMarker: Integer; // at XValue initial offset NEW 2007
|
|
FXAxisDateTimeSkipBy: Integer; // an XValue indexing multiplier NEW 2007
|
|
FXAxisDateTimeLines: Integer; // number of lines we're displaying NEW 2007
|
|
|
|
{ event interface }
|
|
procedure NotifyOptionsChange;
|
|
public
|
|
constructor Create(Owner: TJvChart); virtual;
|
|
destructor Destroy; override;
|
|
{ runtime properties }
|
|
property AverageValue[Index: Integer]: Double read GetAverageValue write SetAverageValue;
|
|
property PenAxis[Index: Integer]: TJvChartYAxisOptions read GetPenAxis;
|
|
property XLegends: TStrings read GetXLegends write SetXLegends; { X Axis Legends as Strings }
|
|
{ plot-canvas size, depends on size of control }
|
|
property XEnd: Longint read FXEnd write FXEnd;
|
|
property YEnd: Longint read FYEnd write FYEnd;
|
|
{Gradient NEW 2007 }
|
|
property GradientColor: TColor read FGradientColor write FGradientColor; // new 2007
|
|
property GradientDirection: TJvChartGradientDirection read FGradientDirection write FGradientDirection; // new 2007
|
|
|
|
{ pixel spacing : multipliers to scale real values into X/Y pixel amounts before plotting. CRITICALLY important. }
|
|
property XPixelGap: Double read FXPixelGap write FXPixelGap;
|
|
property XLegendMaxTextWidth: Integer read FXLegendMaxTextWidth write FXLegendMaxTextWidth;
|
|
{ Per Pen Array/List Properties -- settable at RUNTIME only. }
|
|
property PenColor[Index: Integer]: TColor read GetPenColor write SetPenColor;
|
|
property PenStyle[Index: Integer]: TPenStyle read GetPenStyle write SetPenStyle;
|
|
property PenMarkerKind[Index: Integer]: TJvChartPenMarkerKind read GetPenMarkerKind write SetPenMarkerKind;
|
|
property PenSecondaryAxisFlag[Index: Integer]: Boolean read GetPenSecondaryAxisFlag write SetPenSecondaryAxisFlag;
|
|
property PenValueLabels[Index: Integer]: Boolean read GetPenValueLabels write SetPenValueLabels;
|
|
published
|
|
{ design time}
|
|
{ Per Pen Array/List Properties - settable at DESIGNTIME. Others (color/style, marker) are runtime only. }
|
|
property PenLegends: TStrings read GetPenLegends write SetPenLegends;
|
|
property PenUnit: TStrings read GetPenUnit write SetPenUnit;
|
|
property ChartKind: TJvChartKind read FChartKind write SetChartKind default ckChartLine;
|
|
property Title: string read FTitle write FTitle;
|
|
property NoDataMessage: string read FNoDataMessage write FNoDataMessage;
|
|
//NEW! NOV 2004. Optionally display this instead of fixed resource string rsNoData
|
|
|
|
{ X Axis Properties }
|
|
property YAxisHeader: string read FYAxisHeader write FYAxisHeader;
|
|
property YAxisDivisionMarkers: Boolean read FYAxisDivisionMarkers write FYAxisDivisionMarkers default True;
|
|
// Do you want grid-paper look?
|
|
{ X Axis Properties }
|
|
property XAxisDivisionMarkers: Boolean read FXAxisDivisionMarkers write FXAxisDivisionMarkers default True;
|
|
// Do you want grid-paper look?
|
|
property XAxisValuesPerDivision: Integer read FXAxisValuesPerDivision write FXAxisValuesPerDivision;
|
|
// Number of Values (aka samples) in each vertical dotted lines that are divisision marker.
|
|
property XAxisLabelAlignment: TAlignment read FXAxisLabelAlignment write FXAxisLabelAlignment;
|
|
// New: Text alignment for X axis labels. Default is left alignment.
|
|
|
|
property XAxisDateTimeMode: Boolean read FXAxisDateTimeMode write FXAxisDateTimeMode;
|
|
// REWORKED LOGIC NEW IN 2007! See GraphXAxisDivisionMarkers
|
|
property XAxisDateTimeDivision: Double read FXAxisDateTimeDivision write SetXAxisDateTimeDivision;
|
|
// NEW 2007 : What is the nominal date/time division (1.0=day, 1.0/24=1 hour)
|
|
|
|
property XAxisDateTimeFormat: string read FXAxisDateTimeFormat write FXAxisDateTimeFormat;
|
|
property XAxisHeader: string read FXAxisHeader write FXAxisHeader;
|
|
property XAxisLegendSkipBy: Integer read FXAxisLegendSkipBy write FXAxisLegendSkipBy default 1;
|
|
property DateTimeFormat: string read FDateTimeFormat write FDateTimeFormat;
|
|
// Usually a long date-time label, ISO standard yyyy-mm-dd hh:nn:ss is fine, as is Windows locale defaults.
|
|
property PenCount: Integer read FPenCount write SetPenCount default 1;
|
|
property XGap: Double read FXGap write FXGap;
|
|
property XOrigin: Integer read FXOrigin write FXOrigin;
|
|
property YOrigin: Integer read FYOrigin write FYOrigin; // Position of bottom of chart (not always the zero origin)
|
|
property XStartOffset: Longint read FXStartOffset write SetXStartOffset default 45;
|
|
property YStartOffset: Longint read FYStartOffset write FYStartOffset default 10;
|
|
{ Y Range }
|
|
{ plotting markers }
|
|
property MarkerSize: Integer read FMarkerSize write FMarkerSize default JvChartDefaultMarkerSize;
|
|
{ !! New: Primary (left side) Y axis, and Secondary (right side) Y Axis !!}
|
|
property PrimaryYAxis: TJvChartYAxisOptions read FPrimaryYAxis write SetPrimaryYAxis;
|
|
property SecondaryYAxis: TJvChartYAxisOptions read FSecondaryYAxis write SetSecondaryYAxis;
|
|
//1=print every X axis label, 2=every other, and so on. default=1
|
|
{ vertical numeric decimal places }
|
|
{ more design time }
|
|
property AutoUpdateGraph: Boolean read FAutoUpdateGraph write FAutoUpdateGraph default True;
|
|
property MouseEdit: Boolean read FMouseEdit write FMouseEdit default True;
|
|
property MouseDragObjects: Boolean read FMouseDragObjects write FMouseDragObjects;
|
|
// Can mouse drag floating objects?
|
|
property MouseInfo: Boolean read FMouseInfo write FMouseInfo default True;
|
|
//OLD:property ShowLegend: Boolean read FShowLegend write FShowLegend default True;
|
|
//CHANGEDTO:
|
|
property Legend: TJvChartLegend read FLegend write FLegend default clChartLegendNone;
|
|
property LegendRowCount: Integer read FLegendRowCount write FLegendRowCount;
|
|
property LegendWidth: Integer read FLegendWidth write FLegendWidth default 150;
|
|
property PenLineWidth: Integer read FPenLineWidth write FPenLineWidth default 1;
|
|
property AxisLineWidth: Integer read FAxisLineWidth write FAxisLineWidth default 2;
|
|
{ more and more design time. these ones not sure about whether they are designtime or not.}
|
|
property XValueCount: Integer read FXValueCount write FXValueCount default 10;
|
|
{Font properties}
|
|
property HeaderFont: TFont read FHeaderFont write SetHeaderFont;
|
|
property LegendFont: TFont read FLegendFont write SetLegendFont;
|
|
property AxisFont: TFont read FAxisFont write SetAxisFont;
|
|
{ Color properties}
|
|
property DivisionLineColor: TColor read FDivisionLineColor write FDivisionLineColor default
|
|
JvDefaultDivisionLineColor; // NEW! Division line
|
|
property ShadowColor: TColor read FShadowColor write FShadowColor default JvDefaultShadowColor; // NEW! Shadow color
|
|
|
|
property PaperColor: TColor read FPaperColor write SetPaperColor;
|
|
property AxisLineColor: TColor read FAxisLineColor write FAxisLineColor;
|
|
property HintColor: TColor read FHintColor write FHintColor default JvDefaultHintColor;
|
|
property AverageLineColor: TColor read FAverageLineColor write FAverageLineColor default JvDefaultAvgLineColor;
|
|
property CursorColor: TColor read FCursorColor write FCursorColor;
|
|
property CursorStyle: TPenStyle read FCursorStyle write FCursorStyle;
|
|
end;
|
|
|
|
TJvChart = class(TJvGraphicControl)
|
|
private
|
|
FUpdating: Boolean; // PREVENT ENDLESS EVENT LOOPING.
|
|
FAutoPlotDone: Boolean; // If Options.AutoUpdateGraph is set, then has paint method called PlotGraph already?
|
|
FPlotGraphCalled: Boolean; // Has bitmap ever been painted?
|
|
FInPlotGraph: Boolean; // recursion blocker.
|
|
|
|
// NEW: The component has always had a feature when you click on margin areas
|
|
// that the user can enter a value (Y Axis Scale, title, and X header)
|
|
// The right margin however didn't do anything. Now all four have a user
|
|
// event that can be fired. If you don't want the default editor behaviours
|
|
// turn off Options.MouseEdit to make it 100% user-defined what happens when
|
|
// the user clicks on an area of the chart.
|
|
FOnYAxisClick: TJvChartEvent; // Left margin (Primary Y Axis labels) click
|
|
FOnXAxisClick: TJvChartEvent; // Bottom margin (X Axis Header) click
|
|
FOnAltYAxisClick: TJvChartEvent; // Right margin click (Secondary Y Axis labels)
|
|
FOnTitleClick: TJvChartEvent; // Title area click (Top margin)
|
|
FOnChartClick: TJvChartClickEvent; // mouse click event
|
|
|
|
FOnBeginFloatingMarkerDrag: TJvChartFloatingMarkerDragEvent;
|
|
FOnEndFloatingMarkerDrag: TJvChartFloatingMarkerDragEvent;
|
|
|
|
// low level mouse and paint events intended ONLY FOR ADVANCED USERS:
|
|
FOnChartPaint: TJvChartPaintEvent;
|
|
// After chart bitmap is painted onto control surface we can "decorate" it with owner-drawn extras.
|
|
|
|
FMouseDownShowHint: Boolean; // True=showing hint.
|
|
FMouseDownHintBold: Boolean; // True=first line of hint is bold.
|
|
FMouseDownHintStrs: TStringList;
|
|
{ TImage stuff}
|
|
FPicture: TPicture; // An image drawn via GDI primitives, saveable as
|
|
// bitmap or WMF, or displayable to screen
|
|
FData: TJvChartData;
|
|
|
|
FDragFloatingMarker: TJvChartFloatingMarker; //Current object we are dragging ( nil=none )
|
|
FFloatingMarker: TObjectList; // NEW: collection of TJvChartFloatingMarker objects.
|
|
FHorizontalBars: TObjectList; // NEW 2007
|
|
FVerticalBars: TObjectList; // NEW 2007
|
|
|
|
FAverageData: TJvChartData;
|
|
FBitmap: TBitmap;
|
|
FOptions: TJvChartOptions; //^TOptions;
|
|
//Options2 : ^TOptions2; // now FData
|
|
PrintInSession: Boolean;
|
|
FStartDrag: Boolean;
|
|
FMouseLegend: Boolean;
|
|
FContainsNegative: Boolean;
|
|
{ strColorFile: string;}// not used (ahuser)
|
|
FOldYOrigin: Integer;
|
|
FOldYGap: Double;
|
|
FMouseDownX: Longint;
|
|
FMouseDownY: Longint;
|
|
FMouseValue: Integer;
|
|
FMousePen: Integer;
|
|
FYFont: TFont; // Delphi Font object wrapper.
|
|
//NEW:
|
|
FXOrigin: Double; {was in TJvChart.PlotGraph}
|
|
FYOrigin: Double; {was in TJvChart.PlotGraph}
|
|
FXAxisPosition: Integer; // how far down (in Y dimension) is the X axis?
|
|
FOnOptionsChangeEvent: TJvChartEvent; { Component fires this event for when options change.}
|
|
FOnPaint: TJvChartPaintEvent; {NEW JAN 2005: Custom paint event called from TjvChart.Paint.}
|
|
|
|
FCursorPosition: Integer; // NEW: -1 means no visible cursor, 0..n means make
|
|
// particular value highlighted. The highlight is painted
|
|
// over top of the TImage, so that we can just restore the TImage
|
|
// without replotting the whole chart.
|
|
{$IFDEF VCL}
|
|
// Y Axis Vertical Font
|
|
FYFontHandle: HFONT; // Y AXIS VERTICAL TEXT: Vertical Font Handle (remember to DeleteObject)
|
|
FYLogFont: TLogFont; // Y AXIS VERTICAL TEXT: Logical Font Options Record
|
|
procedure MakeVerticalFont; // Call GDI calls to get the Y Axis Vertical Font handle
|
|
procedure MyGraphVertFont(ACanvas: TCanvas); // vertical font handle
|
|
{$ENDIF VCL}
|
|
procedure PaintCursor; // called from Paint iif a Cursor is visible. does NOT modify FPicture!
|
|
protected
|
|
procedure DrawFloatingMarkers;
|
|
procedure DrawHorizontalBars; // NEW 2007
|
|
procedure DrawVerticalBars; // NEW 2007
|
|
procedure DrawGradient; // NEW 2007
|
|
|
|
function GetFloatingMarker(Index: Integer): TJvChartFloatingMarker;
|
|
|
|
{ Right Side Legend showing Pen Names, and/or Data Descriptors }
|
|
procedure GraphXAxisLegendMarker(ACanvas: TCanvas; MarkerKind: TJvChartPenMarkerKind; X, Y: Integer);
|
|
procedure GraphXAxisLegend;
|
|
procedure MyHeader(ACanvas: TCanvas; StrText: string);
|
|
procedure MyXHeader(ACanvas: TCanvas; StrText: string);
|
|
procedure MyYHeader(ACanvas: TCanvas; StrText: string); // NEW
|
|
procedure MyHeaderFont(ACanvas: TCanvas);
|
|
procedure MyAxisFont(ACanvas: TCanvas);
|
|
procedure MySmallGraphFont(ACanvas: TCanvas);
|
|
function MyTextHeight(ACanvas: TCanvas; StrText: string): Longint;
|
|
{ TEXTOUT stuff }
|
|
procedure MyRightTextOut(ACanvas: TCanvas; X, Y: Integer; const Text: string); // RIGHT TEXT
|
|
procedure MyCenterTextOut(ACanvas: TCanvas; X, Y: Integer; const Text: string); // CENTER TEXT
|
|
procedure MyLeftTextOut(ACanvas: TCanvas; X, Y: Integer; const Text: string); // LEFT ALIGN TEXT
|
|
|
|
// Use HintColor:
|
|
procedure MyLeftTextOutHint(ACanvas: TCanvas; X, Y: Integer; const Text: string);
|
|
|
|
{ line, curve, rectangle stuff }
|
|
procedure MyPenLineTo(ACanvas: TCanvas; X, Y: Integer);
|
|
procedure MyAxisLineTo(ACanvas: TCanvas; X, Y: Integer);
|
|
procedure MyRectangle(ACanvas: TCanvas; X, Y, X2, Y2: Integer);
|
|
procedure MyColorRectangle(ACanvas: TCanvas; Pen: Integer; X, Y, X2, Y2: Integer);
|
|
procedure MyPie(ACanvas: TCanvas; X1, Y1, X2, Y2, X3, Y3, X4, Y4: Longint); { pie chart segment }
|
|
// procedure MyArc(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Integer); { arc } // not used (ahuser)
|
|
|
|
// procedure MyEllipse(X1, Y1, X2, Y2: Integer); // not used (ahuser)
|
|
procedure MyDrawLine(ACanvas: TCanvas; X1, Y1, X2, Y2: Integer); // not used (ahuser)
|
|
procedure MyDrawAxisMark(ACanvas: TCanvas; X1, Y1, X2, Y2: Integer); // solid line as a tick on an axis.
|
|
procedure MyDrawDotLine(ACanvas: TCanvas; X1, Y1, X2, Y2: Integer);
|
|
|
|
procedure EditXHeader;
|
|
procedure EditYScale;
|
|
procedure EditHeader;
|
|
|
|
procedure SetSolidLines(ACanvas: TCanvas);
|
|
procedure SetDotLines(ACanvas: TCanvas);
|
|
|
|
procedure SetLineColor(ACanvas: TCanvas; Pen: Integer);
|
|
procedure SetRectangleColor(ACanvas: TCanvas; Pen: Integer);
|
|
procedure SetFontColor(ACanvas: TCanvas; Pen: Integer);
|
|
procedure CountGraphAverage;
|
|
procedure DrawPenColorBox(ACanvas: TCanvas; NColor, W, H, X, Y: Integer);
|
|
{ function GetDefaultColorString(nIndex: Integer): string;}// (rom) not used
|
|
procedure MyPiePercentage(X1, Y1, W: Longint; NPercentage: Double);
|
|
procedure GraphPieChart(NPen: Integer);
|
|
procedure GraphDeltaAverage;
|
|
procedure MyPieLegend(NPen: Integer);
|
|
procedure ShowMouseMessage(X, Y: Integer);
|
|
// marker symbols:
|
|
procedure MyPolygon(ACanvas: TCanvas; Points: array of TPoint);
|
|
procedure PlotCross(ACanvas: TCanvas; X, Y: Integer);
|
|
procedure PlotDiamond(ACanvas: TCanvas; X, Y: Integer);
|
|
procedure PlotFilledDiamond(ACanvas: TCanvas; X, Y: Integer);
|
|
procedure PlotCircle(ACanvas: TCanvas; X, Y: Integer);
|
|
procedure PlotSquare(ACanvas: TCanvas; X, Y: Integer);
|
|
|
|
procedure PlotMarker(ACanvas: TCanvas; MarkerKind: TJvChartPenMarkerKind; X, Y: Integer);
|
|
// Calls one of the Plot<Shape> functions.
|
|
|
|
procedure ClearScreen;
|
|
// internal graphics methods
|
|
procedure GraphSetup; // These set up variables used for all the rest of the plotting functions
|
|
procedure GraphXAxis;
|
|
procedure GraphYAxis;
|
|
procedure GraphYAxisDivisionMarkers;
|
|
procedure GraphXAxisDivisionMarkers; // new.
|
|
procedure CalcYEnd; // Determine where the below-the bottom axis area starts
|
|
|
|
function GetChartCanvas: TCanvas; // Get Picture.Bitmap Canvas.
|
|
function GetChartCanvasWidth: Integer; //WP NEW 2007
|
|
function GetChartCanvasHeight: Integer; //WP NEW 2007
|
|
|
|
function DestRect: TRect; // from TImage
|
|
procedure DesignModePaint; // Invoked by Paint method when we're in design mode.
|
|
procedure Paint; override; // from TImage
|
|
procedure Resize; override; // from TControl
|
|
procedure Loaded; override;
|
|
{ draw dummy data for design mode}
|
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
|
|
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
|
|
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
|
|
|
|
procedure PrimaryYAxisLabels; // Put contents into Options.PrimaryYAxis.YLegends
|
|
procedure NotifyOptionsChange; {NEW}
|
|
|
|
procedure InternalPlotGraph; { internal version of _PlotGraph that doesn't call Invalidate. }
|
|
|
|
{ internal drawing properties, valid during Paint method invocations only }
|
|
property XOrigin: Double read FXOrigin; {was in TJvChart.PlotGraph}
|
|
property YOrigin: Double read FYOrigin; {was in TJvChart.PlotGraph}
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
// Get the X and Y Value for a particular Mouse location:
|
|
function MouseToXValue(X: Integer): Integer;
|
|
// convert X pixel mouse position to data index, ie Data.Values[..,<INDEX>].
|
|
function MouseToYValue(Y: Integer): Double;
|
|
// convert Y pixel mouse position to value in range Options.PrimaryYAxis.Min to Options.PrimaryYAxis.mAx
|
|
|
|
{General procedures for the graph...}
|
|
procedure ResetGraphModule; {Call this before totally new values and Pen}
|
|
//procedure AutoFormatGraph; {XXX BAD CODE. TO BE DELETED. MAY BE REPLACED LATER BY NEW AutoRange FUNCTION!}
|
|
|
|
procedure PlotGraph; {Update screen / draw graph to screen. calls Invalidate. Don't call from inside Paint code!}
|
|
|
|
procedure PrintGraph; {Send picture to printer; all printing done by component}
|
|
procedure AddGraphToOpenPrintCanvas(XStartPos, YStartPos, GraphWidth, GraphHeight: Longint);
|
|
{adds the graph to the "OPEN" printer canvas}
|
|
{printing control=outside this component; add other text etc}
|
|
procedure GraphToClipboard; {Puts picture on clipboard}
|
|
procedure ResizeChartCanvas; {Call this after screen resize and after start up}
|
|
procedure PivotData; { Pivot Table. Switches the x values with Pen! Resets AverageLine}
|
|
procedure AutoHint; // Make the automatic hint message showing all pens and their values.
|
|
procedure SetCursorPosition(Pos: Integer);
|
|
procedure DisplayBars; // NEW 2007
|
|
|
|
// FLOATING MARKERS: NEW JAN 2005. -WP
|
|
function AddFloatingMarker: TJvChartFloatingMarker; // NEW Jan 2005!
|
|
|
|
property FloatingMarker[Index: Integer]: TJvChartFloatingMarker read GetFloatingMarker; // NEW Jan 2005!
|
|
|
|
procedure DeleteFloatingMarker(Index: Integer); // NEW Jan 2005!
|
|
|
|
// --NEW 2007 METHOD--
|
|
|
|
procedure DeleteFloatingMarkerObj(Marker: TJvChartFloatingMarker); // NEW 2007
|
|
procedure CopyFloatingMarkers(Source: TJvChart);
|
|
procedure ClearFloatingMarkers;
|
|
function FloatingMarkerCount: Integer; // NEW 2007
|
|
|
|
function AddHorizontalBar: TJvChartHorizontalBar; // NEW 2007
|
|
procedure ClearHorizontalBars; // NEW 2007
|
|
function HorizontalBarsCount: Integer; // NEW 2007
|
|
|
|
function AddVerticalBar: TJvChartVerticalBar; // NEW 2007
|
|
procedure ClearVerticalBars; // NEW 2007
|
|
function VerticalBarsCount: Integer; // NEW 2007
|
|
|
|
// -- END NEW 2007 METHOD--
|
|
|
|
property Data: TJvChartData read FData;
|
|
property AverageData: TJvChartData read FAverageData;
|
|
public
|
|
{runtime only helper properties}
|
|
{ TImage-like stuff }
|
|
property Picture: TPicture read FPicture; // write SetPicture;
|
|
// NEW: Ability to highlight a particular sample by setting the Cursor position!
|
|
property CursorPosition: Integer read FCursorPosition write SetCursorPosition;
|
|
// procedure DataTests; // TESTING. WAP.
|
|
published
|
|
{ Standard TControl Stuff}
|
|
//property Color default clWindow;
|
|
property Font;
|
|
property Align;
|
|
property Anchors;
|
|
property Constraints;
|
|
property OnDblClick; { TNotifyEvent from TControl }
|
|
{$IFDEF VCL}
|
|
property AutoSize;
|
|
property DragCursor;
|
|
property DragKind;
|
|
//property OnKeyDown; // Tried to add this, but it was too hard. -WP APril 2004.
|
|
{$ENDIF VCL}
|
|
property DragMode;
|
|
property Enabled;
|
|
property ParentShowHint;
|
|
property PopupMenu;
|
|
property ShowHint;
|
|
property Visible;
|
|
{ chart options}
|
|
property Options: TJvChartOptions read FOptions write FOptions;
|
|
{ chart events}
|
|
|
|
property OnChartClick: TJvChartClickEvent read FOnChartClick write FOnChartClick;
|
|
property OnChartPaint: TJvChartPaintEvent read FOnChartPaint write FOnChartPaint;
|
|
// After chart bitmap is painted onto control surface we can "decorate" it with owner-drawn extras.
|
|
|
|
{ Drag and Drop of Floating Marker Events - NEW Jan 2005 -WP}
|
|
property OnBeginFloatingMarkerDrag: TJvChartFloatingMarkerDragEvent read FOnBeginFloatingMarkerDrag write
|
|
FOnBeginFloatingMarkerDrag; // Drag/drop of floating markers beginning.
|
|
property OnEndFloatingMarkerDrag: TJvChartFloatingMarkerDragEvent read FOnEndFloatingMarkerDrag write
|
|
FOnEndFloatingMarkerDrag; // Drag/drop of floating markers ending.
|
|
|
|
{
|
|
Chart Margin Click Events - you can click on the four
|
|
'margin' areas (left,right,top,bottom) around the main chart
|
|
area. The left and top margins have default behaviours
|
|
which you can disable by turning off Options.MouseEdit.
|
|
The other 2 margin areas are entirely up to the user to define.
|
|
Clicking bottom or right margins does nothing by default.
|
|
}
|
|
property OnYAxisClick: TJvChartEvent read FOnYAxisClick write FOnYAxisClick;
|
|
// When user clicks on Y axis, they can enter a new Y Scale value.
|
|
property OnXAxisClick: TJvChartEvent read FOnXAxisClick write FOnXAxisClick;
|
|
// Also allow user to define some optional action for clicking on the X axis.
|
|
property OnAltYAxisClick: TJvChartEvent read FOnAltYAxisClick write FOnAltYAxisClick;
|
|
// Right margin click (Secondary Y Axis labels)
|
|
property OnTitleClick: TJvChartEvent read FOnTitleClick write FOnTitleClick; // Top margin area (Title area) click.
|
|
end;
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
const
|
|
UnitVersioning: TUnitVersionInfo = (
|
|
RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvChart.pas $';
|
|
Revision: '$Revision: 11242 $';
|
|
Date: '$Date: 2007-03-29 09:25:03 +0200 (jeu., 29 mars 2007) $';
|
|
LogPath: 'JVCL\run'
|
|
);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
implementation
|
|
|
|
uses
|
|
SysUtils, Forms, Dialogs, Printers, Clipbrd,
|
|
Math, // uses Ceil routine, also defines IsNaN on Delphi 6 and up.
|
|
JclMath, // function IsNaN for Delphi 5 (ahuser)
|
|
JvJCLUtils, // StrToFloatDef
|
|
JvJVCLUtils, JvConsts, JvResources;
|
|
|
|
const
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
CHART_SANITY_LIMIT = 60000;
|
|
{$ELSE}
|
|
CHART_SANITY_LIMIT = 12000000;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
|
|
// Any attempt to have more than CHART_SANITY_LIMIT elements in this
|
|
// graph will be treated as an internal failure on our part. This prevents
|
|
// ugly situations where we thrash because of excessive memory usage.
|
|
// Better to set this than to have the system pig out when we
|
|
// don't want it to. Set this very small when debugging,
|
|
// large when releasing component, and don't remove it unless
|
|
// you're absolutely sure. Increase it whenever necessary.
|
|
// Remember, it's a debugging tool, here on purpose to help keep you
|
|
// out of thrashing-virtual-memory-hell. You probably have a screen
|
|
// to view the chart that is a maximum of 1600x1200, so more than 1600
|
|
// samples will mean the data should be reduced before charting.
|
|
|
|
MAX_VALUES = 20000;
|
|
// Any attempt to exceed this values count will cause array size and performance problems, thus we limit it.
|
|
MAX_PEN = 100;
|
|
// Any attempt to exceed this pen count will cause array size and performance problems, thus we hardcode the pen limit to 100 pens.
|
|
DEFAULT_PEN_COUNT = 16; // By Default TJvChartData's internal data structures have room for up to 16 pens
|
|
MAX_X_LEGENDS = 50;
|
|
MAX_GRAPH_LEGEND_LEN = 9;
|
|
REALPREC = 7;
|
|
DEFAULT_MARKER_SIZE = 3;
|
|
DEFAULT_VALUE_COUNT = 100;
|
|
// By Default TJvChartData holds 100 values per pen. Grows autofragellisticexpialidociously. :-)
|
|
|
|
// NEW 2007:
|
|
|
|
// HELPER FUNCTIONS - NEW 2007
|
|
//-------------------Helper to draw a Gradient. Use it with TJvChartHorizontalBar, for example-------------------------
|
|
|
|
procedure GradHorizontal(Canvas: TCanvas; Rect: TRect; FromColor, ToColor: TColor); // NEW 2007
|
|
var
|
|
X: Integer;
|
|
dr, dg, db: Extended;
|
|
C1, C2: TColor;
|
|
r1, r2, g1, g2, b1, b2: Byte;
|
|
R, G, B: Byte;
|
|
Cnt: Integer;
|
|
XDelta: Integer;
|
|
begin
|
|
C1 := FromColor;
|
|
R1 := GetRValue(C1);
|
|
G1 := GetGValue(C1);
|
|
B1 := GetBValue(C1);
|
|
|
|
C2 := ToColor;
|
|
R2 := GetRValue(C2);
|
|
G2 := GetGValue(C2);
|
|
B2 := GetBValue(C2);
|
|
|
|
XDelta := Rect.Right - Rect.Left;
|
|
if XDelta <= 0 then
|
|
Exit;
|
|
|
|
dr := (R2 - R1) / XDelta;
|
|
dg := (G2 - G1) / XDelta;
|
|
db := (B2 - B1) / XDelta;
|
|
|
|
Cnt := 0;
|
|
for X := Rect.Left to Rect.Right - 1 do
|
|
begin
|
|
R := R1 + Ceil(dr * Cnt); // uses Math.
|
|
G := G1 + Ceil(dg * Cnt);
|
|
B := B1 + Ceil(db * Cnt);
|
|
|
|
Canvas.Pen.Color := RGB(R, G, B);
|
|
Canvas.MoveTo(X, Rect.Top);
|
|
Canvas.LineTo(X, Rect.Bottom);
|
|
Inc(Cnt);
|
|
end;
|
|
end;
|
|
|
|
procedure GradVertical(Canvas: TCanvas; Rect: TRect; FromColor, ToColor: TColor); // NEW 2007
|
|
var
|
|
Y: Integer;
|
|
dr, dg, db: Extended;
|
|
C1, C2: TColor;
|
|
r1, r2, g1, g2, b1, b2: Byte;
|
|
R, G, B: Byte;
|
|
Cnt: Integer;
|
|
YDelta: Integer;
|
|
begin
|
|
C1 := FromColor;
|
|
R1 := GetRValue(C1);
|
|
G1 := GetGValue(C1);
|
|
B1 := GetBValue(C1);
|
|
|
|
C2 := ToColor;
|
|
R2 := GetRValue(C2);
|
|
G2 := GetGValue(C2);
|
|
B2 := GetBValue(C2);
|
|
|
|
YDelta := Rect.Bottom - Rect.Top;
|
|
if YDelta <= 0 then
|
|
Exit;
|
|
dr := (R2 - R1) / YDelta;
|
|
dg := (G2 - G1) / YDelta;
|
|
db := (B2 - B1) / YDelta;
|
|
|
|
Cnt := 0;
|
|
for Y := Rect.Top to Rect.Bottom - 1 do
|
|
begin
|
|
R := R1 + Ceil(dr * Cnt);
|
|
G := G1 + Ceil(dg * Cnt);
|
|
B := B1 + Ceil(db * Cnt);
|
|
|
|
Canvas.Pen.Color := RGB(R, G, B);
|
|
Canvas.MoveTo(Rect.Left, Y);
|
|
Canvas.LineTo(Rect.Right, Y);
|
|
Inc(Cnt);
|
|
end;
|
|
end;
|
|
|
|
//=== { TJvChartGradientBar } ================================================
|
|
|
|
constructor TJvChartGradientBar.Create(Owner: TJvChart);
|
|
begin
|
|
inherited Create;
|
|
FOwner := Owner;
|
|
FVisible := false;
|
|
FColor := clWhite;
|
|
FGradDirection := grNone;
|
|
FGradColor := FColor;
|
|
end;
|
|
|
|
procedure TJvChartGradientBar.SetVisible(AVisible: Boolean);
|
|
begin
|
|
if AVisible <> FVisible then
|
|
begin
|
|
FVisible := AVisible;
|
|
if Assigned(FOwner) and not FOwner.FUpdating then
|
|
FOwner.Invalidate;
|
|
end
|
|
end;
|
|
|
|
procedure TJvChartGradientBar.SetColor(AColor: TColor);
|
|
begin
|
|
if AColor <> FColor then
|
|
begin
|
|
FColor := AColor;
|
|
if Assigned(FOwner) and not FOwner.FUpdating then
|
|
FOwner.Invalidate;
|
|
end
|
|
end;
|
|
|
|
procedure TJvChartGradientBar.SetGradientColor(AColor: TColor);
|
|
begin
|
|
if AColor <> FGradColor then
|
|
begin
|
|
FGradColor := AColor;
|
|
if Assigned(FOwner) and not FOwner.FUpdating then
|
|
FOwner.Invalidate;
|
|
end
|
|
end;
|
|
|
|
procedure TJvChartGradientBar.SetGradientType(AType: TJvChartGradientDirection);
|
|
begin
|
|
if AType <> FGradDirection then
|
|
begin
|
|
FGradDirection := AType;
|
|
if Assigned(FOwner) and not FOwner.FUpdating then
|
|
FOwner.Invalidate;
|
|
end
|
|
end;
|
|
|
|
//=== { TJvChartHorizontalBar } ==============================================
|
|
|
|
constructor TJvChartHorizontalBar.Create(Owner: TJvChart);
|
|
begin
|
|
inherited Create(Owner);
|
|
FYTop := 0;
|
|
FYBottom := 0;
|
|
end;
|
|
|
|
//=== { TJvChartVerticalBar } ================================================
|
|
|
|
constructor TJvChartVerticalBar.Create(Owner: TJvChart);
|
|
begin
|
|
inherited Create(Owner);
|
|
FXLeft := 0;
|
|
FXRight := 0;
|
|
end;
|
|
|
|
//=== {TJvChartFloatingMarker} ===============================================
|
|
|
|
constructor TJvChartFloatingMarker.Create(Owner: TJvChart);
|
|
begin
|
|
inherited Create;
|
|
FOwner := Owner;
|
|
FVisible := False; // NOT visible by default.
|
|
FIndex := -1; // not yet set.
|
|
FLineToMarker := -1; // Don't draw a line to connect to another marker.
|
|
//FYPositionToPen := -1; // Don't copy FYPosition from the pen values.
|
|
FMarkerColor := clRed;
|
|
FMarker := pmkDiamond; // default is diamond marker.
|
|
FLineStyle := psDot;
|
|
FLineColor := clBlue;
|
|
//FCaptionBorderStyle := psClear;
|
|
FXDragMin := -1; // no limit.
|
|
FXDragMax := -1; // no limit.
|
|
FRawXPosition := -1;
|
|
FRawYPosition := -1;
|
|
FLineWidth := 1;
|
|
//FXPosition := 0;
|
|
//FYPosition := 0.0;
|
|
end;
|
|
|
|
procedure TJvChartFloatingMarker.Assign(Source: TPersistent); // NEW 2007.
|
|
var
|
|
Src: TJvChartFloatingMarker;
|
|
begin
|
|
// don't assign FOwner, FIndex, etc.
|
|
//FRawXPosition {don't copy}
|
|
//FRawYPosition {don't copy}
|
|
if Source is TJvChartFloatingMarker then
|
|
begin
|
|
Src := TJvChartFloatingMarker(Source);
|
|
|
|
FCaption := Src.Caption;
|
|
FTag := Src.Tag;
|
|
|
|
//FYPositionToPen := Src.YPositionToPen;
|
|
FMarkerColor := Src.MarkerColor;
|
|
FMarker := Src.Marker;
|
|
FLineStyle := Src.LineStyle;
|
|
FLineColor := Src.LineColor;
|
|
//FCaptionBorderStyle := psClear;
|
|
FXDragMin := Src.XDragMin;
|
|
FXDragMax := Src.XDragMax;
|
|
|
|
FLineWidth := Src.LineWidth;
|
|
FLineToMarker := Src.LineToMarker;
|
|
FLineVertical := Src.LineVertical;
|
|
|
|
FCaptionColor := Src.CaptionColor;
|
|
FCaptionPosition := Src.CaptionPosition;
|
|
FCaptionBoxed := Src.CaptionBoxed;
|
|
|
|
{don't use internal property set for these:}
|
|
XPosition := Src.XPosition;
|
|
YPosition := Src.YPosition;
|
|
Visible := Src.Visible;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChartFloatingMarker.SetCaption(ACaption: string);
|
|
begin
|
|
if ACaption <> FCaption then
|
|
begin
|
|
FCaption := ACaption;
|
|
if Assigned(FOwner) and FVisible then
|
|
if not FOwner.FUpdating then
|
|
FOwner.Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChartFloatingMarker.SetCaptionColor(const Value: TColor);
|
|
begin
|
|
FCaptionColor := Value;
|
|
end;
|
|
|
|
procedure TJvChartFloatingMarker.SetXPosition(XPos: Integer); // should invalidate the chart (FOwner) if changed.
|
|
begin
|
|
if XPos <> FXPosition then
|
|
begin
|
|
FXPosition := XPos;
|
|
if Assigned(FOwner) and FVisible then
|
|
if not FOwner.FUpdating then
|
|
FOwner.Invalidate;
|
|
end
|
|
end;
|
|
|
|
procedure TJvChartFloatingMarker.SetYPosition(YPos: Double); // should invalidate the chart (FOwner) if changed.
|
|
begin
|
|
if YPos <> FYPosition then
|
|
begin
|
|
FYPosition := YPos;
|
|
if Assigned(FOwner) and FVisible then
|
|
if not FOwner.FUpdating then
|
|
FOwner.Invalidate;
|
|
end
|
|
end;
|
|
|
|
procedure TJvChartFloatingMarker.SetVisible(AVisible: Boolean);
|
|
begin
|
|
if AVisible <> FVisible then
|
|
begin
|
|
FVisible := AVisible;
|
|
if Assigned(FOwner) then
|
|
if not FOwner.FUpdating then
|
|
FOwner.Invalidate;
|
|
end
|
|
end;
|
|
|
|
//=== { TJvChartData } =======================================================
|
|
|
|
constructor TJvChartData.Create;
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
var
|
|
I: Integer;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
begin
|
|
inherited Create;
|
|
FPenCount := DEFAULT_PEN_COUNT; // Can never set less than one inside TJvChartData!
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
// FPenCount must be valid here:
|
|
for I := 0 to DEFAULT_PEN_COUNT do
|
|
Grow(I, DEFAULT_VALUE_COUNT);
|
|
{$ELSE}
|
|
Grow(DEFAULT_PEN_COUNT, DEFAULT_VALUE_COUNT);
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
end;
|
|
|
|
{$IFNDEF CLR}
|
|
destructor TJvChartData.Destroy;
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
var
|
|
I: Integer;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
begin
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
for I := 0 to FDataAlloc - 1 do
|
|
Finalize(FData[I]);
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
Finalize(FData); // Free array.
|
|
inherited Destroy;
|
|
end;
|
|
{$ENDIF !CLR}
|
|
|
|
function TJvChartData.GetValue(Pen, ValueIndex: Integer): Double;
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
begin
|
|
Assert(ValueIndex >= 0);
|
|
Grow(Pen, ValueIndex);
|
|
Result := FData[ValueIndex, Pen]; // This will raise EInvalidOP for NaN values.
|
|
end;
|
|
{$ELSE}
|
|
var
|
|
Idx: Integer;
|
|
begin
|
|
// Grow base array
|
|
if (Pen < 0) or (Pen >= FPenCount) then
|
|
Result := NaN
|
|
else
|
|
begin
|
|
Assert(FPenCount > 0);
|
|
Idx := (ValueIndex * FPenCount) + Pen;
|
|
|
|
if (Idx < 0) or (Idx > CHART_SANITY_LIMIT) then // Sanity check!
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEDataIndexTooLargeProbablyAnInternal);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEDataIndexTooLargeProbablyAnInternal);
|
|
{$ENDIF CLR}
|
|
|
|
if Idx >= Length(FData) then
|
|
Grow(Pen, ValueIndex);
|
|
Result := FData[Idx];
|
|
end;
|
|
end;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
|
|
procedure TJvChartData.SetValue(Pen, ValueIndex: Integer; NewValue: Double);
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
begin
|
|
// Grow base array
|
|
Grow(Pen, ValueIndex);
|
|
FData[ValueIndex, Pen] := NewValue;
|
|
if ValueIndex >= FValueCount then
|
|
begin
|
|
Grow(Pen, ValueIndex + 1);
|
|
FData[ValueIndex + 1, Pen] := NewValue; // Workaround for a graphical bug. Sorry.
|
|
FValueCount := ValueIndex + 1;
|
|
end;
|
|
end;
|
|
{$ELSE}
|
|
var
|
|
Idx: Integer;
|
|
begin
|
|
Assert(FPenCount > 0);
|
|
|
|
// Grow base array
|
|
if (Pen < 0) or (Pen >= FPenCount) then
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEPenIndexInvalid);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEPenIndexInvalid);
|
|
{$ENDIF CLR}
|
|
|
|
Idx := (ValueIndex * FPenCount) + Pen;
|
|
|
|
if (Idx < 0) or (Idx > CHART_SANITY_LIMIT) then // Sanity check!
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEDataIndexTooLargeProbablyAnInternal);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEDataIndexTooLargeProbablyAnInternal);
|
|
{$ENDIF CLR}
|
|
|
|
if Idx >= Length(FData) then
|
|
Grow(Pen, ValueIndex);
|
|
FData[Idx] := NewValue;
|
|
|
|
if ValueIndex >= FValueCount then
|
|
FValueCount := ValueIndex + 1;
|
|
end;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
|
|
function TJvChartData.GetTimestamp(ValueIndex: Integer): TDateTime;
|
|
begin
|
|
if (ValueIndex < 0) or (ValueIndex >= Length(FTimeStamp)) then
|
|
Result := 0.0 // null datetime
|
|
else
|
|
Result := FTimeStamp[ValueIndex];
|
|
end;
|
|
|
|
procedure TJvChartData.SetEndDateTime(const Value: TDateTime);
|
|
begin
|
|
FEndDateTime := Value;
|
|
end;
|
|
|
|
procedure TJvChartData.SetStartDateTime(const Value: TDateTime);
|
|
begin
|
|
FStartDateTime := Value;
|
|
end;
|
|
|
|
procedure TJvChartData.SetTimestamp(ValueIndex: Integer; AValue: TDateTime);
|
|
begin
|
|
if ValueIndex < 0 then
|
|
Exit;
|
|
if ValueIndex >= Length(FTimeStamp) then
|
|
SetLength(FTimeStamp, ValueIndex + 1);
|
|
FTimeStamp[ValueIndex] := AValue;
|
|
end;
|
|
|
|
procedure TJvChartData.Scroll;
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
var
|
|
I, J: Integer;
|
|
begin
|
|
if FValueCount < 2 then
|
|
begin
|
|
Clear;
|
|
Exit;
|
|
end;
|
|
{ ULTRA SLOW BUT NON-CRASHING Version }
|
|
for I := 0 to FValueCount - 1 do
|
|
begin
|
|
for J := 0 to Length(FData[I]) - 1 do
|
|
FData[I, J] := FData[I + 1, J];
|
|
SetTimestamp(I, GetTimestamp(I + 1));
|
|
end;
|
|
FTimeStamp[FValueCount - 1] := 0;
|
|
// Check we didn't Break the heap:
|
|
end;
|
|
{$ELSE}
|
|
var
|
|
T: Integer;
|
|
Idx: Integer;
|
|
begin
|
|
if FValueCount > FPenCount then
|
|
begin
|
|
Assert(FPenCount > 0);
|
|
Idx := FValueCount * FPenCount;
|
|
|
|
// Yeah, I wish:
|
|
//System.Move( {Source} FData[FPenCount], {Dest} FData[0], Idx-FPenCount);
|
|
for T := 0 to Idx - FPenCount do
|
|
FData[T] := FData[T + FPenCount];
|
|
|
|
for T := Idx - FPenCount to Idx - 1 do
|
|
begin
|
|
if T > Length(FData) then
|
|
Break;
|
|
FData[T] := FClearToValue;
|
|
end;
|
|
//Dec(FValueCount,FPenCount);
|
|
end
|
|
else
|
|
begin
|
|
FPenCount := 0;
|
|
Clear;
|
|
end;
|
|
end;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
|
|
procedure TJvChartData.PreGrow(Pen, ValueIndex: Integer);
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
var
|
|
T: Integer;
|
|
begin
|
|
if Length(FData) < ValueIndex then
|
|
SetLength(FData, ValueIndex);
|
|
for T := 0 to ValueIndex - 1 do
|
|
SetLength(FData[T], Pen);
|
|
FDataAlloc := ValueIndex;
|
|
end;
|
|
{$ELSE}
|
|
begin
|
|
if Pen > FPenCount then
|
|
FPenCount := Pen;
|
|
Grow(Pen, ValueIndex);
|
|
FDataAlloc := ValueIndex;
|
|
end;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
procedure TJvChartData.Grow(Pen, ValueIndex: Integer);
|
|
var
|
|
I, J, OldLength: Integer;
|
|
begin
|
|
if (Pen < 0) or (ValueIndex < 0) then
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEDataIndexCannotBeNegative);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEDataIndexCannotBeNegative);
|
|
{$ENDIF CLR}
|
|
if (Pen > CHART_SANITY_LIMIT) or (ValueIndex > CHART_SANITY_LIMIT) then
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEDataIndexTooLargeProbablyAnInternal);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEDataIndexTooLargeProbablyAnInternal);
|
|
{$ENDIF CLR}
|
|
|
|
if ValueIndex >= FDataAlloc then
|
|
begin
|
|
//--------------------------------------------------------
|
|
// Performance tweak: Uses more memory but makes JvChart
|
|
// much faster!
|
|
// We Double our allocation unit size
|
|
// until we start to get Really Huge, then grow in chunks!
|
|
//--------------------------------------------------------
|
|
if ValueIndex < 640000 then
|
|
FDataAlloc := ValueIndex * 2 // Double in size
|
|
else
|
|
FDataAlloc := ValueIndex + 64000;
|
|
|
|
OldLength := Length(FData);
|
|
SetLength(FData, FDataAlloc);
|
|
|
|
// new: If we set FClearToValue to NaN, special handling in growing arrays:
|
|
if JclMath.IsNaN(FClearToValue) then
|
|
for I := OldLength to FDataAlloc - 1 do
|
|
for J := 0 to Length(FData[I]) - 1 do
|
|
FData[I][J] := FClearToValue; // XXX Debug me!
|
|
|
|
end;
|
|
if Pen >= Length(FData[ValueIndex]) then
|
|
begin
|
|
OldLength := Length(FData[ValueIndex]);
|
|
SetLength(FData[ValueIndex], Pen + 1);
|
|
if JclMath.IsNaN(FClearToValue) then
|
|
begin
|
|
for I := OldLength to FDataAlloc - 1 do
|
|
begin
|
|
Assert(Length(FData) > ValueIndex);
|
|
if (Length(FData[ValueIndex]) < FDataAlloc) then
|
|
SetLength(FData[ValueIndex], FDataAlloc); // Safety code!
|
|
FData[ValueIndex][I] := FClearToValue; // XXX Debug me!
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{$ELSE}
|
|
procedure TJvChartData.Grow(Pen, ValueIndex: Integer);
|
|
var
|
|
N, Idx: Integer;
|
|
OldLen: Integer;
|
|
begin
|
|
Assert(Assigned(Self));
|
|
Assert(FPenCount > 0);
|
|
Idx := (ValueIndex + 1) * FPenCount;
|
|
OldLen := Length(FData);
|
|
if Idx >= OldLen then
|
|
begin
|
|
Idx := Idx + 1024; // Add 1024 floats (8k) headroom.
|
|
SetLength(FData, Idx + 1);
|
|
for N := OldLen to Idx do
|
|
FData[N] := FClearToValue;
|
|
end;
|
|
end;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
|
|
function TJvChartData.DebugStr(ValueIndex: Integer): string; // dump all pens for particular valueindex, as string.
|
|
var
|
|
S: string;
|
|
I: Integer;
|
|
LValue: Double;
|
|
begin
|
|
if (ValueIndex < 0) or (ValueIndex >= FDataAlloc) then
|
|
Exit;
|
|
|
|
if Timestamp[ValueIndex] > 0.0 then
|
|
S := FormatDateTime('hh:nn:ss ', Timestamp[ValueIndex]);
|
|
for I := 0 to FPenCount - 1 do
|
|
begin
|
|
LValue := GetValue(I, ValueIndex);
|
|
if JclMath.IsNaN(LValue) then
|
|
S := S + '-'
|
|
else
|
|
S := S + Format('%5.2f', [LValue]);
|
|
|
|
if I < FPenCount - 1 then
|
|
S := S + ', '
|
|
end;
|
|
Result := S;
|
|
end;
|
|
|
|
procedure TJvChartData.Clear; // Resets FValuesCount/FPenCount to zero. Zeroes everything too, just for good luck.
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
var
|
|
I, J: Integer;
|
|
begin
|
|
for I := 0 to FDataAlloc - 1 do
|
|
for J := 0 to Length(FData[I]) - 1 do
|
|
FData[I, J] := FClearToValue;
|
|
FValueCount := 0;
|
|
end;
|
|
{$ELSE}
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to Length(FData) - 1 do
|
|
FData[I] := FClearToValue;
|
|
end;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
|
|
procedure TJvChartData.ClearPenValues; // Clears all pen values to NaN but does not reset pen definitions etc.
|
|
{$IFDEF TJVCHART_ARRAY_OF_ARRAY}
|
|
var
|
|
I, J: Integer;
|
|
begin
|
|
for I := 0 to FDataAlloc - 1 do
|
|
for J := 0 to Length(FData[I]) - 1 do
|
|
FData[I, J] := ClearToValue; // 0.0;
|
|
end;
|
|
{$ELSE}
|
|
begin
|
|
Clear;
|
|
end;
|
|
{$ENDIF TJVCHART_ARRAY_OF_ARRAY}
|
|
|
|
//=== { TJvChartYAxisOptions } ===============================================
|
|
|
|
constructor TJvChartYAxisOptions.Create(Owner: TJvChartOptions);
|
|
begin
|
|
inherited Create;
|
|
FOwner := Owner;
|
|
|
|
FMarkerValueDecimals := -1; // -1 = default (automatic decimals)
|
|
|
|
FYLegends := TStringList.Create;
|
|
FMaxYDivisions := 20;
|
|
FMinYDivisions := 5;
|
|
FYDivisions := 10;
|
|
FDefaultYLegends := JvDefaultYLegends;
|
|
end;
|
|
|
|
destructor TJvChartYAxisOptions.Destroy;
|
|
begin
|
|
FYLegends.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvChartYAxisOptions.Clear;
|
|
begin
|
|
YDivisions := DefaultYLegends;
|
|
YLegends.Clear;
|
|
Normalize;
|
|
end;
|
|
|
|
procedure TJvChartYAxisOptions.Normalize;
|
|
var
|
|
// CheckYDivisions: Integer;
|
|
VC: Integer;
|
|
begin
|
|
if FYMax - FYMin < 0.00001 then // make sure that there is some difference here!
|
|
FYMax := FYMin + 10;
|
|
|
|
if (DefaultYLegends > 0) and (YDivisions = 0) then
|
|
YDivisions := DefaultYLegends;
|
|
|
|
// DON'T KNOW WHY WE NEEDED THIS. REMOVED IT.
|
|
(*
|
|
if (YGap>0.0) then
|
|
begin
|
|
CheckYDivisions := Round((YMax + (YGap - 1)) / YGap);
|
|
if CheckYDivisions<>YDivisions then
|
|
YDivisions :=CheckYDivisions;
|
|
end;*)
|
|
|
|
VC := YDivisions;
|
|
if VC < 1 then
|
|
VC := 1;
|
|
FYGap := (YMax - YMin) / VC;
|
|
FYGap1 := ((YMax - YMin) + 1) / VC;
|
|
|
|
YPixelGap := (FOwner.YEnd - 1) / VC; // Vertical Pixels Per Value Division counter.
|
|
|
|
(*CheckYDivisions := Round(((YMax-YMin) + (YGap - 1)) / YGap);
|
|
if CheckYDivisions <> YDivisions then
|
|
YDivisions := CheckYDivisions; *)
|
|
|
|
//---------------------------------------------------------------------
|
|
// Here's the normalization section:
|
|
// !!!The 10 and 20 here should be properties settable by the user!!!
|
|
//---------------------------------------------------------------------
|
|
if YDivisions < MinYDivisions then
|
|
begin
|
|
YDivisions := MinYDivisions;
|
|
FYGap := (YMax - YMin) / YDivisions;
|
|
end
|
|
else
|
|
if YDivisions > MaxYDivisions then
|
|
begin
|
|
YDivisions := MaxYDivisions;
|
|
FYGap := (YMax - YMin) / YDivisions;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChartYAxisOptions.SetYMin(NewYMin: Double);
|
|
begin
|
|
if JclMath.IsNaN(NewYMin) then
|
|
Exit;
|
|
|
|
try
|
|
if NewYMin = FYMin then
|
|
Exit;
|
|
except
|
|
{$IFDEF DEBUGINFO_ON}
|
|
OutputDebugString('TJvChartYAxisOptions.SetYMin-WTF?');
|
|
{$ENDIF DEBUGINFO_ON}
|
|
Exit;
|
|
end;
|
|
|
|
FYMin := NewYMin;
|
|
|
|
if not Assigned(FOwner) then
|
|
Exit;
|
|
if not Assigned(FOwner.FOwner) then
|
|
Exit;
|
|
if csLoading in FOwner.FOwner.ComponentState then
|
|
Exit;
|
|
|
|
// Rework other values around new YMin:
|
|
Normalize;
|
|
FOwner.NotifyOptionsChange;
|
|
{NEW: Auto-Regenerate Y Axis Labels}
|
|
if Assigned(FYLegends) then
|
|
if FYLegends.Count > 0 then
|
|
begin
|
|
FYLegends.Clear;
|
|
FOwner.FOwner.PrimaryYAxisLabels;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChartYAxisOptions.SetYMax(NewYMax: Double);
|
|
begin
|
|
if JclMath.IsNaN(NewYMax) then
|
|
Exit;
|
|
|
|
if NewYMax = FYMax then
|
|
Exit;
|
|
|
|
FYMax := NewYMax;
|
|
|
|
if not Assigned(FOwner) then
|
|
Exit;
|
|
if not Assigned(FOwner.FOwner) then
|
|
Exit;
|
|
if csLoading in FOwner.FOwner.ComponentState then
|
|
Exit;
|
|
|
|
// Rework other values around new YMax:
|
|
Normalize;
|
|
FOwner.NotifyOptionsChange;
|
|
|
|
{NEW: Auto-Regenerate Y Axis Labels}
|
|
if Assigned(FYLegends) then
|
|
if FYLegends.Count > 0 then
|
|
begin
|
|
FYLegends.Clear;
|
|
FOwner.FOwner.PrimaryYAxisLabels;
|
|
end;
|
|
end;
|
|
|
|
(*procedure TJvChartYAxisOptions.SetYGap(newYgap: Double);
|
|
begin
|
|
if (FYGap < 5.0) and (YMax>100) then
|
|
begin
|
|
OutputDebugString('Bug');
|
|
end;
|
|
|
|
FYGap := newYGap;
|
|
// TODO: Fire event, and cause a refresh, recalculate other
|
|
// dependant fields that are calculated from the YGap.
|
|
FOwner.NotifyOptionsChange; // Fire event before we auto-format graph. Allows some customization to occur here.
|
|
end;
|
|
*)
|
|
|
|
function TJvChartYAxisOptions.GetYLegends: TStrings;
|
|
begin
|
|
Result := TStrings(FYLegends);
|
|
end;
|
|
|
|
procedure TJvChartYAxisOptions.SetYLegends(Value: TStrings);
|
|
begin
|
|
FYLegends.Assign(Value);
|
|
if Assigned(FOwner) then
|
|
FOwner.NotifyOptionsChange; // Fire event before we auto-format graph. Allows some customization to occur here.
|
|
end;
|
|
|
|
procedure TJvChartYAxisOptions.SetYDivisions(AValue: Integer);
|
|
begin
|
|
FYDivisions := AValue;
|
|
|
|
if not Assigned(FOwner) then
|
|
Exit;
|
|
if not Assigned(FOwner.FOwner) then
|
|
Exit;
|
|
if csLoading in FOwner.FOwner.ComponentState then
|
|
Exit;
|
|
|
|
// Rework other values around new YMax:
|
|
Normalize;
|
|
FOwner.NotifyOptionsChange;
|
|
end;
|
|
|
|
//=== { TJvChartOptions } ====================================================
|
|
|
|
constructor TJvChartOptions.Create(Owner: TJvChart);
|
|
begin
|
|
inherited Create;
|
|
FOwner := Owner;
|
|
|
|
FAutoUpdateGraph := True;
|
|
|
|
FPrimaryYAxis := TJvChartYAxisOptions.Create(Self);
|
|
FSecondaryYAxis := TJvChartYAxisOptions.Create(Self);
|
|
|
|
FXAxisDivisionMarkers := True; //default property.
|
|
FYAxisDivisionMarkers := True; //default property.
|
|
|
|
SetLength(FPenColors, 12);
|
|
FPenColors[0] := clLime;
|
|
FPenColors[1] := clRed;
|
|
FPenColors[2] := clBlue;
|
|
FPenColors[3] := clYellow;
|
|
FPenColors[4] := clMaroon;
|
|
FPenColors[5] := clGreen;
|
|
FPenColors[6] := clOlive;
|
|
FPenColors[7] := clNavy;
|
|
FPenColors[8] := clPurple;
|
|
FPenColors[9] := clTeal;
|
|
FPenColors[10] := clFuchsia;
|
|
FPenColors[11] := clAqua;
|
|
|
|
FChartKind := ckChartLine;
|
|
|
|
FPenCount := 1;
|
|
|
|
FLegend := clChartLegendNone; //default Legend is None.
|
|
|
|
// Create TStringList property objects
|
|
FXLegends := TStringList.Create;
|
|
FPenLegends := TStringList.Create;
|
|
FPenUnit := TStringList.Create;
|
|
// dynamic array setup
|
|
SetLength(FAverageValue, DEFAULT_VALUE_COUNT);
|
|
|
|
// Defaults for Graph Options:
|
|
|
|
FMarkerSize := JvChartDefaultMarkerSize;
|
|
FXStartOffset := 45; {DEFAULT}
|
|
FYStartOffset := 10;
|
|
FTitle := '';
|
|
// FXAxisHeader := 'X';
|
|
// FYAxisHeader := 'Y';
|
|
|
|
FPaperColor := clWhite;
|
|
FAxisLineColor := clBlack;
|
|
FAverageLineColor := JvDefaultAvgLineColor;
|
|
FDivisionLineColor := JvDefaultDivisionLineColor; // NEW!
|
|
FShadowColor := JvDefaultShadowColor; //NEW!
|
|
|
|
FHeaderFont := TFont.Create;
|
|
FLegendFont := TFont.Create;
|
|
FAxisFont := TFont.Create;
|
|
|
|
//FShowLegend := True;
|
|
FMouseEdit := True;
|
|
FMouseInfo := True;
|
|
FLegendWidth := 150;
|
|
FPenLineWidth := 1;
|
|
FAxisLineWidth := 3;
|
|
|
|
FXValueCount := 10;
|
|
|
|
FXAxisLegendSkipBy := 1;
|
|
FXLegendHoriz := 0;
|
|
|
|
FHintColor := JvDefaultHintColor;
|
|
end;
|
|
|
|
destructor TJvChartOptions.Destroy;
|
|
begin
|
|
FreeAndNil(FPrimaryYAxis); //memory leak fix SEPT 21, 2004.WAP.
|
|
FreeAndNil(FSecondaryYAxis); //memory leak fix SEPT 21, 2004. WAP.
|
|
|
|
FreeAndNil(FXLegends);
|
|
FreeAndNil(FPenLegends);
|
|
FreeAndNil(FPenUnit);
|
|
|
|
FreeAndNil(FHeaderFont);
|
|
FreeAndNil(FLegendFont);
|
|
FreeAndNil(FAxisFont);
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvChartOptions.NotifyOptionsChange;
|
|
begin
|
|
if Assigned(FOwner) then
|
|
FOwner.NotifyOptionsChange;
|
|
end;
|
|
|
|
// Each pen can be associated with either the primary or secondary axis,
|
|
// this function decides which axis to return depending on the pen configuration:
|
|
|
|
function TJvChartOptions.GetPenAxis(Index: Integer): TJvChartYAxisOptions;
|
|
begin
|
|
if (Index < 0) or (Index >= Length(FPenSecondaryAxisFlag)) then
|
|
Result := FPrimaryYAxis // default
|
|
else
|
|
if FPenSecondaryAxisFlag[Index] then
|
|
Result := FSecondaryYAxis // alternate!
|
|
else
|
|
Result := FPrimaryYAxis; // default
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetChartKind(AKind: TJvChartKind);
|
|
begin
|
|
if AKind <> FChartKind then
|
|
FChartKind := AKind;
|
|
end;
|
|
|
|
function TJvChartOptions.GetPenMarkerKind(Index: Integer): TJvChartPenMarkerKind;
|
|
begin
|
|
if (Index >= 0) and (Index < Length(FPenMarkerKind)) then
|
|
Result := FPenMarkerKind[Index]
|
|
else
|
|
Result := pmkNone;
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetPenMarkerKind(Index: Integer; AMarkKind: TJvChartPenMarkerKind);
|
|
begin
|
|
if Index >= 0 then
|
|
begin
|
|
if Index >= Length(FPenMarkerKind) then
|
|
SetLength(FPenMarkerKind, Index + 1);
|
|
FPenMarkerKind[Index] := AMarkKind;
|
|
end;
|
|
end;
|
|
|
|
function TJvChartOptions.GetPenColor(Index: Integer): TColor;
|
|
begin
|
|
// Don't check for out of range values, since we use that on purpose in this
|
|
// function. Okay, ugly, but it works. -WP.
|
|
case Index of
|
|
jvChartAverageLineColorIndex:
|
|
Result := FAverageLineColor;
|
|
jvChartDivisionLineColorIndex: // horizontal and vertical division line color
|
|
Result := FDivisionLineColor;
|
|
jvChartShadowColorIndex: // legend shadow (light gray)
|
|
Result := FShadowColor;
|
|
jvChartAxisColorIndex:
|
|
Result := FAxisLineColor; // get property.
|
|
jvChartHintColorIndex:
|
|
Result := FHintColor; // Get property.
|
|
jvChartPaperColorIndex:
|
|
Result := FPaperColor; // Get property.
|
|
else
|
|
if Index < jvChartAverageLineColorIndex then
|
|
Result := clBtnFace
|
|
else
|
|
if Index >= 0 then
|
|
Result := FPenColors[Index]
|
|
else
|
|
Result := clNone; // I hope clNone is a good unknown value (ahuser). {{Good enough. -WP.}}
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetPenColor(Index: Integer; AColor: TColor);
|
|
begin
|
|
if (Index < 0) or (Index >= MAX_PEN) then
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEChartOptionsPenCountPenCountOutOf);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEChartOptionsPenCountPenCountOutOf);
|
|
{$ENDIF CLR}
|
|
|
|
if Index >= Length(FPenColors) then
|
|
SetLength(FPenColors, Index + 1);
|
|
FPenColors[Index] := AColor;
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetPenStyle(Index: Integer; APenStyle: TPenStyle);
|
|
begin
|
|
if (Index < 0) or (Index >= MAX_PEN) then
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEChartOptionsPenCountPenCountOutOf);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEChartOptionsPenCountPenCountOutOf);
|
|
{$ENDIF CLR}
|
|
|
|
if Index >= Length(FPenStyles) then
|
|
SetLength(FPenStyles, Index + 1);
|
|
FPenStyles[Index] := APenStyle;
|
|
end;
|
|
|
|
function TJvChartOptions.GetPenStyle(Index: Integer): TPenStyle;
|
|
begin
|
|
if (Index >= 0) and (Index < Length(FPenStyles)) then
|
|
Result := FPenStyles[Index]
|
|
else
|
|
Result := psSolid;
|
|
end;
|
|
|
|
function TJvChartOptions.GetAverageValue(Index: Integer): Double;
|
|
begin
|
|
if Index < 0 then
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEGetAverageValueIndexNegative);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEGetAverageValueIndexNegative);
|
|
{$ENDIF CLR}
|
|
if Index >= Length(FAverageValue) then
|
|
Result := 0.0
|
|
else
|
|
Result := FAverageValue[Index];
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetAverageValue(Index: Integer; AValue: Double);
|
|
begin
|
|
if Index < 0 then
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsESetAverageValueIndexNegative);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsESetAverageValueIndexNegative);
|
|
{$ENDIF CLR}
|
|
if Index >= Length(FAverageValue) then
|
|
SetLength(FAverageValue, Index + 1);
|
|
FAverageValue[Index] := AValue;
|
|
end;
|
|
|
|
function TJvChartOptions.GetPenSecondaryAxisFlag(Index: Integer): Boolean;
|
|
begin
|
|
if (Index < 0) or (Index >= Length(FPenSecondaryAxisFlag)) then
|
|
Result := False
|
|
else
|
|
Result := FPenSecondaryAxisFlag[Index];
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetPenSecondaryAxisFlag(Index: Integer; NewValue: Boolean);
|
|
begin
|
|
if (Index < 0) or (Index >= MAX_PEN) then
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEChartOptionsPenCountPenCountOutOf);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEChartOptionsPenCountPenCountOutOf);
|
|
{$ENDIF CLR}
|
|
if Index >= Length(FPenSecondaryAxisFlag) then
|
|
SetLength(FPenSecondaryAxisFlag, Index + 1);
|
|
FPenSecondaryAxisFlag[Index] := NewValue;
|
|
end;
|
|
|
|
function TJvChartOptions.GetPenValueLabels(Index: Integer): Boolean;
|
|
begin
|
|
if (Index < 0) or (Index >= Length(FPenValueLabels)) then
|
|
Result := False
|
|
else
|
|
Result := FPenValueLabels[Index];
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetPenValueLabels(Index: Integer; NewValue: Boolean);
|
|
begin
|
|
if (Index < 0) or (Index >= MAX_PEN) then
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEChartOptionsPenCountPenCountOutOf);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEChartOptionsPenCountPenCountOutOf);
|
|
{$ENDIF CLR}
|
|
|
|
if Index >= Length(FPenValueLabels) then
|
|
SetLength(FPenValueLabels, Index + 1);
|
|
FPenValueLabels[Index] := NewValue;
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetPenCount(Count: Integer);
|
|
begin
|
|
if (Count < 0) or (Count >= MAX_PEN) then
|
|
{$IFDEF CLR}
|
|
raise ERangeError.Create(RsEChartOptionsPenCountPenCountOutOf);
|
|
{$ELSE}
|
|
raise ERangeError.CreateRes(@RsEChartOptionsPenCountPenCountOutOf);
|
|
{$ENDIF CLR}
|
|
FPenCount := Count;
|
|
SetLength(FPenSecondaryAxisFlag, FPenCount + 1);
|
|
// notify data object:
|
|
NotifyOptionsChange;
|
|
end;
|
|
|
|
function TJvChartOptions.GetPenLegends: TStrings;
|
|
begin
|
|
Result := TStrings(FPenLegends);
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetPenLegends(Value: TStrings);
|
|
begin
|
|
FPenLegends.Assign(Value);
|
|
end;
|
|
|
|
function TJvChartOptions.GetPenUnit: TStrings;
|
|
begin
|
|
Result := TStrings(FPenUnit);
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetPenUnit(Value: TStrings);
|
|
begin
|
|
FPenUnit.Assign(Value);
|
|
end;
|
|
|
|
function TJvChartOptions.GetXLegends: TStrings;
|
|
begin
|
|
Result := TStrings(FXLegends);
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetXAxisDateTimeDivision(const Value: Double);
|
|
begin
|
|
FXAxisDateTimeDivision := Value;
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetXLegends(Value: TStrings);
|
|
begin
|
|
FXLegends.Assign(Value);
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetHeaderFont(AFont: TFont);
|
|
begin
|
|
FHeaderFont.Assign(AFont);
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetLegendFont(AFont: TFont);
|
|
begin
|
|
FLegendFont.Assign(AFont);
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetAxisFont(AFont: TFont);
|
|
begin
|
|
FAxisFont.Assign(AFont);
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetPrimaryYAxis(AssignFrom: TJvChartYAxisOptions);
|
|
begin
|
|
FPrimaryYAxis.Assign(AssignFrom);
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetSecondaryYAxis(AssignFrom: TJvChartYAxisOptions);
|
|
begin
|
|
FSecondaryYAxis.Assign(AssignFrom);
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetPaperColor(AColor: TColor);
|
|
begin
|
|
if AColor <> FPaperColor then
|
|
begin
|
|
FPaperColor := AColor;
|
|
if Assigned(FOwner) then
|
|
FOwner.Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChartOptions.SetXStartOffset(Offset: Integer);
|
|
begin
|
|
//if not PrintInSession then
|
|
// if (Offset < 10) or (Offset > (FOwner.Width div 2)) then
|
|
// raise ERangeError.CreateRes(@RsEChartOptionsXStartOffsetValueOutO);
|
|
FXStartOffset := Offset;
|
|
end;
|
|
|
|
//=== { TJvChart } ===========================================================
|
|
|
|
{ GRAPH }
|
|
{**************************************************************************}
|
|
{ call this function : NEVER! }
|
|
{**************************************************************************}
|
|
|
|
constructor TJvChart.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner); {by TImage...}
|
|
|
|
ControlStyle := ControlStyle + [csOpaque];
|
|
// XXX FLICKER REDUCTION: Set ControlStyle properly. -WP. APRIL 2004.
|
|
|
|
FPicture := TPicture.Create;
|
|
|
|
FCursorPosition := -1; // Invisible until CursorPosition is set >=0 to make it visible.
|
|
|
|
FMouseDownHintStrs := TStringList.Create;
|
|
|
|
{ logical font used for rotating text to show vertical labels }
|
|
|
|
FData := TJvChartData.Create;
|
|
FAverageData := TJvChartData.Create;
|
|
|
|
FFloatingMarker := TObjectList.Create; // NEW: collection of TJvChartFloatingMarker objects.
|
|
FFloatingMarker.OwnsObjects := True;
|
|
|
|
FHorizontalBars := TObjectList.Create; // NEW: collection of TJvChartFloatingMarker objects.
|
|
FHorizontalBars.OwnsObjects := True;
|
|
|
|
FVerticalBars := TObjectList.Create; // NEW: collection of TJvChartFloatingMarker objects.
|
|
FVerticalBars.OwnsObjects := True;
|
|
|
|
FOptions := TJvChartOptions.Create(Self);
|
|
CalcYEnd;
|
|
|
|
PrintInSession := False;
|
|
|
|
FOldYGap := 1;
|
|
FOldYOrigin := 0;
|
|
FStartDrag := False;
|
|
FMouseLegend := False;
|
|
FContainsNegative := False;
|
|
FMouseValue := 0;
|
|
FMousePen := 0;
|
|
|
|
{Set default values for component fields...}
|
|
|
|
if csDesigning in ComponentState then
|
|
begin
|
|
// default height and width
|
|
if not Assigned(Parent) then
|
|
begin
|
|
Width := 300;
|
|
Height := 300;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{**************************************************************************}
|
|
{ call this function : NEVER! }
|
|
{**************************************************************************}
|
|
|
|
destructor TJvChart.Destroy;
|
|
begin
|
|
{Add code for destroying my own data...here}
|
|
FBitmap.Free;
|
|
{$IFDEF VCL}
|
|
if Ord(FYFontHandle) <> 0 then
|
|
DeleteObject(FYFontHandle); // vertical font object
|
|
{$ENDIF VCL}
|
|
FreeAndNil(FYFont);
|
|
|
|
FreeAndNil(FPicture);
|
|
FreeAndNil(FAverageData);
|
|
FreeAndNil(FOptions);
|
|
FreeAndNil(FData);
|
|
|
|
FreeAndNil(FFloatingMarker); // Destroy collection of TJvChartFloatingMarker objects. Destroys contained objects also.
|
|
FreeAndNil(FHorizontalBars); // NEW 2007
|
|
FreeAndNil(FVerticalBars); // NEW 2007
|
|
|
|
FreeAndNil(FMouseDownHintStrs); //new.
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{Paint helper}
|
|
|
|
function TJvChart.DestRect: TRect;
|
|
var
|
|
W, H {, cw, ch}: Integer; // not used (ahuser)
|
|
// xyaspect: Double; // not used (ahuser)
|
|
begin
|
|
W := Picture.Width;
|
|
H := Picture.Height;
|
|
(* cw := ClientWidth;
|
|
ch := ClientHeight;
|
|
if Stretch or (Proportional and ((W > cw) or (H > ch))) then
|
|
begin
|
|
if Proportional and (W > 0) and (H > 0) then
|
|
begin
|
|
xyaspect := W / H;
|
|
if W > H then
|
|
begin
|
|
W := cw;
|
|
H := Trunc(cw / xyaspect);
|
|
if H > ch then // woops, too big
|
|
begin
|
|
H := ch;
|
|
W := Trunc(ch * xyaspect);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
H := ch;
|
|
W := Trunc(ch * xyaspect);
|
|
if W > cw then // woops, too big
|
|
begin
|
|
W := cw;
|
|
H := Trunc(cw / xyaspect);
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
W := cw;
|
|
H := ch;
|
|
end;
|
|
end;
|
|
*)
|
|
with Result do
|
|
begin
|
|
Left := 0;
|
|
Top := 0;
|
|
Right := W;
|
|
Bottom := H;
|
|
end;
|
|
|
|
(* if Center then
|
|
OffsetRect(Result, (cw - W) div 2, (ch - H) div 2); *)
|
|
end;
|
|
|
|
procedure TJvChart.Loaded;
|
|
begin
|
|
inherited Loaded;
|
|
ResizeChartCanvas;
|
|
end;
|
|
|
|
procedure TJvChart.Resize;
|
|
begin
|
|
inherited Resize;
|
|
ResizeChartCanvas;
|
|
// Invalidate already happens in ResizeChartCanvas.
|
|
end;
|
|
|
|
{ PAINT }
|
|
|
|
procedure TJvChart.DesignModePaint;
|
|
var
|
|
DesignStr: string;
|
|
TW, TH: Integer;
|
|
LCanvas: TCanvas;
|
|
begin
|
|
LCanvas := GetChartCanvas;
|
|
|
|
LCanvas.Brush.Color := Options.PaperColor;
|
|
LCanvas.Rectangle(0, 0, Width, Height);
|
|
|
|
DesignStr := ClassName + RsChartDesigntimeLabel;
|
|
|
|
if Options.PrimaryYAxis.YMin >= Options.PrimaryYAxis.YMax then
|
|
begin
|
|
if Options.PrimaryYAxis.YMax > 0 then
|
|
Options.PrimaryYAxis.YMin := 0.0;
|
|
end;
|
|
|
|
if (Abs(Options.PrimaryYAxis.YMax) < 0.000001) and (Abs(Options.PrimaryYAxis.YMin) < 0.000001) then
|
|
Options.PrimaryYAxis.YMax := 10.0; // Reasonable non-zero default, so that charting works!
|
|
|
|
Options.PrimaryYAxis.Normalize;
|
|
Options.SecondaryYAxis.Normalize;
|
|
GraphSetup;
|
|
|
|
DrawGradient;
|
|
DisplayBars;
|
|
|
|
PrimaryYAxisLabels;
|
|
GraphXAxis;
|
|
GraphXAxisDivisionMarkers;
|
|
GraphYAxis;
|
|
GraphYAxisDivisionMarkers;
|
|
|
|
{ designtime component label }
|
|
TW := LCanvas.TextWidth(DesignStr);
|
|
TH := LCanvas.TextHeight(DesignStr);
|
|
|
|
LCanvas.Brush.Color := Options.PaperColor;
|
|
LCanvas.Pen.Color := Color;
|
|
|
|
//ACanvas.Pen.Style := psDot;
|
|
//ACanvas.Rectangle( (width div 2) - (TW div 2), (height div 2) - (TH div 2), TW, TH);
|
|
if (TW < Width) and (TH < Height) then
|
|
LCanvas.TextOut((Width div 2) - (TW div 2), (Height div 2) - (TH div 2), DesignStr);
|
|
end;
|
|
|
|
procedure TJvChart.Paint; { based on TImage.Paint }
|
|
begin
|
|
if csDesigning in ComponentState then
|
|
begin
|
|
DesignModePaint;
|
|
Exit;
|
|
end;
|
|
|
|
if Options.AutoUpdateGraph and not FAutoPlotDone then
|
|
begin
|
|
FAutoPlotDone := True;
|
|
PlotGraph; // Makes sure something is visible in the TPicture.
|
|
end;
|
|
|
|
Assert(Assigned(FPicture));
|
|
//inherited ACanvas.Lock;
|
|
inherited Canvas.StretchDraw(DestRect, Picture.Graphic);
|
|
|
|
// New: Draw custom moveable markers on TOP of base data pen layer:
|
|
DrawFloatingMarkers;
|
|
|
|
// Draw cursor (vertical dotted line) if present:
|
|
if (FCursorPosition >= 0) and (FCursorPosition <= Options.XValueCount) then
|
|
PaintCursor;
|
|
|
|
// Allow end-user to custom paint on the Chart chanvas:
|
|
if Assigned(FOnPaint) then
|
|
FOnPaint(Self, Canvas);
|
|
end;
|
|
|
|
// Draw an oscilliscope-like cursor over the place where the current sample is in the chart.
|
|
// This is very handy when you want to associate your table, grid, or other data source,
|
|
// with the chart, and highlight one row in the chart.
|
|
|
|
procedure TJvChart.PaintCursor;
|
|
var
|
|
X: Integer;
|
|
XPixelGap: Double;
|
|
begin
|
|
with inherited Canvas do
|
|
begin
|
|
Pen.Color := Options.CursorColor;
|
|
Pen.Style := Options.CursorStyle;
|
|
|
|
XPixelGap := ((Options.XEnd - 2) - Options.XStartOffset) / (Options.XValueCount - 1);
|
|
|
|
X := Round(Options.XStartOffset + XPixelGap * FCursorPosition);
|
|
|
|
// Vertical line along X position:
|
|
MoveTo(X, Options.YStartOffset);
|
|
LineTo(X, FXAxisPosition - 1);
|
|
end;
|
|
end;
|
|
|
|
{device independent functions... no checking for printer / screen needed}
|
|
|
|
{**************************************************************************}
|
|
{ call this function : }
|
|
{ a) before setting totally new values to the graph }
|
|
{ b) note that any custom strings in the PrimaryYAxis.Legends or }
|
|
{ SecondaryYAxis.Legends are CLEARED by this function. }
|
|
{**************************************************************************}
|
|
|
|
procedure TJvChart.ResetGraphModule;
|
|
begin
|
|
Data.Clear;
|
|
|
|
FPlotGraphCalled := False;
|
|
FContainsNegative := False;
|
|
Options.Title := '';
|
|
Options.PenCount := 1;
|
|
Options.XValueCount := 0;
|
|
|
|
Options.PrimaryYAxis.Clear;
|
|
Options.SecondaryYAxis.Clear;
|
|
|
|
Options.XOrigin := 0;
|
|
Options.YOrigin := 0;
|
|
Options.XGap := 1;
|
|
|
|
Options.PenLegends.Clear;
|
|
|
|
(* for I := 0 to MAX_VALUES-1 do
|
|
begin
|
|
Options.AverageValue[I] := 0;
|
|
end; *)
|
|
|
|
Data.Clear;
|
|
AverageData.Clear;
|
|
|
|
Options.XLegends.Clear;
|
|
end;
|
|
|
|
procedure TJvChart.PrimaryYAxisLabels;
|
|
var
|
|
I, J: Integer;
|
|
YDivision: Double;
|
|
FormatStr, YDivisionStr, PrevYDivisionStr: string;
|
|
// left hand side, vertically ascending labels for scale of Y values.
|
|
Decimals: Integer;
|
|
Unique: Boolean;
|
|
begin
|
|
Decimals := Options.PrimaryYAxis.YLegendDecimalPlaces;
|
|
Unique := False; { Add Decimals until we get unique values }
|
|
while not Unique do
|
|
begin
|
|
Unique := True;
|
|
PrevYDivisionStr := '';
|
|
Options.PrimaryYAxis.YLegends.Clear;
|
|
FormatStr := '0.0';
|
|
for J := 2 to Decimals do
|
|
FormatStr := FormatStr + '0';
|
|
for I := 0 to Options.PrimaryYAxis.YDivisions do // NOTE! Don't make this YDivisions-1 That'd be bad! !!!!
|
|
begin
|
|
YDivision := Options.PrimaryYAxis.YMin + (I * Options.PrimaryYAxis.YGap);
|
|
if Decimals <= 0 then
|
|
YDivisionStr := IntToStr(Round(YDivision)) // Whole Numbers Only.
|
|
else
|
|
YDivisionStr := FormatFloat(FormatStr, YDivision); // Variable Decimals
|
|
if (PrevYDivisionStr = YDivisionStr) and (Decimals < 5) then
|
|
begin
|
|
Inc(Decimals);
|
|
Unique := False; // Force repeat
|
|
Break; // Exit for loop.
|
|
end;
|
|
Options.PrimaryYAxis.YLegends.Add(YDivisionStr);
|
|
PrevYDivisionStr := YDivisionStr;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Setup Graph Formatting Properties
|
|
|
|
*** AutoFormatGraph CONSIDERED HARMFUL. REMOVED. ***
|
|
This procedure does nothing helpful, and will be removed from CVS soon.
|
|
What it *does* do is wildly screw up plotting of graphs with negative
|
|
values in it.
|
|
-Wpostma.
|
|
}
|
|
|
|
(* XXXX BAD CODE. TO BE DELETED SOON. Wpostma.
|
|
procedure TJvChart.AutoFormatGraph;
|
|
var
|
|
V, NYMax, NYMin: Double;
|
|
// NPen: Longint;
|
|
I, J: Integer;
|
|
// calcYGap: Double; // not used (ahuser)
|
|
ATextWidth, SkipBy, MaxFit: Integer;
|
|
begin
|
|
// nMaxXValue := 0;
|
|
// NPen := 0;
|
|
Options.PrimaryYAxis.Normalize;
|
|
Options.SecondaryYAxis.Normalize;
|
|
|
|
{Set graph type according to component property}
|
|
|
|
if Options.PrimaryYAxis.YMax <= Options.PrimaryYAxis.YMin then
|
|
begin
|
|
{Analyse graph for max and min values...}
|
|
NYMax := Low(Integer);
|
|
NYMin := High(Integer);
|
|
for I := 0 to Data.ValueCount - 1 do
|
|
begin
|
|
for J := 0 to Options.PenCount - 1 do
|
|
begin
|
|
if Options.PenAxis[J] <> Options.PrimaryYAxis then
|
|
Continue; // XXX !!! AUTO SCALING ONLY ON PRIMARY AXIS, FOR NOW. !!!
|
|
|
|
V := FData.Value[J, I];
|
|
|
|
if JclMath.IsNaN(V) then
|
|
Continue;
|
|
if NYMin > V then
|
|
NYMin := V;
|
|
//if (I>nMaxXValue) and (FData.Value[J,I]<>0) then
|
|
//nMaxXValue := I;
|
|
//if (J>NPen) and (FData.Value[J,I]<>0) then
|
|
// NPen := J;
|
|
if NYMax < FData.Value[J, I] then
|
|
NYMax := FData.Value[J, I];
|
|
end;
|
|
if (NYMin > 0) and (Options.PrimaryYAxis.YMin = 0) then
|
|
NYMin := 0;
|
|
end;
|
|
// Round up YMax so it's got some zeros after it:
|
|
if NYMax > 5000 then
|
|
NYMax := Trunc(Trunc(NYMax + 499) / 500) * 500
|
|
else
|
|
if NYMax > 1000 then
|
|
NYMax := Trunc(Trunc(NYMax + 99) / 100) * 100
|
|
else
|
|
if NYMax > 10 then
|
|
NYMax := Trunc(Trunc(NYMax + 9) / 10) * 10;
|
|
|
|
// And now the really bad hack:
|
|
Options.PrimaryYAxis.SetYMax(0);
|
|
Options.PrimaryYAxis.SetYMax(NYMax);
|
|
end
|
|
else
|
|
begin
|
|
// !!!!!!!!!!!!! WARNING WARNING WARNING !!!!!!!!!!!!!!!!!!!!
|
|
// The following line has been commented out because it triggers
|
|
// a warning because NYMax is not used anywhere after the
|
|
// setting of its value
|
|
//NYMax := Options.PrimaryYAxis.YMax;
|
|
|
|
NYMin := Options.PrimaryYAxis.YMin;
|
|
end;
|
|
|
|
// And some negative handling crap.
|
|
FContainsNegative := False;
|
|
if NYMin < 0 then
|
|
begin
|
|
FContainsNegative := True;
|
|
|
|
// if Options.PrimaryYAxis.DefaultYLegends>0 then
|
|
// Options.PrimaryYAxis.Normalize
|
|
// else
|
|
// Options.PrimaryYAxis.YGap := 1;
|
|
|
|
// if Options.PrimaryYAxis.YGap <= 0 then {* XXX WORKAROUND A BUG. Better to have bad looking data than divide by zero exceptions. XXX *}
|
|
// Options.PrimaryYAxis.YGap := 0.00001;
|
|
|
|
Options.ChartKind := ckChartLine;
|
|
Options.YOrigin := Round(-NYMin / Options.PrimaryYAxis.YGap);
|
|
end;
|
|
|
|
if Options.PrimaryYAxis.YDivisions = 0 then
|
|
Options.PrimaryYAxis.YDivisions := 1;
|
|
|
|
//Options.PenCount := NPen;
|
|
if Options.XValueCount < Data.ValueCount then
|
|
Options.XValueCount := Data.ValueCount;
|
|
|
|
//XXX if Options.PrimaryYAxis.YDivisions < 3 then
|
|
// Options.PrimaryYAxis.YDivisions := 3; // some labels
|
|
|
|
// Primary Y Axis Labels. This version only supports 0,1,2 decimal places.
|
|
PrimaryYAxisLabels;
|
|
|
|
// XXX TODO: Draw secondary Y Axis labels, if enabled!
|
|
|
|
// if we put too many labels on the bottom X axis, they crowd or overlap,
|
|
// so this prevents that:
|
|
|
|
for I := 0 to Options.XLegends.Count - 1 do
|
|
begin
|
|
ATextWidth := ChartCanvas.TextWidth(Options.XLegends[I]) + 10;
|
|
|
|
if ATextWidth > Options.XLegendMaxTextWidth then
|
|
Options.XLegendMaxTextWidth := ATextWidth;
|
|
end;
|
|
if Options.XLegendMaxTextWidth < 20 then
|
|
Options.XLegendMaxTextWidth := 20;
|
|
|
|
MaxFit := ((Width - (Options.XStartOffset * 2)) div
|
|
(Options.XLegendMaxTextWidth + (Options.XLegendMaxTextWidth div 4)));
|
|
if MaxFit < 1 then
|
|
MaxFit := 1;
|
|
|
|
SkipBy := Data.ValueCount div MaxFit;
|
|
if SkipBy < 1 then
|
|
SkipBy := 1;
|
|
//if SkipBy > Options.XAxisLegendSkipBy then
|
|
Options.XAxisLegendSkipBy := SkipBy;
|
|
|
|
// Now do the graphing.
|
|
CountGraphAverage;
|
|
|
|
PlotGraph;
|
|
end;
|
|
XXX BAD CODE. READ WARNING ABOVE.
|
|
*)
|
|
|
|
procedure TJvChart.CountGraphAverage;
|
|
var
|
|
I, J: Integer;
|
|
begin
|
|
if Options.ChartKind = ckChartLine then
|
|
Exit; // no average needed.
|
|
|
|
for I := 0 to Data.ValueCount - 1 do
|
|
begin
|
|
Options.AverageValue[I] := 0;
|
|
for J := 0 to MAX_PEN - 1 do
|
|
Options.AverageValue[I] := Options.AverageValue[I] + FData.Value[J, I];
|
|
if Options.PenCount = 0 then
|
|
Options.AverageValue[I] := 0
|
|
else
|
|
Options.AverageValue[I] := Options.AverageValue[I] / Options.PenCount;
|
|
end;
|
|
end;
|
|
|
|
// These set up variables used for all the rest of the plotting functions.
|
|
|
|
procedure TJvChart.GraphSetup;
|
|
var
|
|
X1, X2, Y1, Y2, PYVC, VC: Integer;
|
|
ACanvas: TCanvas;
|
|
begin
|
|
ACanvas := GetChartCanvas;
|
|
|
|
ACanvas.Brush.Style := bsSolid;
|
|
if FData.ValueCount > 0 then
|
|
Options.XValueCount := FData.ValueCount;
|
|
|
|
{ Get X value count }
|
|
VC := Options.XValueCount;
|
|
if VC < 1 then
|
|
VC := 1;
|
|
|
|
{ Get Y value count. First normalize. }
|
|
Options.PrimaryYAxis.Normalize;
|
|
Options.SecondaryYAxis.Normalize;
|
|
PYVC := Options.PrimaryYAxis.YDivisions;
|
|
if PYVC < 1 then
|
|
PYVC := 1;
|
|
|
|
Options.XPixelGap := ((Options.XEnd - 1) - Options.XStartOffset) / VC;
|
|
|
|
FXOrigin := Options.XStartOffset + Options.XPixelGap * (Options.XOrigin);
|
|
FYOrigin := Options.YStartOffset + Round(Options.PrimaryYAxis.YPixelGap * PYVC);
|
|
|
|
ACanvas.Brush.Style := bsClear;
|
|
|
|
{ NEW: Box around entire chart area. }
|
|
X1 := Round(XOrigin);
|
|
X2 := Round(Options.XStartOffset + Options.XPixelGap * VC);
|
|
Y1 := Options.YStartOffset - 1;
|
|
Y2 := Round(YOrigin) + 1; // was YTempOrigin
|
|
|
|
if Y2 > Height then
|
|
begin
|
|
// I suspect that the value of YPixelGap is too large in some cases.
|
|
Options.PrimaryYAxis.Normalize;
|
|
//OutputDebugString( PChar('Y2 is bogus. PYVC='+IntToStr(PYVC)) );
|
|
end;
|
|
MyRectangle(ACanvas, X1, Y1, X2, Y2);
|
|
|
|
ACanvas.Brush.Style := bsSolid;
|
|
end;
|
|
|
|
// internal methods
|
|
|
|
procedure TJvChart.GraphYAxis;
|
|
var
|
|
ACanvas: TCanvas;
|
|
begin
|
|
ACanvas := GetChartCanvas;
|
|
ACanvas.Pen.Style := psSolid;
|
|
ACanvas.Pen.Color := Options.AxisLineColor;
|
|
ACanvas.MoveTo(Round(XOrigin), Options.YStartOffset);
|
|
MyAxisLineTo(ACanvas, Round(XOrigin),
|
|
Round((Options.YStartOffset - 1) +
|
|
Options.PrimaryYAxis.YPixelGap * (Options.PrimaryYAxis.YDivisions)));
|
|
end;
|
|
|
|
// internal methods
|
|
|
|
procedure TJvChart.GraphXAxis;
|
|
var
|
|
LCanvas: TCanvas;
|
|
begin
|
|
LCanvas := GetChartCanvas;
|
|
|
|
LCanvas.Pen.Style := psSolid;
|
|
LCanvas.Pen.Color := Options.AxisLineColor;
|
|
LCanvas.Pen.Width := Options.AxisLineWidth; // was missing. Added Feb 2005. -WPostma.
|
|
FXAxisPosition := Options.YStartOffset + Round(Options.PrimaryYAxis.YPixelGap * (Options.PrimaryYAxis.YDivisions));
|
|
|
|
{Draw X-axis}
|
|
LCanvas.MoveTo(Options.XStartOffset, FXAxisPosition);
|
|
MyAxisLineTo(LCanvas, Round(Options.XStartOffset + Options.XPixelGap * Options.XValueCount), FXAxisPosition);
|
|
end;
|
|
|
|
procedure TJvChart.GraphXAxisDivisionMarkers; // new.
|
|
var
|
|
I, X: Integer;
|
|
Lines: Integer;
|
|
LCanvas: TCanvas;
|
|
// these are used only in special XAxisDateTimeMode:
|
|
TimePerXValue: Double;
|
|
ElapsedTime: Double;
|
|
begin
|
|
if not Enabled then
|
|
Exit;
|
|
if Options.XValueCount <= 0 then // NOT VISIBLE WHEN NO VALUES TO SHOW. NEW 2007
|
|
Exit;
|
|
if Options.XStartOffset <= 0 then // NOT VISIBLE WHEN NO ROOM TO SHOW IT. NEW 2007
|
|
Exit;
|
|
|
|
LCanvas := GetChartCanvas;
|
|
|
|
if not Options.XAxisDivisionMarkers then
|
|
Exit;
|
|
if Options.XAxisValuesPerDivision <= 0 then
|
|
Exit;
|
|
|
|
//XAxisDateTimeMode: [NEW 2007]
|
|
// Make charts with XAxis divisions synchronized
|
|
// to some regular time division such as hourly periods.
|
|
//
|
|
// new mode! when looking at date/time charts
|
|
// it's useful to be able to force the divisions to be
|
|
// shown at hourly intervals, or if you're looking at a month of data
|
|
// perhaps you might want to plot a division marker at midnight
|
|
// or at weekly intervals.
|
|
//
|
|
if (Options.XAxisDateTimeMode) and
|
|
(Options.XAxisDateTimeDivision > 0.000000001) and
|
|
(FData.EndDateTime > FData.StartDateTime) then
|
|
begin
|
|
|
|
// How much time goes by in this chart? ( 1.0 = one day)
|
|
ElapsedTime := FData.EndDateTime - FData.StartDateTime;
|
|
|
|
// How far apart the bars are spaced is determined by
|
|
// XAxisDateTimeDivision.
|
|
// if we plot one day of values, and we want a marker every
|
|
// hour, we want XAxisDateTimeDivision=(1.0/24).
|
|
|
|
// Given the elapsed time in this chart, how many divisions
|
|
// should we be showing?
|
|
Options.FXAxisDateTimeLines := Round(ElapsedTime / Options.XAxisDateTimeDivision);
|
|
if (Options.FXAxisDateTimeLines < 0) or (Options.FXAxisDateTimeLines > 10000) then // sanity check!
|
|
Exit;
|
|
|
|
// this value is to help us figure out how much time goes by
|
|
// for each time we go from one X value to the next one.
|
|
TimePerXValue := ElapsedTime / Options.XValueCount;
|
|
|
|
// figure out how many divisions to move over for firstMarker
|
|
// given TimePerXValue (1.0=one day) and StartDateTime and
|
|
// XAxisDateTimeDivision.
|
|
Options.FXAxisDateTimeFirstMarker := 0;
|
|
// If XAxisDateTimeDivion=1.0, and TimePerXValue=0.25, then
|
|
// we want a division marker for every 4th value
|
|
Options.FXaxisDateTimeSkipBy := Round(Options.XAxisDateTimeDivision / TimePerXValue);
|
|
|
|
for I := 0 to Options.FXAxisDateTimeLines - 1 do
|
|
begin
|
|
X := Round(Options.XStartOffset + (Options.XPixelGap * I * Options.FXaxisDateTimeSkipBy)) +
|
|
Options.FXAxisDateTimeFirstMarker;
|
|
if X > Options.XEnd then
|
|
Break;
|
|
// don't draw dotted line right at X Axis.
|
|
if X <> Options.XStartOffset then
|
|
begin
|
|
LCanvas.Pen.Color := Options.GetPenColor(jvChartDivisionLineColorIndex);
|
|
MyDrawDotLine(LCanvas, X, Options.YStartOffset + 1, X, FXAxisPosition - 1);
|
|
end;
|
|
end;
|
|
|
|
// Note: datetime labels aren't drawn yet, they are drawn later,
|
|
// see local procedure XAxisDateTimeModeLabels2 inside
|
|
// GraphXAxisLegend, for the printing of the datetime labels!
|
|
|
|
Exit; // done!
|
|
end; // END OF NEW CODE IN 2007 FOR THIS METHOD. -WP-
|
|
|
|
Lines := (((Options.XValueCount + (Options.XAxisValuesPerDivision div 2)) div Options.XAxisValuesPerDivision)) - 1;
|
|
|
|
for I := 1 to Lines do
|
|
begin
|
|
X := Round(Options.XStartOffset + Options.XPixelGap * I * Options.XAxisValuesPerDivision);
|
|
LCanvas.Pen.Color := Options.GetPenColor(jvChartDivisionLineColorIndex);
|
|
MyDrawDotLine(LCanvas, X, Options.YStartOffset + 1, X, FXAxisPosition - 1);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChart.GraphYAxisDivisionMarkers;
|
|
var
|
|
I, Y: Integer;
|
|
LCanvas: TCanvas;
|
|
begin
|
|
Assert(Assigned(Self));
|
|
Assert(Assigned(Options));
|
|
Assert(Assigned(Options.PrimaryYAxis));
|
|
Assert(Options.PrimaryYAxis.YPixelGap > 0);
|
|
LCanvas := GetChartCanvas;
|
|
|
|
LCanvas.Font := Options.AxisFont;
|
|
|
|
for I := 0 to Options.PrimaryYAxis.YDivisions do
|
|
begin
|
|
Y := Round(YOrigin - (Options.PrimaryYAxis.YPixelGap * ((I) - Options.YOrigin)));
|
|
|
|
if I < Options.PrimaryYAxis.YLegends.Count then
|
|
MyRightTextOut(LCanvas, Round(XOrigin - 3), Y, Options.PrimaryYAxis.YLegends[I]);
|
|
|
|
Y := Round(YOrigin - (Options.PrimaryYAxis.YPixelGap * ((I) - Options.YOrigin)));
|
|
if (I > 0) and (I < (Options.PrimaryYAxis.YDivisions)) and Options.YAxisDivisionMarkers then
|
|
begin
|
|
LCanvas.Pen.Color := Options.GetPenColor(jvChartDivisionLineColorIndex);
|
|
MyDrawDotLine(LCanvas, Options.XStartOffset, Y,
|
|
Round(Options.XStartOffset + Options.XPixelGap * Options.XValueCount) - 1, Y);
|
|
end;
|
|
if I > 0 then
|
|
if Options.PrimaryYAxis.YPixelGap > 20 then
|
|
begin // more than 20 pixels per major division?
|
|
LCanvas.Pen.Color := Options.GetPenColor(jvChartAxisColorIndex);
|
|
|
|
Y := Round(Y + (Options.PrimaryYAxis.YPixelGap / 2));
|
|
MyDrawAxisMark(LCanvas, Options.XStartOffset, Y,
|
|
Options.XStartOffset - 4, // Tick at halfway between major marks.
|
|
Y);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChart.PlotMarker(ACanvas: TCanvas; MarkerKind: TJvChartPenMarkerKind; X, Y: Integer);
|
|
begin
|
|
case MarkerKind of
|
|
pmkDiamond:
|
|
PlotFilledDiamond(ACanvas, X, Y);
|
|
pmkCircle:
|
|
begin
|
|
ACanvas.Brush.Style := bsClear;
|
|
PlotCircle(ACanvas, X, Y);
|
|
ACanvas.Brush.Style := bsSolid;
|
|
end;
|
|
pmkSquare:
|
|
begin
|
|
ACanvas.Brush.Style := bsClear;
|
|
PlotSquare(ACanvas, X, Y);
|
|
ACanvas.Brush.Style := bsSolid;
|
|
end;
|
|
pmkCross:
|
|
PlotCross(ACanvas, X, Y);
|
|
end;
|
|
end;
|
|
|
|
{**************************************************************************}
|
|
{ call this function : }
|
|
{ a) you want to show the graph stored in memory }
|
|
{ b) you have changed single graph value (call AutoFormatGraph if all new)}
|
|
{ c) you have changed the settings of the graph and if you do not use }
|
|
{ FAutoUpdateGraph option }
|
|
{**************************************************************************}
|
|
|
|
procedure TJvChart.PlotGraph;
|
|
begin
|
|
Assert(Assigned(Options));
|
|
|
|
// Sanity check on YEnd/XEnd:
|
|
if (Options.YEnd <= 0) or (Options.XEnd <= 0) or
|
|
(Options.YEnd > Height) or (Options.XEnd > Width) then
|
|
begin
|
|
FInPlotGraph := True; // recursion blocker.
|
|
ResizeChartCanvas; // Recovery. This shouldn't happen.
|
|
FInPlotGraph := False;
|
|
end;
|
|
|
|
InternalPlotGraph;
|
|
Invalidate; // Force repaint.
|
|
end;
|
|
|
|
procedure TJvChart.InternalPlotGraph;
|
|
var
|
|
ACanvas: TCanvas;
|
|
nStackGap: Integer;
|
|
n100Sum: Double;
|
|
// nOldY: Longint;
|
|
YOldOrigin: Integer;
|
|
nMaxTextHeight: Integer;
|
|
// Rectangle plotting:
|
|
X, Y, X2, Y2: Integer;
|
|
|
|
{ Here be lots of local functions }
|
|
|
|
{ Draw symbol markers and text labels on a chart... }
|
|
|
|
procedure PlotGraphChartMarkers;
|
|
var
|
|
TW, TH, VC, I, J: Integer;
|
|
PenAxisOpt: TJvChartYAxisOptions;
|
|
V: Double;
|
|
MaxV, MinV: array of Double;
|
|
LineXPixelGap: Double;
|
|
LastX, LastY: Integer;
|
|
MinIndex, MaxIndex: array of Integer;
|
|
Decimals: Integer;
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
|
|
ACanvas.Brush.Color := Options.PaperColor;
|
|
ACanvas.Pen.Style := psSolid;
|
|
ACanvas.Pen.Color := Options.AxisLineColor;
|
|
ACanvas.Brush.Style := bsSolid;
|
|
|
|
VC := Options.XValueCount;
|
|
LastX := Round(XOrigin);
|
|
LastY := 0;
|
|
|
|
if VC < 2 then
|
|
VC := 2;
|
|
LineXPixelGap := ((Options.XEnd - 2) - Options.XStartOffset) / (VC - 1);
|
|
|
|
SetLength(MaxV, Options.PenCount);
|
|
SetLength(MinV, Options.PenCount);
|
|
SetLength(MinIndex, Options.PenCount);
|
|
SetLength(MaxIndex, Options.PenCount);
|
|
|
|
for I := 0 to Options.PenCount - 1 do
|
|
begin
|
|
if Options.PenMarkerKind[I] = pmkNone then
|
|
Continue;
|
|
PenAxisOpt := Options.PenAxis[I]; // Get whether this pen is plotted using the lefthand or righthand Y axis.
|
|
MaxV[I] := PenAxisOpt.YMin;
|
|
MinV[I] := PenAxisOpt.YMax;
|
|
MinIndex[I] := -1;
|
|
MaxIndex[I] := -1;
|
|
|
|
for J := 0 to Options.XValueCount - 1 do
|
|
begin
|
|
V := FData.Value[I, J];
|
|
if JclMath.IsNaN(V) then
|
|
Continue;
|
|
//MaxFlag := False;
|
|
//MinFlag := False;
|
|
if V > MaxV[I] then
|
|
begin
|
|
MaxV[I] := V;
|
|
MaxIndex[I] := J;
|
|
end;
|
|
|
|
if V < MinV[I] then
|
|
begin
|
|
MinV[I] := V;
|
|
MinIndex[I] := J;
|
|
end;
|
|
|
|
// Calculate Marker position:
|
|
X := Round(XOrigin + J * LineXPixelGap);
|
|
|
|
//old:Y := Round(YOrigin - ((V / PenAxisOpt.YGap1) * PenAxisOpt.YPixelGap));
|
|
Y := Round(YOrigin - (((V - PenAxisOpt.YMin) / PenAxisOpt.YGap) * PenAxisOpt.YPixelGap));
|
|
SetLineColor(ACanvas, I);
|
|
if Y < Options.YStartOffset then
|
|
Y := Options.YStartOffset; // constrain Y to stay on chart.
|
|
|
|
(*
|
|
if MinFlag or MaxFlag then // local min/max markers!
|
|
ACanvas.Pen.Width := 2
|
|
else
|
|
ACanvas.Pen.Width := 1;
|
|
*)
|
|
|
|
// Now plot the right kind of marker:
|
|
PlotMarker(ACanvas, Options.PenMarkerKind[I], X, Y);
|
|
end;
|
|
end;
|
|
|
|
{ Now plot labels After all the markers. Looks nicer than doing
|
|
it all together }
|
|
for I := 0 to Options.PenCount - 1 do
|
|
begin
|
|
if not Options.PenValueLabels[I] then
|
|
Continue;
|
|
PenAxisOpt := Options.PenAxis[I]; // Get whether this pen is plotted using the lefthand or righthand Y axis.
|
|
for J := 0 to Options.XValueCount - 1 do
|
|
begin
|
|
V := FData.Value[I, J];
|
|
if JclMath.IsNaN(V) then
|
|
Continue;
|
|
// Calculate Marker position:
|
|
X := Round(XOrigin + J * LineXPixelGap);
|
|
Y := Round(YOrigin - ((V / PenAxisOpt.YGap1) * PenAxisOpt.YPixelGap));
|
|
if Y < (Options.YStartOffset + 10) then
|
|
Y := (Options.YStartOffset + 10); // constrain Y to stay on chart.
|
|
|
|
// Format with fixed number of decimal places (avoid screen clutter)
|
|
Decimals := Options.PenAxis[I].MarkerValueDecimals;
|
|
if Decimals < 0 then // auto
|
|
if V < 100.0 then
|
|
Decimals := 1 // handy automatic percentage mode.
|
|
else
|
|
Decimals := 0;
|
|
Text := FloatToStrF(V, ffFixed, 16, Decimals);
|
|
|
|
if Options.PenUnit.Count >= I then
|
|
Text := Text + Options.PenUnit[I];
|
|
|
|
TW := ACanvas.TextWidth(Text);
|
|
TH := ACanvas.TextHeight(Text);
|
|
|
|
if Options.GetPenValueLabels(I) and
|
|
((X > (LastX + (TW div 2))) or // Show if it's not going to collide
|
|
((Abs(Y - LastY) > (TH * 2)) and
|
|
(X > LastX)) or
|
|
((J = MinIndex[I]) or (J = MaxIndex[I]))) then // Always show max/mins
|
|
begin
|
|
// TODO: EVENT FOR END-USER-CUSTOMIZED OR FORMATTED LABELS
|
|
//if Assigned(FOnGetValueLabel) then
|
|
// FOnGetValueLabel(Sender, {Pen}I, {Sample#}J, {Value}V, {var}Text );
|
|
if Length(Text) > 0 then
|
|
begin
|
|
Dec(Y, 2);
|
|
// nifty little bit to draw a box around min/max values.
|
|
if (J = MinIndex[I]) or (J = MaxIndex[I]) then
|
|
begin
|
|
ACanvas.Pen.Style := psClear; //was psDot
|
|
ACanvas.Brush.Color := Options.PaperColor; //was HintColor
|
|
MyPolygon(ACanvas, [Point(X - ((TW div 2) + 2), Y - (TH + Options.MarkerSize + 2)),
|
|
Point(X - ((TW div 2) + 2), Y - Options.MarkerSize),
|
|
Point(X + (TW div 2) + 2, Y - Options.MarkerSize),
|
|
Point(X + (TW div 2) + 2, Y - (TH + Options.MarkerSize + 2))]);
|
|
ACanvas.Pen.Style := psSolid;
|
|
end;
|
|
|
|
if Y >= Options.YStartOffset + 20 then
|
|
begin
|
|
ACanvas.Brush.Style := bsSolid;
|
|
MyCenterTextOut(ACanvas, X + 1, (Y - (Options.MarkerSize + TH)) - 1, Text);
|
|
ACanvas.Brush.Color := Options.PaperColor;
|
|
LastX := X + TW;
|
|
LastY := Y;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure PlotGraphStackedBar;
|
|
var
|
|
I, J: Integer;
|
|
begin
|
|
for J := 0 to Options.XValueCount - 1 do
|
|
begin
|
|
YOldOrigin := 0;
|
|
for I := 0 to Options.PenCount - 1 do
|
|
begin
|
|
if Options.PenStyle[I] <> psClear then
|
|
begin
|
|
if Options.XPixelGap < 3.0 then
|
|
ACanvas.Pen.Color := Options.PenColor[I]; // greek-out the borders
|
|
MyColorRectangle(ACanvas, I,
|
|
Round((XOrigin + J * Options.XPixelGap) + (Options.XPixelGap / 6)),
|
|
Round(YOrigin - YOldOrigin),
|
|
Round(XOrigin + (J + 1) * Options.XPixelGap - nStackGap),
|
|
Round((YOrigin - YOldOrigin) -
|
|
((FData.Value[I, J] / Options.PenAxis[I].YGap) * Options.PrimaryYAxis.YPixelGap)));
|
|
YOldOrigin := Round(YOldOrigin +
|
|
((FData.Value[I, J] / Options.PenAxis[I].YGap) * Options.PrimaryYAxis.YPixelGap));
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure PlotGraphBar;
|
|
var
|
|
I, J, N: Integer;
|
|
BarCount: Double;
|
|
V, BarGap: Double;
|
|
YTempOrigin: Integer;
|
|
|
|
function BarXPosition(Index: Integer): Integer;
|
|
begin
|
|
Result := Round(XOrigin + (Index * BarGap));
|
|
end;
|
|
|
|
begin
|
|
YTempOrigin := Options.YStartOffset + Round(Options.PrimaryYAxis.YPixelGap * (Options.PrimaryYAxis.YDivisions));
|
|
|
|
BarCount := Options.PenCount * Options.XValueCount;
|
|
BarGap := (((Options.XEnd - 1) - Options.XStartOffset) / BarCount);
|
|
|
|
for I := 0 to Options.PenCount - 1 do
|
|
begin
|
|
if Options.PenAxis[I].YGap = 0 then
|
|
Continue; // Can't plot this one.
|
|
for J := 0 to Options.XValueCount - 1 do
|
|
begin
|
|
N := (J * Options.PenCount) + I; // Which Bar Number!?
|
|
// Plot a rectangle for each Bar in our bar chart...
|
|
X := BarXPosition(N) + 1;
|
|
// Make a space between groups, 4 pixels per XValue Index:
|
|
//Dec(X,4);
|
|
//Inc(X, 2*J);
|
|
Y := YTempOrigin;
|
|
Assert(Y < Height);
|
|
Assert(Y > 0);
|
|
Assert(X > 0);
|
|
//if (X>=Width) then
|
|
// OutputDebugString('foo!');
|
|
Assert(X < Width);
|
|
X2 := BarXPosition(N + 1) - 3;
|
|
// Make a space between groups, 4 pixels per XValue Index:
|
|
//Dec(X2,4);
|
|
//Inc(X2, 2*J);
|
|
V := FData.Value[I, J];
|
|
if JclMath.IsNaN(V) then
|
|
Continue;
|
|
Y2 := Round(YOrigin - ((V / Options.PenAxis[I].YGap) * Options.PrimaryYAxis.YPixelGap));
|
|
//Assert(Y2 < Height);
|
|
if Y2 < 0 then
|
|
Y2 := -1; //clip extreme negatives.
|
|
if Y2 >= Y then
|
|
Y2 := Y - 1;
|
|
Assert(Y2 < Y);
|
|
Assert(X2 > 0);
|
|
//if (X2<Width) then
|
|
// OutputDebugString('foo!');
|
|
//Assert(X2<Width);
|
|
//Assert(X2>X);
|
|
if Options.PenCount > 1 then
|
|
if X2 > X then
|
|
Dec(X2); // Additional 1 pixel gap
|
|
if Options.PenStyle[I] <> psClear then
|
|
begin
|
|
if (X2 - X) < 4 then // don't draw black line around bar if it is a very narrow bar.
|
|
ACanvas.Pen.Style := psClear
|
|
else
|
|
ACanvas.Pen.Style := Options.PenStyle[I];
|
|
MyColorRectangle(ACanvas, I, X, Y, X2, Y2);
|
|
end;
|
|
end;
|
|
end;
|
|
{add average line for the type...}
|
|
if Options.ChartKind = ckChartBarAverage then
|
|
begin
|
|
SetLineColor(ACanvas, jvChartAverageLineColorIndex);
|
|
ACanvas.MoveTo(Round(XOrigin + 1 * Options.XPixelGap),
|
|
Round(YOrigin - ((Options.AverageValue[1] / Options.PrimaryYAxis.YGap) * Options.PrimaryYAxis.YPixelGap)));
|
|
for J := 0 to Options.XValueCount do
|
|
MyPenLineTo(ACanvas, Round(XOrigin + J * Options.XPixelGap),
|
|
Round(YOrigin - ((Options.AverageValue[J] / Options.PrimaryYAxis.YGap) * Options.PrimaryYAxis.YPixelGap)));
|
|
SetLineColor(ACanvas, jvChartAxisColorIndex);
|
|
end;
|
|
// NEW: Add markers to bar chart:
|
|
PlotGraphChartMarkers;
|
|
end;
|
|
|
|
// Keep Y in visible chart range:
|
|
|
|
function GraphConstrainedLineY(Pen, Sample: Integer): Double;
|
|
var
|
|
V: Double;
|
|
PenAxisOpt: TJvChartYAxisOptions;
|
|
begin
|
|
V := FData.Value[Pen, Sample];
|
|
PenAxisOpt := Options.PenAxis[Pen];
|
|
if JclMath.IsNaN(V) then
|
|
begin
|
|
Result := NaN; // blank placeholder value in chart!
|
|
Exit;
|
|
end;
|
|
if PenAxisOpt.YGap < 0.0000001 then
|
|
begin
|
|
Result := 0.0; // can't chart! YGap is near zero, zero, or negative.
|
|
Exit;
|
|
end;
|
|
Result := YOrigin - (((V - PenAxisOpt.YMin) / PenAxisOpt.YGap) * PenAxisOpt.YPixelGap);
|
|
if Result >= YOrigin - 1 then
|
|
Result := Round(YOrigin) - 1 // hit the top of the chart
|
|
else
|
|
if Result < Options.YStartOffset - 2 then
|
|
Result := Options.YStartOffset - 2; // Not quite good enough, but better than before.
|
|
end;
|
|
|
|
procedure PlotGraphChartLine;
|
|
var
|
|
I, I2, J, Y1: Integer;
|
|
V, LineXPixelGap: Double;
|
|
NanFlag: Boolean;
|
|
VC: Integer;
|
|
// PenAxisOpt: TJvChartYAxisOptions;
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
|
|
VC := Options.XValueCount;
|
|
if VC < 2 then
|
|
VC := 2;
|
|
LineXPixelGap := ((Options.XEnd - 2) - Options.XStartOffset) / (VC - 1);
|
|
|
|
ACanvas.Pen.Style := psSolid;
|
|
for I := 0 to Options.PenCount - 1 do
|
|
begin
|
|
// PenAxisOpt := Options.PenAxis[I];
|
|
// No line types?
|
|
if Options.PenStyle[I] = psClear then
|
|
Continue;
|
|
SetLineColor(ACanvas, I);
|
|
J := 0;
|
|
V := GraphConstrainedLineY(I, J);
|
|
NanFlag := JclMath.IsNaN(V);
|
|
if not NanFlag then
|
|
begin
|
|
Y := Round(V);
|
|
ACanvas.MoveTo(Round(XOrigin), Y);
|
|
end;
|
|
|
|
for J := 1 to Options.XValueCount - 1 do
|
|
begin
|
|
V := GraphConstrainedLineY(I, J);
|
|
if JclMath.IsNaN(V) then
|
|
begin
|
|
NanFlag := True; // skip.
|
|
ACanvas.MoveTo(Round(XOrigin + J * LineXPixelGap), 200); //DEBUG!
|
|
end
|
|
else
|
|
begin
|
|
if NanFlag then
|
|
begin // resume, valid value.
|
|
NanFlag := False;
|
|
Y := Round(V);
|
|
// pick up the pen and slide forward
|
|
ACanvas.MoveTo(Round(XOrigin + J * LineXPixelGap), Y);
|
|
end
|
|
else
|
|
begin
|
|
Y := Round(V);
|
|
ACanvas.Pen.Style := Options.PenStyle[I];
|
|
if I > 0 then
|
|
begin
|
|
for I2 := 0 to I - 1 do
|
|
begin
|
|
V := GraphConstrainedLineY(I2, J);
|
|
if JclMath.IsNaN(V) then
|
|
Continue;
|
|
Y1 := Round(V);
|
|
if Y1 = Y then
|
|
begin
|
|
Dec(Y); // Prevent line-overlap. Show dotted line above other line.
|
|
if ACanvas.Pen.Style = psSolid then
|
|
ACanvas.Pen.Style := psDot;
|
|
end;
|
|
end;
|
|
end;
|
|
MyPenLineTo(ACanvas, Round(XOrigin + J * LineXPixelGap), Y);
|
|
//OldV := V; // keep track of last valid value, for handling gaps.
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
PlotGraphChartMarkers;
|
|
// MARKERS:
|
|
SetLineColor(ACanvas, jvChartAxisColorIndex);
|
|
end;
|
|
|
|
procedure PlotGraphStackedBarAverage;
|
|
var
|
|
I, J: Integer;
|
|
begin
|
|
for J := 0 to Options.XValueCount - 1 do
|
|
begin
|
|
n100Sum := 0;
|
|
for I := 0 to Options.PenCount - 1 do
|
|
n100Sum := n100Sum + FData.Value[I, J];
|
|
|
|
for I := 0 to Options.PenCount - 1 do
|
|
if n100Sum <> 0 then
|
|
AverageData.Value[I, J] := (FData.Value[I, J] / n100Sum) * 100
|
|
else
|
|
AverageData.Value[I, J] := 0;
|
|
end;
|
|
|
|
for J := 0 to Options.XValueCount - 1 do
|
|
begin
|
|
YOldOrigin := 0;
|
|
for I := 0 to Options.PenCount - 1 do
|
|
begin
|
|
if I = Options.PenCount then {last one; draw it always to the top line}
|
|
MyColorRectangle(ACanvas, I,
|
|
Round(XOrigin + J * Options.XPixelGap + (Options.XPixelGap / 2)),
|
|
Round(YOrigin - YOldOrigin),
|
|
Round(XOrigin + (J + 1) * Options.XPixelGap + (Options.XPixelGap / 2) - nStackGap),
|
|
Options.YStartOffset)
|
|
else
|
|
begin
|
|
MyColorRectangle(ACanvas, I,
|
|
Round(XOrigin + J * Options.XPixelGap + (Options.XPixelGap / 2)),
|
|
Round(YOrigin - YOldOrigin),
|
|
Round(XOrigin + (J + 1) * Options.XPixelGap + (Options.XPixelGap / 2) - nStackGap),
|
|
Round((YOrigin - YOldOrigin) -
|
|
((AverageData.Value[I, J] / Options.PenAxis[I].YGap) * Options.PrimaryYAxis.YPixelGap)));
|
|
YOldOrigin := YOldOrigin + Round((AverageData.Value[I, J] / Options.PenAxis[I].YGap) *
|
|
Options.PrimaryYAxis.YPixelGap);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure CheckYAxisFlags;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Options.PrimaryYAxis.FActive := True;
|
|
Options.SecondaryYAxis.FActive := False;
|
|
for I := 0 to Options.PenCount - 1 do
|
|
if Options.PenSecondaryAxisFlag[I] then
|
|
begin
|
|
Options.SecondaryYAxis.FActive := True;
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
begin { Enough local functions for ya? -WP }
|
|
ACanvas := GetChartCanvas;
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
Assert(Assigned(ACanvas.Pen));
|
|
|
|
FPlotGraphCalled := True;
|
|
|
|
// refuse to refresh under these conditions:
|
|
if not (Enabled and Visible) then
|
|
Exit;
|
|
if csDesigning in ComponentState then
|
|
begin
|
|
if not Assigned(Self.Parent) then
|
|
Exit;
|
|
if Self.Name = '' then
|
|
Exit;
|
|
end;
|
|
|
|
// safety before we paint.
|
|
Assert(Assigned(Self));
|
|
Assert(Assigned(Data));
|
|
Assert(Assigned(Options));
|
|
Assert(Assigned(Options.PrimaryYAxis));
|
|
Assert(Assigned(Options.SecondaryYAxis));
|
|
Assert(Assigned(AverageData));
|
|
|
|
// NEW: Primary Y axis is always shown, but secondary is only shown
|
|
// if a pen is set up to plot on the secondary Y Axis scale.
|
|
CheckYAxisFlags;
|
|
|
|
ClearScreen;
|
|
|
|
if Options.XValueCount > MAX_VALUES then
|
|
Options.XValueCount := MAX_VALUES;
|
|
if Options.PenCount > MAX_PEN then
|
|
Options.PenCount := MAX_PEN;
|
|
(*
|
|
if Options.PrimaryYAxis.YGap = 0 then
|
|
Options.PrimaryYAxis.YGap := 1;
|
|
if Options.SecondaryYAxis.YGap = 0 then
|
|
Options.SecondaryYAxis.YGap := 1;
|
|
*)
|
|
|
|
PrimaryYAxisLabels; // Make sure there are Y Axis labels!
|
|
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
|
|
{ Resize Header area according to HeaderFont size }
|
|
if (not PrintInSession) and (Length(Options.XAxisHeader) > 0) then
|
|
begin
|
|
MyHeaderFont(ACanvas);
|
|
// nOldY := Options.YStartOffset;
|
|
nMaxTextHeight := CanvasMaxTextHeight(ACanvas) + 8;
|
|
// Bump bottom margins if the fonts don't fit!
|
|
if Options.YStartOffset < 2 * nMaxTextHeight then
|
|
begin
|
|
Options.YStartOffset := nMaxTextHeight * 2;
|
|
//Options.YEnd := Options.YEnd + (nOldY - Options.YStartOffset);
|
|
CalcYEnd;
|
|
Options.PrimaryYAxis.Normalize;
|
|
Options.SecondaryYAxis.Normalize;
|
|
end;
|
|
end;
|
|
|
|
if (Options.ChartKind = ckChartStackedBar) or
|
|
(Options.ChartKind = ckChartStackedBarAverage) then
|
|
begin
|
|
FOldYOrigin := Options.YOrigin;
|
|
Options.YOrigin := 0;
|
|
end
|
|
else
|
|
FOldYOrigin := Options.YOrigin;
|
|
if Options.ChartKind = ckChartStackedBarAverage then
|
|
FOldYGap := Options.PrimaryYAxis.YGap
|
|
else
|
|
FOldYGap := Options.PrimaryYAxis.YGap;
|
|
|
|
{ This effects only graph type: JvChartStackedBar(_100) }
|
|
nStackGap := 1;
|
|
if Options.XEnd > 200 then
|
|
nStackGap := 3;
|
|
|
|
MyAxisFont(ACanvas);
|
|
|
|
YOldOrigin := Trunc(YOrigin);
|
|
|
|
{Draw header and other stuff...}
|
|
GraphSetup;
|
|
|
|
// If FData.ValueCount is zero we can preview the visual appearance of the chart,
|
|
// but there is no plotting of pens that can occur, so stop now...
|
|
if FData.ValueCount = 0 then // EMPTY!
|
|
begin
|
|
{ draw a blank chart. Component shouldn't just be a white square, it makes
|
|
users think we're broken, when we're not, they just haven't given us any
|
|
data.}
|
|
Options.PrimaryYAxis.Normalize;
|
|
Options.SecondaryYAxis.Normalize;
|
|
GraphSetup;
|
|
|
|
GraphXAxis;
|
|
GraphXAxisDivisionMarkers;
|
|
GraphYAxis;
|
|
GraphYAxisDivisionMarkers;
|
|
|
|
ACanvas.Font.Color := clRed;
|
|
if Length(Options.NoDataMessage) = 0 then
|
|
MyLeftTextOut(ACanvas, Round(XOrigin), Round(YOrigin) + 4, RsNoData)
|
|
else
|
|
MyLeftTextOut(ACanvas, Round(XOrigin), Round(YOrigin) + 4, Options.NoDataMessage); // NEW! NOV 2004. WP.
|
|
|
|
Exit;
|
|
end;
|
|
|
|
DrawGradient; // NEW 2007
|
|
DisplayBars; // NEW 2007
|
|
|
|
{Y Axis}
|
|
GraphYAxis;
|
|
GraphYAxisDivisionMarkers; // dotted lines making graph-paper across graph
|
|
|
|
{X Axis}
|
|
GraphXAxis;
|
|
GraphXAxisDivisionMarkers; // new.
|
|
|
|
{X-axis legends...}
|
|
GraphXAxisLegend;
|
|
|
|
{Main Header}
|
|
if Options.Title <> '' then
|
|
MyHeader(ACanvas, Options.Title);
|
|
|
|
{X axis header}
|
|
if Options.XAxisHeader <> '' then
|
|
MyXHeader(ACanvas, Options.XAxisHeader);
|
|
|
|
{Create the actual graph...}
|
|
case Options.ChartKind of
|
|
ckChartBar, ckChartBarAverage:
|
|
PlotGraphBar;
|
|
ckChartStackedBar:
|
|
PlotGraphStackedBar;
|
|
ckChartLine: //, ckChartLineWithMarkers:
|
|
PlotGraphChartLine;
|
|
ckChartMarkers:
|
|
PlotGraphChartMarkers;
|
|
ckChartStackedBarAverage:
|
|
PlotGraphStackedBarAverage;
|
|
ckChartPieChart:
|
|
GraphPieChart(1); { special types}
|
|
ckChartDeltaAverage:
|
|
GraphDeltaAverage; { special types}
|
|
end;
|
|
{Y axis header}
|
|
MyYHeader(ACanvas, Options.YAxisHeader); // vertical text out on Y axis
|
|
end;
|
|
|
|
procedure TJvChart.GraphXAxisLegendMarker(ACanvas: TCanvas; MarkerKind: TJvChartPenMarkerKind; X, Y: Integer);
|
|
begin
|
|
case MarkerKind of
|
|
pmkDiamond:
|
|
PlotFilledDiamond(ACanvas, X, Y);
|
|
pmkCircle:
|
|
PlotCircle(ACanvas, X, Y);
|
|
pmkSquare:
|
|
PlotSquare(ACanvas, X, Y);
|
|
pmkCross:
|
|
PlotCross(ACanvas, X, Y);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChart.GraphXAxisLegend; // reworked in 2007.
|
|
var
|
|
I: Integer;
|
|
Timestamp: TDateTime;
|
|
TimestampStr: string;
|
|
XOverlap: Integer;
|
|
ACanvas: TCanvas;
|
|
|
|
{ draw x axis text at various alignments:}
|
|
function LeftXAxisText: Boolean;
|
|
begin
|
|
Result := True;
|
|
// Don't exceed right margin - causes some undesirable clipping. removed. -wpostma.
|
|
{if I < Options.XLegends.Count then
|
|
if ACanvas.TextWidth(Options.XLegends[I]) + Options.FXLegendHoriz > (Width XEnd+10) then begin
|
|
Result := False;
|
|
Exit;
|
|
end;}
|
|
|
|
// Label X axis above or below?
|
|
if FContainsNegative then
|
|
begin
|
|
if I < Options.XLegends.Count then
|
|
begin // fix exception. June 23, 2004- WPostma.
|
|
if Options.FXLegendHoriz < XOverlap then
|
|
Exit; // would overlap, don't draw it.
|
|
MyLeftTextOut(ACanvas, Options.FXLegendHoriz, Options.YEnd + 3, Options.XLegends[I]);
|
|
XOverlap := Options.FXLegendHoriz + ACanvas.TextWidth(Options.XLegends[I]);
|
|
end;
|
|
end
|
|
else
|
|
if I < Options.XLegends.Count then
|
|
begin
|
|
if Options.FXLegendHoriz < XOverlap then
|
|
Exit; // would overlap, don't draw it.
|
|
MyLeftTextOut(ACanvas, Options.FXLegendHoriz,
|
|
{bottom:}FXAxisPosition + Options.AxisLineWidth {top: Round(YTempOrigin - Options.PrimaryYAxis.YPixelGap)},
|
|
Options.XLegends[I]);
|
|
XOverlap := Options.FXLegendHoriz + ACanvas.TextWidth(Options.XLegends[I]);
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function RightXAxisText: Boolean;
|
|
begin
|
|
Result := True;
|
|
// Label X axis above or below?
|
|
if FContainsNegative then
|
|
begin
|
|
if I < Options.XLegends.Count then // fix exception. June 23, 2004- WPostma.
|
|
MyRightTextOut(ACanvas, Options.FXLegendHoriz, Options.YEnd + 3, Options.XLegends[I])
|
|
end
|
|
else
|
|
if I < Options.XLegends.Count then
|
|
MyRightTextOut(ACanvas, Options.FXLegendHoriz,
|
|
{bottom:}FXAxisPosition + Options.AxisLineWidth {top: Round(YTempOrigin - Options.PrimaryYAxis.YPixelGap)},
|
|
Options.XLegends[I])
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function CenterXAxisText: Boolean;
|
|
begin
|
|
Result := True;
|
|
// Label X axis above or below?
|
|
if FContainsNegative then
|
|
begin
|
|
if I < Options.XLegends.Count then // fix exception. June 23, 2004- WPostma.
|
|
MyCenterTextOut(ACanvas, Options.FXLegendHoriz, Options.YEnd + 3, Options.XLegends[I])
|
|
end
|
|
else
|
|
if I < Options.XLegends.Count then
|
|
MyCenterTextOut(ACanvas, Options.FXLegendHoriz,
|
|
{bottom:}FXAxisPosition + Options.AxisLineWidth {top: Round(YTempOrigin - Options.PrimaryYAxis.YPixelGap)},
|
|
Options.XLegends[I])
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
procedure XAxisDateTimeModeLabels1; // Classic mode [REFACTORED 2007]
|
|
var
|
|
L: Integer;
|
|
begin
|
|
// classic JvChart XAxisDateTime mode labels painting code.
|
|
|
|
// if not Options.XAxisDivisionMarkers then Exit;
|
|
if Options.XAxisValuesPerDivision <= 0 then
|
|
Exit;
|
|
if Options.XStartOffset <= 0 then
|
|
Exit;
|
|
|
|
for L := 1 to Options.XValueCount div Options.XAxisValuesPerDivision - 1 do
|
|
begin
|
|
Options.FXLegendHoriz := Round(Options.XStartOffset + Options.XPixelGap * L * Options.XAxisValuesPerDivision);
|
|
|
|
Timestamp := FData.Timestamp[L * Options.XAxisValuesPerDivision - 1];
|
|
if Timestamp < 0.0000001 then
|
|
Continue;
|
|
|
|
if Length(Options.FXAxisDateTimeFormat) = 0 then // not specified, means use Locale defaults
|
|
TimestampStr := TimeToStr(Timestamp)
|
|
else
|
|
TimestampStr := FormatDateTime(Options.FXAxisDateTimeFormat, Timestamp);
|
|
|
|
// Check if writing this label would collide with previous label, if not, plot it
|
|
if (Options.FXLegendHoriz - (ACanvas.TextWidth(TimestampStr) div 2)) > XOverlap then
|
|
begin
|
|
MyCenterTextOut(ACanvas, Options.FXLegendHoriz,
|
|
{bottom:}FXAxisPosition + Options.AxisLineWidth
|
|
{top: Round(YTempOrigin - Options.PrimaryYAxis.YPixelGap)},
|
|
TimestampStr);
|
|
|
|
// draw a ticky-boo (technical term used by scientists the world over)
|
|
// so that we can see where on the chart the X axis datetime is pointing to.
|
|
ACanvas.Pen.Width := 1;
|
|
ACanvas.MoveTo(Options.FXLegendHoriz, FXAxisPosition);
|
|
ACanvas.LineTo(Options.FXLegendHoriz, FXAxisPosition + Options.AxisLineWidth + 2);
|
|
|
|
XOverlap := Options.FXLegendHoriz + ACanvas.TextWidth(TimestampStr);
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
//XAxisDateTimeModeLabels2: [NEW 2007]
|
|
// make text labels line up with new division line drawing code
|
|
// in GraphXAxisDivisionMarkers:
|
|
|
|
procedure XAxisDateTimeModeLabels2; // [NEW 2007]
|
|
var
|
|
L: Integer;
|
|
X: Integer;
|
|
DivPixels: Integer;
|
|
TextWidth: Integer;
|
|
Modn: Integer;
|
|
begin
|
|
Assert(Options.FXAxisDateTimeSkipBy > 0);
|
|
|
|
DivPixels := Round(Options.XPixelGap * Options.FXaxisDateTimeSkipBy);
|
|
|
|
for L := 0 to FOptions.FXAxisDateTimeLines - 1 do
|
|
begin
|
|
Timestamp := FData.Timestamp[(L * Options.FXaxisDateTimeSkipBy)] + Options.FXAxisDateTimeFirstMarker;
|
|
|
|
if Timestamp < 0.0000001 then
|
|
Continue;
|
|
|
|
if Length(Options.FXAxisDateTimeFormat) = 0 then // not specified, means use Locale defaults
|
|
TimestampStr := TimeToStr(Timestamp)
|
|
else
|
|
TimestampStr := FormatDateTime(Options.FXAxisDateTimeFormat, Timestamp);
|
|
|
|
TextWidth := ACanvas.TextWidth(TimeStampStr);
|
|
if DivPixels > 0 then
|
|
begin
|
|
Modn := Trunc(TextWidth / DivPixels) + 1;
|
|
if Modn > 1 then
|
|
begin
|
|
if (L mod Modn) <> 0 then
|
|
Continue; // skip labels if they are too densely spaced.
|
|
end;
|
|
end;
|
|
|
|
X := Round(Options.XStartOffset + (Options.XPixelGap * L * Options.FXaxisDateTimeSkipBy)) +
|
|
Options.FXAxisDateTimeFirstMarker;
|
|
if X > Options.XEnd then
|
|
Break;
|
|
if X = Options.XStartOffset then
|
|
Continue; // don't draw dotted line right at X Axis.
|
|
|
|
MyCenterTextOut(ACanvas, X,
|
|
{bottom:}FXAxisPosition + Options.AxisLineWidth,
|
|
TimestampStr);
|
|
|
|
ACanvas.Pen.Color := Options.GetPenColor(jvChartDivisionLineColorIndex);
|
|
MyDrawDotLine(ACanvas, X, Options.YStartOffset + 1, X, FXAxisPosition - 1);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
{X-LEGEND: ...}
|
|
if (Options.XStartOffset = 0) and (Options.YStartOffset = 0) then
|
|
Exit;
|
|
ACanvas := GetChartCanvas;
|
|
|
|
XOverlap := 0; // XAxis Label Overlap protection checking variable.
|
|
|
|
{Count how many characters to show in the separate legend}
|
|
|
|
SetLineColor(ACanvas, jvChartAxisColorIndex);
|
|
MyAxisFont(ACanvas);
|
|
|
|
{ datetime mode for X axis legends : follow the time division markers }
|
|
if Options.XAxisDateTimeMode then
|
|
begin { if DateTime mode then legends are painted where the division markers are painted }
|
|
if (Data.EndDateTime > Data.StartDateTime) and (Options.XAxisDateTimeDivision > 0.00001) then
|
|
XAxisDateTimeModeLabels2 // new mode! align division markers to even hour/day/etc boundaries!
|
|
else
|
|
XAxisDateTimeModeLabels1; // classic mode! let the labels displayed be any old time.
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChart.DrawPenColorBox(ACanvas: TCanvas; NColor, W, H, X, Y: Integer);
|
|
begin
|
|
MyColorRectangle(ACanvas, NColor, X, Y, X + W, Y + H);
|
|
SetRectangleColor(ACanvas, jvChartPaperColorIndex);
|
|
end;
|
|
|
|
{**************************************************************************}
|
|
{ call this function : }
|
|
{ a) when you want to print the graph to Windows default printer }
|
|
{**************************************************************************}
|
|
|
|
procedure TJvChart.PrintGraph;
|
|
var
|
|
nXEnd, nYEnd: Longint;
|
|
nXStart, nYStart: Longint;
|
|
nLegendWidth: Longint;
|
|
begin
|
|
{Save display values...}
|
|
nXEnd := Options.XEnd;
|
|
nYEnd := Options.YEnd;
|
|
nXStart := Options.XStartOffset;
|
|
nYStart := Options.YStartOffset;
|
|
nLegendWidth := Options.LegendWidth;
|
|
{Calculate new values for printer....}
|
|
Options.LegendWidth := Round((Options.LegendWidth / (nXEnd + Options.LegendWidth)) * Printer.PageWidth);
|
|
Options.XStartOffset := Round(Printer.PageWidth * 0.08); {8%}
|
|
Options.YStartOffset := Round(Printer.PageHeight * 0.1); {10%}
|
|
Options.XEnd := Round(Printer.PageWidth - (1.2 * Options.LegendWidth)) - Options.XStartOffset;
|
|
Options.YEnd := Round(Printer.PageHeight * 0.75);
|
|
if Options.YEnd > Options.XEnd then
|
|
Options.YEnd := Options.XEnd;
|
|
{Begin printing...}
|
|
PrintInSession := True;
|
|
Printer.BeginDoc;
|
|
PlotGraph; {Here it goes!}
|
|
Printer.EndDoc;
|
|
PrintInSession := False;
|
|
{Restore display values...}
|
|
Options.XStartOffset := nXStart; {margin}
|
|
Options.YStartOffset := nYStart;
|
|
Options.XEnd := nXEnd;
|
|
Options.YEnd := nYEnd;
|
|
Options.LegendWidth := nLegendWidth;
|
|
end;
|
|
|
|
{**************************************************************************}
|
|
{ call this function : }
|
|
{ a) when you want to print the graph to Windows default printer }
|
|
{ AND you add something else on the same paper. This function }
|
|
{ will just add the chart to the OPEN printer canvas at given position }
|
|
{**************************************************************************}
|
|
|
|
// (rom) XStartPos, YStartPos unused
|
|
|
|
procedure TJvChart.AddGraphToOpenPrintCanvas(XStartPos, YStartPos, GraphWidth, GraphHeight: Longint);
|
|
var
|
|
nXEnd, nYEnd: Longint;
|
|
nXStart, nYStart: Longint;
|
|
nLegendWidth: Longint;
|
|
begin
|
|
{Save display values...}
|
|
nXEnd := Options.XEnd;
|
|
nYEnd := Options.YEnd;
|
|
nXStart := Options.XStartOffset;
|
|
nYStart := Options.YStartOffset;
|
|
nLegendWidth := Options.LegendWidth;
|
|
{Set new values for printing the graph at EXISTING print canvas....}
|
|
Options.LegendWidth := Round((Options.LegendWidth / (nXEnd + Options.LegendWidth)) * GraphWidth);
|
|
Options.XStartOffset := Round(GraphWidth * 0.08); {8%}
|
|
Options.YStartOffset := Round(GraphHeight * 0.1); {10%}
|
|
Options.XEnd := Round(GraphWidth - (1.2 * Options.LegendWidth)) - Options.XStartOffset;
|
|
Options.YEnd := Round(GraphHeight * 0.75);
|
|
{Begin printing...NOTICE BeginDoc And EndDoc must be done OUTSIDE this procedure call}
|
|
PrintInSession := True;
|
|
PlotGraph; {Here it goes!}
|
|
PrintInSession := False;
|
|
{Restore display values...}
|
|
Options.XStartOffset := nXStart; {margin}
|
|
Options.YStartOffset := nYStart;
|
|
Options.XEnd := nXEnd;
|
|
Options.YEnd := nYEnd;
|
|
Options.LegendWidth := nLegendWidth;
|
|
end;
|
|
|
|
{NEW}
|
|
{ when the user clicks the chart and changes the axis, we need a notification
|
|
so we can save the new settings. }
|
|
|
|
procedure TJvChart.NotifyOptionsChange;
|
|
begin
|
|
if FUpdating then
|
|
Exit;
|
|
if csDesigning in ComponentState then
|
|
begin
|
|
Invalidate;
|
|
Exit;
|
|
end;
|
|
if csLoading in ComponentState then
|
|
Exit; // Component properties being set at runtime.
|
|
|
|
if Options.PenCount <> Self.Data.FPenCount then
|
|
begin
|
|
if Options.PenCount > 0 then { never set Data.FPenCount to zero internally! }
|
|
Data.FPenCount := Options.PenCount;
|
|
end;
|
|
|
|
// Event fire:
|
|
if Assigned(FOnOptionsChangeEvent) then
|
|
FOnOptionsChangeEvent(Self);
|
|
if Options.AutoUpdateGraph then
|
|
begin
|
|
FAutoPlotDone := False; // Next paint will also call PlotGraph
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
{ Warren implemented TImage related code directly into TJvChart, to remove TImage as base class.}
|
|
// (rom) simplified by returning the Printer ACanvas when printing
|
|
|
|
function TJvChart.GetChartCanvas: TCanvas;
|
|
var
|
|
Bitmap: TBitmap;
|
|
begin
|
|
{ designtime - draw directly to screen }
|
|
if csDesigning in ComponentState then
|
|
begin
|
|
Result := Self.Canvas;
|
|
Assert(Assigned(Result));
|
|
Assert(Assigned(Result.Brush));
|
|
Exit;
|
|
end;
|
|
|
|
{ printer canvas }
|
|
if PrintInSession then
|
|
begin
|
|
Result := Printer.Canvas;
|
|
Assert(Assigned(Result));
|
|
Assert(Assigned(Result.Brush));
|
|
Exit;
|
|
end;
|
|
|
|
{ FPicture.Graphic -bitmap canvas - normal display method. }
|
|
if FPicture.Graphic = nil then
|
|
begin
|
|
Bitmap := TBitmap.Create;
|
|
try
|
|
Bitmap.Width := Width;
|
|
Bitmap.Height := Height;
|
|
FPicture.Graphic := Bitmap;
|
|
finally
|
|
Bitmap.Free;
|
|
end;
|
|
end;
|
|
if FPicture.Graphic is TBitmap then
|
|
begin
|
|
Result := TBitmap(FPicture.Graphic).Canvas;
|
|
Assert(Assigned(Result));
|
|
Assert(Assigned(Result.Brush));
|
|
end
|
|
else
|
|
{$IFDEF CLR}
|
|
raise EInvalidOperation.Create(RsEUnableToGetCanvas);
|
|
{$ELSE}
|
|
raise EInvalidOperation.CreateRes(@RsEUnableToGetCanvas);
|
|
{$ENDIF CLR}
|
|
end;
|
|
|
|
function TJvChart.GetChartCanvasWidth: Integer; // WP NEW 2007
|
|
begin
|
|
{ designtime - draw directly to screen }
|
|
if csDesigning in ComponentState then
|
|
begin
|
|
Result := Width;
|
|
Exit;
|
|
end;
|
|
if PrintInSession then
|
|
begin
|
|
Result := Printer.PageWidth;
|
|
Exit;
|
|
end;
|
|
if Assigned(FPicture) then
|
|
Result := FPicture.Width
|
|
else
|
|
Result := Width;
|
|
end;
|
|
|
|
function TJvChart.GetChartCanvasHeight: Integer; // WP NEW 2007
|
|
begin
|
|
{ designtime - draw directly to screen }
|
|
if csDesigning in ComponentState then
|
|
begin
|
|
Result := Self.Height;
|
|
Exit;
|
|
end;
|
|
|
|
{ printer canvas }
|
|
if PrintInSession then
|
|
begin
|
|
Result := Printer.PageHeight;
|
|
Exit;
|
|
end;
|
|
|
|
{ FPicture.Graphic -bitmap canvas - normal display method. }
|
|
if Assigned(FPicture) then
|
|
Result := FPicture.Height
|
|
else
|
|
Result := Self.Height;
|
|
end;
|
|
|
|
procedure TJvChart.CalcYEnd;
|
|
begin
|
|
// OutputDebugString(PChar('CalcYEnd Height='+IntToStr(Height) ) );
|
|
if not Assigned(FBitmap) then
|
|
Options.YEnd := 0
|
|
else
|
|
Options.YEnd := FBitmap.Height - 2 * Options.YStartOffset; {canvas size, excluding margin}
|
|
end;
|
|
{**************************************************************************}
|
|
{ call this function : }
|
|
{ a) when you resize the canvas for the AABsoftGraph }
|
|
{ b) at program startup before drawing the first graph }
|
|
{**************************************************************************}
|
|
|
|
// ResizeChartCanvas/PlotGraph endless recursion loop fixed. --WP
|
|
|
|
procedure TJvChart.ResizeChartCanvas;
|
|
begin
|
|
{Add code for my own data...here}
|
|
if not Assigned(FBitmap) then
|
|
begin
|
|
FBitmap := TBitmap.Create;
|
|
FBitmap.Height := Height;
|
|
FBitmap.Width := Width;
|
|
FPicture.Graphic := FBitmap;
|
|
end
|
|
else
|
|
begin
|
|
FBitmap.Width := Width;
|
|
FBitmap.Height := Height;
|
|
FPicture.Graphic := FBitmap;
|
|
end;
|
|
|
|
CalcYEnd; // YEnd depends on YStartOffset.
|
|
|
|
if Options.Legend = clChartLegendRight then
|
|
Options.XEnd := Round(((FBitmap.Width - 2) - 1.5 * Options.XStartOffset) - Options.LegendWidth)
|
|
else
|
|
Options.XEnd := Round((FBitmap.Width - 2) - 0.5 * Options.XStartOffset);
|
|
|
|
if Options.XEnd < 10 then
|
|
Options.XEnd := 10;
|
|
if Options.YEnd < 10 then
|
|
Options.YEnd := 10;
|
|
|
|
if not Assigned(Data) then
|
|
Exit; //safety.
|
|
// if Data.ValueCount = 0 then
|
|
// Exit; // no use, there's no data yet.
|
|
|
|
Options.PrimaryYAxis.Normalize;
|
|
Options.SecondaryYAxis.Normalize;
|
|
|
|
if (not FInPlotGraph) and Visible {and (Data.ValueCount>0)} then // endless recursion protection.
|
|
if Options.AutoUpdateGraph or FPlotGraphCalled then
|
|
begin
|
|
FInPlotGraph := True; // recursion blocker.
|
|
InternalPlotGraph; { must not call Invalidate here, causes exceptions in some cases. }
|
|
FInPlotGraph := False;
|
|
end;
|
|
end;
|
|
|
|
{This procedure is called when user clicks on the main header}
|
|
|
|
procedure TJvChart.EditHeader;
|
|
var
|
|
StrString: string;
|
|
begin
|
|
StrString := Options.Title;
|
|
if InputQuery(RsGraphHeader, Format(RsCurrentHeaders, [Options.Title]), StrString) then
|
|
Options.Title := StrString;
|
|
InternalPlotGraph;
|
|
Invalidate;
|
|
if Assigned(FOnTitleClick) then
|
|
FOnTitleClick(Self);
|
|
end;
|
|
|
|
{This procedure is called when user clicks on the X-axis header}
|
|
|
|
procedure TJvChart.EditXHeader;
|
|
var
|
|
StrString: string;
|
|
begin
|
|
StrString := Options.XAxisHeader;
|
|
if InputQuery(RsGraphHeader, Format(RsXAxisHeaders, [Options.XAxisHeader]), StrString) then
|
|
Options.XAxisHeader := StrString;
|
|
InternalPlotGraph;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TJvChart.EditYScale;
|
|
var
|
|
StrString: string;
|
|
begin
|
|
StrString := FloatToStr(Options.PrimaryYAxis.YMax);
|
|
if InputQuery(RsGraphScale, Format(RsYAxisScales, [FloatToStr(Options.PrimaryYAxis.YMax)]), StrString) then
|
|
Options.PrimaryYAxis.YMax := StrToFloatDef(StrString, Options.PrimaryYAxis.YMax)
|
|
else
|
|
Exit;
|
|
|
|
// Fire event so the application can save this new Options.PrimaryYAxis.YMax value
|
|
if Assigned(FOnYAxisClick) then
|
|
FOnYAxisClick(Self);
|
|
|
|
//XXX AutoFormatGraph; BAD CODE REMOVED. Wpostma. Call PlotGraph instead.
|
|
InternalPlotGraph;
|
|
Invalidate;
|
|
end;
|
|
|
|
// NEW: X Axis Header has to move to make room if there is a horizontal
|
|
// X axis legend:
|
|
|
|
procedure TJvChart.MyXHeader(ACanvas: TCanvas; StrText: string);
|
|
var
|
|
X, Y: Integer;
|
|
begin
|
|
MyAxisFont(ACanvas);
|
|
Y := Options.YStartOffset + Options.YEnd + (2 * MyTextHeight(ACanvas, StrText) - 4);
|
|
if Options.Legend = clChartLegendBelow then
|
|
begin
|
|
{ left aligned X Axis Title, right after the legend itself}
|
|
X := Options.FXLegendHoriz + 32;
|
|
MyLeftTextOut(ACanvas, X, Y, StrText);
|
|
end
|
|
else
|
|
begin
|
|
X := Options.XStartOffset + (Options.XEnd div 2);
|
|
MyCenterTextOut(ACanvas, X, Y, StrText);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChart.MyYHeader(ACanvas: TCanvas; StrText: string);
|
|
var
|
|
{ht,}WD, Vert, Horiz: Integer; // not used (ahuser)
|
|
begin
|
|
if Length(StrText) = 0 then
|
|
Exit;
|
|
ACanvas.Brush.Color := Color;
|
|
{$IFDEF VCL}
|
|
{ !!warning: uses Win32 only font-handle stuff!!}
|
|
MyGraphVertFont(ACanvas); // Select Vertical Font Output.
|
|
{$ENDIF VCL}
|
|
{$IFDEF VisualCLX}
|
|
MyAxisFont;
|
|
{$ENDIF VisualCLX}
|
|
if Options.XStartOffset > 10 then
|
|
begin
|
|
{ht := MyTextHeight(StrText); }// not used (ahuser)
|
|
WD := ACanvas.TextWidth(StrText);
|
|
// Kindof a fudge, but we'll work out something better later... :-) -WAP.
|
|
Vert := Options.YStartOffset * 2 + Height div 2 - WD div 2;
|
|
if Vert < 0 then
|
|
Vert := 0;
|
|
Horiz := 2;
|
|
{$IFDEF VCL}
|
|
// NOTE: Because of the logical font selected, this time TextOut goes vertical.
|
|
// If this doesn't go vertical, it may be because the font selection above failed.
|
|
MyLeftTextOut(ACanvas, Horiz, Vert, StrText);
|
|
{$ENDIF VCL}
|
|
{$IFDEF VisualCLX}
|
|
WD := ACanvas.TextHeight(StrText);
|
|
TextOutAngle(ACanvas, 90, Horiz + WD, Vert, StrText);
|
|
{$ENDIF VisualCLX}
|
|
end;
|
|
MyAxisFont(ACanvas);
|
|
// Self.MyLeftTextOut(Horiz, Vert+50, '*');
|
|
end;
|
|
|
|
{***************************************************************************}
|
|
{ MOUSE FUNCTIONS AND PROCEDURES }
|
|
{***************************************************************************}
|
|
|
|
function TJvChart.MouseToXValue(X: Integer): Integer;
|
|
var
|
|
XPixelGap: Double;
|
|
begin
|
|
XPixelGap := ((Options.XEnd - Options.XStartOffset) / Options.XValueCount);
|
|
if XPixelGap > 0.001 then
|
|
begin
|
|
Result := Round(((X - 1) - Options.XStartOffset) / (XPixelGap));
|
|
if (Result >= Data.ValueCount - 1) then
|
|
Result := Data.ValueCount - 1
|
|
else
|
|
if Result < 0 then
|
|
Result := 0;
|
|
end
|
|
else
|
|
Result := 0; // can't figure it out.
|
|
end;
|
|
|
|
function TJvChart.MouseToYValue(Y: Integer): Double;
|
|
begin
|
|
with FOptions.PrimaryYAxis do
|
|
begin
|
|
//Y = (YOrigin - (((Result - YMin) / YGap) * YPixelGap))
|
|
|
|
Result := -1 * (((Y / YPixelGap) * YGap) - ((YOrigin / YPixelGap) * YGap) - YMin);
|
|
|
|
if Result < YMin then
|
|
Result := YMin
|
|
else
|
|
if Result > YMax then
|
|
Result := YMax;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChart.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
Screen.Cursor := crDefault;
|
|
inherited MouseUp(Button, Shift, X, Y);
|
|
|
|
if Options.MouseDragObjects then
|
|
begin
|
|
if Assigned(FDragFloatingMarker) then
|
|
begin
|
|
FDragFloatingMarker.FDragging := False;
|
|
// Solve for X Position etc.
|
|
FDragFloatingMarker.XPosition := MouseToXValue(X);
|
|
|
|
FDragFloatingMarker.YPosition := MouseToYValue(Y);
|
|
//OutputDebugString(PChar( 'End Mouse Drag Floating Object at '+InTToStr(FDragFloatingMarker.XPosition)+','+FloatToStr(FDragFloatingMarker.YPosition)) );
|
|
if Assigned(FOnEndFloatingMarkerDrag) then
|
|
FOnEndFloatingMarkerDrag(Self, FDragFloatingMarker);
|
|
|
|
FDragFloatingMarker := nil;
|
|
Invalidate;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
if FStartDrag then
|
|
begin
|
|
Options.LegendWidth := Options.LegendWidth + (FMouseDownX - X);
|
|
Options.XEnd := Options.XEnd - (FMouseDownX - X);
|
|
InternalPlotGraph;
|
|
end;
|
|
if FMouseLegend then
|
|
begin
|
|
InternalPlotGraph;
|
|
FMouseLegend := False;
|
|
end;
|
|
FStartDrag := False;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TJvChart.MouseMove(Shift: TShiftState; X, Y: Integer); //override;
|
|
begin
|
|
inherited MouseMove(Shift, X, Y);
|
|
if Assigned(FDragFloatingMarker) then
|
|
begin
|
|
if FDragFloatingMarker.XDraggable then
|
|
begin
|
|
if X < Options.XStartOffset then
|
|
X := Options.XStartOffset;
|
|
if X > Options.XEnd then
|
|
X := Options.XEnd;
|
|
FDragFloatingMarker.FRawXPosition := X;
|
|
end;
|
|
if FDragFloatingMarker.YDraggable then
|
|
if Y > FXAxisPosition then
|
|
Y := FXAxisPosition;
|
|
|
|
if Y < Options.YStartOffset then
|
|
Y := Options.YStartOffset;
|
|
|
|
FDragFloatingMarker.FRawYPosition := Y;
|
|
|
|
Self.Invalidate; // Repaint control!
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure TJvChart.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
var
|
|
XPixelGap: Double;
|
|
I: Integer;
|
|
// YPixelGap: Double; // not used (ahuser)
|
|
Marker: TJvChartFloatingMarker;
|
|
begin
|
|
inherited MouseDown(Button, Shift, X, Y);
|
|
|
|
if Options.MouseDragObjects then
|
|
begin
|
|
if not Assigned(FDragFloatingMarker) then
|
|
begin
|
|
for I := 0 to FFloatingMarker.Count - 1 do
|
|
begin
|
|
Marker := GetFloatingMarker(I);
|
|
if not Marker.Visible then
|
|
Continue;
|
|
if not (Marker.XDraggable or Marker.YDraggable) then
|
|
Continue;
|
|
if (Abs(X - Marker.FRawXPosition) < (Options.MarkerSize * 2)) and
|
|
((Marker.Marker = pmkNone) or (Abs(Y - Marker.FRawYPosition) < Options.MarkerSize * 2)) then
|
|
begin
|
|
FDragFloatingMarker := Marker;
|
|
FDragFloatingMarker.FDragging := True;
|
|
//OutputDebugString('Begin Mouse Drag Floating Object');
|
|
if Assigned(FOnBeginFloatingMarkerDrag) then
|
|
FOnBeginFloatingMarkerDrag(Self, FDragFloatingMarker);
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
if Options.MouseEdit then
|
|
begin
|
|
if X < Options.XStartOffset then
|
|
begin
|
|
EditYScale;
|
|
Exit;
|
|
end
|
|
else
|
|
// New: Don't let end user mess with title, if we
|
|
// provide our own way to set the title or title options,
|
|
// however, if Options.MouseEdit is on, they can still set the
|
|
// scale via mouse clicking.
|
|
if (Y < Options.YStartOffset) and not Assigned(FOnTitleClick) then
|
|
begin
|
|
EditHeader;
|
|
Exit;
|
|
end;
|
|
|
|
if (Y > Options.YStartOffset + Options.YEnd) and not Assigned(FOnXAxisClick) then
|
|
begin
|
|
EditXHeader;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
if X < Options.XStartOffset then
|
|
begin
|
|
// Just fire the Y axis clicked event, don't popup the editor
|
|
if Assigned(FOnYAxisClick) then
|
|
begin
|
|
FOnYAxisClick(Self);
|
|
Exit;
|
|
end;
|
|
end
|
|
else
|
|
if Y < Options.YStartOffset then
|
|
if Assigned(FOnTitleClick) then
|
|
FOnTitleClick(Self);
|
|
// New: Click on bottom area of chart (X Axis) can be defined by
|
|
// user of the component to do something.
|
|
if Assigned(FOnXAxisClick) then
|
|
if (Y > Options.YEnd) and (X > Options.XStartOffset) then
|
|
begin
|
|
FOnXAxisClick(Self);
|
|
Exit;
|
|
end;
|
|
if Assigned(FOnAltYAxisClick) then
|
|
if (Y < Options.YEnd) and (X > Options.XEnd) then
|
|
FOnAltYAxisClick(Self);
|
|
|
|
if Options.MouseInfo then
|
|
begin
|
|
FStartDrag := False;
|
|
FMouseDownX := X;
|
|
FMouseDownY := Y;
|
|
if (Y > Options.YStartOffset) and
|
|
(Y < Options.YStartOffset + Options.YEnd) and
|
|
(X > Options.XStartOffset) and
|
|
(X < Options.XStartOffset + Options.XEnd + 10) then
|
|
begin
|
|
{Legend resize...}
|
|
if X > (Options.XStartOffset + Options.XEnd) - 5 then
|
|
begin
|
|
FStartDrag := True;
|
|
Screen.Cursor := crSizeWE;
|
|
end;
|
|
{Inside the actual graph...}
|
|
if (X <= (Options.XStartOffset + Options.XEnd) - 5) and
|
|
(Options.ChartKind <> ckChartPieChart) and
|
|
(Options.ChartKind <> ckChartDeltaAverage) then
|
|
begin
|
|
XPixelGap := ((Options.XEnd - Options.XStartOffset) / (Options.XValueCount + 1));
|
|
//if XPixelGap <1 then
|
|
// XPixelGal := 1;
|
|
if XPixelGap > 0.001 then
|
|
FMouseValue := Round((X - Options.XStartOffset) / (XPixelGap))
|
|
else
|
|
FMouseValue := 0; // can't figure it out.
|
|
|
|
case Options.ChartKind of
|
|
ckChartBar, ckChartBarAverage:
|
|
if Options.PenCount = 1 then {check for Pen count}
|
|
FMousePen := Round(((X + (XPixelGap / 2)) -
|
|
(Options.XStartOffset +
|
|
Options.XOrigin * XPixelGap +
|
|
XPixelGap * FMouseValue)) /
|
|
Round(XPixelGap / (Options.PenCount + 0.1)) + 0.1)
|
|
else
|
|
FMousePen := Round(((X + (XPixelGap / 2)) -
|
|
(Options.XStartOffset +
|
|
Options.XOrigin * XPixelGap +
|
|
XPixelGap * FMouseValue)) /
|
|
Round(XPixelGap / (Options.PenCount + 0.5)) + 0.5);
|
|
ckChartStackedBar, ckChartLine, ckChartStackedBarAverage:
|
|
FMousePen := 0;
|
|
end;
|
|
if (FMouseValue > Options.XValueCount) or (FMouseValue < 0) then
|
|
FMouseValue := 0;
|
|
if FMousePen > Options.PrimaryYAxis.YDivisions then
|
|
FMousePen := 0;
|
|
|
|
// New: Allow user to do custom hints, or else do other things
|
|
// when a chart is clicked.
|
|
if Assigned(FOnChartClick) then
|
|
begin
|
|
FMouseDownShowHint := False;
|
|
FMouseDownHintBold := False;
|
|
// This event can handle chart clicks on data area only.
|
|
if X <= Options.XEnd then
|
|
FOnChartClick(Self, Button, Shift, X, Y, FMouseValue,
|
|
FMousePen, FMouseDownShowHint,
|
|
FMouseDownHintBold, FMouseDownHintStrs);
|
|
end
|
|
else
|
|
begin
|
|
if Button = mbLeft then
|
|
begin
|
|
FMouseDownShowHint := True;
|
|
FMouseDownHintBold := True;
|
|
AutoHint;
|
|
end
|
|
else
|
|
FMouseDownShowHint := False; { don't show }
|
|
end;
|
|
|
|
if (FMouseDownHintStrs.Count > 0) and FMouseDownShowHint then
|
|
ShowMouseMessage(X, Y);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChart.SetCursorPosition(Pos: Integer);
|
|
begin
|
|
FCursorPosition := Pos;
|
|
Invalidate; // repaint!
|
|
end;
|
|
|
|
procedure TJvChart.DisplayBars; // NEW 2007!
|
|
begin
|
|
DrawHorizontalBars;
|
|
DrawVerticalBars;
|
|
end;
|
|
|
|
{ make list of 'PenName=Value' strings for each pen.. }
|
|
|
|
procedure TJvChart.AutoHint; // Make the automatic hint message showing all pens and their values.
|
|
var
|
|
I: Integer;
|
|
Str: string;
|
|
Val: Double;
|
|
begin
|
|
FMouseDownHintStrs.Clear;
|
|
|
|
if Options.XAxisDateTimeMode then
|
|
begin
|
|
if Length(Options.DateTimeFormat) = 0 then
|
|
Str := DateTimeToStr(FData.GetTimestamp(FMouseValue))
|
|
else
|
|
Str := FormatDateTime(Options.DateTimeFormat, FData.GetTimestamp(FMouseValue));
|
|
|
|
FMouseDownHintStrs.Add(Str);
|
|
end
|
|
else
|
|
if Options.XLegends.Count > FMouseValue then
|
|
FMouseDownHintStrs.Add(Options.XLegends[FMouseValue]);
|
|
|
|
for I := 0 to Options.PenCount - 1 do
|
|
begin
|
|
if Options.PenLegends.Count <= I then
|
|
Break; // Exception fixed. WP
|
|
Str := Options.PenLegends[I];
|
|
Val := FData.Value[I, FMouseValue];
|
|
if Length(Str) = 0 then
|
|
Str := IntToStr(I + 1);
|
|
Str := Str + ' : ';
|
|
if JclMath.IsNaN(Val) then
|
|
Str := Str + RsNA
|
|
else
|
|
begin
|
|
Str := Str + FloatToStrF(Val, ffFixed, REALPREC, 3);
|
|
if Options.PenUnit.Count > I then
|
|
Str := Str + ' ' + Options.PenUnit[I];
|
|
end;
|
|
|
|
FMouseDownHintStrs.Add(Str);
|
|
{$IFDEF DEBUGINFO_ON}
|
|
{$IFDEF CLR}
|
|
OutputDebugString('TJvChart.AutoHint: ' + Str);
|
|
{$ELSE}
|
|
OutputDebugString(PChar('TJvChart.AutoHint: ' + Str));
|
|
{$ENDIF CLR}
|
|
{$ENDIF DEBUGINFO_ON}
|
|
end;
|
|
end;
|
|
|
|
(* orphaned
|
|
|
|
{We will show some Double values...}
|
|
if FMousePen = 0 then
|
|
begin
|
|
{show all values in the Pen...}
|
|
nLineCount := Options.PenCount;
|
|
nHeight := nLineH * (nLineCount + 2);
|
|
if Options.XLegends.Count > FMouseValue then
|
|
strMessage1 := Options.XLegends[FMouseValue]
|
|
else
|
|
strMessage1 := '';
|
|
strMessage2 := '-';
|
|
if nWidth < ChartCanvas.TextWidth(strMessage1) then
|
|
nWidth := ChartCanvas.TextWidth(strMessage1);
|
|
end
|
|
else
|
|
begin
|
|
nLineCount := 1;
|
|
nHeight := nLineH * (nLineCount + 2);
|
|
strMessage1 := Options.XLegends[FMouseValue];
|
|
if nWidth < ChartCanvas.TextWidth(strMessage1) then
|
|
nWidth := ChartCanvas.TextWidth(strMessage1);
|
|
if FMousePen > 0 then
|
|
strMessage2 := Options.PenLegends[FMousePen];
|
|
if ChartCanvas.TextWidth(strMessage2) > nWidth then
|
|
nWidth := ChartCanvas.TextWidth(strMessage2);
|
|
strMessage3 := FloatToStrF(FData.Value[FMousePen, FMouseValue], ffFixed, REALPREC, 3);
|
|
end;
|
|
|
|
*)
|
|
|
|
{ ShowMouseMessage can invoke an OnChartClick event, and/or
|
|
shows hint boxes, etc. }
|
|
|
|
procedure TJvChart.ShowMouseMessage(X, Y: Integer);
|
|
var
|
|
nWidth: Integer;
|
|
nHeight: Integer;
|
|
nLineH: Integer;
|
|
nLineCount: Integer;
|
|
I: Integer;
|
|
StrWidth, StrHeight: Integer;
|
|
ACanvas: TCanvas;
|
|
begin
|
|
ACanvas := GetChartCanvas;
|
|
ACanvas.Font.Color := Font.Color; // March 2004 Fixed.
|
|
|
|
// scan and set nWidth,nLineH
|
|
nWidth := 100; // minimum 100 pixel hint box width.
|
|
nLineH := 8; // minimum 8 pixel line height for hints.
|
|
nLineCount := FMouseDownHintStrs.Count;
|
|
|
|
for I := 0 to nLineCount - 1 do
|
|
begin
|
|
StrWidth := ACanvas.TextWidth(FMouseDownHintStrs[I]);
|
|
if StrWidth > nWidth then
|
|
nWidth := StrWidth;
|
|
StrHeight := ACanvas.TextHeight(FMouseDownHintStrs[I]);
|
|
if StrHeight > nLineH then
|
|
nLineH := StrHeight;
|
|
end;
|
|
|
|
// bump height of text in hint box,
|
|
// leaving a little extra pixel space between rows.
|
|
nLineH := Round(nLineH * 1.07) + 1;
|
|
|
|
{RsNoValuesHere}
|
|
if FMouseDownHintStrs.Count = 0 then
|
|
begin
|
|
StrWidth := ACanvas.TextWidth(RsNoValuesHere);
|
|
if StrWidth > nWidth then
|
|
nWidth := StrWidth;
|
|
MyColorRectangle(ACanvas, jvChartHintColorIndex, X + 3, Y + 3, X + nWidth + 3 + 5, Y + nLineH + 3);
|
|
MyColorRectangle(ACanvas, jvChartPaperColorIndex, X, Y, X + nWidth + 5, Y + nLineH);
|
|
ACanvas.Font.Color := Self.Font.Color;
|
|
MyLeftTextOutHint(ACanvas, X + 2, Y, RsNoValuesHere);
|
|
FMouseLegend := True;
|
|
Exit;
|
|
end;
|
|
|
|
// Get hint box height/width, size to contents:
|
|
nWidth := nWidth + 25;
|
|
nHeight := nLineH * nLineCount + 8;
|
|
|
|
// keep hint from clipping at bottom and right.
|
|
if (Y + nHeight) > Self.Height then
|
|
Y := (Self.Height - nHeight);
|
|
if (X + nWidth) > Self.Width then
|
|
X := (Self.Width - nWidth);
|
|
|
|
// Draw hint box:
|
|
MyColorRectangle(ACanvas, jvChartPaperColorIndex, X + 3, Y + 3, X + nWidth + 3, Y + nHeight + 3);
|
|
MyColorRectangle(ACanvas, jvChartHintColorIndex, X, Y, X + nWidth, Y + nHeight);
|
|
|
|
//MyLeftTextOut( ACanvas, X + 3, Y + 3, 'Foo');
|
|
|
|
// Draw text inside the hint box:
|
|
ACanvas.Font.Color := Self.Font.Color;
|
|
//ACanvas.Font.Style :=
|
|
|
|
if FMouseDownHintBold then
|
|
ACanvas.Font.Style := [fsBold];
|
|
|
|
for I := 0 to nLineCount - 1 do
|
|
begin
|
|
if (I = 1) and FMouseDownHintBold then
|
|
ACanvas.Font.Style := [];
|
|
MyLeftTextOutHint(ACanvas, X + 2, 4 + Y + (I * nLineH), FMouseDownHintStrs[I]); // draw text for each line.
|
|
end;
|
|
|
|
FMouseLegend := True;
|
|
|
|
Invalidate; // TODO: Should we do this or draw directly onto the canvas?
|
|
//ResizeChartCanvas;
|
|
end;
|
|
|
|
{***************************************************************************}
|
|
{ PIE FUNCTIONS AND PROCEDURES }
|
|
{***************************************************************************}
|
|
|
|
procedure TJvChart.GraphPieChart(NPen: Integer);
|
|
var
|
|
nSize: Integer;
|
|
I: Integer;
|
|
nLast: Integer;
|
|
nXExtra: Integer;
|
|
nSum: Double;
|
|
n100Sum: Double;
|
|
nP: Double;
|
|
ACanvas: TCanvas;
|
|
begin
|
|
ACanvas := GetChartCanvas;
|
|
ClearScreen;
|
|
|
|
{Main Header}
|
|
MyHeader(ACanvas, Options.Title);
|
|
MyPieLegend(NPen);
|
|
if Options.XEnd < Options.YEnd then
|
|
begin
|
|
nSize := Options.XEnd;
|
|
nXExtra := 0;
|
|
end
|
|
else
|
|
begin
|
|
nSize := Options.YEnd;
|
|
nXExtra := Round((Options.XEnd - Options.YEnd) / 2);
|
|
end;
|
|
{Count total sum...}
|
|
n100Sum := 0;
|
|
for I := 1 to MAX_VALUES do
|
|
n100Sum := n100Sum + FData.Value[NPen, I];
|
|
{Show background pie....}
|
|
SetRectangleColor(ACanvas, jvChartAxisColorIndex); {black...}
|
|
MyPiePercentage(Options.XStartOffset + nXExtra + 2,
|
|
Options.YStartOffset + 2, nSize, 100);
|
|
{Show pie if not zero...}
|
|
if n100Sum <> 0 then
|
|
begin
|
|
nSum := n100Sum;
|
|
nLast := Options.XValueCount + 1;
|
|
if nLast > MAX_VALUES then
|
|
nLast := MAX_VALUES;
|
|
for I := nLast downto 2 do
|
|
begin
|
|
nSum := nSum - FData.Value[NPen, I];
|
|
nP := 100 * (nSum / n100Sum);
|
|
SetRectangleColor(ACanvas, I - 1);
|
|
MyPiePercentage(Options.XStartOffset + nXExtra,
|
|
Options.YStartOffset, nSize, nP);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChart.MyPiePercentage(X1, Y1, W: Longint; NPercentage: Double);
|
|
var
|
|
nOriginX, nOriginY: Longint;
|
|
nGrade: Double;
|
|
nStartGrade: Double;
|
|
X, Y: Double;
|
|
nLen: Double;
|
|
begin
|
|
nOriginX := Round((W - 1.01) / 2) + X1;
|
|
nOriginY := Round((W - 1.01) / 2) + Y1;
|
|
nGrade := (NPercentage / 100) * 2 * Pi;
|
|
nStartGrade := (2 / 8) * 2 * Pi;
|
|
nLen := Round((W - 1) / 2);
|
|
X := Cos(nStartGrade + nGrade) * nLen;
|
|
Y := Sin(nStartGrade + nGrade) * nLen;
|
|
MyPie(GetChartCanvas, X1, Y1, X1 + W, Y1 + W,
|
|
nOriginX, Y1, nOriginX + Round(X), nOriginY - Round(Y));
|
|
end;
|
|
|
|
procedure TJvChart.MyPieLegend(NPen: Integer);
|
|
var
|
|
I: Integer;
|
|
nTextHeight: Longint;
|
|
{nChars: Integer;}// not used (ahuser)
|
|
XLegendStr: string;
|
|
ACanvas: TCanvas;
|
|
begin
|
|
ACanvas := GetChartCanvas;
|
|
{Count how many characters to show in the separate legend}
|
|
{nChars := Round(Options.LegendWidth / ChartCanvas.TextWidth('1'));}// not used (ahuser)
|
|
{Decrease the value due to the color box shown}
|
|
{if (nChars>4) then nChars := nChars-4;}// not used (ahuser)
|
|
|
|
MySmallGraphFont(ACanvas);
|
|
nTextHeight := Round(CanvasMaxTextHeight(ACanvas) * 1.2);
|
|
|
|
// Pie Chart Right Side Legend.
|
|
if Options.Legend = clChartLegendRight then
|
|
begin
|
|
MyColorRectangle(ACanvas, 0,
|
|
Options.XStartOffset + Options.XEnd + 6,
|
|
Options.YStartOffset + 1 * nTextHeight + 6 + 4,
|
|
Options.XStartOffset + Options.XEnd + Options.LegendWidth + 6,
|
|
Options.YStartOffset + (Options.XValueCount + 1) * nTextHeight + 6 + 4);
|
|
MyColorRectangle(ACanvas, jvChartPaperColorIndex,
|
|
Options.XStartOffset + Options.XEnd + 3,
|
|
Options.YStartOffset + 1 * nTextHeight + 3 + 4,
|
|
Options.XStartOffset + Options.XEnd + Options.LegendWidth + 3,
|
|
Options.YStartOffset + (Options.XValueCount + 1) * nTextHeight + 3 + 4);
|
|
for I := 1 to Options.XValueCount do
|
|
begin
|
|
DrawPenColorBox(ACanvas, I, ACanvas.TextWidth('12') - 2, nTextHeight - 4,
|
|
Options.XStartOffset + Options.XEnd + 7,
|
|
Options.YStartOffset + I * nTextHeight + 9);
|
|
SetFontColor(ACanvas, jvChartAxisColorIndex);
|
|
if I - 1 < Options.XLegends.Count then
|
|
XLegendStr := Options.XLegends[I - 1]
|
|
else
|
|
XLegendStr := IntToStr(I);
|
|
MyLeftTextOut(ACanvas, Options.XStartOffset + Options.XEnd + 7 + ACanvas.TextWidth('12'),
|
|
Options.YStartOffset + I * nTextHeight + 7,
|
|
XLegendStr);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// Used in line charting as a Marker kind:
|
|
|
|
procedure TJvChart.PlotSquare(ACanvas: TCanvas; X, Y: Integer);
|
|
begin
|
|
MyPolygon(ACanvas, [Point(X - Options.MarkerSize, Y - Options.MarkerSize),
|
|
Point(X + Options.MarkerSize, Y - Options.MarkerSize),
|
|
Point(X + Options.MarkerSize, Y + Options.MarkerSize),
|
|
Point(X - Options.MarkerSize, Y + Options.MarkerSize)]);
|
|
end;
|
|
|
|
// Used in line charting as a Marker kind:
|
|
|
|
procedure TJvChart.PlotDiamond(ACanvas: TCanvas; X, Y: Integer);
|
|
begin
|
|
MyPolygon(ACanvas, [Point(X, Y - Options.MarkerSize),
|
|
Point(X + Options.MarkerSize, Y),
|
|
Point(X, Y + Options.MarkerSize),
|
|
Point(X - Options.MarkerSize, Y)]);
|
|
end;
|
|
|
|
procedure TJvChart.PlotFilledDiamond(ACanvas: TCanvas; X, Y: Integer);
|
|
begin
|
|
with ACanvas.Brush do
|
|
begin
|
|
Style := bsSolid;
|
|
Color := ACanvas.Pen.Color;
|
|
PlotDiamond(ACanvas, X, Y);
|
|
Style := bsClear;
|
|
end;
|
|
end;
|
|
|
|
// Used in line charting as a Marker kind:
|
|
|
|
procedure TJvChart.PlotCircle(ACanvas: TCanvas; X, Y: Integer);
|
|
begin
|
|
ACanvas.Pen.Style := psSolid;
|
|
ACanvas.Ellipse(X - Options.MarkerSize,
|
|
Y - Options.MarkerSize,
|
|
X + Options.MarkerSize,
|
|
Y + Options.MarkerSize); // Marker Circle radius 3.
|
|
end;
|
|
|
|
// Used in line charting as a Marker kind:
|
|
|
|
procedure TJvChart.PlotCross(ACanvas: TCanvas; X, Y: Integer);
|
|
begin
|
|
MyDrawLine(ACanvas, X - Options.MarkerSize, Y, X + Options.MarkerSize, Y);
|
|
MyDrawLine(ACanvas, X, Y - Options.MarkerSize, X, Y + Options.MarkerSize);
|
|
end;
|
|
|
|
procedure TJvChart.ClearScreen;
|
|
var
|
|
ACanvas: TCanvas;
|
|
begin
|
|
ACanvas := GetChartCanvas;
|
|
{Clear screen}
|
|
SetLineColor(ACanvas, jvChartPaperColorIndex);
|
|
// Fishy:
|
|
MyColorRectangle(ACanvas, jvChartPaperColorIndex, 0, 0,
|
|
// XXX The point here is to exceed the edges, wipe it all, thus the 3* and 5* multipliers.
|
|
3 * Options.XStartOffset + Options.XEnd + Options.LegendWidth,
|
|
5 * Options.YStartOffset + Options.YEnd);
|
|
SetRectangleColor(ACanvas, jvChartAxisColorIndex);
|
|
SetLineColor(ACanvas, jvChartAxisColorIndex);
|
|
end;
|
|
|
|
{NEW chart type!!!}
|
|
|
|
procedure TJvChart.GraphDeltaAverage;
|
|
var
|
|
XPixelGap: Longint;
|
|
YPixelGap: Longint;
|
|
XOrigin: Longint;
|
|
YOrigin: Longint;
|
|
I, J: Longint;
|
|
TempYOrigin: Longint;
|
|
ACanvas: TCanvas;
|
|
begin
|
|
ACanvas := GetChartCanvas;
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
|
|
{new type of chart...}
|
|
ClearScreen;
|
|
|
|
{Check graph values and correct if wrong. Actually not needed if there are no bugs}
|
|
// if (Options.PrimaryYAxis.YDivisions>MAX_Y_LEGENDS) then
|
|
// Options.PrimaryYAxis.YDivisions := MAX_Y_LEGENDS;
|
|
if Options.PrimaryYAxis.YDivisions = 0 then
|
|
Options.PrimaryYAxis.YDivisions := 1;
|
|
if Options.XValueCount > MAX_VALUES then
|
|
Options.XValueCount := MAX_VALUES;
|
|
if Options.XValueCount = 0 then
|
|
Options.XValueCount := 1;
|
|
if Options.PenCount > MAX_PEN then
|
|
Options.PenCount := MAX_PEN;
|
|
// if Options.PrimaryYAxis.YGap = 0 then
|
|
// Options.PrimaryYAxis.YGap := 1;
|
|
|
|
XPixelGap := Round((Options.YEnd - Options.YStartOffset) /
|
|
(Options.XValueCount));
|
|
YPixelGap := Round((Options.XEnd - Options.XStartOffset) /
|
|
(Options.PrimaryYAxis.YDivisions + 1)); // SPECIALIZED.
|
|
|
|
TempYOrigin := Options.YOrigin;
|
|
Options.YOrigin := Options.PrimaryYAxis.YDivisions div 2;
|
|
|
|
YOrigin := Options.XStartOffset + (YPixelGap * Options.YOrigin);
|
|
XOrigin := Options.YStartOffset;
|
|
|
|
{Create texts for Y-axis}
|
|
// Options.PrimaryYAxis.YLegends.Clear;
|
|
// for I := 0 to MAX_Y_LEGENDS-1 do
|
|
// Options.PrimaryYAxis.YLegends.Add( IntToStr(Round(((I-1)-Options.YOrigin)*Options.PrimaryYAxis.YGap)) );
|
|
|
|
{Y-axis legends and lines...}
|
|
MyAxisFont(ACanvas);
|
|
for I := 1 to Options.PrimaryYAxis.YDivisions + 1 do
|
|
begin
|
|
if I >= Options.PrimaryYAxis.YLegends.Count then
|
|
Exit;
|
|
MyLeftTextOut(ACanvas, YOrigin + (YPixelGap * ((I - 1) - Options.YOrigin)),
|
|
XOrigin + XPixelGap * Options.XValueCount + 2,
|
|
Options.PrimaryYAxis.YLegends[I]);
|
|
MyDrawDotLine(ACanvas, YOrigin - (YPixelGap * ((I - 1) - Options.YOrigin)),
|
|
XOrigin,
|
|
YOrigin - (YPixelGap * ((I - 1) - Options.YOrigin)),
|
|
XOrigin + (XPixelGap * (Options.XValueCount)));
|
|
end;
|
|
|
|
{Draw Y-axis}
|
|
ACanvas.MoveTo(Options.XStartOffset, XOrigin);
|
|
MyAxisLineTo(ACanvas, Options.XEnd, XOrigin);
|
|
{Draw second Y-axis}
|
|
ACanvas.MoveTo(Options.XStartOffset, XOrigin + XPixelGap * Options.XValueCount + 1);
|
|
MyAxisLineTo(ACanvas, Options.XEnd, XOrigin + XPixelGap * Options.XValueCount + 1);
|
|
{Draw X-axis}
|
|
ACanvas.MoveTo(YOrigin, XOrigin);
|
|
MyAxisLineTo(ACanvas, YOrigin, XOrigin + XPixelGap * Options.XValueCount + 1);
|
|
|
|
{X-axis legends...}
|
|
GraphXAxisLegend;
|
|
|
|
{Main Header}
|
|
MyHeader(ACanvas, Options.Title);
|
|
|
|
{X axis header}
|
|
MyXHeader(ACanvas, Options.XAxisHeader);
|
|
|
|
// Now draw the delta average...
|
|
for I := 0 to Options.PenCount - 1 do
|
|
for J := 0 to Options.XValueCount - 1 do
|
|
if Options.PenCount = 1 then
|
|
MyColorRectangle(ACanvas, I,
|
|
YOrigin,
|
|
XOrigin + J * XPixelGap + (I) * Round(XPixelGap / (Options.PenCount + 0.1)) - XPixelGap,
|
|
YOrigin + Round(((FData.Value[I, J] - Options.AverageValue[J]) /
|
|
Options.PrimaryYAxis.YGap) * YPixelGap),
|
|
XOrigin + J * XPixelGap + (I + 1) * Round(XPixelGap / (Options.PenCount + 0.1)) - XPixelGap)
|
|
else
|
|
MyColorRectangle(ACanvas, I,
|
|
YOrigin,
|
|
XOrigin + J * XPixelGap + (I) * Round(XPixelGap / (Options.PenCount + 0.5)) - XPixelGap,
|
|
YOrigin + Round(((FData.Value[I, J] - Options.AverageValue[J]) /
|
|
Options.PrimaryYAxis.YGap) * YPixelGap),
|
|
XOrigin + J * XPixelGap + (I + 1) * Round(XPixelGap / (Options.PenCount + 0.5)) - XPixelGap);
|
|
Options.YOrigin := TempYOrigin;
|
|
end;
|
|
|
|
{****************************************************************************}
|
|
{ Device dependent functions for the rest of this module...check for printer }
|
|
{ or check for metafile output! }
|
|
{****************************************************************************}
|
|
|
|
{$IFDEF VCL}
|
|
{ !!warning: uses Win32 only font-handle stuff!!}
|
|
procedure TJvChart.MakeVerticalFont;
|
|
begin
|
|
if Ord(FYFontHandle) <> 0 then
|
|
DeleteObject(FYFontHandle); // delete old object
|
|
// Clear the contents of FLogFont
|
|
{$IFNDEF CLR}
|
|
FillChar(FYLogFont, SizeOf(TLogFont), 0);
|
|
{$ENDIF !CLR}
|
|
// Set the TLOGFONT's fields - Win32 Logical Font Details.
|
|
with FYLogFont do
|
|
begin
|
|
lfHeight := Abs(Font.Height) + 2;
|
|
lfWidth := 0; //Font.Width;
|
|
lfEscapement := 900; // 90 degree vertical rotation
|
|
lfOrientation := 900; // not used.
|
|
lfWeight := FW_BOLD; //FW_HEAVY; // bold, etc
|
|
lfItalic := 1; // no
|
|
lfUnderline := 0; // no
|
|
lfStrikeOut := 0; // no
|
|
lfCharSet := ANSI_CHARSET; // or DEFAULT_CHARSET
|
|
lfOutPrecision := OUT_TT_ONLY_PRECIS; //Require TrueType!
|
|
// OUT_DEFAULT_PRECIS;
|
|
// OUT_STRING_PRECIS, OUT_CHARACTER_PRECIS, OUT_STROKE_PRECIS,
|
|
// OUT_TT_PRECIS, OUT_DEVICE_PRECIS, OUT_RASTER_PRECIS,
|
|
// OUT_TT_ONLY_PRECIS
|
|
|
|
lfClipPrecision := CLIP_DEFAULT_PRECIS;
|
|
lfQuality := DEFAULT_QUALITY;
|
|
lfPitchAndFamily := DEFAULT_PITCH or FF_DONTCARE;
|
|
{$IFDEF CLR}
|
|
lfFaceName := Font.Name;
|
|
{$ELSE}
|
|
StrPCopy(lfFaceName, Font.Name);
|
|
{$ENDIF CLR}
|
|
end;
|
|
|
|
// Retrieve the requested font
|
|
FYFontHandle := CreateFontIndirect(FYLogFont);
|
|
Assert(Ord(FYFontHandle) <> 0);
|
|
// Assign to the Font.Handle
|
|
//Font.Handle := FYFont; // XXX DEBUG
|
|
//pbxFont.Refresh;
|
|
FYFont := TFont.Create;
|
|
FYFont.Assign(Font);
|
|
FYFont.Color := Options.AxisFont.Color;
|
|
FYFont.Handle := FYFontHandle;
|
|
end;
|
|
{$ENDIF VCL}
|
|
|
|
procedure TJvChart.MyHeader(ACanvas: TCanvas; StrText: string);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
|
|
MyHeaderFont(ACanvas);
|
|
MyCenterTextOut(ACanvas, Options.XStartOffset + Round(Options.XEnd / 2),
|
|
(Options.YStartOffset div 2) - (MyTextHeight(ACanvas, StrText) div 2),
|
|
StrText);
|
|
MyAxisFont(ACanvas);
|
|
end;
|
|
|
|
procedure TJvChart.MySmallGraphFont(ACanvas: TCanvas);
|
|
begin
|
|
ACanvas.Brush.Color := Options.PaperColor; // was hard coded to clWhite.
|
|
ACanvas.Font.Assign(Options.LegendFont);
|
|
end;
|
|
|
|
procedure TJvChart.MyAxisFont(ACanvas: TCanvas);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
Assert(Assigned(ACanvas.Font));
|
|
Assert(Assigned(Options));
|
|
ACanvas.Brush.Color := Options.PaperColor; // was hard coded to clWhite.
|
|
ACanvas.Font.Assign(Options.AxisFont);
|
|
end;
|
|
|
|
{$IFDEF VCL}
|
|
{ !!warning: uses Win32 only font-handle stuff!!}
|
|
procedure TJvChart.MyGraphVertFont(ACanvas: TCanvas);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
|
|
if Ord(FYFontHandle) = 0 then
|
|
MakeVerticalFont;
|
|
ACanvas.Font.Assign(FYFont); //Handle := FYFontHnd;
|
|
if not PrintInSession then
|
|
Assert(ACanvas.Font.Handle = FYFontHandle);
|
|
end;
|
|
{$ENDIF VCL}
|
|
|
|
procedure TJvChart.MyHeaderFont(ACanvas: TCanvas);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
Assert(Assigned(Options));
|
|
ACanvas.Brush.Color := Options.PaperColor; //was clWhite;
|
|
ACanvas.Font.Assign(Options.HeaderFont);
|
|
end;
|
|
|
|
procedure TJvChart.MyPenLineTo(ACanvas: TCanvas; X, Y: Integer);
|
|
begin
|
|
ACanvas.Pen.Width := Options.PenLineWidth;
|
|
ACanvas.LineTo(X, Y);
|
|
ACanvas.Pen.Width := 1;
|
|
end;
|
|
|
|
procedure TJvChart.MyAxisLineTo(ACanvas: TCanvas; X, Y: Integer);
|
|
begin
|
|
ACanvas.Pen.Width := Options.AxisLineWidth;
|
|
ACanvas.LineTo(X, Y);
|
|
ACanvas.Pen.Width := 1;
|
|
end;
|
|
|
|
function TJvChart.MyTextHeight(ACanvas: TCanvas; StrText: string): Longint;
|
|
begin
|
|
Result := ACanvas.TextHeight(StrText);
|
|
end;
|
|
|
|
{ Text Left Aligned to X,Y boundary }
|
|
|
|
procedure TJvChart.MyLeftTextOut(ACanvas: TCanvas; X, Y: Integer; const Text: string);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
ACanvas.Brush.Color := Options.PaperColor; // non default paper color.
|
|
ACanvas.TextOut(X, Y + 1, Text);
|
|
end;
|
|
|
|
procedure TJvChart.MyLeftTextOutHint(ACanvas: TCanvas; X, Y: Integer; const Text: string);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
ACanvas.Brush.Color := Options.HintColor;
|
|
ACanvas.TextOut(X, Y + 1, Text);
|
|
end;
|
|
|
|
procedure TJvChart.MyCenterTextOut(ACanvas: TCanvas; X, Y: Integer; const Text: string);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
ACanvas.Brush.Color := Options.PaperColor; // non default paper color.
|
|
ACanvas.TextOut(X - Round(ACanvas.TextWidth(Text) / 2), Y + 1, Text);
|
|
end;
|
|
|
|
procedure TJvChart.MyRightTextOut(ACanvas: TCanvas; X, Y: Integer; const Text: string);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
ACanvas.Brush.Color := Options.PaperColor; // non default paper color.
|
|
ACanvas.TextOut(X - ACanvas.TextWidth(Text),
|
|
Y - Round(ACanvas.TextHeight(Text) / 2), Text);
|
|
end;
|
|
|
|
procedure TJvChart.MyRectangle(ACanvas: TCanvas; X, Y, X2, Y2: Integer);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
ACanvas.Rectangle(X, Y, X2, Y2);
|
|
end;
|
|
|
|
(*Procedure TJvChart.MyShadowRectangle(Pen : Integer; X, Y, X2, Y2: Integer);
|
|
begin
|
|
SetRectangleColor(Shadow);
|
|
ACanvas.Rectangle(X, Y, X2, Y2);
|
|
end;*)
|
|
|
|
procedure TJvChart.MyColorRectangle(ACanvas: TCanvas; Pen: Integer; X, Y, X2, Y2: Integer);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
SetRectangleColor(ACanvas, Pen);
|
|
//OutputDebugString(PChar('MyColorRectangle X='+IntToStr(X)+' Y='+IntToStr(Y)+ ' X2='+IntToStr(X2)+ ' Y2='+IntToStr(Y2) ));
|
|
ACanvas.Rectangle(X, Y, X2, Y2);
|
|
end;
|
|
|
|
procedure TJvChart.MyPie(ACanvas: TCanvas; X1, Y1, X2, Y2, X3, Y3, X4, Y4: Longint);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
ACanvas.Pie(X1, Y1, X2, Y2, X3, Y3, X4, Y4);
|
|
end;
|
|
|
|
{Procedure TJvChart.MyArc(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Integer);
|
|
begin
|
|
ACanvas.Arc(X1, Y1, X2, Y2, X3, Y3, X4, Y4);
|
|
end;}// not used (ahuser)
|
|
|
|
procedure TJvChart.MyPolygon(ACanvas: TCanvas; Points: array of TPoint);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
ACanvas.Polygon(Points);
|
|
end;
|
|
|
|
{Procedure TJvChart.MyEllipse(X1, Y1, X2, Y2: Integer);
|
|
begin
|
|
ACanvas.Ellipse(X1, Y1, X2, Y2);
|
|
end;}
|
|
|
|
procedure TJvChart.MyDrawLine(ACanvas: TCanvas; X1, Y1, X2, Y2: Integer);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
ACanvas.MoveTo(X1, Y1);
|
|
ACanvas.LineTo(X2, Y2);
|
|
end;
|
|
|
|
procedure TJvChart.MyDrawAxisMark(ACanvas: TCanvas; X1, Y1, X2, Y2: Integer);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
SetSolidLines(ACanvas);
|
|
ACanvas.Pen.Width := 1; // always width 1
|
|
ACanvas.MoveTo(X1, Y1);
|
|
ACanvas.LineTo(X2, Y2);
|
|
end;
|
|
|
|
procedure TJvChart.MyDrawDotLine(ACanvas: TCanvas; X1, Y1, X2, Y2: Integer);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
SetDotLines(ACanvas);
|
|
ACanvas.MoveTo(X1, Y1);
|
|
ACanvas.LineTo(X2, Y2);
|
|
SetSolidLines(ACanvas);
|
|
end;
|
|
|
|
{ (rom) not used
|
|
function TJvChart.GetDefaultColorString(nIndex: Integer): string;
|
|
begin
|
|
if nIndex <= 10 then
|
|
case nIndex of
|
|
-2:
|
|
Result := 'clWhite'; // MouseDownBox
|
|
-1:
|
|
Result := 'clWhite';
|
|
0:
|
|
Result := 'clBlack';
|
|
1:
|
|
Result := 'clLime';
|
|
2:
|
|
Result := 'clBlue';
|
|
3:
|
|
Result := 'clRed';
|
|
4:
|
|
Result := 'clGreen';
|
|
5:
|
|
Result := 'clMaroon';
|
|
6:
|
|
Result := 'clOlive';
|
|
7:
|
|
Result := 'clSilver';
|
|
8:
|
|
Result := 'clTeal';
|
|
9:
|
|
Result := 'clBlack';
|
|
10:
|
|
Result := 'clAqua';
|
|
end
|
|
else
|
|
Result := '$00888888';
|
|
end;
|
|
}
|
|
|
|
procedure TJvChart.SetFontColor(ACanvas: TCanvas; Pen: Integer);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
ACanvas.Font.Color := Options.PenColor[Pen];
|
|
end;
|
|
|
|
procedure TJvChart.SetRectangleColor(ACanvas: TCanvas; Pen: Integer);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
ACanvas.Brush.Color := Options.PenColor[Pen];
|
|
end;
|
|
|
|
procedure TJvChart.SetLineColor(ACanvas: TCanvas; Pen: Integer);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
Assert(Assigned(ACanvas.Pen));
|
|
ACanvas.Pen.Color := Options.PenColor[Pen];
|
|
end;
|
|
|
|
procedure TJvChart.SetDotLines(ACanvas: TCanvas);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
Assert(Assigned(ACanvas.Pen));
|
|
ACanvas.Pen.Style := psDot;
|
|
end;
|
|
|
|
procedure TJvChart.SetSolidLines(ACanvas: TCanvas);
|
|
begin
|
|
Assert(Assigned(ACanvas));
|
|
Assert(Assigned(ACanvas.Brush));
|
|
Assert(Assigned(ACanvas.Pen));
|
|
ACanvas.Pen.Style := psSolid;
|
|
end;
|
|
|
|
procedure TJvChart.GraphToClipboard;
|
|
begin
|
|
{This works with bitmaps at least...how to do it as a metafile?}
|
|
Clipboard.Assign(FPicture);
|
|
end;
|
|
|
|
{ PivotData: Pivot Data in Table. Formerly ChangeXValuesWithPen }
|
|
|
|
procedure TJvChart.PivotData;
|
|
var
|
|
I, J: Integer;
|
|
PenCount, XValueCount: Integer;
|
|
TempData: TJvChartData;
|
|
TempStrings: TStringList;
|
|
begin
|
|
TempData := TJvChartData.Create;
|
|
PenCount := Options.PenCount;
|
|
XValueCount := Options.XValueCount;
|
|
try
|
|
{ Move data to temp }
|
|
for I := 0 to PenCount - 1 do
|
|
for J := 0 to XValueCount - 1 do
|
|
TempData.Value[I, J] := FData.Value[I, J];
|
|
FData.Clear;
|
|
{ copy back, pivot X/Y axis }
|
|
for I := 0 to PenCount - 1 do
|
|
for J := 0 to XValueCount - 1 do
|
|
TempData.Value[I, J] := FData.Value[J, I];
|
|
|
|
{swap labels}
|
|
TempStrings := Options.FXLegends;
|
|
Options.FXLegends := Options.FPenLegends;
|
|
Options.FPenLegends := TempStrings;
|
|
|
|
Options.XValueCount := PenCount;
|
|
Options.PenCount := XValueCount;
|
|
|
|
{recalc average}
|
|
CountGraphAverage;
|
|
InternalPlotGraph;
|
|
Invalidate;
|
|
finally
|
|
TempData.Free;
|
|
end;
|
|
end;
|
|
|
|
{FLOATING MARKERS: new Jan 2005 by WP }
|
|
|
|
procedure TJvChart.DrawFloatingMarkers; { called from TJvChart.Paint! }
|
|
var
|
|
Marker, Marker2: TJvChartFloatingMarker;
|
|
LineXPixelGap: Double;
|
|
CaptionYPosition, TextWidth, TextHeight, VC, I: Integer;
|
|
begin
|
|
if csDesigning in ComponentState then
|
|
Exit;
|
|
if FFloatingMarker.Count = 0 then
|
|
Exit;
|
|
|
|
VC := Options.XValueCount;
|
|
if (VC < 2) then
|
|
VC := 2;
|
|
LineXPixelGap := ((Options.XEnd - 2) - Options.XStartOffset) / (VC - 1);
|
|
|
|
{-- First loop through all and update their Raw X and Y Positions --}
|
|
for I := 0 to FFloatingMarker.Count - 1 do
|
|
begin
|
|
Marker := GetFloatingMarker(I);
|
|
if not Marker.Visible then
|
|
Continue;
|
|
if (Marker.XPosition < 0) or (Marker.XPosition >= VC) then
|
|
Continue; // out of visible X range.
|
|
|
|
// if following a pen, get the updated pen value:
|
|
//if Marker.YPositionToPen>=0 then begin
|
|
// Marker.YPosition := Self.Data.Value[Marker.YPositionToPen, Marker.XPosition];
|
|
//end;
|
|
|
|
// find Raw X,Y co-ordinates:
|
|
if not Marker.FDragging then
|
|
begin
|
|
with FOptions.PrimaryYAxis do
|
|
Marker.FRawYPosition := Trunc((YOrigin - (((Marker.YPosition - YMin) / YGap) * YPixelGap)));
|
|
Marker.FRawXPosition := Round(XOrigin + Marker.XPosition * LineXPixelGap);
|
|
end;
|
|
end;
|
|
|
|
{-- Now draw any connecting lines or vertical lines --}
|
|
for I := 0 to FFloatingMarker.Count - 1 do
|
|
begin
|
|
Marker := GetFloatingMarker(I);
|
|
if not Marker.Visible then
|
|
Continue;
|
|
if (Marker.XPosition < 0) or (Marker.XPosition >= VC) then
|
|
Continue; // out of visible X range.
|
|
|
|
// Draw connecting (rubberband) line:
|
|
if (Marker.LineToMarker >= 0) and (Marker.FLineStyle <> psClear) then
|
|
begin
|
|
Marker2 := GetFloatingMarker(Marker.LineToMarker);
|
|
Self.Canvas.Pen.Style := Marker.FLineStyle;
|
|
Self.Canvas.Pen.Color := Marker.FLineColor;
|
|
Self.Canvas.Pen.Width := Marker.FLineWidth;
|
|
Self.Canvas.MoveTo(Marker.FRawXPosition, Marker.FRawYPosition);
|
|
Self.Canvas.LineTo(Marker2.FRawXPosition, Marker2.FRawYPosition);
|
|
end
|
|
else
|
|
if Marker.FLineVertical then
|
|
begin
|
|
// Vertical line along X position:
|
|
Self.Canvas.Pen.Style := Marker.FLineStyle;
|
|
Self.Canvas.Pen.Color := Marker.FLineColor;
|
|
Self.Canvas.Pen.Width := Marker.FLineWidth;
|
|
Self.Canvas.MoveTo(Marker.FRawXPosition, Options.YStartOffset);
|
|
Self.Canvas.LineTo(Marker.FRawXPosition, FXAxisPosition - 1);
|
|
end;
|
|
end;
|
|
|
|
{-- Now draw the markers themselves, we draw them LAST so they are ON TOP. --}
|
|
MySmallGraphFont(Self.Canvas);
|
|
for I := 0 to FFloatingMarker.Count - 1 do
|
|
begin
|
|
Marker := GetFloatingMarker(I);
|
|
if not Marker.Visible then
|
|
Continue;
|
|
if (Marker.XPosition < 0) or (Marker.XPosition >= VC) then
|
|
Continue; // out of visible X range.
|
|
if Marker.Marker <> pmkNone then
|
|
begin
|
|
// Draw Marker:
|
|
Self.Canvas.Pen.Color := Marker.FMarkerColor;
|
|
PlotMarker(Self.Canvas, Marker.Marker, Marker.FRawXPosition, Marker.FRawYPosition);
|
|
end;
|
|
|
|
if Marker.Caption <> '' then
|
|
begin
|
|
TextHeight := Self.Canvas.TextHeight(Marker.Caption);
|
|
|
|
CaptionYPosition := 0; // not used.
|
|
case Marker.CaptionPosition of
|
|
cpMarker:
|
|
CaptionYPosition := Marker.FRawYPosition - Round(TextHeight * 1.4);
|
|
cpXAxisBottom:
|
|
CaptionYPosition := Options.YStartOffset + Options.YEnd + Round(TextHeight * 1.4);
|
|
cpXAxisTop:
|
|
CaptionYPosition := Trunc(XOrigin - Round(TextHeight * 1.4));
|
|
cpTitleArea:
|
|
CaptionYPosition := (Options.YStartOffset div 2) - (TextHeight div 2);
|
|
end;
|
|
|
|
if Marker.CaptionBoxed then
|
|
begin
|
|
TextWidth := Self.Canvas.TextWidth(Marker.Caption) + 10;
|
|
|
|
Self.Canvas.Pen.Color := Marker.LineColor;
|
|
Self.Canvas.Pen.Width := 1;
|
|
Self.Canvas.Pen.Style := Marker.LineStyle;
|
|
MyRectangle(Self.Canvas,
|
|
Marker.FRawXPosition - TextWidth div 2,
|
|
CaptionYPosition,
|
|
Marker.FRawXPosition + TextWidth div 2,
|
|
CaptionYPosition + TextHeight + TextHeight div 4);
|
|
Self.Canvas.Pen.Style := psSolid;
|
|
//MySmallGraphFont(Self.Canvas); <-redundant.
|
|
//MyCenterTextOut(Self.Canvas, Marker.FRawXPosition, Options.FYStartOffset + Round(TextHeight / 4),
|
|
// Marker.Caption);
|
|
end;
|
|
|
|
MyCenterTextOut(Self.Canvas, Marker.FRawXPosition, CaptionYPosition, Marker.Caption);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TJvChart.AddFloatingMarker: TJvChartFloatingMarker;
|
|
begin
|
|
Assert(Assigned(FFloatingMarker));
|
|
Result := TJvChartFloatingMarker.Create(Self);
|
|
Result.FIndex := FFloatingMarker.Count;
|
|
FFloatingMarker.Add(Result);
|
|
end;
|
|
|
|
procedure TJvChart.DeleteFloatingMarker(Index: Integer);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Assert(Assigned(FFloatingMarker));
|
|
if Assigned(FDragFloatingMarker) then
|
|
FDragFloatingMarker := nil;
|
|
|
|
FFloatingMarker.Delete(Index);
|
|
for I := Index to FFloatingMarker.Count - 1 do
|
|
with GetFloatingMarker(I) do
|
|
begin
|
|
FIndex := I; // update index.
|
|
if LineToMarker = Index then
|
|
LineToMarker := -1 // Disconnected now.
|
|
else
|
|
if LineToMarker > Index then
|
|
LineToMarker := LineToMarker - 1; // Index changed.
|
|
end;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TJvChart.DeleteFloatingMarkerObj(Marker: TJvChartFloatingMarker); // NEW 2007
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to FFloatingMarker.Count - 1 do
|
|
begin
|
|
if TJvChartFloatingMarker(FFloatingMarker[I]) = Marker then
|
|
begin
|
|
DeletefloatingMarker(I);
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChart.CopyFloatingMarkers(Source: TJvChart);
|
|
var
|
|
I: Integer;
|
|
NewMarker: TJvChartFloatingMarker;
|
|
begin
|
|
ClearFloatingMarkers;
|
|
for I := 0 to Source.FloatingMarkerCount - 1 do
|
|
begin
|
|
NewMarker := Self.AddFloatingMarker;
|
|
NewMarker.Assign(Source.GetFloatingMarker(I));
|
|
end;
|
|
Invalidate; // repaint!
|
|
end;
|
|
|
|
procedure TJvChart.ClearFloatingMarkers;
|
|
begin
|
|
if Assigned(FDragFloatingMarker) then
|
|
FDragFloatingMarker := nil;
|
|
FFloatingMarker.Clear;
|
|
end;
|
|
|
|
function TJvChart.GetFloatingMarker(Index: Integer): TJvChartFloatingMarker;
|
|
begin
|
|
Assert(Assigned(FFloatingMarker));
|
|
Result := TJvChartFloatingMarker(FFloatingMarker[Index]);
|
|
end;
|
|
|
|
function TJvChart.FloatingMarkerCount: Integer;
|
|
begin
|
|
Assert(Assigned(FFloatingMarker));
|
|
Result := FFloatingMarker.Count;
|
|
end;
|
|
|
|
// NEW HORIZONTAL BAR AND VERTICAL BAR AND GRADIENT PAINTING METHODS (2007) - W.Postma.
|
|
|
|
procedure TJvChart.DrawGradient; // new 2007
|
|
var
|
|
ACanvas: TCanvas;
|
|
RawRect: TRect;
|
|
VC: Integer;
|
|
begin
|
|
if csDesigning in ComponentState then
|
|
Exit;
|
|
if (Options.FGradientDirection = grNone) or (Options.PaperColor = Options.FGradientColor) then
|
|
Exit;
|
|
ACanvas := GetChartCanvas;
|
|
VC := Options.XValueCount;
|
|
if VC < 1 then
|
|
VC := 1;
|
|
RawRect.Top := FOptions.YStartOffset;
|
|
RawRect.Bottom := Trunc(YOrigin);
|
|
RawRect.Left := Round(XOrigin);
|
|
RawRect.Right := Round(Options.XStartOffset + Options.XPixelGap * VC) - 1;
|
|
case Options.FGradientDirection of
|
|
//grNone:
|
|
// ;
|
|
grUp:
|
|
GradVertical(ACanvas, RawRect, Options.FGradientColor, Options.PaperColor);
|
|
grDown:
|
|
GradVertical(ACanvas, RawRect, Options.PaperColor, Options.FGradientColor);
|
|
grLeft:
|
|
GradHorizontal(ACanvas, RawRect, Options.PaperColor, Options.FGradientColor);
|
|
grRight:
|
|
GradHorizontal(ACanvas, RawRect, Options.FGradientColor, Options.PaperColor);
|
|
end;
|
|
end;
|
|
|
|
{ Gradient bars - indicators on background of various vertical subranges }
|
|
|
|
procedure TJvChart.DrawHorizontalBars; // new 2007
|
|
var
|
|
HB: TJvChartHorizontalBar;
|
|
J: Integer;
|
|
ACanvas: TCanvas;
|
|
VC: Integer;
|
|
RawRect: TRect;
|
|
begin
|
|
if csDesigning in ComponentState then
|
|
Exit;
|
|
if FHorizontalBars.Count = 0 then
|
|
Exit;
|
|
ACanvas := GetChartCanvas;
|
|
VC := Options.XValueCount;
|
|
if VC < 1 then
|
|
VC := 1;
|
|
|
|
for J := 0 to FHorizontalBars.Count - 1 do
|
|
begin
|
|
HB := TJvChartHorizontalBar(FHorizontalBars[J]);
|
|
if not HB.FVisible then
|
|
Continue;
|
|
|
|
with FOptions.PrimaryYAxis do
|
|
if (YGap <> 0) then
|
|
begin
|
|
if JclMath.IsNaN(HB.FYTop) then
|
|
RawRect.Top := FOptions.YStartOffset
|
|
else
|
|
begin
|
|
RawRect.Top := Trunc((YOrigin - (((HB.FYTop - YMin) / YGap) * YPixelGap)));
|
|
if RawRect.Top < 0 then
|
|
RawRect.Top := FOptions.YStartOffset;
|
|
end;
|
|
|
|
if JclMath.IsNaN(HB.FYBottom) then
|
|
RawRect.Bottom := Trunc(YOrigin)
|
|
else
|
|
begin
|
|
RawRect.Bottom := Trunc((YOrigin - (((HB.FYBottom - YMin) / YGap) * YPixelGap)));
|
|
if (RawRect.Bottom < 0) or (RawRect.Bottom > YOrigin) then
|
|
RawRect.Bottom := Trunc(YOrigin);
|
|
end;
|
|
|
|
RawRect.Left := Round(XOrigin);
|
|
RawRect.Right := Round(Options.XStartOffset + Options.XPixelGap * VC) - 1;
|
|
end;
|
|
ACanvas.Brush.Color := HB.FColor;
|
|
ACanvas.Brush.Style := bsSolid;
|
|
ACanvas.FillRect(RawRect);
|
|
if HB.FColor <> HB.FGradColor then
|
|
case HB.FGradDirection of
|
|
//grNone:
|
|
// ;
|
|
grUp:
|
|
GradVertical(ACanvas, RawRect, HB.FGradColor, HB.FColor);
|
|
grDown:
|
|
GradVertical(ACanvas, RawRect, HB.FColor, HB.FGradColor);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TJvChart.DrawVerticalBars; // new 2007
|
|
var
|
|
VB: TJvChartVerticalBar;
|
|
J: Integer;
|
|
ACanvas: TCanvas;
|
|
VC: Integer;
|
|
RawRect: TRect;
|
|
begin
|
|
if csDesigning in ComponentState then
|
|
Exit;
|
|
if FVerticalBars.Count = 0 then
|
|
Exit;
|
|
ACanvas := GetChartCanvas;
|
|
{VC :=Options.XValueCount;
|
|
if VC<1 then VC:=1;}
|
|
|
|
for J := 0 to FVerticalBars.Count - 1 do
|
|
begin
|
|
VB := TJvChartVerticalBar(FVerticalBars[J]);
|
|
if not VB.FVisible then
|
|
Continue;
|
|
RawRect.Top := FOptions.YStartOffset;
|
|
RawRect.Bottom := Trunc(YOrigin);
|
|
RawRect.Left := Round(Options.XStartOffset + Options.XPixelGap * VB.FXLeft);
|
|
if RawRect.Left <= 0 then
|
|
RawRect.Left := Round(XOrigin);
|
|
RawRect.Right := Round(Options.XStartOffset + Options.XPixelGap * VB.FXRight);
|
|
VC := Round(Options.XStartOffset + Options.XPixelGap * Options.XValueCount);
|
|
if RawRect.Right > VC then
|
|
RawRect.Right := VC;
|
|
|
|
ACanvas.Brush.Color := VB.FColor;
|
|
ACanvas.Brush.Style := bsSolid;
|
|
ACanvas.FillRect(RawRect);
|
|
if VB.FColor <> VB.FGradColor then
|
|
case VB.FGradDirection of
|
|
//grNone:
|
|
// ;
|
|
grUp:
|
|
GradVertical(ACanvas, RawRect, VB.FGradColor, VB.FColor);
|
|
grDown:
|
|
GradVertical(ACanvas, RawRect, VB.FColor, VB.FGradColor);
|
|
grLeft:
|
|
GradHorizontal(ACanvas, RawRect, VB.FColor, VB.FGradColor);
|
|
grRight:
|
|
GradHorizontal(ACanvas, RawRect, VB.FGradColor, VB.FColor);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TJvChart.AddHorizontalBar: TJvChartHorizontalBar; // NEW 2007
|
|
begin
|
|
Assert(Assigned(FHorizontalBars));
|
|
Result := TJvChartHorizontalBar.Create(Self);
|
|
Result.FIndex := FHorizontalBars.Count;
|
|
FHorizontalBars.Add(Result);
|
|
end;
|
|
|
|
function TJvChart.AddVerticalBar: TJvChartVerticalBar; // NEW 2007
|
|
begin
|
|
Assert(Assigned(FVerticalBars));
|
|
Result := TJvChartVerticalBar.Create(Self);
|
|
Result.FIndex := FVerticalBars.Count;
|
|
FVerticalBars.Add(Result);
|
|
end;
|
|
|
|
procedure TJvChart.ClearHorizontalBars; // NEW 2007
|
|
begin
|
|
FHorizontalBars.Clear;
|
|
end;
|
|
|
|
procedure TJvChart.ClearVerticalBars; // NEW 2007
|
|
begin
|
|
FVerticalBars.Clear;
|
|
end;
|
|
|
|
function TJvChart.HorizontalBarsCount: Integer; // NEW 2007
|
|
begin
|
|
Result := FHorizontalBars.Count;
|
|
end;
|
|
|
|
function TJvChart.VerticalBarsCount: Integer; // NEW 2007
|
|
begin
|
|
Result := FVerticalBars.Count;
|
|
end;
|
|
|
|
{$IFDEF UNITVERSIONING}
|
|
initialization
|
|
RegisterUnitVersion(HInstance, UnitVersioning);
|
|
|
|
finalization
|
|
UnregisterUnitVersion(HInstance);
|
|
{$ENDIF UNITVERSIONING}
|
|
|
|
end.
|
|
|