Files
lazarus-ccr/components/nicechart/source/NiceChart.pas

1805 lines
44 KiB
ObjectPascal

{-------------------------------------------------------------------------------
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/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
the specific language governing rights and limitations under the License.
The Original Code is NiceChart.pas released at May 26th, 2007.
The Initial Developer of the Original Code is Priyatna.
(Website: http://www.priyatna.org Email: me@priyatna.org)
All Rights Reserved.
Alternatively, the contents of this file may be used under the terms of the
GNU General Public License Version 2 or later (the "GPL"), in which case
the provisions of the GPL are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms
of the GPL and not to allow others to use your version of this file
under the MPL, indicate your decision by deleting the provisions above and
replace them with the notice and other provisions required by the GPL.
If you do not delete the provisions above, a recipient may use your version
of this file under either the MPL or the GPL.
-------------------------------------------------------------------------------}
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
unit NiceChart;
interface
uses
{$IFDEF FPC}
LclIntf, LCLType, LMessages,
{$ELSE}
Windows, Messages,
{$ENDIF}
Classes, Graphics, Forms, Controls, ExtCtrls, SysUtils,
BSplines, Math;
const
UndefinedValueChart: Double = MaxDouble;
type
TNiceChart = class;
TSeriesKind = (skLine, skSmooth, skBar);
TNiceSeries = class(TObject)
private
Top: Integer;
Values: TList;
Chart: TNiceChart;
FCaption: string;
Spline: TBSpline;
FKind: TSeriesKind;
FLineWidth: Integer;
procedure SetCaption(const Value: string);
function GetMaxXValue: Double;
function GetMinXValue: Double;
function GetMinYValue: Double;
function GetMaxYValue: Double;
procedure SetKind(const Value: TSeriesKind);
procedure SetLineWidth(const Value: Integer);
protected
procedure InternalClear;
public
constructor Create(AChart: TNiceChart; AKind: TSeriesKind);
destructor Destroy; override;
function AddXY(AX, AY: Double; AHint: string = ''): Integer;
procedure Remove(Index: Integer);
procedure Clear;
property Caption: string read FCaption write SetCaption;
property Kind: TSeriesKind read FKind write SetKind;
property LineWidth: Integer read FLineWidth write SetLineWidth default 1;
end;
TValueTranslator = record
MinValue: Double;
Scale: Double;
Base: Integer;
end;
TMarkerProc = procedure (ACanvas: TCanvas; X, Y, Size: Integer);
TNiceChart = class(TCustomPanel)
private
Brushes: array [0..15] of TBitmap;
Temp: TStringList;
MarkSize: Integer;
Marker: TMarkerProc;
BarCount: Integer;
BarWidth: Integer;
DestWidth, DestHeight: Integer;
YZero: Integer;
ChartEmpty: Boolean;
List: TList;
XAxis: TList;
YAxis: TList;
FShowLegend: Boolean;
FShowTitle: Boolean;
FShowXAxisLine: Boolean;
FShowYAxisLine: Boolean;
FTitle: string;
FTitleFont: TFont;
FNormalFont: TFont;
FUpdating: Boolean;
RcChart, RcLegend, RcTitle: TRect;
FXTranslate: TValueTranslator;
FYTranslate: TValueTranslator;
FAxisXOnePerValue: Boolean;
FAxisYTitle: string;
FAxisXTitle: string;
FAxisLineWidth: Integer;
FShowYGrid: Boolean;
FShowXGrid: Boolean;
FAxisYScale: Single;
FAxisXScale: Single;
FMonochrome: Boolean;
FGridColor: TColor;
FSoftColors: Boolean;
FTickLength: Integer;
FOuterMargin: Integer;
FInnerMargin: Integer;
FSmallMargin: Integer;
FAxisDefSize: Integer;
FLegendItemSize: Integer;
{$IFNDEF FPC}
FClipRgn: HRGN;
{$ENDIF}
procedure InternalClear;
procedure InternalPaint(ACanvas: TCanvas);
procedure Calculate(AWidth, AHeight: Integer);
procedure DoCalculate(ACanvas: TCanvas; AWidth, AHeight: Integer);
procedure DoPaint;
procedure SetGridColor(const Value: TColor);
procedure SetShowLegend(const Value: Boolean);
procedure SetShowTitle(const Value: Boolean);
procedure SetTickLength(const Value: Integer);
procedure SetTitle(const Value: string);
procedure SetTitleFont(const Value: TFont);
procedure TitleFontChanged(Sender: TObject);
{$IFDEF FPC}
procedure WMSize(var Msg: TLMSize); message LM_SIZE;
procedure WMEraseBkgnd(var Msg: TLMEraseBkgnd); message LM_ERASEBKGND;
{$ELSE}
procedure WMSize(var Msg: TWMSize); message WM_SIZE;
procedure WMEraseBkgnd(var Msg: TWMEraseBkgnd); message WM_ERASEBKGND;
{$ENDIF}
function GetSeries(Index: Integer): TNiceSeries;
function GetSeriesCount: Integer;
procedure DrawLegend(ACanvas: TCanvas);
procedure DrawTitle(ACanvas: TCanvas);
procedure SetAxisXTitle(const Value: string);
procedure SetAxisYTitle(const Value: string);
procedure BuildYAxis;
procedure DrawYAxis(ACanvas: TCanvas);
procedure DrawXAxis(ACanvas: TCanvas);
procedure DrawChart(ACanvas: TCanvas);
procedure BuildXAxis;
procedure ClearAxis;
procedure AdjustYAxis;
procedure SetAxisXOnePerValue(const Value: Boolean);
procedure SetShowXGrid(const Value: Boolean);
procedure SetShowYGrid(const Value: Boolean);
procedure SetShowXAxisLine(const Value: Boolean);
procedure SetShowYAxisLine(const Value: Boolean);
procedure CalculateSeries;
procedure DrawSeries(ACanvas: TCanvas; Index: Integer);
procedure AutoColors(ACanvas: TCanvas; Index: Integer; IsBar: Boolean);
procedure SetAxisXScale(const Value: Single);
procedure SetAxisYScale(const Value: Single);
procedure SetMonochrome(const Value: Boolean);
function GetLabel(Value: Double): string;
procedure SetSoftColors(const Value: Boolean);
function IsAxisXScaleStored: Boolean;
function IsAxisXTitleStored: Boolean;
function IsAxisYScaleStored: Boolean;
function IsAxisYTitleStored: Boolean;
function IsTitleStored: Boolean;
procedure SetAxisLineWidth(const Value: Integer);
protected
procedure Paint; override;
procedure Changed;
procedure ChartToClient(const AX, AY: Double; var X, Y: Integer);
procedure CreateHandle; override;
procedure ClipToRect(ACanvas: TCanvas; const ARect: TRect; AEnable: Boolean);
{$IFDEF FPC}
procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy; const AXProportion, AYProportion: Double); override;
{$ENDIF}
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure BeginUpdate;
procedure EndUpdate;
function AddSeries(AKind: TSeriesKind): TNiceSeries;
function ClientToChart(const X, Y: Integer; var AX, AY: Double): Boolean;
procedure RemoveSeries(ASeries: TNiceSeries);
procedure Clear;
property Series[Index: Integer]: TNiceSeries read GetSeries;
property SeriesCount: Integer read GetSeriesCount;
{$IFDEF FPC}
procedure FixDesignFontsPPI(const ADesignTimePPI: Integer); override;
procedure ScaleFontsPPI(const AToPPI: Integer; const AProportion: Double); override;
{$ELSE}
// Lazarus does not have TMetaFile...
function CreateMetafile: TMetafile;
procedure CopyToClipboard;
{$ENDIF}
function CreateBitmap(AWidth, AHeight: Integer): TBitmap;
procedure CopyToClipboardAsBitmap;
published
property AxisLineWidth: Integer read FAxisLineWidth write SetAxisLineWidth default 3;
property ShowLegend: Boolean read FShowLegend write SetShowLegend default true;
property ShowTitle: Boolean read FShowTitle write SetShowTitle default true;
property ShowXGrid: Boolean read FShowXGrid write SetShowXGrid default true;
property ShowYGrid: Boolean read FShowYGrid write SetShowYGrid default true;
property ShowXAxisLine: boolean read FShowXAxisLine write SetShowXAxisLine default false;
property ShowYAxisLine: Boolean read FShowYAxisLine write SetShowYAxisLine default false;
property Title: string read FTitle write SetTitle stored IsTitleStored;
property TitleFont: TFont read FTitleFont write SetTitleFont;
property AxisXTitle: string read FAxisXTitle write SetAxisXTitle stored IsAxisXTitleStored;
property AxisYTitle: string read FAxisYTitle write SetAxisYTitle stored IsAxisYTitleStored;
property AxisXOnePerValue: Boolean read FAxisXOnePerValue write SetAxisXOnePerValue default false;
property AxisXScale: Single read FAxisXScale write SetAxisXScale stored IsAxisXScaleStored;
property AxisYScale: Single read FAxisYScale write SetAxisYScale stored IsAxisYScaleStored;
property GridColor: TColor read FGridColor write SetGridColor default clGray;
property Monochrome: Boolean read FMonochrome write SetMonochrome default false;
property SoftColors: Boolean read FSoftColors write SetSoftColors default false;
property TickLength: Integer read FTickLength write SetTickLength default 2;
property Align;
property Anchors;
property BevelInner default bvNone;
property BevelOuter default bvNone;
property BorderStyle default bsSingle;
{$IFDEF FPC}
property BorderSpacing;
{$ELSE}
property BevelKind;
{$ENDIF}
property OnMouseMove;
property OnMouseDown;
property OnMouseUp;
property PopupMenu;
end;
procedure CalculateAxis(AMin, AMax: Double; Count: Integer;
out Delta, Lowest: Double);
procedure Register;
implementation
{$R NiceChart.res}
{$IFDEF FPC}
{$R nicechart_images.res}
{$ENDIF}
uses
ClipBrd;
procedure Register;
begin
RegisterComponents('priyatna.org', [TNiceChart]);
end;
const
OUTER_MARGIN = 20;
INNER_MARGIN = 10;
SMALL_MARGIN = 2;
LEGEND_ITEM = 20;
AXIS_DEFSIZE = 50;
Formatter = '0.##';
type
PXYInfo = ^TXYInfo;
TXYInfo = record
X, Y: Double;
Px, Py: Integer;
Rc: TRect;
Hint: string;
end;
PAxisInfo = ^TAxisInfo;
TAxisInfo = record
Value: Double;
Px, Py: Integer;
Caption: string;
end;
function GetMan10(Value: Double): Double;
var
Str: string;
begin
Str := UpperCase(Format('%E', [Value]));
Result := StrToFloat('1E' + Copy(Str, Pos('E', Str) + 1, Length(Str)));
end;
procedure CalculateAxis(AMin, AMax: Double; Count: Integer;
out Delta, Lowest: Double);
label
Retry;
var
c, n, m10: Double;
begin
c := Max(2, Count-1);
n := (Abs(AMax - AMin) / c);
m10 := GetMan10(n);
Delta := 0;
while (Delta < n)
do Delta := Delta + (0.5 * m10);
if (Delta = 0) then
begin
Delta := 1;
Lowest := AMin - (Count div 2);
Exit;
end;
Retry:
Lowest := Trunc(AMin / Delta) * Delta;
if (Lowest > AMin)
then Lowest := Lowest - Delta;
if ((Lowest + (Delta * c)) < AMax) then
begin
Delta := Delta + (0.5 * m10);
goto Retry;
end;
end;
{ TNiceSeries }
constructor TNiceSeries.Create(AChart: TNiceChart; AKind: TSeriesKind);
begin
inherited Create;
Chart := AChart;
Values := TList.Create;
FCaption := 'Series';
Spline := TBSpline.Create;
FKind := AKind;
FLineWidth := 1;
end;
destructor TNiceSeries.Destroy;
begin
Spline.Free;
InternalClear;
Values.Free;
inherited Destroy;
end;
procedure TNiceSeries.InternalClear;
var
x: Integer;
begin
for x := 0 to Values.Count-1 do
Dispose(PXYInfo(Values[x]));
Values.Clear;
end;
procedure TNiceSeries.Clear;
begin
InternalClear;
Chart.Changed;
end;
function TNiceSeries.AddXY(AX, AY: Double; AHint: string): Integer;
var
Info: PXYInfo;
begin
Info := New(PXYInfo);
Info^.X := AX;
Info^.Y := AY;
Info^.Px := 0;
Info^.Py := 0;
Info^.Rc := Rect(0, 0, 0, 0);
Info^.Hint := AHint;
Result := Values.Add(Info);
Chart.Changed;
end;
procedure TNiceSeries.Remove(Index: Integer);
var
P: PXYInfo;
begin
if (Index >= 0) and (Index < Values.Count) then
begin
P := Values[Index];
Values.Remove(P);
Dispose(P);
Chart.Changed;
end;
end;
function TNiceSeries.GetMaxXValue: Double;
var
x: Integer;
begin
Result := -MaxDouble;
for x := 0 to Values.Count-1 do
Result := Max(Result, PXYInfo(Values[x])^.X);
end;
function TNiceSeries.GetMinXValue: Double;
var
x: Integer;
begin
Result := MaxDouble;
for x := 0 to Values.Count-1 do
Result := Min(Result, PXYInfo(Values[x])^.X);
end;
function TNiceSeries.GetMaxYValue: Double;
var
x: Integer;
begin
Result := -MaxDouble;
for x := 0 to Values.Count-1 do
Result := Max(Result, PXYInfo(Values[x])^.Y);
end;
function TNiceSeries.GetMinYValue: Double;
var
x: Integer;
begin
Result := MaxDouble;
for x := 0 to Values.Count-1 do
Result := Min(Result, PXYInfo(Values[x])^.Y);
end;
procedure TNiceSeries.SetCaption(const Value: string);
begin
if (FCaption <> Value) then
begin
FCaption := Value;
Chart.Changed;
end;
end;
procedure TNiceSeries.SetKind(const Value: TSeriesKind);
begin
if (FKind <> Value) then
begin
FKind := Value;
Chart.Changed;
end;
end;
procedure TNiceSeries.SetLineWidth(const Value: Integer);
begin
if FLineWidth <> Value then
begin
FLineWidth := Value;
Chart.Changed;
end;
end;
{ TNiceChart }
constructor TNiceChart.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ParentColor := False;
ParentBackground := False;
ParentFont := False;
Temp := TStringList.Create;
Width := 300;
Height := 200;
Color := clWhite;
BevelOuter := bvNone;
BevelInner := bvNone;
BorderStyle := bsSingle;
ControlStyle := ControlStyle + [csNeedsBorderPaint];
List := TList.Create;
FShowLegend := True;
FShowTitle := True;
FShowXGrid := True;
FShowYGrid := True;
FGridColor := clGray;
FMonochrome := False;
FTitle := 'Chart Title';
FTitleFont := TFont.Create;
FTitleFont.Name := 'Arial';
FTitleFont.Size := 14;
FTitleFont.Style := [];
FTitleFont.OnChange := TitleFontChanged;
FNormalFont := TFont.Create;
FNormalFont.Name := 'Arial';
FAxisLineWidth := 3;
FAxisXTitle := 'X Axis';
FAxisYTitle := 'Y Axis';
FAxisXScale := 1;
FAxisYScale := 1;
FTickLength := 2;
FOuterMargin := OUTER_MARGIN;
FInnerMargin := INNER_MARGIN;
FSmallMargin := SMALL_MARGIN;
FAxisDefSize := AXIS_DEFSIZE;
FLegendItemSize := LEGEND_ITEM;
XAxis := TList.Create;
YAxis := TList.Create;
FUpdating := False;
end;
destructor TNiceChart.Destroy;
var
x: Integer;
begin
for x := 0 to 15 do
begin
if Assigned(Brushes[x])
then Brushes[x].Free;
end;
InternalClear;
List.Free;
FTitleFont.Free;
FNormalFont.Free;
XAxis.Free;
YAxis.Free;
Temp.Free;
inherited Destroy;
end;
procedure TNiceChart.InternalClear;
var
x: Integer;
begin
for x := 0 to List.Count-1
do TNiceSeries(List[x]).Free;
ClearAxis;
List.Clear;
end;
procedure TNiceChart.Paint;
begin
if HandleAllocated
then DoPaint;
end;
procedure TNiceChart.DoPaint;
begin
InternalPaint(Canvas);
end;
procedure TNiceChart.SetGridColor(const Value: TColor);
begin
if FGridColor <> value then
begin
FGridColor := Value;
Invalidate;
end;
end;
procedure TNiceChart.SetMonochrome(const Value: Boolean);
begin
if (FMonochrome <> Value) then
begin
FMonochrome := Value;
Changed;
end;
end;
procedure TNiceChart.SetSoftColors(const Value: Boolean);
begin
if (FSoftColors <> Value) then
begin
FSoftColors := Value;
Changed;
end;
end;
procedure TNiceChart.SetShowLegend(const Value: Boolean);
begin
if (FShowLegend <> Value) then
begin
FShowLegend := Value;
Changed;
end;
end;
procedure TNiceChart.SetAxisXOnePerValue(const Value: Boolean);
begin
if (FAxisXOnePerValue <> Value) then
begin
FAxisXOnePerValue := Value;
Changed;
end;
end;
procedure TNiceChart.SetShowTitle(const Value: Boolean);
begin
if (FShowTitle <> Value) then
begin
FShowTitle := Value;
Changed;
end;
end;
procedure TNiceChart.SetTitle(const Value: string);
begin
if (FTitle <> Value) then
begin
FTitle := Value;
Changed;
end;
end;
procedure TNiceChart.SetTitleFont(const Value: TFont);
begin
FTitleFont.Assign(Value);
end;
procedure TNiceChart.TitleFontChanged(Sender: TObject);
begin
Changed;
end;
procedure TNiceChart.SetAxisXTitle(const Value: string);
begin
if (FAxisXTitle <> Value) then
begin
FAxisXTitle := Value;
Changed;
end;
end;
procedure TNiceChart.SetAxisYTitle(const Value: string);
begin
if (FAxisYTitle <> Value) then
begin
FAxisYTitle := Value;
Changed;
end;
end;
function TNiceChart.IsAxisXScaleStored: Boolean;
begin
Result := FAxisXScale <> 1.0;
end;
function TNiceChart.IsAxisXTitleStored: Boolean;
begin
Result := FaxisXTitle <> '';
end;
function TNiceChart.IsAxisYScaleStored: Boolean;
begin
Result := FAxisYScale <> 1.0;
end;
function TNiceChart.IsAxisYTitleStored: Boolean;
begin
Result := FAxisYTitle <> '';
end;
function TNiceChart.IsTitleStored: Boolean;
begin
Result := FTitle <> '';
end;
procedure TNiceChart.SetAxisXScale(const Value: Single);
begin
if (FAxisXScale <> Value) then
begin
FAxisXScale := Value;
if (FAxisXScale = 0)
then FAxisXScale := 1;
Changed;
end;
end;
procedure TNiceChart.SetAxisLineWidth(const Value: Integer);
begin
if (FAxisLineWidth <> Value) then
begin
FAxisLineWidth := Value;
if FAxisLineWidth < 1 then
FAxisLineWidth := 1;
Changed;
end;
end;
procedure TNiceChart.SetAxisYScale(const Value: Single);
begin
if (FAxisYScale <> Value) then
begin
FAxisYScale := Value;
if (FAxisYScale = 0)
then FAxisYScale := 1;
Changed;
end;
end;
procedure TNiceChart.SetShowXAxisLine(const Value: Boolean);
begin
if (FShowXAxisLine <> Value) then
begin
FShowXAxisLine := Value;
Invalidate;
end;
end;
procedure TNiceChart.SetShowXGrid(const Value: Boolean);
begin
if (FShowXGrid <> Value) then
begin
FShowXGrid := Value;
Invalidate;
end;
end;
procedure TNiceChart.SetShowYAxisLine(const Value: Boolean);
begin
if (FShowYAxisLine <> Value) then
begin
FShowYAxisLine := Value;
Invalidate;
end;
end;
procedure TNiceChart.SetShowYGrid(const Value: Boolean);
begin
if (FShowYGrid <> Value) then
begin
FShowYGrid := Value;
Invalidate;
end;
end;
procedure TNiceChart.SetTickLength(const Value: Integer);
begin
if (FTickLength <> Value) and (Value >= 0) then
begin
FTickLength := Value;
Invalidate;
end;
end;
procedure TNiceChart.BeginUpdate;
begin
FUpdating := True;
end;
procedure TNiceChart.EndUpdate;
begin
FUpdating := False;
Calculate(ClientWidth, ClientHeight);
Invalidate;
end;
procedure TNiceChart.Changed;
begin
if not FUpdating then
begin
Calculate(ClientWidth, ClientHeight);
Invalidate;
end;
end;
procedure TNiceChart.WMSize(var Msg: {$IFDEF FPC}TLMSize{$ELSE}TWMSize{$ENDIF});
begin
inherited;
Changed;
end;
procedure TNiceChart.WMEraseBkgnd(var Msg: {$IFDEF FPC}TLMEraseBkgnd{$ELSE}TWMEraseBkgnd{$ENDIF});
begin
Msg.Result := 1;
end;
function TNiceChart.GetSeries(Index: Integer): TNiceSeries;
begin
Result := TNiceSeries(List[Index]);
end;
function TNiceChart.AddSeries(AKind: TSeriesKind): TNiceSeries;
begin
Result := TNiceSeries.Create(Self, AKind);
List.Add(Result);
end;
procedure TNiceChart.Clear;
begin
InternalClear;
Changed;
end;
procedure TNiceChart.RemoveSeries(ASeries: TNiceSeries);
begin
if Assigned(ASeries) then
begin
List.Remove(ASeries);
ASeries.Free;
Changed;
end;
end;
function TNiceChart.GetSeriesCount: Integer;
begin
Result := List.Count;
end;
procedure TNiceChart.DrawLegend(ACanvas: TCanvas);
var
x, y, l, t: Integer;
th, g: Integer;
begin
with ACanvas do
begin
Pen.Width := 1;
Pen.Style := psSolid;
Font.Assign(FNormalFont);
g := TextHeight('Ag');
th := (FLegendItemSize - g) div 2;
Brush.Style := bsSolid;
Brush.Color := clBlack;
FillRect(Rect(RcLegend.Right, RcLegend.Top + 3, RcLegend.Right + 3, RcLegend.Bottom + 3));
FillRect(Rect(RcLegend.Left + 3, RcLegend.Bottom, RcLegend.Right + 3, RcLegend.Bottom + 3));
Brush.Style := bsClear;
Rectangle(RcLegend);
Brush.Style := bsClear;
l := RcLegend.Left + FInnerMargin + FLegendItemSize + FSmallMargin;
for x := 0 to List.Count-1 do
begin
Temp.Text := Trim(TNiceSeries(List[x]).FCaption);
t := RcLegend.Top + TNiceSeries(List[x]).Top;
for y := 0 to Temp.Count-1 do
begin
TextOut(l, t + th, Trim(Temp[y]));
Inc(t, g);
end;
end;
end;
end;
procedure TNiceChart.DrawTitle(ACanvas: TCanvas);
begin
with ACanvas do
begin
Brush.Style := bsClear;
Font.Assign(FTitleFont);
DrawText(Handle, PChar(FTitle), Length(FTitle), RcTitle,
DT_CENTER or DT_VCENTER or DT_WORDBREAK);
end;
end;
procedure RotTextOut(ACanvas: TCanvas; x, y, Angle: Integer; Txt: String);
{$IFNDEF FPC}
var
RotFont, OldFont: Integer;
FBold, FItalic, FUnderline, FStrikeOut: integer;
{$ENDIF}
begin
if (Txt = '')
then Exit;
SetBkMode(ACanvas.Handle, TRANSPARENT);
{$IFDEF FPC}
ACanvas.Font.Orientation := Angle * 10;
ACanvas.TextOut(x, y, Txt);
ACanvas.Font.Orientation := 0;
{$ELSE}
if (fsItalic in ACanvas.Font.Style)
then FItalic := 1
else FItalic := 0;
if (fsUnderline in ACanvas.Font.Style)
then FUnderline := 1
else FUnderline := 0;
if (fsStrikeOut in ACanvas.Font.Style)
then FStrikeOut := 1
else FStrikeOut := 0;
if (fsBold in ACanvas.Font.Style)
then FBold := FW_BOLD
else FBold := FW_NORMAL;
RotFont := CreateFont(ACanvas.Font.Height, 0, Angle*10, 0,
FBold, FItalic, FUnderline, FStrikeOut, 1, 4, $10,
ANTIALIASED_QUALITY, 4, PChar(ACanvas.Font.Name));
OldFont := SelectObject(ACanvas.Handle, RotFont);
TextOut(ACanvas.Handle, x, y, PChar(Txt), Length(Txt));
SelectObject(ACanvas.Handle, OldFont);
DeleteObject(RotFont);
{$ENDIF}
end;
procedure TNiceChart.InternalPaint(ACanvas: TCanvas);
begin
with ACanvas do
begin
Pen.Color := clBlack;
Pen.Width := 1;
Brush.Style := bsSolid;
Brush.Color := Color;
FillRect(Rect(0, 0, DestWidth, DestHeight));
end;
if FShowLegend and (List.Count > 0)
then DrawLegend(ACanvas);
if FShowTitle and (FTitle <> '')
then DrawTitle(ACanvas);
DrawXAxis(ACanvas);
DrawYAxis(ACanvas);
DrawChart(ACanvas);
end;
procedure TNiceChart.Calculate(AWidth, AHeight: Integer);
var
bmp: TBitmap;
begin
if Canvas.HandleAllocated then
DoCalculate(Canvas, AWidth, AHeight)
else
begin
// Use an auxiliary bitmap in case of early calls when the Canvas has not handle, yet.
bmp := TBitmap.Create;
try
bmp.Width := AWidth;
bmp.Height := AHeight;
DoCalculate(bmp.Canvas, AWidth, AHeight);
finally
bmp.Free;
end;
end;
end;
procedure TNiceChart.DoCalculate(ACanvas: TCanvas; AWidth, AHeight: Integer);
var
x, w, h, y, g: Integer;
Titled: Boolean;
begin
ClearAxis;
DestWidth := AWidth;
DestHeight := AHeight;
RcChart := Rect(0, 0, DestWidth, DestHeight);
MarkSize := Max(1, Round(DestWidth * 0.006));
InflateRect(RcChart, -FOuterMargin, -FOuterMargin);
Titled := False;
if FShowTitle and (FTitle <> '') then
begin
ACanvas.Font.Assign(TitleFont);
w := ACanvas.TextHeight(FTitle);
RcTitle := Rect(RcChart.Left, RcChart.Top, RcChart.Right, RcChart.Left + w);
DrawText(ACanvas.Handle, PChar(FTitle), Length(FTitle), RcTitle,
DT_CENTER or DT_VCENTER or DT_WORDBREAK or DT_CALCRECT);
RcChart.Top := RcTitle.Bottom + FInnerMargin;
Titled := True;
end else
SetRectEmpty(RcTitle);
ACanvas.Font.Assign(FNormalFont);
h := ACanvas.TextHeight('Ag');
RcChart.Bottom := RcChart.Bottom - (2 * h) - FInnerMargin - FTickLength - FSmallmargin;
BuildYAxis;
w := 0;
for x := 0 to YAxis.Count-1 do
w := Max(w, ACanvas.TextWidth(PAxisInfo(YAxis[x])^.Caption));
RcChart.Left := RcChart.Left + h + FInnerMargin + w + FTickLength + FSmallMargin;
RcTitle.Left := RcChart.Left;
RcTitle.Right := RcChart.Right;
AdjustYAxis;
if FShowLegend and (List.Count > 0) then
begin
ACanvas.Font.Assign(FNormalFont);
w := 0;
h := FInnerMargin;
g := ACanvas.TextHeight('Ag');
for x := 0 to List.Count-1 do
begin
TNiceSeries(List[x]).Top := h;
Temp.Text := Trim(TNiceSeries(List[x]).FCaption);
for y := 0 to Temp.Count-1 do
w := Max(w, ACanvas.TextWidth(Trim(Temp[y])));
h := h + Max(FLegendItemSize, Temp.Count * g);
if (x <> List.Count-1)
then h := h + FSmallMargin;
end;
w := w + (2 * FInnerMargin) + FLegendItemSize + FSmallMargin;
h := h + FInnerMargin;
RcLegend := Rect(RcChart.Right - w, RcChart.Top, RcChart.Right, RcChart.Top + h);
RcChart.Right := RcLegend.Left - (2 * FInnerMargin);
if Titled
then RcTitle.Right := RcChart.Right;
end else
SetRectEmpty(RcLegend);
BuildXAxis;
CalculateSeries;
end;
procedure TNiceChart.ClearAxis;
var
x: Integer;
begin
for x := 0 to XAxis.Count-1
do Dispose(PAxisInfo(XAxis[x]));
XAxis.Clear;
for x := 0 to YAxis.Count-1
do Dispose(PAxisInfo(YAxis[x]));
YAxis.Clear;
end;
type
PDoubleList = ^TDoubleList;
TDoubleList = array [0..0] of Double;
procedure QuickSortDouble(SortList: PDoubleList; L, R: Integer);
var
I, J: Integer;
P, T: Double;
begin
repeat
I := L;
J := R;
P := SortList^[(L + R) shr 1];
repeat
while (SortList^[I] < P)
do Inc(I);
while (SortList^[J] > P)
do Dec(J);
if I <= J then
begin
T := SortList^[I];
SortList^[I] := SortList^[J];
SortList^[J] := T;
Inc(I);
Dec(J);
end;
until I > J;
if L < J
then QuickSortDouble(SortList, L, J);
L := I;
until I >= R;
end;
function TNiceChart.GetLabel(Value: Double): string;
begin
if (Value = UndefinedValueChart)
then Result := '~'
else Result := FormatFloat(Formatter, Value);
end;
procedure TNiceChart.BuildXAxis;
var
x, y, w: Integer;
mi, ma: Double;
Cnt, i, n: Integer;
Delta, Lowest, l: Double;
P: PAxisInfo;
tmp: PDoubleList;
Vals: TList;
Last: Double;
Scale: Double;
dx: Integer;
begin
if (List.Count = 0) or ChartEmpty
then Exit;
BarCount := 0;
for x := 0 to List.Count-1 do
begin
if (TNiceSeries(List[x]).FKind = skBar)
then Inc(BarCount);
end;
if (BarCount > 0)
then FAxisXOnePerValue := True;
if FAxisXOnePerValue then
begin
w := RcChart.Right - RcChart.Left;
Cnt := 0;
for x := 0 to List.Count-1
do Cnt := Cnt + Series[x].Values.Count;
GetMem(tmp, Cnt * SizeOf(Double));
i := 0;
for x := 0 to List.Count-1 do
begin
Vals := TNiceSeries(List[x]).Values;
for y := 0 to Vals.Count-1 do
begin
tmp^[i] := PXYInfo(Vals[y])^.X;
Inc(i);
end;
end;
QuickSortDouble(tmp, 0, Cnt-1);
n := 0;
Last := MaxDouble;
for x := 0 to Cnt-1 do
begin
l := tmp^[x];
if (l = Last)
then Continue;
Inc(n);
Last := l;
end;
if (BarCount > 0) then
begin
Scale := w / n;
dx := Round(Scale / 2);
BarWidth := Round(Scale);
end else
begin
Scale := w / (n-1);
dx := 0;
end;
Last := MaxDouble;
i := 0;
for x := 0 to Cnt-1 do
begin
l := tmp^[x];
if (l = Last)
then Continue;
P := New(PAxisInfo);
P^.Value := l;
P^.Py := RcChart.Bottom;
P^.Px := RcChart.Left + dx + Round(i * Scale);
P^.Caption := GetLabel(l / FAxisXScale);
XAxis.Add(P);
Last := l;
Inc(i);
end;
FreeMem(tmp);
end else
begin
w := RcChart.Right - RcChart.Left;
Cnt := (w div FAxisDefSize) + 1;
mi := MaxDouble;
ma := -MaxDouble;
for x := 0 to List.Count-1 do
begin
mi := Min(mi, Series[x].GetMinXValue);
ma := Max(ma, Series[x].GetMaxXValue);
end;
CalculateAxis(mi, ma, Cnt, Delta, Lowest);
Scale := w / (Delta * Max(1, Cnt-1));
for x := 0 to Cnt-1 do
begin
l := x * Delta;
P := New(PAxisInfo);
P^.Py := RcChart.Bottom;
P^.Px := RcChart.Left + Round(l * Scale);
P^.Caption := GetLabel((Lowest + l) / FAxisXScale);
XAxis.Add(P);
end;
FXTranslate.MinValue := Lowest;
FXTranslate.Scale := Scale;
FXTranslate.Base := RcChart.Left;
end;
end;
procedure TNiceChart.BuildYAxis;
var
x, w: Integer;
mi, ma: Double;
Cnt: Integer;
Delta, Lowest, t: Double;
P: PAxisInfo;
Scale: Double;
begin
if (List.Count = 0)
then Exit;
w := RcChart.Bottom - RcChart.Top;
Cnt := (w div FAxisDefSize) + 1;
ChartEmpty := True;
mi := MaxDouble;
ma := -MaxDouble;
for x := 0 to List.Count-1 do
begin
if (Series[x].Values.Count > 0) then
begin
mi := Min(mi, Series[x].GetMinYValue);
ma := Max(ma, Series[x].GetMaxYValue);
ChartEmpty := False;
end;
end;
if ChartEmpty
then Exit;
CalculateAxis(mi, ma, Cnt, Delta, Lowest);
Scale := w / (Delta * Max(1, Cnt-1));
for x := 0 to Cnt-1 do
begin
t := x * Delta;
P := New(PAxisInfo);
P^.Value := Lowest + t;
P^.Py := Round(t * Scale);
P^.Caption := GetLabel((Lowest + t) / FAxisYScale);
YAxis.Add(P);
end;
FYTranslate.MinValue := Lowest;
FYTranslate.Scale := Scale;
end;
procedure TNiceChart.AdjustYAxis;
var
x: Integer;
P: PAxisInfo;
l: Integer;
begin
l := RcChart.Left;
YZero := -1;
for x := 0 to YAxis.Count-1 do
begin
P := PAxisInfo(YAxis[x]);
P^.Px := l;
P^.Py := RcChart.Bottom - P^.Py;
if (P^.Value = 0)
then YZero := P^.Py;
end;
if (YZero = -1)
then YZero := RcChart.Bottom;
FYTranslate.Base := RcChart.Bottom;
end;
procedure TNiceChart.DrawXAxis(ACanvas: TCanvas);
var
l, t, w, i: Integer;
P: PAxisInfo;
Str: string;
Last: Integer;
dummy, origin: Integer;
begin
with ACanvas do
begin
if FShowXGrid then
begin
Pen.Style := psDot;
Pen.Width := 1;
if FMonochrome then
Pen.Color := clBlack
else
Pen.Color := FGridColor;
t := RcChart.Top;
for i := 0 to XAxis.Count-1 do
begin
P := PAxisInfo(XAxis[i]);
if (P^.Px <= RcChart.Left) or (P^.Px >= RcChart.Right) then
Continue;
MoveTo(P^.Px, P^.Py);
LineTo(P^.px, t);
end;
end;
// Drawing the y axis here seems to be misplaced. But otherwise
// it could be overpainted by an x grid line.
if FShowYAxisLine then
begin
ChartToClient(0, 0, origin, dummy);
if (origin > RcChart.Left) and (origin < RcChart.Right) then
begin
Pen.Style := psSolid;
Pen.Color := clBlack;
Pen.Width := 1;
MoveTo(origin, RcChart.Top);
LineTo(origin, RcChart.Bottom);
end;
end;
Pen.Style := psSolid;
Pen.Width := FAxisLineWidth;
Pen.Color := clBlack;
MoveTo(RcChart.Left, RcChart.Bottom);
LineTo(RcChart.Right, RcChart.Bottom);
Font.Assign(FNormalFont);
Font.Style := [fsBold];
w := RcChart.Right - RcChart.Left;
t := RcChart.Bottom + FInnerMargin + FTickLength + FSmallMargin + TextHeight('Ag');
l := RcChart.Left + ((w - TextWidth(FAxisXTitle)) div 2);
TextOut(l, t, FAxisXTitle);
Font.Assign(FNormalFont);
Pen.Color := clBlack;
Pen.Width := 1;
Pen.Style := psSolid;
t := RcChart.Bottom + FTickLength + SMALL_MARGIN;
Last := 0;
for i := 0 to XAxis.Count-1 do
begin
P := PAxisInfo(XAxis[i]);
Str := P^.Caption;
w := TextWidth(Str);
l := P^.Px - (w div 2);
if (Last < l) then
begin
TextOut(l, t, Str);
Last := l + w;
end;
MoveTo(P^.Px, P^.Py);
LineTo(P^.Px, P^.Py + FTickLength + 1);
end;
end;
end;
procedure TNiceChart.DrawYAxis(ACanvas: TCanvas);
var
l, t, h, w: Integer;
i: Integer;
Str: string;
P: PAxisInfo;
origin, dummy: Integer;
begin
with ACanvas do
begin
if FShowYGrid then
begin
Pen.Style := psDot;
Pen.Width := 1;
if FMonochrome then
Pen.Color := clBlack
else
Pen.Color := FGridColor;
for i := 0 to YAxis.Count-1 do
begin
P := PAxisInfo(YAxis[i]);
if (P^.Py <= RcChart.Top) or (P^.Py >= RcChart.Bottom) then
Continue;
MoveTo(P^.Px, P^.Py);
LineTo(RcChart.Right, P^.Py);
end;
Pen.Color := clBlack;
end;
// Drawing the x axis here seems to be misplaced. But otherwise
// it could be overpainted by a y grid line.
if FShowXAxisLine then
begin
ChartToClient(0, 0, dummy, origin);
if (origin > RcChart.Top) and (origin < RcChart.Bottom) then
begin
Pen.Style := psSolid;
Pen.Color := clBlack;
Pen.Width := 1;
MoveTo(RcChart.Left, origin);
LineTo(RcChart.Right, origin);
end;
end;
Pen.Style := psSolid;
Pen.Width := FAxisLineWidth;
MoveTo(RcChart.Left, RcChart.Top);
LineTo(RcChart.Left, RcChart.Bottom);
h := RcChart.Bottom - RcChart.Top;
l := FOuterMargin;
Font.Assign(FNormalFont);
Font.Style := [fsBold];
t := RcChart.Bottom - ((h - TextWidth(FAxisYTitle)) div 2);
RotTextOut(ACanvas, l, t, 90, FAxisYTitle);
Font.Assign(FNormalFont);
Pen.Color := clBlack;
Pen.Width := 1;
Pen.Style := psSolid;
l := RcChart.Left - FTickLength - SMALL_MARGIN;
for i := 0 to YAxis.Count-1 do
begin
P := PAxisInfo(YAxis[i]);
Str := P^.Caption;
w := TextWidth(Str);
h := TextHeight(Str);
t := P^.Py - (h div 2);
TextOut(l - w, t, Str);
MoveTo(P^.Px - FTickLength, P^.Py);
LineTo(P^.Px, P^.Py);
end;
end;
end;
procedure TNiceChart.DrawChart(ACanvas: TCanvas);
var
x: Integer;
begin
with ACanvas do
begin
Brush.Style := bsClear;
Pen.Style := psSolid;
Pen.Width := 1;
MoveTo(RcChart.Left, RcChart.Top);
LineTo(RcChart.Right, RcChart.Top);
LineTo(RcChart.Right, RcChart.Bottom);
end;
for x := 0 to List.Count-1 do
begin
if (TNiceSeries(List[x]).FKind = skBar)
then DrawSeries(ACanvas, x);
end;
for x := 0 to List.Count-1 do
begin
if (TNiceSeries(List[x]).FKind <> skBar)
then DrawSeries(ACanvas, x);
end;
end;
//-----------------------------------------------------------------------------//
procedure MarkerRectangle(ACanvas: TCanvas; X, Y, Size: Integer);
begin
ACanvas.Rectangle(X-Size, Y-Size, X+Size, Y+Size);
end;
procedure MarkerCircle(ACanvas: TCanvas; X, Y, Size: Integer);
begin
ACanvas.Ellipse(X-Size, Y-Size, X+Size, Y+Size);
end;
procedure MarkerTriangle1(ACanvas: TCanvas; X, Y, Size: Integer);
begin
ACanvas.Polygon([Point(x, y-Size), Point(x+Size, y+Size), Point(x-Size, y+Size)]);
end;
procedure MarkerTriangle2(ACanvas: TCanvas; X, Y, Size: Integer);
begin
ACanvas.Polygon([Point(x+Size, y-Size), Point(x-Size, y-Size), Point(x, y+Size)]);
end;
procedure MarkerDiamond(ACanvas: TCanvas; X, Y, Size: Integer);
begin
ACanvas.Polygon([Point(x, y-Size), Point(x+Size, y), Point(x, y+Size), Point(x-Size, y)]);
end;
const
Colors1: array [0..13] of TColor = (
clRed, clBlue, clGreen, clFuchsia, clNavy, clMaroon, clBlack, clOlive,
clPurple, clTeal, clGray, clLime, clYellow, clAqua
);
Colors2: array [0..13] of TColor = (
$0066C2FF, $005AFADA, $00F4C84D, $00B54DF4, $00669FFF, $00F44D5A,
$0066E0FF, $0066FFFF, $00F44DAE, $006863FE, $004DF474, $00F4934D,
clSilver, clGray
);
Markers: array [0..4] of TMarkerProc = (
MarkerRectangle, MarkerCircle, MarkerTriangle1, MarkerTriangle2,
MarkerDiamond);
procedure TNiceChart.AutoColors(ACanvas: TCanvas; Index: Integer; Isbar: Boolean);
var
cl: TColor;
Idx: Integer;
Bmp: TBitmap;
begin
if FMonochrome
then cl := clBlack else
if FSoftColors
then cl := Colors2[Index mod 14]
else cl := Colors1[Index mod 14];
Marker := Markers[Index mod 5];
with ACanvas do
begin
Pen.Color := cl;
Brush.Bitmap := nil;
Brush.Style := bsSolid;
if IsBar
then Brush.Color := cl
else Brush.Color := clWhite;
if IsBar and FMonochrome then
begin
Idx := Index mod 16;
if not Assigned(Brushes[Idx]) then
begin
Bmp := TBitmap.Create;
Bmp.LoadFromResourceName(hInstance, Format('brush%.2d', [Idx+1]));
Brushes[Idx] := Bmp;
end;
Brush.Bitmap := Brushes[Idx];
end;
end;
end;
//-----------------------------------------------------------------------------//
procedure TNiceChart.DrawSeries(ACanvas: TCanvas; Index: Integer);
var
x: Integer;
P: PXYInfo;
l, t, t2: Integer;
Sr: TNiceSeries;
Rc: TRect;
begin
Sr := TNiceSeries(List[Index]);
AutoColors(ACanvas, Index, sr.FKind = skBar);
with ACanvas do
begin
if (sr.FKind = skBar) then
begin
ClipToRect(ACanvas, RcChart, true);
try
Pen.Width := 1;
for x := 0 to Sr.Values.Count-1 do
begin
P := PXYInfo(Sr.Values[x]);
Rectangle(P^.Rc);
end;
finally
ClipToRect(ACanvas, RcChart, false);
end;
end else
begin
ClipToRect(ACanvas, RcChart, true);
try
if (sr.FKind = skLine) then
begin
Pen.Width := sr.LineWidth;
for x := 0 to Sr.Values.Count-1 do
begin
P := PXYInfo(Sr.Values[x]);
if (x = 0)
then MoveTo(P^.Px, P^.Py)
else LineTo(P^.Px, P^.Py);
end;
end else
if (sr.FKind = skSmooth) then
begin
Pen.Width := sr.LineWidth;
sr.Spline.Draw(ACanvas);
end;
finally
ClipToRect(ACanvas, RcChart, false);
end;
Pen.Width := 1;
for x := 0 to Sr.Values.Count-1 do
begin
P := PXYInfo(Sr.Values[x]);
if PtInRect(RcChart, Point(P^.Px, P^.Py)) then
Marker(ACanvas, P^.Px, P^.Py, MarkSize);
end;
end;
if FShowLegend then
begin
l := RcLegend.Left + FInnerMargin;
t := RcLegend.Top + Sr.Top;
if (sr.FKind = skBar) then
begin
Rc := Rect(l, t, l + FLegendItemSize, t + FLegendItemSize);
InflateRect(Rc, -2, -2);
Rectangle(Rc);
end else
begin
Pen.Width := sr.LineWidth;
t2 := t + (FLegendItemSize div 2);
MoveTo(l, t2);
LineTo(l + FLegendItemSize, t2);
Pen.Width := 1;
Marker(ACanvas, l + (FLegendItemSize div 2), t2, MarkSize);
end;
end;
end;
end;
procedure TNiceChart.CalculateSeries;
var
x, y: Integer;
Values: TList;
P: PXYInfo;
S: TBSpline;
Vertex: TVertex;
sr: TNiceSeries;
bw, rw, bi, dx, l: Integer;
begin
if (List.Count = 0) or ChartEmpty
then Exit;
bi := 0;
bw := 0;
if (BarCount > 0)
then bw := Round(BarWidth / (BarCount + 1));
for x := 0 to List.Count-1 do
begin
sr := TNiceSeries(List[x]);
s := sr.Spline;
s.Clear;
Values := sr.Values;
case sr.FKind of
skBar:
begin
dx := Round(-(BarWidth / 2) + (bw / 2) + (bi * bw) + (bw * 0.1));
rw := Round(bw * 0.8);
for y := 0 to Values.Count-1 do
begin
P := PXYInfo(Values[y]);
ChartToClient(P^.X, P^.Y, P^.Px, P^.Py);
l := P^.Px + dx;
if (P^.Y < 0)
then P^.Rc := Rect(l, YZero, l + rw, P^.Py)
else P^.Rc := Rect(l, P^.Py, l + rw, YZero);
end;
Inc(bi);
end;
skLine:
begin
for y := 0 to Values.Count-1 do
begin
P := PXYInfo(Values[y]);
ChartToClient(P^.X, P^.Y, P^.Px, P^.Py);
P^.Rc := Rect(P^.Px-MarkSize, P^.Py-MarkSize, P^.Px+MarkSize, P^.Py+MarkSize);
end;
end;
skSmooth:
begin
for y := 0 to Values.Count-1 do
begin
P := PXYInfo(Values[y]);
ChartToClient(P^.X, P^.Y, P^.Px, P^.Py);
P^.Rc := Rect(P^.Px-MarkSize, P^.Py-MarkSize, P^.Px+MarkSize, P^.Py+MarkSize);
Vertex.X := P^.Px;
Vertex.Y := P^.Py;
s.AddPoint(Vertex);
end;
s.Interpolated := True;
s.Fragments := s.NumberOfPoints * 20;
end;
end;
end;
end;
procedure TNiceChart.ChartToClient(const AX, AY: Double; var X, Y: Integer);
var
i: Integer;
begin
if FAxisXOnePerValue then
begin
for i := 0 to XAxis.Count-1 do
begin
if (AX = PAxisInfo(XAxis[i])^.Value) then
begin
X := PAxisInfo(XAxis[i])^.Px;
Break;
end;
end;
end else
X := FXTranslate.Base + Round((AX - FXTranslate.MinValue) * FXTranslate.Scale);
Y := FYTranslate.Base - Round((AY - FYTranslate.MinValue) * FYTranslate.Scale);
end;
function TNiceChart.ClientToChart(const X, Y: Integer; var AX, AY: Double): Boolean;
var
i: Integer;
n, d: Integer;
begin
Result := PtInRect(RcChart, Point(X, Y));
if Result then
begin
if FAxisXOnePerValue then
begin
n := MaxInt;
for i := 0 to XAxis.Count-1 do
begin
d := Abs(X - PAxisInfo(XAxis[i])^.Px);
if (d < n) then
begin
AX := PAxisInfo(XAxis[i])^.Value;
n := d;
end;
end;
end else
AX := FXTranslate.MinValue + ((X - FXTranslate.Base) / FXTranslate.Scale);
AY := FYTranslate.MinValue + ((FYTranslate.Base - Y) / FYTranslate.Scale);
end;
end;
procedure TNiceChart.CreateHandle;
begin
inherited;
Changed;
end;
function TNiceChart.CreateBitmap(AWidth, AHeight: Integer): TBitmap;
var
w, h: Integer;
begin
Calculate(AWidth, AHeight);
if (RcLegend.Bottom > (AHeight - FOuterMargin))
then h := RcLegend.Bottom + FOuterMargin
else h := AHeight;
if ((RcChart.Right - RcChart.Left) < (RcChart.Bottom - RcChart.Top))
then w := AWidth + ((RcChart.Bottom - RcChart.Top) - (RCChart.Right - RcChart.Left))
else w := AWidth;
if (AWidth <> w) or (AHeight <> h)
then Calculate(w, h);
Result := TBitmap.Create;
Result.Width := w;
Result.Height := h;
InternalPaint(Result.Canvas);
Calculate(ClientWidth, ClientHeight);
end;
procedure TNiceChart.CopyToClipboardAsBitmap;
var
bmp: TBitmap;
begin
bmp := CreateBitmap(ClientWidth, ClientHeight);
try
Clipboard.Assign(bmp);
finally
bmp.Free;
end;
end;
procedure TNiceChart.ClipToRect(ACanvas: TCanvas; const ARect: TRect; AEnable: Boolean);
begin
{$IFDEF FPC}
if AEnable then
ACanvas.ClipRect := ARect;
ACanvas.Clipping := AEnable;
{$ELSE}
if AEnable then
begin
if FClipRgn <> 0 then
DeleteObject(FClipRgn);
FClipRgn := CreateRectRgn(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom);
SelectClipRgn(ACanvas.Handle, FClipRgn);
end else
begin
SelectClipRgn(ACanvas.Handle, HRGN(nil));
DeleteObject(FClipRgn);
FClipRgn := 0;
end;
{$ENDIF}
end;
{$IFNDEF FPC}
function TNiceChart.CreateMetafile: TMetafile;
const
InitWidth = 800;
InitHeight = 600;
var
mc: TMetafileCanvas;
AWidth, AHeight: Integer;
begin
AWidth := InitWidth;
AHeight := InitHeight;
Calculate(AWidth, AHeight);
if (RcLegend.Bottom > (AHeight - FOuterMargin))
then AHeight := RcLegend.Bottom + FOuterMargin;
if ((RcChart.Right - RcChart.Left) < (RcChart.Bottom - RcChart.Top))
then AWidth := AWidth + ((RcChart.Bottom - RcChart.Top) - (RCChart.Right - RcChart.Left));
if (AWidth <> InitWidth) or (AHeight <> InitHeight)
then Calculate(AWidth, AHeight);
Result := TMetafile.Create;
Result.Width := AWidth;
Result.Height := AHeight;
mc := TMetafileCanvas.Create(Result, 0);
InternalPaint(mc);
mc.Free;
Calculate(ClientWidth, ClientHeight);
end;
procedure TNiceChart.CopyToClipboard;
var
Wmf: TMetafile;
begin
Wmf := CreateMetafile;
Clipboard.Assign(Wmf);
Wmf.Free;
end;
{$ENDIF}
{$IFDEF FPC}
// Handle Lazarus' High-DPI scaling
procedure TNiceChart.DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
const AXProportion, AYProportion: Double);
begin
inherited DoAutoAdjustLayout(AMode, AXProportion, AYProportion);
if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then
begin
FOuterMargin := Round(FOuterMargin * AXProportion);
FInnerMargin := Round(FInnerMargin * AXProportion);
FSmallMargin := Round(FSmallMargin * AXProportion);
FAxisDefSize := Round(FAxisDefSize * AXProportion);
FLegendItemSize := Round(FLegendItemSize * AXProportion);
end;
end;
procedure TNiceChart.FixDesignFontsPPI(const ADesignTimePPI: Integer);
begin
inherited;
DoFixDesignFontPPI(FTitleFont, ADesignTimePPI);
DoFixDesignFontPPI(FNormalFont, ADesignTimePPI);
end;
procedure TNiceChart.ScaleFontsPPI(const AToPPI: Integer; const AProportion: Double);
begin
inherited;
DoScaleFontPPI(FTitleFont, AToPPI, AProportion);
DoScaleFontPPI(FNormalFont, AToPPI, AProportion);
end;
{$ENDIF}
end.