Page 1 of 2
Functions, period range and data leak
Posted: Wed Nov 02, 2005 6:35 pm
by 9337158
Hi,
I have values every days (x = datetime) with a leak of ten days in my data.
When i add a function (average for example) with a period of 1 day, i have a calculated curve with y = 0 during the period range of the data leak (even IncludeNulls = false).
How is it possible to avoid these null values ?
I woul'd like to have a discontinuous curve when there is no data.
Regards
[/img]
Posted: Mon Nov 07, 2005 8:14 am
by Pep
Hi,
have you AddNull method for the leak data ?
You can see one example which shows how to do this in the Demo features project (included into the TeeChart Pro installation) under :
All Features -> Welcome ! -> Functions -> Standard -> Average -> Average and Nulls
Null data
Posted: Mon Nov 07, 2005 9:55 am
by 9337158
Hi,
It's not null data : i haven't data during this short period.
We are working on real time charting (sensors monitoring). if you shutdown, your system, there is no data...
Regards
Data leak during a period and function like median
Posted: Mon Feb 25, 2008 8:14 am
by 10545622
This is and old and annoying bug.
It's not normal to have a calculated curve with values equal to 0 during a data leak. Please, how is it possible to bypass this bug.
Don't add calculated values if you don't have data durin a period.
Regards
Function and null values or no data (teEngine.pas)
Posted: Mon Feb 25, 2008 3:02 pm
by 10545622
Hi,
The problem is that in the Calculate function, the result is 0, even all the data of the period are null values.
If there is no data in a period, i don't know why there is a calculation on this period an why the result is an arbitrary value (0 is a significant value).
Is it possible to have something like that (teEngine.pas) and how to do that:
Code: Select all
Procedure TTeeFunction.CalculatePeriod( Source:TChartSeries;
Const tmpX:Double;
FirstIndex,LastIndex:Integer);
begin
if Source.DataInPeriod(FirstIndex, LastIndex) then
AddFunctionXY( Source.YMandatory, tmpX,Calculate(Source,FirstIndex,LastIndex) );
end;
Regards
function and data leak
Posted: Mon Apr 07, 2008 6:22 am
by 10545622
Is-it possible to have a response ? I think that it's a bug because 0 is a value and different than "no value"....
Posted: Mon Apr 07, 2008 10:13 am
by yeray
Hi stsl,
I've added the feature request to the wish list (TV52012945) to be reviewed and, if possible, enhanced in further releases. This may be quite a sensible issue as implementing it as you request may break existing clients code.
Also notice that we don't provide support for changes to the source code but you are free to implement the necessary customizations to fit your needs.
function and all values
Posted: Tue Nov 04, 2008 10:35 am
by 10545622
There is always the same problem in the last version (8.04). There is a property IgnoreNulls in the Exponential moving average function. Why not in the other function ? It's incredible. All the calculations are wrong, if you assimilate Null values as 0 values...
Please, do something, i wrote my first post in 2005....
Posted: Tue Nov 04, 2008 10:46 am
by narcis
Hi stsl,
Thanks for your feedback. I've increased item's priority on the list.
Posted: Tue Apr 07, 2009 11:47 am
by yeray
Hi stsl,
Testing a little bit further in this, I've tried adding 2 null values to define the interval where you don't get data (having period=2), and with a little trick it seems to work fine. Could you please test this and see if it works as you wish?
Code: Select all
procedure TForm1.FormCreate(Sender: TObject);
var i, j: Integer;
begin
Series2.FunctionType.Period := 2;
for i:=0 to 30 do
begin
if ((i<10) or (i>20)) then
Series1.AddXY(i,random);
if (i=10) or (i=20) then //Start or Stop
Series1.AddNull();
end;
Series2.CheckDataSource;
for j:=0 to Series2.Count-1 do
if Series2.YValues[j] = Series2.DefaultNullValue then
Series2.ValueColor[j]:=clNone;
Series2.TreatNulls:=tnDontPaint;
end;
Note two things:
1. In this example we have Series1 as source and Series2 is the line series linked to an average function.
2. Where I call Series2.DefaultNullValue, you probably won't be able to since this is a new implementation in the sources that will be available with the next maintenance release. You can use a "0".
Posted: Thu Apr 09, 2009 8:53 am
by 10545622
Hi,
I can't test it. In my release, DefaultNullValue is not declared.
An other thing: In my case, i haven't values during a period bigger than the function period (not null values, it's "no values"). So, your test code should be :
Code: Select all
procedure TForm1.FormCreate(Sender: TObject);
var i, j: Integer;
begin
Series2.FunctionType.Period := 2;
for i:=0 to 30 do
begin
if ((i<10) or (i>20)) then
Series1.AddXY(i,random);
//if (i=10) or (i=20) then //Start or Stop
// Series1.AddNull();
end;
...
Best regards
Posted: Thu Apr 09, 2009 9:47 am
by yeray
Hi stsl,
stsl wrote:In my release, DefaultNullValue is not declared.
As I said above, this property will be available in the next maintenance release. Please, use a 0 instead of this property in the meanwhile.
Resuming:
If I understand well, you have a process that adds points to your source. The problem is because you have gaps in your X values and you also have an average function and you would like this function not to be drawn in these gaps.
Here you have two options:
1. If your continuous intervals match with the function period, you could process as described before. You could add as null values to your source (series1) as your function period says every time the acquiring process stops to propagate these null values to your function (series2) to not paint them.
2. You could create a new series plus a new function every time the acquiring process starts.
Posted: Thu Apr 09, 2009 12:03 pm
by 10545622
In my release, DefaultNullValue is not declared.
Excuse me, i can test with a 0 value.
Resuming:
If I understand well, you have a process that adds points to your source. The problem is because you have gaps in your X values and you also have an average function and you would like this function not to be drawn in these gaps.
Exactly. The problem is that the average result (not only the average) is 0 when there is gaps. It would be "null value".
Here you have two options:
1. If your continuous intervals match with the function period, you could process as described before. You could add as null values to your source (series1) as your function period says every time the acquiring process stops to propagate these null values to your function (series2) to not paint them.
NO: The intervals are defined by the user.
2. You could create a new series plus a new function every time the acquiring process starts.
!!! I get the data from the database. There is no connection between the visualization process and the acquisition software.
And, if this solution would be suitable, i will have a lot of series in my legend for the same acqusition point.
Best regards
Posted: Thu Apr 09, 2009 2:45 pm
by yeray
Hi stsl,
For your answers, I deduce that you are loading the data directly to your source series instead of manually retrieving data from your database and populate it using AddXY method. So I recommend you to do it and you'll be able to catch the data gaps and decide what to do:
1. You could add as many null values as your function period says. If this period is variable, you will have to re-loop your data and add or remove null values in order to have as null points as the period says. If you work in this way, maybe you'll need to reorder your series points:
2. You could create a new series plus a new function every time the acquiring process starts. Please, take a look at the
Tutorial 7, Working with functions to see some examples of how you could create functions at run time.
If you don't want so many series in your legend, you could hide them:
Posted: Thu Apr 09, 2009 3:23 pm
by 10545622
1. Get data from the database but i add them manually with addXY.
I have a lot of data and it will take a very long time to re-loop them each time the user change the intervals.
2. You could create a new series plus a new function every time the acquiring process starts.
Very good !! And what about the check box ? If the user will check/uncheck one serie (2 or more series in your solution) only one part of the curve will appear/disappear. Ok, i can use a group but i need a symbol with the color of the main curve of the group but the legend doesn't support the Symbol.OnDraw when we use group !!!
So, i modified your TeeFunction unit (many thanks to teeChart bugs...) :
Code: Select all
unit uTeeFunction;
interface
uses
TeEngine, SysUtils, Classes, cArrays,
Windows; //ODS
type
TMyTeeFunction = class(TTeeFunction)
protected
procedure CalculateByPeriod(Source: TChartSeries; NotMandatorySource: TChartValueList); override;
end;
TMyCustomSortedFunction = class(TMyTeefunction)
protected
Tmp: TDoubleArray;
ICount: Integer;
FIncludeNulls: Boolean;
procedure AddValue(const Value: Double; Index: Integer);
protected
function CalcResult: TChartValue; virtual; // abstract;
procedure SetIncludeNulls(const Value: Boolean);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function Calculate(SourceSeries: TChartSeries; FirstIndex, LastIndex: Integer):
Double; override;
function CalculateMany(SourceSeriesList: TList; ValueIndex: Integer): Double;
override;
property IncludeNulls: Boolean read FIncludeNulls write SetIncludeNulls default True;
end;
TMyMedianTeeFunction = class(TMyCustomSortedFunction) // 6.02
protected
function CalcResult: TChartValue; override;
published
property IncludeNulls;
end;
TMyHighTeeFunction = class(TMyCustomSortedFunction)
public
function CalcResult: TChartValue; override;
published
property IncludeNulls;
end;
TMyLowTeeFunction = class(TMyCustomSortedFunction)
public
function CalcResult: TChartValue; override;
published
property IncludeNulls;
end;
TMyAverageTeeFunction = class(TMyTeeFunction)
private
FIncludeNulls: Boolean;
procedure SetIncludeNulls(const Value: Boolean);
protected
class function GetEditorClass: string; override;
public
constructor Create(AOwner: TComponent); override;
function Calculate(SourceSeries: TChartSeries; FirstIndex, LastIndex: Integer):
Double;
override;
function CalculateMany(SourceSeriesList: TList; ValueIndex: Integer): Double;
override;
published
property IncludeNulls: Boolean read FIncludeNulls write SetIncludeNulls default True;
end;
implementation
uses
TeeProcs;
{ TMyCustomSortedFunction }
constructor TMyCustomSortedFunction.Create(AOwner: TComponent);
begin
inherited;
FIncludeNulls := True;
Tmp := TDoubleArray.Create;
end;
procedure TMyCustomSortedFunction.AddValue(const Value: Double; Index: Integer);
var
tmpOk: Boolean;
tt: Integer;
ttt: Integer;
begin
tmpOk := False;
for tt := 0 to Index do
begin
if tmp[tt] > Value then
begin
for ttt := Index downto tt + 1 do
tmp[ttt] := tmp[ttt - 1];
tmp[tt] := Value;
tmpOk := True;
break;
end;
end;
if not tmpOk then
tmp[Index] := Value;
end;
function TMyCustomSortedFunction.CalcResult: TChartValue; // virtual; abstract;
begin
Result := 0; // A surcharger
end;
function TMyCustomSortedFunction.Calculate(SourceSeries: TChartSeries; FirstIndex,
LastIndex: Integer): Double;
var
t: Integer;
begin
result := 0;
if FirstIndex = -1 then
FirstIndex := 0;
if LastIndex = -1 then
LastIndex := SourceSeries.Count - 1;
ICount := LastIndex - FirstIndex + 1;
if ICount > 0 then
begin
Tmp.Clear;
try
for t := FirstIndex to LastIndex do
begin
if SourceSeries.IsNull(t) then
begin
if IncludeNulls then
Tmp.AppendItem(SourceSeries.MandatoryValueList.Value[t]);
end
else
begin
Tmp.AppendItem(SourceSeries.MandatoryValueList.Value[t]);
end;
end;
Tmp.Sort; // dans l'ordre croissant
Result := CalcResult;
finally
Tmp.Clear;
end;
end;
end;
function TMyCustomSortedFunction.CalculateMany(SourceSeriesList: TList;
ValueIndex: Integer): Double;
var
t: Integer;
begin
ICount := SourceSeriesList.Count;
if ICount > 0 then
begin
Tmp.Clear;
try
for t := 0 to ICount - 1 do
begin
if IncludeNulls or (not TChartSeries(SourceSeriesList[t]).IsNull(ValueIndex)) then
Tmp.AppendItem(TChartSeries(SourceSeriesList[t]).MandatoryValueList.Value[ValueIndex]);
end;
Tmp.Sort;
Result := CalcResult;
finally
Tmp.Clear;
end;
end
else
result := 0;
end;
destructor TMyCustomSortedFunction.Destroy;
begin
Tmp.Free;
inherited;
end;
procedure TMyCustomSortedFunction.SetIncludeNulls(const Value: Boolean);
begin
if FIncludeNulls <> Value then
begin
FIncludeNulls := Value;
ReCalculate;
end;
end;
{ TMyMedianFunction }
function TMyMedianTeeFunction.CalcResult: TChartValue;
var
tmpMiddle: Integer;
begin
if Odd(ICount) then // impair
TmpMiddle := (ICount - 1) div 2
else
TmpMiddle := (ICount div 2) - 1;
Result := tmp[tmpMiddle];
end;
{ TMyTeeFunction }
procedure TMyTeeFunction.CalculateByPeriod(Source: TChartSeries;
NotMandatorySource: TChartValueList);
var
tmpX: Double;
tmpCount: Integer;
tmpFirst: Integer;
tmpLast: Integer;
tmpBreakPeriod: Double;
PosFirst: Double;
PosLast: Double;
tmpStep: TDateTimeStep;
tmpCalc: Boolean;
TmpDataAvailable: Boolean;
// TmpPeriod: Double;
begin
With ParentSeries do
begin
tmpFirst:=0;
tmpCount:=Source.Count;
tmpBreakPeriod:=NotMandatorySource.Value[tmpFirst];
tmpStep:=FindDateTimeStep(Period);
Repeat
PosLast:=0;
if PeriodStyle=psNumPoints then
begin
tmpLast:=tmpFirst+Round(Period)-1;
With NotMandatorySource do
begin
PosFirst:=Value[tmpFirst];
if tmpLast<tmpCount then PosLast:=Value[tmpLast];
end;
end
else
begin
tmpLast:=tmpFirst;
PosFirst:=tmpBreakPeriod;
TeeDateTimeIncrement(GetHorizAxis.ExactDateTime and GetHorizAxis.IsDateTime and (tmpStep>=dtHalfMonth),
True,
tmpBreakPeriod,
Period,
tmpStep );
PosLast:=tmpBreakPeriod-(Period*0.001);
While tmpLast<(tmpCount-1) do
if NotMandatorySource.Value[tmpLast+1]<tmpBreakPeriod then Inc(tmpLast)
else break;
// Modif SSL
TmpDataAvailable := (NotMandatorySource.Value[tmpLast] < PosLast) and
(not Source.IsNull(tmpLast));
// Modif SSL END
end;
tmpCalc:=False;
if tmpLast<tmpCount then
begin
{ align periods }
if PeriodAlign=paFirst then tmpX:=PosFirst else
if PeriodAlign=paLast then tmpX:=PosLast else
tmpX:=(PosFirst+PosLast)*0.5;
if (PeriodStyle=psRange) and (NotMandatorySource.Value[tmpFirst]<tmpBreakPeriod) then
tmpCalc:=True;
if TmpDataAvailable then // MODIF SSL
begin
if (PeriodStyle=psNumPoints) or tmpCalc then
CalculatePeriod(Source,tmpX,tmpFirst,tmpLast)
else
AddFunctionXY( Source.YMandatory, tmpX, 0 );
end;
end;
if (PeriodStyle=psNumPoints) or tmpCalc then
tmpFirst:=tmpLast+1;
until tmpFirst>tmpCount-1;
end;
end;
{ TMyAverageTeeFunction }
constructor TMyAverageTeeFunction.Create(AOwner: TComponent);
begin
inherited;
FIncludeNulls := True;
end;
function TMyAverageTeeFunction.Calculate(SourceSeries: TChartSeries; FirstIndex,
LastIndex: Integer): Double;
var
t: Integer;
tmpCount: Integer;
begin
if (FirstIndex = TeeAllValues) and IncludeNulls then
with SourceSeries do
begin
if Count > 0 then
result := ValueList(SourceSeries).Total / Count
else
result := 0;
end
else
begin
if FirstIndex = TeeAllValues then
begin
FirstIndex := 0;
LastIndex := SourceSeries.Count - 1;
end;
result := 0;
tmpCount := 0;
with ValueList(SourceSeries) do
for t := FirstIndex to LastIndex do
if IncludeNulls or (not SourceSeries.IsNull(t)) then
begin
result := result + Value[t];
Inc(tmpCount);
end;
if tmpCount = 0 then
result := 0
else
result := result / tmpCount;
end;
end;
function TMyAverageTeeFunction.CalculateMany(SourceSeriesList: TList; ValueIndex:
Integer): Double;
var
t: Integer;
Counter: Integer;
tmpList: TChartValueList;
begin
result := 0;
if SourceSeriesList.Count > 0 then
begin
Counter := 0;
for t := 0 to SourceSeriesList.Count - 1 do
begin
tmpList := ValueList(TChartSeries(SourceSeriesList[t]));
if (tmpList.Count > ValueIndex) and
(IncludeNulls or (not TChartSeries(SourceSeriesList[t]).IsNull(t))) then
begin
Inc(Counter);
result := result + tmpList.Value[ValueIndex];
end;
end;
if Counter > 0 then
result := result / Counter;
end;
end;
procedure TMyAverageTeeFunction.SetIncludeNulls(const Value: Boolean);
begin
if FIncludeNulls <> Value then
begin
FIncludeNulls := Value;
ReCalculate;
end;
end;
class function TMyAverageTeeFunction.GetEditorClass: string;
begin
result := 'TAverageFuncEditor';
end;
{ TMyHighTeeFunction }
function TMyHighTeeFunction.CalcResult: TChartValue;
begin
if tmp.Count > 0 then
Result := tmp[tmp.Count - 1];
end;
{ TMyLowTeeFunction }
function TMyLowTeeFunction.CalcResult: TChartValue;
begin
if tmp.Count > 0 then
Result := tmp[0];
end;
initialization
end.