{----------------------------------------------------------------------------- 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 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[..,]. 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 (X2X); 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.