literyzjs-timeline/bbtimeline.js

743 lines
18 KiB
JavaScript
Raw Normal View History

2023-09-30 22:45:55 +00:00
/**
* BBTimeline
2023-10-18 13:44:49 +00:00
* @version v0.1.1.121 beta (2023/10/18 2058)
2023-09-30 22:45:55 +00:00
*/
class BBTimeline {
constructor(el) {
const a = this;
a.Container = document.getElementById(el);
a.Padding = {
Left: 20,
Top: 20,
Right: 20,
2023-10-03 07:01:09 +00:00
Bottom: 0
2023-09-30 22:45:55 +00:00
};
a.Size = {
2023-10-03 07:01:09 +00:00
W: a.Container.innerWidth || a.Container.clientWidth,
H: a.Container.innerHeight || a.Container.clientHeight
2023-09-30 22:45:55 +00:00
};
2023-10-18 13:44:49 +00:00
a.Layer = {
Background: null,
Flourish: null,
Markers: null
};
2023-10-03 07:01:09 +00:00
2023-10-18 13:44:49 +00:00
a.DateParsePattern = "yyyy-MM-dd";
2023-10-03 07:01:09 +00:00
2023-09-30 22:45:55 +00:00
a.Axis = {
LineColour1: "#CFCFCF",
LineWidth: 1,
Font: "8pt Arial",
LabelColour: "#000000",
2023-10-03 07:01:09 +00:00
LabelSpacing: 6,
2023-09-30 22:45:55 +00:00
X: {
NoPartPerDay: 4,
HourLineSpace: 6,
HourLineHeight: 10,
HourLineColour: "#A6A6A6",
DayLineHeight: 20,
DayLineColour: "#282828"
}
};
2023-10-18 13:44:49 +00:00
2023-09-30 22:45:55 +00:00
a.Marker = {
BorderColour: "#3A5D9C",
BorderWidth: 2,
BackColour: "#D4DEEF",
Width: 10,
ForeColour: "#3A5D9C",
Font: "9pt Arial",
2023-10-03 17:17:55 +00:00
CollisionMargin: 8
2023-09-30 22:45:55 +00:00
};
2023-10-18 13:44:49 +00:00
2023-09-30 22:45:55 +00:00
a.HighlightLine = {
Colour: "#A6A6A6",
Width: 1,
};
2023-10-18 13:44:49 +00:00
a.Events = [];
2023-10-03 07:01:09 +00:00
a.StartDate = a.DateToString(new Date(), a.DateParsePattern);
a.ShowDate = a.StartDate;
2023-10-18 13:44:49 +00:00
// a.GraphRectangle = a.calcGraphRectangle();
a.Enabled = false;
2023-10-18 13:44:49 +00:00
a.Debug = false;
a.initialiseComponents();
2023-09-30 22:45:55 +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
let event = a.FindEvent(date);
if (event == null) {
2023-10-02 23:04:03 +00:00
a.Events.push(a.GenerateEvent(date));
event = a.FindEvent(date);
}
if (label != null) {
event.Label = label;
}
event.Events.push(_options);
2023-09-30 22:45:55 +00:00
}
2023-10-03 07:01:09 +00:00
CalcEndDate() {
const a = this;
2023-10-18 13:44:49 +00:00
const clientWidth = (a.Size.W - (a.Padding.Left + a.Padding.Right));
const calcdays = Math.floor(clientWidth / (a.Axis.X.NoPartPerDay * a.Axis.X.HourLineSpace));
2023-10-03 07:01:09 +00:00
let date = a.ConvertToDate(a.ShowDate);
date.setDate(date.getDate() + calcdays);
// Minus one for lead up
date.setDate(date.getDate() - 1);
return a.DateToString(date, a.DateParsePattern);
}
Clear() {
2023-09-30 22:45:55 +00:00
const a = this;
2023-10-18 13:44:49 +00:00
a.Layer.Background.Clear();
a.Layer.Flourish.Clear();
a.Layer.Markers.Clear();
2023-10-03 07:01:09 +00:00
a.StartDate = a.DateToString(new Date(), a.DateParsePattern);
a.ShowDate = a.StartDate;
a.Enabled = false;
a.Events = [];
}
DeleteMarker(date)
{
const a = this;
for (let i=0; i<a.Events.length; i++) {
if (a.Events[i].Date != date) {
continue;
}
2023-09-30 22:45:55 +00:00
a.Events.splice(i, 1);
2023-09-30 22:45:55 +00:00
}
}
FindDatePosition(date) {
2023-09-30 22:45:55 +00:00
const a = this;
2023-10-18 13:44:49 +00:00
const points = a.Layer.Background.calcXAxisPositions();
for (let i=0; i<points.length; i++) {
if (points[i].Date == date){
return points[i];
}
}
return null;
}
FindVisibleEvents() {
const a = this;
let result = [];
2023-10-18 13:44:49 +00:00
const availableX = a.Layer.Background.calcXAxisPositions();
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) {
const a = this;
2023-09-30 22:45:55 +00:00
for (let i=0; i<a.Events.length; i++) {
const e = a.Events[i].HitBox;
if (a.Events[i].HitBox == null) {
continue;
}
2023-10-03 17:17:55 +00:00
const x2 = (e.X + e.W);
const y2 = (e.Y + e.H);
if ((x >= e.X) && (x <= x2) && (y >= e.Y) && (y <= y2)){
return a.Events[i];
2023-09-30 22:45:55 +00:00
}
}
return null;
}
Load(startDate) {
const a = this;
a.StartDate = startDate;
2023-09-30 22:45:55 +00:00
a.Show(startDate);
}
Show(date) {
const a = this;
2023-10-03 07:01:09 +00:00
if (a.ConvertToDate(date) < a.ConvertToDate(a.StartDate)) {
date = a.StartDate;
}
a.ShowDate = date;
a.Enabled = true;
a.Invalidate(true, true);
}
2023-10-18 13:44:49 +00:00
// ShowNext() {
// const a = this;
2023-10-18 13:44:49 +00:00
// let date = a.ConvertToDate(a.ShowDate);
// date.setMonth(date.getMonth() + 1);
2023-10-18 13:44:49 +00:00
// a.Show(a.DateToString(date, a.DateParsePattern));
// }
2023-10-18 13:44:49 +00:00
// ShowPrevious() {
// const a = this;
2023-10-18 13:44:49 +00:00
// let date = a.ConvertToDate(a.ShowDate);
// date.setMonth(date.getMonth() - 1);
2023-10-18 13:44:49 +00:00
// a.Show(a.DateToString(date, a.DateParsePattern));
// }
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;
}
event.BorderColour = borderColour;
event.BackColour = backColour;
a.Invalidate(false, true);
}
2023-10-18 13:44:49 +00:00
// initialiseComponents2() {
// const a = this;
2023-10-18 13:44:49 +00:00
// a.ctx.canvas.addEventListener('mousedown', function (e) {
// if (!a.Enabled) {
// return;
// }
2023-10-18 13:44:49 +00:00
// var event = a.FindEventsByCoords(e.offsetX, e.offsetY);
// if (event == null) {
// return;
// }
2023-10-18 13:44:49 +00:00
// if (a.Debug) console.log(event);
2023-10-18 13:44:49 +00:00
// a.OnMouseDown(this, e, event);
// });
2023-10-18 13:44:49 +00:00
// a.ctx.canvas.addEventListener('click', function (e) {
// if (!a.Enabled) {
// return;
// }
2023-10-18 13:44:49 +00:00
// var event = a.FindEventsByCoords(e.offsetX, e.offsetY);
// if (event == null) {
// return;
// }
2023-10-18 13:44:49 +00:00
// if (a.Debug) console.log(event);
2023-10-18 13:44:49 +00:00
// a.OnClick(this, e, event);
// });
// }
2023-09-30 22:45:55 +00:00
Invalidate(redrawAxis, redrawMarkers) {
2023-09-30 22:45:55 +00:00
const a = this;
2023-10-18 13:44:49 +00:00
if (redrawAxis) {
a.Layer.Background.Invalidate();
}
if (redrawMarkers) {
a.Layer.Markers.Invalidate();
}
return;
if (redrawAxis) {
a.ctx.clearRect(0, 0, a.ctx.canvas.width, a.ctx.canvas.height);
a.drawAxis();
a.drawXAxis();
a.drawXAxisLabels();
}
if (redrawMarkers) {
2023-10-18 13:44:49 +00:00
// a.clearChart();
2023-10-18 13:44:49 +00:00
// const startPosY = (a.GraphRectangle.Y + a.Marker.Width);
// const visibleEvents = a.FindVisibleEvents();
2023-10-18 13:44:49 +00:00
// if (a.Debug) console.log(visibleEvents);
visibleEvents.forEach(function (e, i) {
// Calculate Y position
2023-10-03 07:01:09 +00:00
let posY = a.calcMarkerPosition(e.Position.X, startPosY);
a.drawVerticalLine(e.Position.X, posY);
2023-10-03 07:01:09 +00:00
const markerRectangle = a.drawMarker(e.Position.X, posY, e.BorderColour, e.BackColour);
const labelSize = a.drawText((markerRectangle.X + markerRectangle.W + a.GraphRectangle.Margin), markerRectangle.Y, e.Label, a.Marker.Font, a.Marker.ForeColour, "left");
e.Position = { X: e.Position.X, Y: posY };
e.HitBox = {
2023-10-03 07:01:09 +00:00
X: markerRectangle.X,
Y: markerRectangle.Y,
2023-10-03 17:17:55 +00:00
W: (markerRectangle.W + a.GraphRectangle.Margin + labelSize.Width + a.Marker.CollisionMargin),
H: markerRectangle.H
};
if (a.Debug) a.drawRectangle(e.HitBox);
if (a.Debug) console.log(e);
});
}
if (a.Debug) a.drawRectangle(a.GraphRectangle);
}
2023-10-02 23:04:03 +00:00
GenerateEvent(date) {
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() {
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;
}
OnMouseDown(sender, e, event) {
}
OnClick(sender, e, event) {
}
2023-10-18 13:44:49 +00:00
// calcGraphRectangle() {
// const a = this;
// // const xAxisHeight = a.calcXAxisHeight();
// // let result = {
// // X: a.Padding.Left,
// // Y: a.Padding.Top,
// // W: (a.Size.W - (a.Padding.Left + a.Padding.Right)),
// // H: (a.Size.H - (a.Padding.Top + a.Padding.Bottom) - xAxisHeight),
// // Margin: (a.Marker.BorderWidth * 2)
// // };
// let result = {
// X: a.Padding.Left,
// Y: a.Padding.Top,
// W: (a.Size.W - (a.Padding.Left + a.Padding.Right)),
// H: (a.Size.H - (a.Padding.Top + a.Padding.Bottom)),
// Margin: (a.Marker.BorderWidth * 2)
// };
// result.StepHeight = a.Marker.Width + result.Margin;
// result.NoStep = Math.floor(result.H / result.StepHeight);
// return result;
// }
// calcXAxisHeight() {
// const a = this;
// const labelSize = a.measureText(a.Axis.Font, "0");
// const result = labelSize.Height + a.Axis.LabelSpacing + (a.Axis.X.DayLineHeight * 2);
// return result;
// }
// calcMarkerPosition(x, y) {
// const a = this;
// // Calculate Y position
// let hasMoved = false;
// let posY = y;
// for (let i=0; i<a.GraphRectangle.NoStep; i++)
// {
// posY = y + (a.GraphRectangle.StepHeight * i);
// var clippedEvent = a.FindEventsByCoords(x, posY);
// if (clippedEvent == null) {
// hasMoved = true;
// break;
// }
// }
// if (!hasMoved) {
// posY = y;
// }
// return posY;
// }
// clearChart() {
// const a = this;
// const rect = {
// X: a.GraphRectangle.X,
// Y: a.GraphRectangle.Y,
// W: a.GraphRectangle.W,
// H: a.GraphRectangle.H
// };
// rect.X += a.Axis.LineWidth;
// rect.Y -= a.Padding.Top;
// rect.W -= a.Axis.LineWidth;
// rect.W += a.Padding.Right;
// rect.H -= a.Axis.LineWidth;
// rect.H += a.Padding.Top;
// a.ctx.clearRect(rect.X, rect.Y, rect.W, rect.H);
// if (a.Debug) a.drawRectangle(rect);
// // Clear marker positions
// const visibleEvents = a.FindVisibleEvents();
// visibleEvents.forEach(function (e, i) {
// e.Position = { X: 0, Y: 0 };
// e.HitBox = null;
// });
// }
// drawAxis() {
// const a = this;
// a.ctx.beginPath();
// a.ctx.moveTo(a.GraphRectangle.X, a.GraphRectangle.Y);
// a.ctx.lineTo(a.GraphRectangle.X, (a.GraphRectangle.H + a.GraphRectangle.Y));
// a.ctx.lineTo((a.GraphRectangle.W + a.GraphRectangle.X), (a.GraphRectangle.H + a.GraphRectangle.Y));
// a.ctx.lineWidth = a.Axis.LineWidth;
// a.ctx.strokeStyle = a.Axis.LineColour1;
// a.ctx.stroke();
// }
// drawXAxis() {
// const a = this;
// let startPosX = a.GraphRectangle.X;
// const endPosX = (a.GraphRectangle.X + a.GraphRectangle.W);
// const posY = (a.GraphRectangle.Y + a.GraphRectangle.H) + a.Axis.LineWidth;
// let i = 0;
// while (true) {
// if (startPosX >= endPosX) {
// break;
// }
// a.ctx.beginPath();
// a.ctx.moveTo(startPosX, posY);
// if ((i % a.Axis.X.NoPartPerDay) == 0) {
// a.ctx.lineTo(startPosX, (posY + a.Axis.X.DayLineHeight));
// a.ctx.strokeStyle = a.Axis.X.DayLineColour;
// } else {
// a.ctx.lineTo(startPosX, (posY + a.Axis.X.HourLineHeight));
// a.ctx.strokeStyle = a.Axis.X.HourLineColour;
// }
// a.ctx.lineWidth = a.Axis.LineWidth;
// a.ctx.stroke();
// startPosX += a.Axis.X.HourLineSpace;
// i++;
// }
// }
// drawXAxisLabels() {
// const a = this;
// const result = a.getXAxis();
// const posY = (a.GraphRectangle.Y + a.GraphRectangle.H) + a.Axis.LineWidth;
// result.forEach(function(e, i) {
// const date = a.ConvertToDate(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;
// // }
// const labelSize = a.drawText(e.X, (posY + a.Axis.X.DayLineHeight), a.DateToString(date, "dd"), a.Axis.Font, a.Axis.LabelColour, "center");
// // Write month on first of the month
// if (writeLabel) {
// a.drawText(e.X, (posY + a.Axis.X.DayLineHeight + labelSize.Height + a.Axis.LabelSpacing), a.DateToString(date, "MMMM yyyy"), a.Axis.Font, a.Axis.LabelColour, "left");
// }
// });
// }
// drawMarker(x, y, borderColour, backColour) {
// const a = this;
// const width = a.Marker.Width - (a.Marker.BorderWidth * 2);
// a.ctx.beginPath();
// a.ctx.arc(x, y, width, 0, 2 * Math.PI, false);
// a.ctx.fillStyle = backColour;
// a.ctx.fill();
// a.ctx.lineWidth = a.Marker.BorderWidth;
// a.ctx.strokeStyle = borderColour;
// a.ctx.stroke();
// return a.measureMarker(x, y);
// }
// drawText(x, y, label, font, foreColour, align) {
// const a = this;
// a.ctx.font = font;
// a.ctx.fillStyle = foreColour;
// const size = a.measureText(font, label);
// 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;
// }
// drawRectangle(rectangle) {
// const a = this;
// a.ctx.beginPath();
// a.ctx.rect(rectangle.X, rectangle.Y, rectangle.W, rectangle.H);
// //a.ctx.fillStyle = 'yellow';
// //a.ctx.fill();
// a.ctx.lineWidth = 1;
// a.ctx.strokeStyle = 'red';
// a.ctx.stroke();
// }
// drawVerticalLine(x, y) {
// const a = this;
// const linePosY = (a.GraphRectangle.Y + a.GraphRectangle.H);
// if (y <= 0) {
// y = (a.GraphRectangle.Y + a.HighlightLine.Width);
// }
// a.ctx.beginPath();
// a.ctx.moveTo(x, y);
// a.ctx.lineTo(x, (linePosY - a.HighlightLine.Width));
// a.ctx.lineWidth = a.HighlightLine.Width;
// a.ctx.strokeStyle = a.HighlightLine.Colour;
// a.ctx.stroke();
// }
// getXAxis() {
// const a = this;
// const endPosX = (a.GraphRectangle.X + a.GraphRectangle.W);
// let result = [];
// let x = a.GraphRectangle.X;
// let date = a.ConvertToDate(a.ShowDate);
// // Rollback one day
// date.setDate(date.getDate() - 1);
// while (true) {
// if (x >= endPosX) {
// break;
// }
// result.push({
// Date: a.DateToString(date, a.DateParsePattern),
// X: x
// });
// x += (a.Axis.X.HourLineSpace * a.Axis.X.NoPartPerDay);
// date.setDate(date.getDate() + 1);
// }
// return result;
// }
// half(value) {
// return (value / 2);
// }
// measureMarker(x, y) {
// const a = this;
// const offset = a.half(a.Marker.Width);
// const result = {
// X: x - (offset + a.Marker.BorderWidth),
// Y: y - (offset + a.Marker.BorderWidth),
// W: (a.Marker.Width + (a.Marker.BorderWidth * 2)),
// H: (a.Marker.Width + (a.Marker.BorderWidth * 2))
// };
// return result;
// }
// measureText(font, value) {
// const a = this;
// a.ctx.font = font;
// const size = a.ctx.measureText(value);
// return {
// Width: size.width,
// Height: size.fontBoundingBoxAscent,
// OffsetLeft: a.half(size.width),
// OffsetTop: a.half(size.fontBoundingBoxAscent)
// };
// }
2023-10-18 13:44:49 +00:00
ConvertToDate(value) {
return new Date(Date.parse(value));
2023-09-30 22:45:55 +00:00
}
2023-10-18 13:44:49 +00:00
initialiseComponents() {
const a = this;
2023-10-18 13:44:49 +00:00
a.Container.innerHTML = "<canvas></canvas><canvas></canvas><canvas></canvas>";
2023-10-18 13:44:49 +00:00
const canvasList = a.Container.getElementsByTagName("canvas");
2023-09-30 22:45:55 +00:00
2023-10-18 13:44:49 +00:00
a.Layer.Background = new BBTimelineBackgroundCanvas(a, canvasList[0]);
a.Layer.Flourish = new BBTimelineCanvas(a, canvasList[1]);
a.Layer.Markers = new BBTimelineForegroundCanvas(a, canvasList[2]);
2023-09-30 22:45:55 +00:00
}
}