2023-09-30 22:45:55 +00:00
|
|
|
/**
|
|
|
|
* BBTimeline
|
|
|
|
* @version v0.1.0.076 (2023/09/30 1432)
|
|
|
|
*/
|
|
|
|
class BBTimeline {
|
|
|
|
constructor(el) {
|
|
|
|
const a = this;
|
|
|
|
|
|
|
|
a.Container = document.getElementById(el);
|
|
|
|
|
|
|
|
a.Padding = {
|
|
|
|
Left: 20,
|
|
|
|
Top: 20,
|
|
|
|
Right: 20,
|
|
|
|
Bottom: 40
|
|
|
|
};
|
|
|
|
a.Size = {
|
|
|
|
Width: a.Container.innerWidth || a.Container.clientWidth,
|
|
|
|
Height: a.Container.innerHeight || a.Container.clientHeight
|
|
|
|
};
|
|
|
|
a.Axis = {
|
|
|
|
LineColour1: "#CFCFCF",
|
|
|
|
LineWidth: 1,
|
|
|
|
Font: "8pt Arial",
|
|
|
|
LabelColour: "#000000",
|
|
|
|
X: {
|
|
|
|
NoPartPerDay: 4,
|
|
|
|
HourLineSpace: 6,
|
|
|
|
HourLineHeight: 10,
|
|
|
|
HourLineColour: "#A6A6A6",
|
|
|
|
DayLineHeight: 20,
|
|
|
|
DayLineColour: "#282828"
|
|
|
|
}
|
|
|
|
};
|
|
|
|
a.Marker = {
|
|
|
|
BorderColour: "#3A5D9C",
|
|
|
|
BorderWidth: 2,
|
|
|
|
BackColour: "#D4DEEF",
|
2023-10-02 22:42:08 +00:00
|
|
|
Width: 10,
|
|
|
|
ForeColour: "#3A5D9C",
|
|
|
|
Font: "9pt Arial",
|
|
|
|
LabelCollisionMargin: 4
|
2023-09-30 22:45:55 +00:00
|
|
|
};
|
|
|
|
a.HighlightLine = {
|
|
|
|
Colour: "#A6A6A6",
|
|
|
|
Width: 1,
|
|
|
|
};
|
2023-10-01 02:37:24 +00:00
|
|
|
a.Events = [];
|
2023-10-02 22:42:08 +00:00
|
|
|
a.StartDate = a.DateToString(new Date(), "yyyy-MM-dd");
|
|
|
|
a.ShowDate = a.StartDate;
|
2023-10-02 23:04:03 +00:00
|
|
|
a.LastDate = null;
|
2023-10-02 22:42:08 +00:00
|
|
|
a.GraphRectangle = a.calcGraphArea();
|
2023-10-01 02:37:24 +00:00
|
|
|
a.Enabled = false;
|
2023-10-02 22:42:08 +00:00
|
|
|
a.Debug = false;
|
2023-09-30 22:45:55 +00:00
|
|
|
|
|
|
|
a.ctx = a.Container.getContext("2d");
|
|
|
|
a.ctx.canvas.width = a.Size.Width;
|
|
|
|
a.ctx.canvas.height = a.Size.Height;
|
2023-10-01 02:37:24 +00:00
|
|
|
|
|
|
|
a.initialiseComponents();
|
2023-09-30 22:45:55 +00:00
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
AddEvent(date, label, options) {
|
2023-09-30 22:45:55 +00:00
|
|
|
const a = this;
|
|
|
|
|
2023-10-02 23:04:03 +00:00
|
|
|
const _options = Object.assign(a.GenerateEventItem(), options);
|
2023-09-30 22:45:55 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
let event = a.FindEvent(date);
|
|
|
|
if (event == null) {
|
2023-10-02 23:04:03 +00:00
|
|
|
a.Events.push(a.GenerateEvent(date));
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
event = a.FindEvent(date);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (label != null) {
|
|
|
|
event.Label = label;
|
|
|
|
}
|
|
|
|
|
|
|
|
event.Events.push(_options);
|
2023-09-30 22:45:55 +00:00
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
Clear() {
|
2023-09-30 22:45:55 +00:00
|
|
|
const a = this;
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
a.ctx.clearRect(0, 0, a.ctx.canvas.width, a.ctx.canvas.height);
|
|
|
|
|
|
|
|
a.StartDate = a.DateToString(new Date(), "yyyy-MM-dd");
|
|
|
|
a.ShowDate = a.StartDate;
|
|
|
|
a.Enabled = false;
|
|
|
|
a.Events = [];
|
2023-10-02 23:04:03 +00:00
|
|
|
a.LastDate = null;
|
2023-10-02 22:42:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DeleteMarker(date)
|
|
|
|
{
|
|
|
|
const a = this;
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
for (let i=0; i<a.Events.length; i++) {
|
|
|
|
if (a.Events[i].Date != date) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-09-30 22:45:55 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
a.Events.splice(i, 1);
|
2023-09-30 22:45:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
FindDatePosition(date) {
|
2023-09-30 22:45:55 +00:00
|
|
|
const a = this;
|
|
|
|
|
2023-10-01 02:37:24 +00:00
|
|
|
const points = a.getXAxis();
|
|
|
|
for (let i=0; i<points.length; i++) {
|
|
|
|
if (points[i].Date == date){
|
|
|
|
return points[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
FindVisibleEvents() {
|
|
|
|
const a = this;
|
|
|
|
let result = [];
|
|
|
|
|
|
|
|
const availableX = a.getXAxis();
|
|
|
|
availableX.forEach(function (e) {
|
|
|
|
const event = a.FindEvent(e.Date);
|
|
|
|
if (event == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set offsetX on current view
|
|
|
|
event.Position.X = e.X;
|
|
|
|
|
|
|
|
result.push(event);
|
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
FindEvent(date) {
|
|
|
|
const a = this;
|
|
|
|
|
|
|
|
for (let i=0; i<a.Events.length; i++) {
|
|
|
|
if (a.Events[i].Date == date) {
|
|
|
|
return a.Events[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
FindEventsByCoords(x, y) {
|
2023-10-01 02:37:24 +00:00
|
|
|
const a = this;
|
2023-09-30 22:45:55 +00:00
|
|
|
|
2023-10-01 02:37:24 +00:00
|
|
|
for (let i=0; i<a.Events.length; i++) {
|
2023-10-02 22:42:08 +00:00
|
|
|
const e = a.Events[i].HitBox;
|
|
|
|
if (a.Events[i].HitBox == null) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-10-01 02:37:24 +00:00
|
|
|
|
|
|
|
if ((x >= e.X1) && (x <= e.X2) && (y >= e.Y1) && (y <= e.Y2)){
|
|
|
|
return a.Events[i];
|
2023-09-30 22:45:55 +00:00
|
|
|
}
|
2023-10-01 02:37:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
Load(startDate) {
|
2023-10-01 02:37:24 +00:00
|
|
|
const a = this;
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
a.StartDate = startDate;
|
2023-09-30 22:45:55 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
a.Show(startDate);
|
2023-10-01 02:37:24 +00:00
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
Show(date) {
|
|
|
|
const a = this;
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
if (a.stringToDate(date) < a.stringToDate(a.StartDate)) {
|
|
|
|
date = a.StartDate;
|
|
|
|
}
|
|
|
|
|
|
|
|
a.ShowDate = date;
|
|
|
|
a.Enabled = true;
|
|
|
|
|
|
|
|
a.Invalidate(true, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
ShowNext() {
|
2023-10-01 02:37:24 +00:00
|
|
|
const a = this;
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
let date = a.stringToDate(a.ShowDate);
|
|
|
|
date.setMonth(date.getMonth() + 1);
|
|
|
|
|
|
|
|
a.Show(a.DateToString(date, "yyyy-MM-dd"));
|
|
|
|
}
|
|
|
|
|
|
|
|
ShowPrevious() {
|
|
|
|
const a = this;
|
|
|
|
|
|
|
|
let date = a.stringToDate(a.ShowDate);
|
|
|
|
date.setMonth(date.getMonth() - 1);
|
|
|
|
|
|
|
|
a.Show(a.DateToString(date, "yyyy-MM-dd"));
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateLabel(date, label) {
|
|
|
|
const a = this;
|
|
|
|
|
|
|
|
let event = a.FindEvent(date);
|
|
|
|
if (event == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
event.Label = label;
|
|
|
|
|
|
|
|
a.Invalidate(false, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateMarker(date, borderColour, backColour) {
|
|
|
|
const a = this;
|
|
|
|
|
|
|
|
let event = a.FindEvent(date);
|
|
|
|
if (event == null) {
|
|
|
|
return;
|
|
|
|
}
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
event.BorderColour = borderColour;
|
|
|
|
event.BackColour = backColour;
|
|
|
|
|
|
|
|
a.Invalidate(false, true);
|
|
|
|
}
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
initialiseComponents() {
|
|
|
|
const a = this;
|
|
|
|
const coords = a.getClientCoords();
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-09-30 22:45:55 +00:00
|
|
|
a.ctx.canvas.addEventListener('mousedown', function (e) {
|
2023-10-01 02:37:24 +00:00
|
|
|
if (!a.Enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
var event = a.FindEventsByCoords(e.offsetX, e.offsetY);
|
|
|
|
if (event == null) {
|
|
|
|
return;
|
|
|
|
}
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
if (a.Debug) console.log(event);
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
a.OnEventClick(event);
|
2023-09-30 22:45:55 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
Invalidate(redrawAxis, redrawMarkers) {
|
2023-09-30 22:45:55 +00:00
|
|
|
const a = this;
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
if (redrawAxis) {
|
|
|
|
a.ctx.clearRect(0, 0, a.ctx.canvas.width, a.ctx.canvas.height);
|
2023-10-01 02:37:24 +00:00
|
|
|
|
|
|
|
a.drawAxis();
|
|
|
|
a.drawXAxis();
|
|
|
|
a.drawXAxisLabels();
|
2023-10-02 22:42:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (redrawMarkers) {
|
|
|
|
a.clearChart();
|
|
|
|
|
|
|
|
const visibleEvents = a.FindVisibleEvents();
|
2023-10-01 02:37:24 +00:00
|
|
|
const coords = a.getClientCoords();
|
|
|
|
coords.Y1 += a.Marker.Width;
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
if (a.Debug) console.log(visibleEvents);
|
|
|
|
|
|
|
|
visibleEvents.forEach(function (e, i) {
|
|
|
|
// Calculate Y position
|
|
|
|
let posY = a.calcMarkerPosition(e.Position.X, coords.Y1);
|
|
|
|
|
|
|
|
a.drawVerticalLine(e.Position.X, posY);
|
|
|
|
const markerCoords = a.drawMarker(e.Position.X, posY, e.BorderColour, e.BackColour);
|
|
|
|
const labelSize = a.drawText((markerCoords.X2 + a.GraphRectangle.Margin), markerCoords.Y1, e.Label, a.Marker.Font, a.Marker.ForeColour, "left");
|
|
|
|
|
|
|
|
e.Position = { X: e.Position.X, Y: posY };
|
|
|
|
|
|
|
|
e.HitBox = {
|
|
|
|
X1: markerCoords.X1,
|
|
|
|
Y1: markerCoords.Y1,
|
|
|
|
X2: (markerCoords.X2 + a.GraphRectangle.Margin + labelSize.Width),
|
|
|
|
Y2: markerCoords.Y2
|
|
|
|
};
|
|
|
|
|
|
|
|
if (a.Debug) a.drawRectangle(e.HitBox);
|
|
|
|
if (a.Debug) console.log(e);
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-10-02 23:04:03 +00:00
|
|
|
GenerateEvent(date) {
|
2023-10-02 22:42:08 +00:00
|
|
|
const a = this;
|
|
|
|
|
|
|
|
return {
|
|
|
|
Date: date,
|
|
|
|
Label: "",
|
|
|
|
Position: { X: 0, Y: 0 },
|
|
|
|
Events: [],
|
|
|
|
HitBox: null,
|
|
|
|
BorderColour: a.Marker.BorderColour,
|
|
|
|
BackColour: a.Marker.BackColour
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-10-02 23:04:03 +00:00
|
|
|
GenerateEventItem() {
|
2023-10-02 22:42:08 +00:00
|
|
|
return {
|
|
|
|
Title: "",
|
|
|
|
Description: "",
|
|
|
|
Link: "",
|
|
|
|
Tag: null
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
DateToString(date, pattern) {
|
|
|
|
let result = pattern;
|
|
|
|
|
|
|
|
result = result.replace("fffffff", date.getMilliseconds().toString().padStart(7, '0'));
|
|
|
|
result = result.replace("ffffff", date.getMilliseconds().toString().padStart(6, '0'));
|
|
|
|
result = result.replace("fffff", date.getMilliseconds().toString().padStart(5, '0'));
|
|
|
|
result = result.replace("yyyy", date.getFullYear().toString().padStart(4, '0'));
|
|
|
|
result = result.replace("MMMM", "{1}");
|
|
|
|
result = result.replace("dddd", "{2}");
|
|
|
|
result = result.replace("ffff", date.getMilliseconds().toString().padStart(4, '0'));
|
|
|
|
result = result.replace("yyy", date.getFullYear().toString().padStart(3, '0'));
|
|
|
|
result = result.replace("MMM", "{3}");
|
|
|
|
result = result.replace("ddd", "{4}");
|
|
|
|
result = result.replace("fff", date.getMilliseconds().toString().padStart(3, '0'));
|
|
|
|
result = result.replace("zzz", "");
|
|
|
|
result = result.replace("yy", date.getFullYear().toString().slice(-2));
|
|
|
|
result = result.replace("MM", (date.getMonth() + 1).toString().padStart(2, '0'));
|
|
|
|
result = result.replace("dd", date.getDate().toString().padStart(2, '0'));
|
|
|
|
result = result.replace("HH", date.getHours().toString().padStart(2, '0'));
|
|
|
|
result = result.replace("hh", (date.getHours() > 12 ? (date.getHours() - 12) : date.getHours()).toString().padStart(2, '0'));
|
|
|
|
result = result.replace("mm", date.getMinutes().toString().padStart(2, '0'));
|
|
|
|
result = result.replace("ss", date.getSeconds().toString().padStart(2, '0'));
|
|
|
|
result = result.replace("ff", date.getMilliseconds().toString().padStart(2, '0'));
|
|
|
|
result = result.replace("tt", "{5}");
|
|
|
|
result = result.replace("zz", "");
|
|
|
|
result = result.replace("y", date.getFullYear().toString());
|
|
|
|
result = result.replace("M", (date.getMonth() + 1).toString());
|
|
|
|
result = result.replace("d", date.getDate().toString());
|
|
|
|
result = result.replace("H", date.getHours().toString());
|
|
|
|
result = result.replace("h", (date.getHours() > 12 ? (date.getHours() - 12) : date.getHours()).toString());
|
|
|
|
result = result.replace("m", date.getMinutes().toString());
|
|
|
|
result = result.replace("s", date.getSeconds().toString());
|
|
|
|
result = result.replace("z", "");
|
|
|
|
result = result.replace("t", "{6}");
|
|
|
|
result = result.replace("Z", "");
|
|
|
|
|
|
|
|
result = result.replace("{1}", date.toLocaleString('default', { month: 'long' }));
|
|
|
|
result = result.replace("{2}", date.toLocaleString('default', { weekday: 'long' }));
|
|
|
|
result = result.replace("{3}", date.toLocaleString('default', { month: 'short' }));
|
|
|
|
result = result.replace("{4}", date.toLocaleString('default', { weekday: 'short' }));
|
|
|
|
result = result.replace("{5}", (date.getHours() >= 12 ? "PM" : "AM"));
|
|
|
|
result = result.replace("{6}", (date.getHours() >= 12 ? "P" : "A"));
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
OnEventClick(event) {
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
calcGraphArea() {
|
|
|
|
const a = this;
|
|
|
|
|
|
|
|
let result = {
|
|
|
|
X: a.Padding.Left,
|
|
|
|
Y: a.Padding.Top,
|
|
|
|
Width: (a.Size.Width - a.Padding.Right),
|
|
|
|
Height: (a.Size.Height - a.Padding.Bottom),
|
|
|
|
Margin: (a.Marker.BorderWidth * 2)
|
|
|
|
};
|
|
|
|
|
|
|
|
result.StepHeight = a.Marker.Width + result.Margin;
|
|
|
|
result.NoStep = Math.floor(result.Height / result.StepHeight);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
calcMarkerPosition(x, y) {
|
|
|
|
const a = this;
|
2023-09-30 22:45:55 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
// Calculate Y position
|
|
|
|
let hasMoved = false;
|
|
|
|
let posY = y;
|
|
|
|
for (let i=0; i<a.GraphRectangle.NoStep; i++)
|
|
|
|
{
|
|
|
|
posY = y + (a.GraphRectangle.StepHeight * i);
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
var clippedEvent = a.FindEventsByCoords(x, posY);
|
|
|
|
if (clippedEvent == null) {
|
|
|
|
hasMoved = true;
|
|
|
|
break;
|
2023-10-01 02:37:24 +00:00
|
|
|
}
|
2023-10-02 22:42:08 +00:00
|
|
|
}
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
if (!hasMoved) {
|
|
|
|
posY = y;
|
2023-10-01 02:37:24 +00:00
|
|
|
}
|
2023-10-02 22:42:08 +00:00
|
|
|
|
|
|
|
return posY;
|
2023-09-30 22:45:55 +00:00
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
clearChart() {
|
|
|
|
const a = this;
|
|
|
|
const rect = a.getClientCoords();
|
|
|
|
|
|
|
|
a.ctx.clearRect((rect.X1 + a.Axis.LineWidth), rect.Y1, (rect.X2 - rect.X1 - a.Axis.LineWidth), (rect.Y2 - rect.Y1 - a.Axis.LineWidth));
|
|
|
|
|
|
|
|
// Clear marker positions
|
|
|
|
const visibleEvents = a.FindVisibleEvents();
|
|
|
|
visibleEvents.forEach(function (e, i) {
|
|
|
|
e.Position = { X: 0, Y: 0 };
|
|
|
|
e.HitBox = null;
|
|
|
|
});
|
|
|
|
}
|
2023-09-30 22:45:55 +00:00
|
|
|
|
|
|
|
drawAxis() {
|
|
|
|
const a = this;
|
2023-10-01 02:37:24 +00:00
|
|
|
const coords = a.getClientCoords();
|
2023-09-30 22:45:55 +00:00
|
|
|
if (coords == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
a.ctx.beginPath();
|
|
|
|
a.ctx.moveTo(coords.X1, coords.Y1);
|
|
|
|
a.ctx.lineTo(coords.X1, coords.Y2);
|
|
|
|
a.ctx.lineTo(coords.X2, coords.Y2);
|
|
|
|
a.ctx.lineWidth = a.Axis.LineWidth;
|
|
|
|
a.ctx.strokeStyle = a.Axis.LineColour1;
|
|
|
|
a.ctx.stroke();
|
2023-10-02 22:42:08 +00:00
|
|
|
|
|
|
|
if (a.Debug) a.drawRectangle(coords);
|
2023-09-30 22:45:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
drawXAxis() {
|
|
|
|
const a = this;
|
2023-10-01 02:37:24 +00:00
|
|
|
const coords = a.getClientCoords();
|
2023-09-30 22:45:55 +00:00
|
|
|
if (coords == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let x = coords.X1;
|
|
|
|
let y = coords.Y2 + a.Axis.LineWidth;
|
|
|
|
|
|
|
|
let i = 0;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
if (x >= coords.X2) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
a.ctx.beginPath();
|
|
|
|
a.ctx.moveTo(x, y);
|
|
|
|
|
|
|
|
if ((i % a.Axis.X.NoPartPerDay) == 0) {
|
|
|
|
a.ctx.lineTo(x, (y + a.Axis.X.DayLineHeight));
|
|
|
|
a.ctx.strokeStyle = a.Axis.X.DayLineColour;
|
|
|
|
} else {
|
|
|
|
a.ctx.lineTo(x, (y + a.Axis.X.HourLineHeight));
|
|
|
|
a.ctx.strokeStyle = a.Axis.X.HourLineColour;
|
|
|
|
}
|
|
|
|
|
|
|
|
a.ctx.lineWidth = a.Axis.LineWidth;
|
|
|
|
a.ctx.stroke();
|
|
|
|
|
|
|
|
x += a.Axis.X.HourLineSpace;
|
|
|
|
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
drawXAxisLabels() {
|
|
|
|
const a = this;
|
2023-10-01 02:37:24 +00:00
|
|
|
const coords = a.getClientCoords();
|
2023-09-30 22:45:55 +00:00
|
|
|
if (coords == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const result = a.getXAxis();
|
|
|
|
|
|
|
|
const y = coords.Y2 + a.Axis.LineWidth;
|
|
|
|
|
|
|
|
result.forEach(function(e, i) {
|
|
|
|
const date = a.stringToDate(e.Date);
|
|
|
|
|
|
|
|
let writeLabel = false;
|
|
|
|
if ((i == 0)) {
|
|
|
|
// Don't label first entry if too close to the next month
|
|
|
|
if (date.getDate() < 25) {
|
|
|
|
writeLabel = true;
|
|
|
|
}
|
|
|
|
} else if (date.getDate() == 1) {
|
|
|
|
writeLabel = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if (i == 0) {
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
const labelSize = a.drawText(e.X, (y + a.Axis.X.DayLineHeight), a.DateToString(date, "dd"), a.Axis.Font, a.Axis.LabelColour, "center");
|
2023-09-30 22:45:55 +00:00
|
|
|
const label2Spacing = 6;
|
|
|
|
|
2023-10-02 23:04:03 +00:00
|
|
|
a.LastDate = a.DateToString(date, "yyyy-MM-dd");
|
|
|
|
|
2023-09-30 22:45:55 +00:00
|
|
|
// Write month on first of the month
|
|
|
|
if (writeLabel) {
|
2023-10-02 22:42:08 +00:00
|
|
|
a.drawText(e.X, (y + a.Axis.X.DayLineHeight + labelSize.Height + label2Spacing), a.DateToString(date, "MMMM yyyy"), a.Axis.Font, a.Axis.LabelColour, "left");
|
2023-09-30 22:45:55 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
drawMarker(x, y, borderColour, backColour) {
|
2023-09-30 22:45:55 +00:00
|
|
|
const a = this;
|
2023-10-01 02:37:24 +00:00
|
|
|
const coords = a.getClientCoords();
|
2023-09-30 22:45:55 +00:00
|
|
|
if (coords == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const width = a.Marker.Width - (a.Marker.BorderWidth * 2);
|
|
|
|
|
|
|
|
a.ctx.beginPath();
|
|
|
|
a.ctx.arc(x, y, width, 0, 2 * Math.PI, false);
|
2023-10-02 22:42:08 +00:00
|
|
|
a.ctx.fillStyle = backColour;
|
2023-09-30 22:45:55 +00:00
|
|
|
a.ctx.fill();
|
|
|
|
a.ctx.lineWidth = a.Marker.BorderWidth;
|
2023-10-02 22:42:08 +00:00
|
|
|
a.ctx.strokeStyle = borderColour;
|
2023-09-30 22:45:55 +00:00
|
|
|
a.ctx.stroke();
|
2023-10-01 02:37:24 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
return a.measureMarker(x, y);
|
2023-09-30 22:45:55 +00:00
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
drawText(x, y, label, font, foreColour, align) {
|
2023-09-30 22:45:55 +00:00
|
|
|
const a = this;
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
a.ctx.font = font;
|
|
|
|
a.ctx.fillStyle = foreColour;
|
2023-09-30 22:45:55 +00:00
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
const size = a.measureText(font, label);
|
2023-09-30 22:45:55 +00:00
|
|
|
|
|
|
|
switch (align) {
|
|
|
|
case "center":
|
|
|
|
x = (x - size.OffsetLeft);
|
|
|
|
break;
|
|
|
|
case "right":
|
|
|
|
x = (x - size.Width);
|
|
|
|
break;
|
|
|
|
case "left":
|
|
|
|
default:
|
|
|
|
// do nothing
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
a.ctx.fillText(label, x, (y + size.Height));
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
drawRectangle(coords) {
|
|
|
|
const a = this;
|
|
|
|
|
|
|
|
a.ctx.beginPath();
|
|
|
|
a.ctx.rect(coords.X1, coords.Y1, (coords.X2 - coords.X1), (coords.Y2 - coords.Y1));
|
|
|
|
//a.ctx.fillStyle = 'yellow';
|
|
|
|
//a.ctx.fill();
|
|
|
|
a.ctx.lineWidth = 1;
|
|
|
|
a.ctx.strokeStyle = 'red';
|
|
|
|
a.ctx.stroke();
|
|
|
|
}
|
|
|
|
|
2023-10-01 02:37:24 +00:00
|
|
|
drawVerticalLine(x, y) {
|
2023-09-30 22:45:55 +00:00
|
|
|
const a = this;
|
2023-10-01 02:37:24 +00:00
|
|
|
const coords = a.getClientCoords();
|
2023-09-30 22:45:55 +00:00
|
|
|
if (coords == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-01 02:37:24 +00:00
|
|
|
if (y <= 0) {
|
|
|
|
y = (coords.Y1 + a.HighlightLine.Width);
|
|
|
|
}
|
|
|
|
|
2023-09-30 22:45:55 +00:00
|
|
|
a.ctx.beginPath();
|
2023-10-01 02:37:24 +00:00
|
|
|
a.ctx.moveTo(x, y);
|
2023-09-30 22:45:55 +00:00
|
|
|
a.ctx.lineTo(x, (coords.Y2 - a.HighlightLine.Width));
|
|
|
|
a.ctx.lineWidth = a.HighlightLine.Width;
|
|
|
|
a.ctx.strokeStyle = a.HighlightLine.Colour;
|
|
|
|
a.ctx.stroke();
|
|
|
|
}
|
|
|
|
|
2023-10-01 02:37:24 +00:00
|
|
|
getClientCoords() {
|
2023-09-30 22:45:55 +00:00
|
|
|
const a = this;
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
if (a.GraphRectangle == null) {
|
2023-09-30 22:45:55 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2023-10-02 22:42:08 +00:00
|
|
|
X1: a.GraphRectangle.X,
|
|
|
|
Y1: a.GraphRectangle.Y,
|
|
|
|
X2: (a.GraphRectangle.Width - a.GraphRectangle.X),
|
|
|
|
Y2: (a.GraphRectangle.Height - a.GraphRectangle.Y)
|
2023-09-30 22:45:55 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
getXAxis() {
|
|
|
|
const a = this;
|
2023-10-01 02:37:24 +00:00
|
|
|
const coords = a.getClientCoords();
|
2023-09-30 22:45:55 +00:00
|
|
|
if (coords == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let result = [];
|
|
|
|
let x = coords.X1;
|
2023-10-02 22:42:08 +00:00
|
|
|
let date = a.stringToDate(a.ShowDate);
|
2023-09-30 22:45:55 +00:00
|
|
|
|
|
|
|
// Rollback one day
|
|
|
|
date.setDate(date.getDate() - 1);
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
if (x >= coords.X2) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.push({
|
2023-10-02 22:42:08 +00:00
|
|
|
Date: a.DateToString(date, "yyyy-MM-dd"),
|
2023-09-30 22:45:55 +00:00
|
|
|
X: x
|
|
|
|
});
|
|
|
|
|
|
|
|
x += (a.Axis.X.HourLineSpace * a.Axis.X.NoPartPerDay);
|
|
|
|
date.setDate(date.getDate() + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
half(value) {
|
|
|
|
return (value / 2);
|
|
|
|
}
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
measureMarker(x, y) {
|
|
|
|
const a = this;
|
|
|
|
const offset = a.half(a.Marker.Width);
|
|
|
|
|
|
|
|
const result = {
|
|
|
|
X1: x - (offset + a.Marker.BorderWidth),
|
|
|
|
Y1: y - (offset + a.Marker.BorderWidth),
|
|
|
|
X2: x + (offset + a.Marker.BorderWidth),
|
|
|
|
Y2: y + (offset + a.Marker.BorderWidth)
|
|
|
|
};
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
measureText(font, value) {
|
2023-09-30 22:45:55 +00:00
|
|
|
const a = this;
|
|
|
|
|
2023-10-02 22:42:08 +00:00
|
|
|
a.ctx.font = font;
|
2023-09-30 22:45:55 +00:00
|
|
|
const size = a.ctx.measureText(value);
|
|
|
|
|
|
|
|
return {
|
|
|
|
Width: size.width,
|
|
|
|
Height: size.fontBoundingBoxAscent,
|
|
|
|
OffsetLeft: a.half(size.width),
|
|
|
|
OffsetTop: a.half(size.fontBoundingBoxAscent)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
stringToDate(value) {
|
|
|
|
return new Date(Date.parse(value));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|