Componentes.Terceros.jvcl/official/3.32/run/JvChart.pas

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.