On this Perspective XY Chart (ia.chart.xy), the hourly kWh bullets sit between the x-axis time labels, and some hours have no label. The x-axis is a date axis with baseInterval set to hour / count: 1. The misalignment comes from two axis settings: appearance.grid.position is 0.5 (0.2 in the first revision), and appearance.grid.minDistance is 90 px (50 px earlier). The first draws grid lines and labels at a point inside each hourly cell that does not match where the points are plotted. The second thins out the labels. Setting xAxes.0.appearance.grid.position to 0 and xAxes.0.appearance.grid.minDistance to 1 fixes both. Before changing any axis setting, confirm that the timestamps themselves land on the hour.
Which hops does an hourly kWh value pass through before it becomes a bullet?
Each point travels from the SQL Server table, through a Named Query binding, into a dataset. The chart component (built on amCharts) then assigns each row to a one-hour cell on the date axis. An offset can enter at any hop, so trace one row end to end.
| Hop | What happens | Setting that controls it |
|---|---|---|
1. Table Main
|
Logger writes Date and cumulative KWH (stored as text, e.g. 4.9061e+007) |
Logger/transaction group configuration |
| 2. Named Query | Filters to minute 0 and computes the hourly delta with LAG() into KWH1
|
datepart(minute,Date) % 60 = 0, ORDER BY Date
|
| 3. Dataset |
time (Date, epoch ms), kwh (String), KWH1 (Double) |
Column types returned by the query |
| 4. Series |
data.source: example, x: time, y: KWH1
|
series.0.data, series.0.render
|
| 5. Date axis cell | Row is placed inside a one-hour cell | xAxes.0.date.baseInterval |
| 6. Grid and labels | Grid line and label drawn at a fractional position of each cell. Labels are dropped if they are closer together than minDistance px. |
xAxes.0.appearance.grid.position, .minDistance
|
| 7. Browser | Epoch ms is rendered in the session's timezone | Client/session timezone |
Check 1: Do the epoch timestamps fall on the hour in the session's timezone?
Convert the first time value in the dataset before touching the axis. The value 1698625801203Every row in both posted datasets follows the same pattern::30:0x in UTC. The query keeps only rows where datepart(minute, Date) = 0, so the database stores these rows at minute zero in local time. That means the logging site runs at a UTC offset with a 30-minute component.
-
The chart tooltip (
{dateX}) shows HH:00:0x. The browser and database agree on the timezone. Go to Check 2. - The tooltip shows HH:30. The session renders in a whole-hour offset or UTC. Every point then sits exactly mid-cell, and no axis setting will line it up. Align the session/project timezone with the database, or shift the timestamp in the query. Then repeat this check.
...401710, ...400073). With baseInterval enabled, each row snaps into its hourly cell, so the jitter does not move the bullet. It does show up in tooltips, and it breaks cell assignment if baseInterval is ever disabled. Truncate the timestamp in SQL so each row carries an exact hour boundary:
SELECT DATEADD(hour, DATEDIFF(hour, 0, Date), 0) AS time,
KWH AS kwh,
CONVERT(float, KWH) - LAG(CONVERT(float, KWH)) OVER (ORDER BY Date) AS KWH1
FROM Main
WHERE BG = :bg AND Plant = :pl AND Line = :ll AND Machine = :ma
AND Date BETWEEN :st AND :sp
AND DATEPART(minute, Date) = 0
AND KWH != '0'
ORDER BY Date ASC
The % 60 in the original filter is redundant, because datepart(minute, ...) already returns 0–59.
Check 2: Where does the axis draw its grid relative to each hourly cell?
With a one-hour baseInterval, the date axis divides the range into hour-wide cells. grid.position is a fraction of the cell width: 0 is the start of the hour, 0.5 is the middle, and 1 is the end. The grid line and the time label are drawn at that fraction. This chart has position: 0.5 (0.2 in the first revision), so the "06:00" label is not drawn at the start of the 06:00 cell, where the bullet sits. Every label is therefore offset from its bullet by the same fraction of an hour.
grid.position |
Label/grid location | Result with hour-aligned data |
|---|---|---|
0 |
Start of each hour cell | Label sits under the bullet |
Set xAxes.0.appearance.grid.position to 0.
- If the bullets now sit on the labels, go to Check 3 to recover the missing labels.
Check 3: Why do some hours have no label at all?
grid.minDistance is the minimum pixel spacing between grid lines. When hourly cells are narrower than that value, the axis skips grid lines and their labels. At 90 px, with 8–24 hourly points across roughly 900 px of plot, and 18 pt bold labels rotated 45°, many hours are dropped. The skipped labels make the remaining ones look even further from their bullets.
Set xAxes.0.appearance.grid.minDistance to 1. This forces a grid line and label for every hour cell. If a longer time range makes the labels overlap, reduce appearance.font.size or increase labels.rotation. Do not raise minDistance again, because that brings back the skipped hours.
Two other non-default label values are worth resetting while you are in the axis properties:
-
labels.opacity: 2is outside the 0–1 range. Set it to1. -
labels.positionhas changed between revisions (2, then 90). If labels still drift aftergrid.position: 0, reset it to the component default.
Check 4: Does the time format read the way operators expect?
The current date.format is hh:mma. This produces "06:00AM", a 12-hour clock with a leading zero and no space. That mixes two conventions. Pick one:
date.format |
Rendered 06:00 / 18:00 | Convention |
|---|---|---|
hh:mma (current) |
06:00AM / 06:00PM | Mixed; avoid |
HH:mm |
06:00 / 18:00 | 24-hour with leading zero |
h:mm a |
6:00 AM / 6:00 PM | 12-hour, no leading zero |
AM/PM markers are rendered in uppercase by the amCharts formatter. The underlying library can change them to lowercase, but the component does not expose that option. If lowercase matters, use HH:mm.
date.inputFormat (HH:mm:ss) only applies when the x column arrives as a string. Here time is a Date column, so that setting is inactive.
Check 5: Are the plotted kWh values right before judging alignment?
Several data-side defects can make a correctly aligned chart look wrong. Rule them out using the dataset itself.
| Symptom on chart | Cause | Correction |
|---|---|---|
| Bullets halfway between labels |
grid.position 0.5, or a 30-min timezone mismatch |
Position 0; align the session timezone (Check 1) |
| Labels only every 2nd or 3rd hour |
minDistance 90/50 px |
minDistance 1
|
| First hour has a label but no bullet |
LAG() returns null for the first row |
Expected; widen :st by one hour if the first hour must show a value |
| Every delta is a multiple of 100 (1300, 1000, 800, 600, 500) |
KWH stored as text with 6 significant digits (4.90623e+007). CONVERT(float) cannot recover the lost digits. |
Log the counter to a numeric column at full precision |
| One hour roughly double its neighbours, followed by a gap | Missing :00 sample. LAG() then spans two hours. |
Fill or flag the gaps. skipEmptyPeriods: false keeps the empty cell visible. |
| Shaded area under the line |
line.appearance.fill.opacity: 0.5 turns the series into an area plot. The area of an energy series represents kWh×h, which has no physical meaning. |
Set fill opacity to 0, or render as column
|
The LAG() delta stamped at 06:00 is the energy consumed from 05:00 to 06:00. It is an end-of-interval value. With grid.position: 0, the bullet and the "06:00" label mark the end of that hour. A column series makes the interval nature explicit, which suits hourly energy better than a line.
How do you apply the resolving settings and prove the alignment?
- In the Designer, select the XY Chart and open the
xAxes[0]properties. - Set
appearance.grid.position=0. - Set
appearance.grid.minDistance=1. - Set
date.format=HH:mm(orh:mm a). - Set
appearance.labels.opacity=1. - In
series[0].line.appearance.fill, setopacity=0, or changeseries[0].rendertocolumn. - Replace the Named Query with the hour-truncated version from Check 1 and refresh the binding.
- With the cursor enabled (
cursor.series: example), hover each bullet. Confirm that the{dateX}tooltip reads HH:00. Confirm that the cursor's vertical line passes through the label directly beneath the bullet, and that the tooltip value matches theKWH1cell for that row.
FAQ
What happens if I set grid.position to 0.5 on an hourly date axis?
Each bullet then appears halfway between two time labels. Use0 so the label sits at the start of the hour.
What happens if minDistance is left at 90 on the Ignition XY Chart x-axis?
The axis drops any grid line closer than 90 px to the previous one. With bold 18 pt labels, several hourly labels disappear. Setting xAxes.0.appearance.grid.minDistance to 1 forces one label per hour.
What happens if the browser timezone differs from the database timezone?
Perspective renders the epoch value in the session's timezone. A row stored at 06:00 local can display at a half-hour or other offset, and no axis property will correct it. Convert the first time value, compare it with the {dateX} tooltip, and align the session timezone or shift the timestamp in SQL.
Why are all my hourly kWh values multiples of 100?
The cumulative counter is stored as text with six significant digits (for example 4.90623e+007). The hourly LAG() difference can therefore only change in steps of 100 kWh. Store the counter in a numeric column at full precision to get true hourly deltas.