[SOLVED] Offset between point and axis (TDbChartSource) lazarus / 55-TAChart ---------------------------------------------------------------------- tudi_x · 2016-05-20 20:05 Hi! I am rendering a chart with TAChart in Lazarus 1.6 on Windows 7. I am having the issue that the points have an offset with the axis as per attached PNG file. I am not generating the labels for the bottom axis. I am transforming the dates to Unix time when entering the dataset and converting them again to date when rendering. Please advise where I could look for not having this offset, I am kind of lost. Thank you Dataset values: "81268"   "6/7/2015" "84268"   "6/8/2015" "90842"   "6/9/2015" "86541"   "6/10/2015" "101092"   "6/11/2015" "86012"   "6/12/2015" "94796"   "6/13/2015" "94700"   "6/14/2015" "64700"   "6/15/2015" Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure ChartDSet.UpdateBtmAxisDATEValues(var ATextAxis: string; AMark: double);var  new_text: string;  a: aUtil; begin  new_text := DateToStr(a.UNIXTimeToDateTime(strtoint64(ATextAxis)));  ATextAxis := Format('%s', [new_text]);end; And the series: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure ChartDSet.AddSeries(XFieldName: string; YFieldName: string; AColor: integer);const  COLORS: array[0..4] of TColor = (clBlue, $00000095, $00006A00, $000000D2, $006A0000);  LineStyle: array[0..2] of TFPPenStyle = (psSolid, psSolid, psSolid);   //(psSolid, psDashDot, psDash); var  new_series: TLineSeries;  DB_datasource: TDataSource;  chart_data_source: TDbChartSource; begin  new_series := TLineSeries.Create(nil);   new_series.LinePen.Color := COLORS[AColor];  new_series.LinePen.Style := LineStyle[AColor mod 2];  new_series.LinePen.Width := 2;  (* link between value and line *)  new_series.Marks.LinkPen.Color := clBlack;  new_series.Marks.LinkPen.Width := 1;  new_series.Marks.LinkPen.EndCap := pecRound;  new_series.Marks.LinkPen.JoinStyle := pjsRound;  (* points *)  new_series.Pointer.Brush.Color := clRed;  new_series.Pointer.Visible := True;  new_series.Pointer.Style := psCircle;  new_series.Pointer.HorizSize := 2;  new_series.Pointer.VertSize := 2;  new_series.ShowPoints := True;   new_series.Marks.Clipped := True;  new_series.Marks.LabelBrush.Color := clSkyBlue;  new_series.Marks.LabelBrush.Style := bsSolid;  new_series.Title := YFieldName;  new_series.Marks.Frame.Color := clBlack;  new_series.Marks.Frame.Width := 1;  new_series.Marks.Frame.Mode := pmCopy;   DB_datasource := TDataSource.Create(nil);  DB_datasource.DataSet := _dataset;  DB_datasource.DataSet.Open;  if DB_datasource.DataSet.RecordCount <= 50 then  begin    new_series.Marks.Style := smsValue;    new_series.Marks.Shape := clsRoundRect;  end;     chart_data_source := TDbChartSource.Create(_chart);  chart_data_source.DataSource := DB_datasource;   chart_data_source.FieldX := XFieldName;  chart_data_source.FieldY := YFieldName;   new_series.ListSource.CopyFrom(chart_data_source);  _chart.AddSeries(new_series);   DB_datasource.Free; ---------------------------------------------------------------------- wp · 2016-05-20 22:07 I don't understand why you are using Unix times here. If the dataset contains values as you list then there is no need to use Unix times, you get the the dates directly from the dataset field. If I plug your table of values directly into a TMemDataset then the location of the data points is correct - see screen shot and attached demo. What is the aUtil? It seems to do the UnixTime-to-Datetime conversion. Are you sure that the date values are calculated correctly here and do not contain a fractional part? ---------------------------------------------------------------------- tudi_x · 2016-05-22 01:01 Regarding the UNIX time values I was basically trying to simulate the fact that I would be having UNIX time values from different queries that I would like to have them displayed as dates for OX scale. I rewrote the chart conversion unit using same initialization mainly and using the below to convert to from UNIX time. I am obtaining fractional values. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function RenderChart.TryUNIXTimeToDateTime(UNIXTime: string; var dt: TDateTime): boolean;const  UnixStartDate: TDateTime = 25569.0; // 01/01/1970 var  d: int64; begin  if trystrtoint64(UNIXTime, d) and (Length(UNIXTime) = 10) and (AnsiLeftStr(UNIXTime, 2) = '14') then  begin   dt :=d / 86400 + UnixStartDate;     Result := True;  end  else    Result := False;end; and the OX marks conversion as per the below: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure RenderChart.FormatOXMarks(var ATextAxis: string; AMark: double);var  dt: TDateTime; begin  if _oxIsUNIXTime and TryUNIXTimeToDateTime(ATextAxis, dt) then  begin    ATextAxis := Format('%s', [DateTimeToStr(dt)]);    exit;  end;   if _oxIsDate then  begin    ATextAxis := Format('%s', [DateTimeToStr(StrToInt(ATextAxis))]);    exit;  end;end;  The dataset is populated as: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.FormCreate(Sender: TObject);var  c: ChartInfo;  r: RenderChart; begin  c.ADataset := TVirtualTable.Create(nil);  with c.ADataset.FieldDefs do  begin    Add('ID', ftInteger);    Add('VALS', ftInteger);    Add('TheDate', ftDateTime);    Add('UNIXtime', ftInteger);  end;   c.ADataset.Open;   c.ADataset.AppendRecord([1, 500, '5/5/2016', 1462406400]);  c.ADataset.AppendRecord([2, 600, '5/6/2016', 1462492800]);  c.ADataset.AppendRecord([3, 450, '5/7/2016', 1462579200]);  c.ADataset.AppendRecord([4, 800, '5/15/2016', 1463270400]);  {  c.ADataset.AppendRecord([1, 500, '5/5/2016']);  c.ADataset.AppendRecord([2, 600, '5/6/2016']);  c.ADataset.AppendRecord([3, 450, '5/7/2016']);  c.ADataset.AppendRecord([4, 800, '5/15/2016']); }   c.OXTitle := 'Title OX';  c.Title := 'Title Test';  c.MultipleSeries := False;  c.SpoolPath := 'test.png';   r := RenderChart.Create(c);  r.Render(1);  r.CleanUp;end;     Rendering, for dates I have no offset as per attached. The offset appears for when converting from UNIX time values and most probably is because of the fractional values. I saw that there is an option of generating my own labels. As UNIX time values would be a requirement for me, do you think that generating my own labels would be the way to go? Thank you. ---------------------------------------------------------------------- wp · 2016-05-22 06:51 Please post a small standalone compilable project which shows the issue (only pas, lfm, lpi, lpr files, packed to a single zip). BTW, do you know that there is a Unix-to-DateTime conversion in DateUtils? ---------------------------------------------------------------------- tudi_x · 2016-05-22 12:41 I have switched to the dateutils function. Using TMemDataset. Please find the project attached. The dataset values are showing the issue when spanning many days. When spanning few minutes I cannot distinguish an offset. Thank you! ---------------------------------------------------------------------- wp · 2016-05-22 16:17 OK, I see now what's wrong: TAChart tries to seek "nice" values for the axis labels. But: you are using UNIX-Times for the x values - therefore, TAChart seeks "nice" unix labels. Since unix time and date time are not coincident the "nice" unix labels do not lie on the date boundaries. The solution is that you must convert the unix times to date times before plotting. There are several ways to do this: Add a calculated dataset field which converts the Unix times to date times.Or: In your code, you copy the DbChartSource to the internal listsource of the series. You could iterate through all values and convert the x values from unix to date time.Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i for i := 0 to new_series.Count-1 do   new_series.SetXValue(i, UnixToDateTime(new_series.GetXValue(i));Or: add a user-defined ChartAxisTransformation which you assign to the bottom axis. Provide event handlers for conversion from unix to date time and vice versa. Since you do all the chart design by code, this is more complex than the other two solutions, though ---------------------------------------------------------------------- tudi_x · 2016-05-22 17:33 I implemented the second solution and it worked. Thank you so much.