Compare commits
13 Commits
release/0.
...
master
Author | SHA1 | Date | |
---|---|---|---|
737d07b55f | |||
|
039a17f8a1 | ||
02f170655b | |||
|
501bab98ec | ||
|
ae12b4040c | ||
a9331cb881 | |||
|
e82d90c745 | ||
|
0bbed0dfee | ||
|
8663a39116 | ||
|
5637d8d3b9 | ||
|
e48dca5da1 | ||
f064d12e4c | |||
a8a9c802be |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/dist
|
||||||
|
/node_modules
|
699
bbtimeline.js
699
bbtimeline.js
@ -1,699 +0,0 @@
|
|||||||
/**
|
|
||||||
* BBTimeline
|
|
||||||
* @version v0.1.0.089 beta (2023/10/14 1658)
|
|
||||||
*/
|
|
||||||
class BBTimeline {
|
|
||||||
constructor(el) {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
a.Container = document.getElementById(el);
|
|
||||||
a.DateParsePattern = "yyyy-MM-dd";
|
|
||||||
a.Debug = false;
|
|
||||||
a.Padding = {
|
|
||||||
Left: 20,
|
|
||||||
Top: 20,
|
|
||||||
Right: 20,
|
|
||||||
Bottom: 0
|
|
||||||
};
|
|
||||||
a.Size = {
|
|
||||||
W: a.Container.innerWidth || a.Container.clientWidth,
|
|
||||||
H: a.Container.innerHeight || a.Container.clientHeight
|
|
||||||
};
|
|
||||||
|
|
||||||
a.ctx = a.Container.getContext("2d");
|
|
||||||
a.ctx.canvas.width = a.Size.W;
|
|
||||||
a.ctx.canvas.height = a.Size.H;
|
|
||||||
|
|
||||||
a.Axis = {
|
|
||||||
LineColour1: "#CFCFCF",
|
|
||||||
LineWidth: 1,
|
|
||||||
Font: "8pt Arial",
|
|
||||||
LabelColour: "#000000",
|
|
||||||
LabelSpacing: 6,
|
|
||||||
X: {
|
|
||||||
NoPartPerDay: 4,
|
|
||||||
HourLineSpace: 6,
|
|
||||||
HourLineHeight: 10,
|
|
||||||
HourLineColour: "#A6A6A6",
|
|
||||||
DayLineHeight: 20,
|
|
||||||
DayLineColour: "#282828"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
a.Marker = {
|
|
||||||
BorderColour: "#3A5D9C",
|
|
||||||
BorderWidth: 2,
|
|
||||||
BackColour: "#D4DEEF",
|
|
||||||
Width: 10,
|
|
||||||
ForeColour: "#3A5D9C",
|
|
||||||
Font: "9pt Arial",
|
|
||||||
CollisionMargin: 8
|
|
||||||
};
|
|
||||||
a.HighlightLine = {
|
|
||||||
Colour: "#A6A6A6",
|
|
||||||
Width: 1,
|
|
||||||
};
|
|
||||||
a.Events = [];
|
|
||||||
a.StartDate = a.DateToString(new Date(), a.DateParsePattern);
|
|
||||||
a.ShowDate = a.StartDate;
|
|
||||||
a.GraphRectangle = a.calcGraphRectangle();
|
|
||||||
a.Enabled = false;
|
|
||||||
|
|
||||||
a.initialiseComponents();
|
|
||||||
}
|
|
||||||
|
|
||||||
AddEvent(date, label, options) {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
const _options = Object.assign(a.GenerateEventItem(), options);
|
|
||||||
|
|
||||||
let event = a.FindEvent(date);
|
|
||||||
if (event == null) {
|
|
||||||
a.Events.push(a.GenerateEvent(date));
|
|
||||||
|
|
||||||
event = a.FindEvent(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (label != null) {
|
|
||||||
event.Label = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.Events.push(_options);
|
|
||||||
}
|
|
||||||
|
|
||||||
CalcEndDate() {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
const calcdays = Math.floor(a.GraphRectangle.W / (a.Axis.X.NoPartPerDay * a.Axis.X.HourLineSpace));
|
|
||||||
|
|
||||||
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() {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
a.ctx.clearRect(0, 0, a.ctx.canvas.width, a.ctx.canvas.height);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Events.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FindDatePosition(date) {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
const points = a.getXAxis();
|
|
||||||
for (let i=0; i<points.length; i++) {
|
|
||||||
if (points[i].Date == date){
|
|
||||||
return points[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
for (let i=0; i<a.Events.length; i++) {
|
|
||||||
const e = a.Events[i].HitBox;
|
|
||||||
if (a.Events[i].HitBox == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Load(startDate) {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
a.StartDate = startDate;
|
|
||||||
|
|
||||||
a.Show(startDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
Show(date) {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
if (a.ConvertToDate(date) < a.ConvertToDate(a.StartDate)) {
|
|
||||||
date = a.StartDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.ShowDate = date;
|
|
||||||
a.Enabled = true;
|
|
||||||
|
|
||||||
a.Invalidate(true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowNext() {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
let date = a.ConvertToDate(a.ShowDate);
|
|
||||||
date.setMonth(date.getMonth() + 1);
|
|
||||||
|
|
||||||
a.Show(a.DateToString(date, a.DateParsePattern));
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowPrevious() {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
let date = a.ConvertToDate(a.ShowDate);
|
|
||||||
date.setMonth(date.getMonth() - 1);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
initialiseComponents() {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
a.ctx.canvas.addEventListener('mousedown', function (e) {
|
|
||||||
if (!a.Enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var event = a.FindEventsByCoords(e.offsetX, e.offsetY);
|
|
||||||
if (event == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.Debug) console.log(event);
|
|
||||||
|
|
||||||
a.OnMouseDown(this, e, event);
|
|
||||||
});
|
|
||||||
|
|
||||||
a.ctx.canvas.addEventListener('click', function (e) {
|
|
||||||
if (!a.Enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var event = a.FindEventsByCoords(e.offsetX, e.offsetY);
|
|
||||||
if (event == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.Debug) console.log(event);
|
|
||||||
|
|
||||||
a.OnClick(this, e, event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Invalidate(redrawAxis, redrawMarkers) {
|
|
||||||
const a = this;
|
|
||||||
|
|
||||||
if (redrawAxis) {
|
|
||||||
a.ctx.clearRect(0, 0, a.ctx.canvas.width, a.ctx.canvas.height);
|
|
||||||
|
|
||||||
a.drawAxis();
|
|
||||||
a.drawXAxis();
|
|
||||||
a.drawXAxisLabels();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redrawMarkers) {
|
|
||||||
a.clearChart();
|
|
||||||
|
|
||||||
const startPosY = (a.GraphRectangle.Y + a.Marker.Width);
|
|
||||||
const visibleEvents = a.FindVisibleEvents();
|
|
||||||
|
|
||||||
if (a.Debug) console.log(visibleEvents);
|
|
||||||
|
|
||||||
visibleEvents.forEach(function (e, i) {
|
|
||||||
// Calculate Y position
|
|
||||||
let posY = a.calcMarkerPosition(e.Position.X, startPosY);
|
|
||||||
|
|
||||||
a.drawVerticalLine(e.Position.X, posY);
|
|
||||||
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 = {
|
|
||||||
X: markerRectangle.X,
|
|
||||||
Y: markerRectangle.Y,
|
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ConvertToDate(value) {
|
|
||||||
return new Date(Date.parse(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
5
bbtimeline.min.js
vendored
5
bbtimeline.min.js
vendored
File diff suppressed because one or more lines are too long
117
demo-test.html
117
demo-test.html
@ -10,7 +10,14 @@
|
|||||||
<!-- <script src="http://cdn.hiimray.co.uk/8206c600-707c-469e-8d49-a76ae35782af/bootstrap/5.3.0/dist/js/bootstrap.bundle.min.js"></script> -->
|
<!-- <script src="http://cdn.hiimray.co.uk/8206c600-707c-469e-8d49-a76ae35782af/bootstrap/5.3.0/dist/js/bootstrap.bundle.min.js"></script> -->
|
||||||
<!-- <link href="http://cdn.hiimray.co.uk/8206c600-707c-469e-8d49-a76ae35782af/bootstrap/5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" /> -->
|
<!-- <link href="http://cdn.hiimray.co.uk/8206c600-707c-469e-8d49-a76ae35782af/bootstrap/5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" /> -->
|
||||||
|
|
||||||
<script src="bbtimeline.js"></script>
|
<script src="dist//timeline.min.js"></script>
|
||||||
|
|
||||||
|
<!-- <script src="bbtimeline.js"></script> -->
|
||||||
|
<!-- <script src="bbtimeline-canvas.js"></script> -->
|
||||||
|
<!-- <script src="bbtimeline-background-canvas.js"></script> -->
|
||||||
|
<!-- <script src="bbtimeline-flourish-canvas.js"></script> -->
|
||||||
|
<!-- <script src="bbtimeline-foreground-canvas.js"></script> -->
|
||||||
|
|
||||||
<!-- <script src="bbtimeline.min.js"></script> -->
|
<!-- <script src="bbtimeline.min.js"></script> -->
|
||||||
|
|
||||||
<title></title>
|
<title></title>
|
||||||
@ -19,20 +26,35 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="column1">
|
<div class="column1">
|
||||||
<canvas id="myCanvas"></canvas>
|
<div id="myCanvas"></div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<textarea id="memoBox1" readonly></textarea>
|
<textarea id="memoBox1" readonly></textarea>
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="column2">
|
<div class="column2">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<button onclick="Clear()">Clear</button>
|
<button onclick="Clear()">Clear</button>
|
||||||
<button onclick="ToggleDebug()">Toggle Debug</button>
|
|
||||||
<button onclick="Refresh()">Refresh</button>
|
<button onclick="Refresh()">Refresh</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<button onclick="ToggleDebug()">Toggle Debug</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<p>
|
||||||
|
<button onclick="ToggleHotTracking()">Toggle Hot Tracking</button>
|
||||||
|
<button onclick="ToggleShowLabel()">Toggle Marker Label</button>
|
||||||
|
<button onclick="ToggleMarkerTail()">Toggle Marker Tail</button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button onclick="ToggleXAxisPosition()">Toggle X-Axis Position</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -47,8 +69,8 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
<button onclick="GoToToday()">Show Start Date</button>
|
<button onclick="GoToToday()">Show Start Date</button>
|
||||||
<button onclick="GoToPreviousMonth()">Show Previous Month</button>
|
<button onclick="GoToPrevious()">Show Previous</button>
|
||||||
<button onclick="GoToNextMonth()">Show Next Month</button>
|
<button onclick="GoToNext()">Show Next</button>
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
@ -88,16 +110,6 @@ body {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: #000000;
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
@ -107,6 +119,7 @@ textarea {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.column1 {
|
.column1 {
|
||||||
flex: 70%;
|
flex: 70%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@ -119,11 +132,22 @@ textarea {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#myCanvas {
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #000000;
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var timeline1 = new BBTimeline("myCanvas");
|
var timeline1 = new LiteRyzJS.Timeline("myCanvas");
|
||||||
timeline1.OnMouseDown = function(sender, e, event) {
|
timeline1.OnMouseDown = function(sender, e, event) {
|
||||||
LogInfo("");
|
LogInfo("");
|
||||||
LogInfo("OnMouseDown");
|
LogInfo("OnMouseDown");
|
||||||
@ -140,6 +164,14 @@ timeline1.OnClick = function(sender, e, event) {
|
|||||||
LogInfo(JSON.stringify(event));
|
LogInfo(JSON.stringify(event));
|
||||||
LogInfo("");
|
LogInfo("");
|
||||||
}
|
}
|
||||||
|
timeline1.OnDblClick = function(sender, e, event) {
|
||||||
|
LogInfo("");
|
||||||
|
LogInfo("OnDblClick");
|
||||||
|
LogInfo(JSON.stringify(sender));
|
||||||
|
LogInfo(JSON.stringify(e));
|
||||||
|
LogInfo(JSON.stringify(event));
|
||||||
|
LogInfo("");
|
||||||
|
}
|
||||||
|
|
||||||
SetToday();
|
SetToday();
|
||||||
|
|
||||||
@ -156,17 +188,48 @@ function ToggleDebug() {
|
|||||||
timeline1.Invalidate(true, true);
|
timeline1.Invalidate(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ToggleHotTracking() {
|
||||||
|
timeline1.EnableHotTracking = !timeline1.EnableHotTracking;
|
||||||
|
timeline1.Invalidate(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleShowLabel() {
|
||||||
|
timeline1.ShowMarkerLabel = !timeline1.ShowMarkerLabel;
|
||||||
|
timeline1.Invalidate(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleXAxisPosition() {
|
||||||
|
const xAxis = timeline1.Layer.Background.Options.XAxis;
|
||||||
|
if (xAxis.Position == 'top') {
|
||||||
|
xAxis.Position = 'bottom';
|
||||||
|
timeline1.Padding.Top = 20;
|
||||||
|
timeline1.Padding.Bottom = 0;
|
||||||
|
} else {
|
||||||
|
xAxis.Position = 'top';
|
||||||
|
timeline1.Padding.Top = 0;
|
||||||
|
timeline1.Padding.Bottom = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeline1.Invalidate(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleMarkerTail() {
|
||||||
|
|
||||||
|
timeline1.Layer.Markers.Options.Label.Line.Width = ((timeline1.Layer.Markers.Options.Label.Line.Width <= 0) ? 1 : 0);
|
||||||
|
timeline1.Invalidate(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
function Refresh() {
|
function Refresh() {
|
||||||
timeline1.Invalidate(true, true);
|
timeline1.Invalidate(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetToday() {
|
function SetToday() {
|
||||||
const msPerDay = 1000 * 60 * 60 * 24;
|
const msPerDay = 1000 * 60 * 60 * 24;
|
||||||
const startDate = timeline1.DateToString(new Date(), "yyyy-MM-dd");
|
const startDate = timeline1.DateToInternalString(new Date());
|
||||||
|
|
||||||
timeline1.Load(startDate);
|
timeline1.Load(startDate);
|
||||||
|
|
||||||
const endDate = timeline1.CalcEndDate();
|
const endDate = timeline1.VisibleEndDate;
|
||||||
const noDays = Math.floor((timeline1.ConvertToDate(endDate) - timeline1.ConvertToDate(startDate)) / msPerDay);
|
const noDays = Math.floor((timeline1.ConvertToDate(endDate) - timeline1.ConvertToDate(startDate)) / msPerDay);
|
||||||
|
|
||||||
LogInfo("Set start date to today (" + startDate + ")");
|
LogInfo("Set start date to today (" + startDate + ")");
|
||||||
@ -175,7 +238,7 @@ function SetToday() {
|
|||||||
|
|
||||||
function GenerateRandomMarker() {
|
function GenerateRandomMarker() {
|
||||||
const msPerDay = 1000 * 60 * 60 * 24;
|
const msPerDay = 1000 * 60 * 60 * 24;
|
||||||
const endDate = timeline1.CalcEndDate();
|
const endDate = timeline1.VisibleEndDate;
|
||||||
const noDays = Math.floor((timeline1.ConvertToDate(endDate) - timeline1.ConvertToDate(timeline1.ShowDate)) / msPerDay);
|
const noDays = Math.floor((timeline1.ConvertToDate(endDate) - timeline1.ConvertToDate(timeline1.ShowDate)) / msPerDay);
|
||||||
|
|
||||||
let randomDay = GetRandy(1, (noDays - 1));
|
let randomDay = GetRandy(1, (noDays - 1));
|
||||||
@ -183,7 +246,7 @@ function GenerateRandomMarker() {
|
|||||||
let date = timeline1.ConvertToDate(timeline1.ShowDate);
|
let date = timeline1.ConvertToDate(timeline1.ShowDate);
|
||||||
date.setDate(date.getDate() + randomDay);
|
date.setDate(date.getDate() + randomDay);
|
||||||
|
|
||||||
const markerDate = timeline1.DateToString(date, timeline1.DateParsePattern);
|
const markerDate = timeline1.DateToInternalString(date);
|
||||||
|
|
||||||
const markerName = "Random Marker #" + GetRandy(10000, 99999);
|
const markerName = "Random Marker #" + GetRandy(10000, 99999);
|
||||||
|
|
||||||
@ -199,20 +262,20 @@ function GoToToday() {
|
|||||||
LogInfo("Go to " + timeline1.ShowDate);
|
LogInfo("Go to " + timeline1.ShowDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GoToPreviousMonth() {
|
function GoToPrevious() {
|
||||||
timeline1.ShowPrevious();
|
timeline1.ShowPrevious();
|
||||||
|
|
||||||
LogInfo("Go to " + timeline1.ShowDate);
|
LogInfo("Go to " + timeline1.ShowDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GoToNextMonth() {
|
function GoToNext() {
|
||||||
timeline1.ShowNext();
|
timeline1.ShowNext();
|
||||||
|
|
||||||
LogInfo("Go to " + timeline1.ShowDate);
|
LogInfo("Go to " + timeline1.ShowDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
function UpdateLabel() {
|
function UpdateLabel() {
|
||||||
const visibleMarkers = timeline1.FindVisibleEvents();
|
const visibleMarkers = timeline1.VisibleEvents;
|
||||||
if (visibleMarkers.length <= 0) {
|
if (visibleMarkers.length <= 0) {
|
||||||
LogInfo("No visible markers");
|
LogInfo("No visible markers");
|
||||||
return;
|
return;
|
||||||
@ -227,7 +290,7 @@ function UpdateLabel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function UpdateMarker() {
|
function UpdateMarker() {
|
||||||
const visibleMarkers = timeline1.FindVisibleEvents();
|
const visibleMarkers = timeline1.VisibleEvents;
|
||||||
if (visibleMarkers.length <= 0) {
|
if (visibleMarkers.length <= 0) {
|
||||||
LogInfo("No visible markers");
|
LogInfo("No visible markers");
|
||||||
return;
|
return;
|
||||||
@ -241,7 +304,7 @@ function UpdateMarker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DeleteMarker() {
|
function DeleteMarker() {
|
||||||
const visibleMarkers = timeline1.FindVisibleEvents();
|
const visibleMarkers = timeline1.VisibleEvents;
|
||||||
if (visibleMarkers.length <= 0) {
|
if (visibleMarkers.length <= 0) {
|
||||||
LogInfo("No visible markers");
|
LogInfo("No visible markers");
|
||||||
return;
|
return;
|
||||||
@ -256,7 +319,7 @@ function DeleteMarker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FindVisibleEvents() {
|
function FindVisibleEvents() {
|
||||||
const visibleMarkers = timeline1.FindVisibleEvents();
|
const visibleMarkers = timeline1.VisibleEvents;
|
||||||
|
|
||||||
LogInfo("");
|
LogInfo("");
|
||||||
LogInfo(JSON.stringify(visibleMarkers));
|
LogInfo(JSON.stringify(visibleMarkers));
|
||||||
|
2343
package-lock.json
generated
Normal file
2343
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
package.json
Normal file
11
package.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "LiteRyzJS/Timeline",
|
||||||
|
"version": "0.2.0.121",
|
||||||
|
"devDependencies": {
|
||||||
|
"webpack": "^5.93.0",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack"
|
||||||
|
}
|
||||||
|
}
|
5
src/index.js
Normal file
5
src/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// src/index.js
|
||||||
|
import Timeline from './timeline/timeline.js';
|
||||||
|
|
||||||
|
|
||||||
|
export { Timeline };
|
227
src/timeline/timeline-background-canvas.js
Normal file
227
src/timeline/timeline-background-canvas.js
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
import TimelineCanvas from './timeline-canvas.js';
|
||||||
|
|
||||||
|
|
||||||
|
class TimelineBackgroundCanvas extends TimelineCanvas {
|
||||||
|
constructor(parentEl, el) {
|
||||||
|
super(parentEl, el);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialiseOptions() {
|
||||||
|
super.initialiseOptions();
|
||||||
|
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Options = {
|
||||||
|
Axis: {
|
||||||
|
LineColour1: "#000000",
|
||||||
|
LineWidth: 1,
|
||||||
|
Font: "8pt Arial",
|
||||||
|
LabelColour: "#000000",
|
||||||
|
LabelSpacing: 6
|
||||||
|
},
|
||||||
|
XAxis: {
|
||||||
|
NoPartPerDay: 4,
|
||||||
|
HourLineSpace: 6,
|
||||||
|
HourLineHeight: 10,
|
||||||
|
HourLineColour: "#EAEAEA",
|
||||||
|
DayLineHeight: 20,
|
||||||
|
DayLineColour: "#9E9E9E",
|
||||||
|
Position: 'bottom'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
initialiseComponents() {
|
||||||
|
super.initialiseComponents();
|
||||||
|
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get GraphRectangle() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.Options.XAxis.Position == 'top') {
|
||||||
|
return {
|
||||||
|
X: a.ClientRectangle.X,
|
||||||
|
Y: (a.ClientRectangle.Y + a.XAxisHeight),
|
||||||
|
W: a.ClientRectangle.W,
|
||||||
|
H: (a.ClientRectangle.H - a.XAxisHeight)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
X: a.ClientRectangle.X,
|
||||||
|
Y: a.ClientRectangle.Y,
|
||||||
|
W: a.ClientRectangle.W,
|
||||||
|
H: (a.ClientRectangle.H - a.XAxisHeight)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get XAxisHeight() {
|
||||||
|
const a = this;
|
||||||
|
const labelSize = a.measureText(a.Options.Axis.Font, "0");
|
||||||
|
|
||||||
|
return labelSize.H + a.Options.Axis.LabelSpacing + (a.Options.XAxis.DayLineHeight * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
get XAxisPositions() {
|
||||||
|
const a = this;
|
||||||
|
const endPosX = (a.GraphRectangle.X + a.GraphRectangle.W);
|
||||||
|
|
||||||
|
let result = [];
|
||||||
|
let x = a.GraphRectangle.X;
|
||||||
|
let date = a.Parent.ConvertToDate(a.Parent.ShowDate);
|
||||||
|
|
||||||
|
// Rollback one day
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (x >= endPosX) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
Date: a.Parent.DateToInternalString(date),
|
||||||
|
X: x
|
||||||
|
});
|
||||||
|
|
||||||
|
x += (a.Options.XAxis.HourLineSpace * a.Options.XAxis.NoPartPerDay);
|
||||||
|
date.setDate(date.getDate() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Invalidate() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Clear();
|
||||||
|
|
||||||
|
a.drawAxis();
|
||||||
|
a.drawXAxisTicks();
|
||||||
|
a.drawXAxisLabels();
|
||||||
|
|
||||||
|
if (a.Parent.Debug) a.drawRectangle(a.ClientRectangle, "red");
|
||||||
|
if (a.Parent.Debug) a.drawRectangle(a.GraphRectangle, "red");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
drawAxis() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.CTX.beginPath();
|
||||||
|
|
||||||
|
if (a.Options.XAxis.Position == 'top') {
|
||||||
|
a.CTX.moveTo(a.GraphRectangle.X, (a.GraphRectangle.Y + a.GraphRectangle.H));
|
||||||
|
a.CTX.lineTo(a.GraphRectangle.X, a.GraphRectangle.Y);
|
||||||
|
a.CTX.lineTo((a.GraphRectangle.X + a.GraphRectangle.W), a.GraphRectangle.Y);
|
||||||
|
} else {
|
||||||
|
a.CTX.moveTo(a.GraphRectangle.X, a.GraphRectangle.Y);
|
||||||
|
a.CTX.lineTo(a.GraphRectangle.X, (a.GraphRectangle.Y +a.GraphRectangle.H));
|
||||||
|
a.CTX.lineTo((a.GraphRectangle.X + a.GraphRectangle.W), (a.GraphRectangle.Y + a.GraphRectangle.H));
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CTX.lineWidth = a.Options.Axis.LineWidth;
|
||||||
|
a.CTX.strokeStyle = a.Options.Axis.LineColour1;
|
||||||
|
a.CTX.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawXAxisLabels() {
|
||||||
|
const a = this;
|
||||||
|
const labelSize = a.measureText(a.Options.Axis.Font, "0");
|
||||||
|
|
||||||
|
let posY = 0;
|
||||||
|
let posDayY = 0;
|
||||||
|
let posMonthY = 0;
|
||||||
|
|
||||||
|
if (a.Options.XAxis.Position == 'top') {
|
||||||
|
posY = (a.GraphRectangle.Y - a.Options.Axis.LineWidth) - 2;
|
||||||
|
posDayY = (posY - (labelSize.H + a.Options.XAxis.DayLineHeight));
|
||||||
|
posMonthY = (posDayY - (labelSize.H + a.Options.Axis.LabelSpacing));
|
||||||
|
} else {
|
||||||
|
posY = (a.GraphRectangle.Y + a.GraphRectangle.H) + a.Options.Axis.LineWidth;
|
||||||
|
posDayY = (posY + a.Options.XAxis.DayLineHeight);
|
||||||
|
posMonthY = (posDayY + labelSize.H + a.Options.Axis.LabelSpacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.XAxisPositions.forEach(function(e, i) {
|
||||||
|
const date = a.Parent.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) {
|
||||||
|
// // Don't write first date
|
||||||
|
// } else {
|
||||||
|
// Write date (dd)
|
||||||
|
a.drawText(e.X, posDayY, a.Parent.DateToString(date, "dd"), a.Options.Axis.Font, a.Options.Axis.LabelColour, "center");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Write month (MMMM) on first of the month
|
||||||
|
if (writeLabel) {
|
||||||
|
a.drawText(e.X, posMonthY, a.Parent.DateToString(date, "MMMM yyyy"), a.Options.Axis.Font, a.Options.Axis.LabelColour, "left");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
drawXAxisTicks() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
let startPosX = a.GraphRectangle.X;
|
||||||
|
const endPosX = (a.GraphRectangle.X + a.GraphRectangle.W);
|
||||||
|
let posY = 0;
|
||||||
|
let posDayY = 0;
|
||||||
|
let posHourY = 0;
|
||||||
|
|
||||||
|
if (a.Options.XAxis.Position == 'top') {
|
||||||
|
posY = (a.GraphRectangle.Y - a.Options.Axis.LineWidth);
|
||||||
|
posDayY = (posY - a.Options.XAxis.DayLineHeight);
|
||||||
|
posHourY = (posY - a.Options.XAxis.HourLineHeight);
|
||||||
|
} else {
|
||||||
|
posY = (a.GraphRectangle.Y + a.GraphRectangle.H) + a.Options.Axis.LineWidth;
|
||||||
|
posDayY = (posY + a.Options.XAxis.DayLineHeight);
|
||||||
|
posHourY = (posY + a.Options.XAxis.HourLineHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (startPosX >= endPosX) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CTX.beginPath();
|
||||||
|
a.CTX.moveTo(startPosX, posY);
|
||||||
|
|
||||||
|
if ((i % a.Options.XAxis.NoPartPerDay) == 0) {
|
||||||
|
a.CTX.lineTo(startPosX, posDayY);
|
||||||
|
a.CTX.strokeStyle = a.Options.XAxis.DayLineColour;
|
||||||
|
} else {
|
||||||
|
a.CTX.lineTo(startPosX, posHourY);
|
||||||
|
a.CTX.strokeStyle = a.Options.XAxis.HourLineColour;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CTX.lineWidth = a.Options.Axis.LineWidth;
|
||||||
|
a.CTX.stroke();
|
||||||
|
|
||||||
|
startPosX += a.Options.XAxis.HourLineSpace;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default TimelineBackgroundCanvas;
|
185
src/timeline/timeline-canvas.js
Normal file
185
src/timeline/timeline-canvas.js
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
class TimelineCanvas {
|
||||||
|
constructor(parentEl, el) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Parent = parentEl;
|
||||||
|
a.Container = el;
|
||||||
|
a.CTX = a.Container.getContext("2d");
|
||||||
|
|
||||||
|
a.initialiseOptions();
|
||||||
|
a.initialiseComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialiseOptions() {
|
||||||
|
// Inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
initialiseComponents() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Container.style.width = a.Parent.Size.W + "px";
|
||||||
|
a.Container.style.height = a.Parent.Size.H + "px";
|
||||||
|
a.Container.style.position = 'absolute';
|
||||||
|
a.Container.style.border = 'none';
|
||||||
|
|
||||||
|
a.CTX.canvas.width = a.Parent.Size.W;
|
||||||
|
a.CTX.canvas.height = a.Parent.Size.H;
|
||||||
|
|
||||||
|
a.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get ClientRectangle() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
X: a.Parent.Padding.Left,
|
||||||
|
Y: a.Parent.Padding.Top,
|
||||||
|
W: (a.Parent.Size.W - (a.Parent.Padding.Left + a.Parent.Padding.Right)),
|
||||||
|
H: (a.Parent.Size.H - (a.Parent.Padding.Top + a.Parent.Padding.Bottom))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Clear() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.CTX.clearRect(0, 0, a.CTX.canvas.width, a.CTX.canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
Invalidate() {
|
||||||
|
// placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCircle(x, y, width, borderWidth, borderColour, backColour) {
|
||||||
|
const a = this;
|
||||||
|
const calcBorderWidth = (borderWidth * 2);
|
||||||
|
const calcWidth = width - calcBorderWidth;
|
||||||
|
const offset = a.half(width);
|
||||||
|
|
||||||
|
a.CTX.beginPath();
|
||||||
|
a.CTX.arc(x, y, calcWidth, 0, 2 * Math.PI, false);
|
||||||
|
a.CTX.fillStyle = backColour;
|
||||||
|
a.CTX.fill();
|
||||||
|
a.CTX.lineWidth = borderWidth;
|
||||||
|
a.CTX.strokeStyle = borderColour;
|
||||||
|
a.CTX.stroke();
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
X: x - (offset + borderWidth),
|
||||||
|
Y: y - (offset + borderWidth),
|
||||||
|
W: (width + calcBorderWidth),
|
||||||
|
H: (width + calcBorderWidth)
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawRectangle(rectangle, colour) {
|
||||||
|
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 = colour;
|
||||||
|
a.CTX.stroke();
|
||||||
|
|
||||||
|
return rectangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawText(x, y, label, font, foreColour, align) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.CTX.font = font;
|
||||||
|
a.CTX.fillStyle = foreColour;
|
||||||
|
|
||||||
|
let size = a.measureText(font, label);
|
||||||
|
size.Y = y;
|
||||||
|
|
||||||
|
switch (align) {
|
||||||
|
case "center":
|
||||||
|
size.X = (x - size.X);
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
size.X = (x - size.W);
|
||||||
|
break;
|
||||||
|
case "left":
|
||||||
|
default:
|
||||||
|
size.X = x;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.CTX.fillText(label, size.X, (size.Y + size.H));
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawVerticalLine(x, y1, y2, width, colour) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.CTX.beginPath();
|
||||||
|
a.CTX.moveTo(x, y1);
|
||||||
|
a.CTX.lineTo(x, (y2 - width));
|
||||||
|
a.CTX.lineWidth = width;
|
||||||
|
a.CTX.strokeStyle = colour;
|
||||||
|
a.CTX.stroke();
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
X: x,
|
||||||
|
Y: y1,
|
||||||
|
W: width,
|
||||||
|
H: (y2 - y1)
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
half(value) {
|
||||||
|
return (value / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
measureText(font, value) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.CTX.font = font;
|
||||||
|
const size = a.CTX.measureText(value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
W: size.width,
|
||||||
|
H: size.fontBoundingBoxAscent,
|
||||||
|
X: a.half(size.width),
|
||||||
|
Y: a.half(size.fontBoundingBoxAscent)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
isPointInRectangle(rect, point) {
|
||||||
|
const x2 = (rect.X + rect.W);
|
||||||
|
const y2 = (rect.Y + rect.H);
|
||||||
|
|
||||||
|
return ((point.X >= rect.X) && (point.X <= x2) && (point.Y >= rect.Y) && (point.Y <= y2));
|
||||||
|
}
|
||||||
|
|
||||||
|
combineRectangle(rect1, rect2) {
|
||||||
|
const x2 = Math.max((rect1.X + rect1.W), (rect2.X + rect2.W));
|
||||||
|
const y2 = Math.max((rect1.Y + rect1.H), (rect2.Y + rect2.H));
|
||||||
|
|
||||||
|
const rect = {
|
||||||
|
X: Math.min(rect1.X, rect2.X),
|
||||||
|
Y: Math.min(rect1.Y, rect2.Y),
|
||||||
|
W: 0,
|
||||||
|
H: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
rect.W = x2 - rect.X;
|
||||||
|
rect.H = y2 - rect.Y;
|
||||||
|
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default TimelineCanvas;
|
42
src/timeline/timeline-flourish-canvas.js
Normal file
42
src/timeline/timeline-flourish-canvas.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import TimelineCanvas from './timeline-canvas.js';
|
||||||
|
|
||||||
|
|
||||||
|
class TimelineFlourishCanvas extends TimelineCanvas {
|
||||||
|
constructor(parentEl, el) {
|
||||||
|
super(parentEl, el);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialiseOptions() {
|
||||||
|
super.initialiseOptions();
|
||||||
|
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.XPos = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Invalidate() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Clear();
|
||||||
|
|
||||||
|
if (a.XPos < 0) {
|
||||||
|
a.Clear();
|
||||||
|
} else {
|
||||||
|
let posY = 0;
|
||||||
|
|
||||||
|
if (a.Parent.XAxis.Position == 'top') {
|
||||||
|
posY = a.Parent.Layer.Background.GraphRectangle.Y;
|
||||||
|
} else {
|
||||||
|
posY = (a.Parent.Layer.Background.GraphRectangle.Y + a.Parent.Layer.Background.GraphRectangle.H);
|
||||||
|
}
|
||||||
|
|
||||||
|
// a.drawVerticalLine(a.XPos, a.Parent.Layer.Background.GraphRectangle.Y, (a.Parent.Layer.Background.GraphRectangle.Y + a.Parent.Layer.Background.GraphRectangle.H), a.Parent.MarkerLabel.Line.Width, a.Parent.HotTrack.Colour);
|
||||||
|
a.drawCircle(a.XPos, posY, a.Parent.HotTrack.Width, 0, a.Parent.HotTrack.Colour, a.Parent.HotTrack.Colour);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default TimelineFlourishCanvas;
|
216
src/timeline/timeline-foreground-canvas.js
Normal file
216
src/timeline/timeline-foreground-canvas.js
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import TimelineCanvas from './timeline-canvas.js';
|
||||||
|
|
||||||
|
|
||||||
|
class TimelineForegroundCanvas extends TimelineCanvas {
|
||||||
|
constructor(parentEl, el) {
|
||||||
|
super(parentEl, el);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialiseOptions() {
|
||||||
|
super.initialiseOptions();
|
||||||
|
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Options = {
|
||||||
|
Marker: {
|
||||||
|
BorderColour: "#3A5D9C",
|
||||||
|
BorderWidth: 2,
|
||||||
|
BackColour: "#D4DEEF",
|
||||||
|
Width: 10
|
||||||
|
},
|
||||||
|
Label: {
|
||||||
|
Colour: "#3A5D9C",
|
||||||
|
Font: "9pt Arial",
|
||||||
|
Margin: {
|
||||||
|
X: 2,
|
||||||
|
Y: 10
|
||||||
|
},
|
||||||
|
Line: {
|
||||||
|
Colour: "#A6A6A6",
|
||||||
|
Width: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
initialiseComponents() {
|
||||||
|
super.initialiseComponents();
|
||||||
|
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.CTX.canvas.addEventListener('mousedown', function (e) {
|
||||||
|
if (!a.Parent.Enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY, false);
|
||||||
|
if (event == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.Parent.Debug) console.log(event);
|
||||||
|
|
||||||
|
a.Parent.OnMouseDown(this, e, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
a.CTX.canvas.addEventListener('click', function (e) {
|
||||||
|
if (!a.Parent.Enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY, false);
|
||||||
|
if (event == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.Parent.Debug) console.log(event);
|
||||||
|
|
||||||
|
a.Parent.OnClick(this, e, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
a.CTX.canvas.addEventListener('dblclick', function (e) {
|
||||||
|
if (!a.Parent.Enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY, false);
|
||||||
|
if (event == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.Parent.Debug) console.log(event);
|
||||||
|
|
||||||
|
a.Parent.OnDblClick(this, e, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (a.Parent.EnableHotTracking) {
|
||||||
|
a.CTX.canvas.addEventListener('mousemove', function (e) {
|
||||||
|
if (!a.Parent.Enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY, false);
|
||||||
|
if (event != null) {
|
||||||
|
a.Container.style.cursor = 'pointer';
|
||||||
|
} else {
|
||||||
|
a.Container.style.cursor = 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hottracking
|
||||||
|
if (a.Parent.EnableHotTracking) {
|
||||||
|
const point = { X: e.offsetX, Y: e.offsetY };
|
||||||
|
if (a.isPointInRectangle(a.Parent.Layer.Background.GraphRectangle, point)) {
|
||||||
|
if (a.Parent.Debug) console.log(point);
|
||||||
|
|
||||||
|
a.Parent.Layer.Flourish.XPos = point.X;
|
||||||
|
|
||||||
|
a.Parent.OnMouseMove(this, e, event);
|
||||||
|
} else {
|
||||||
|
// Clear hot tracking
|
||||||
|
a.Parent.Layer.Flourish.XPos = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Parent.Layer.Flourish.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Invalidate() {
|
||||||
|
const a = this;
|
||||||
|
const rect = a.Parent.Layer.Background.GraphRectangle;
|
||||||
|
|
||||||
|
a.Clear();
|
||||||
|
|
||||||
|
let startPosY = 0;
|
||||||
|
const visibleEvents = a.Parent.VisibleEvents;
|
||||||
|
|
||||||
|
if (a.Parent.XAxis.Position == 'top') {
|
||||||
|
startPosY = (rect.Y + a.Options.Marker.Width + 20);
|
||||||
|
} else {
|
||||||
|
startPosY = (rect.Y + a.Options.Marker.Width);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear for collisions detection
|
||||||
|
visibleEvents.forEach(function (e, i) {
|
||||||
|
e.HitBox = { X: 0, Y:0, W: 0, H: 0};
|
||||||
|
});
|
||||||
|
|
||||||
|
visibleEvents.forEach(function (e, i) {
|
||||||
|
// Calculate Y position
|
||||||
|
let posY = a.calcMarkerPosition(e.Position.X, startPosY);
|
||||||
|
let posY2 = 0;
|
||||||
|
|
||||||
|
if (a.Parent.XAxis.Position == 'top') {
|
||||||
|
posY2 = a.Parent.Layer.Background.GraphRectangle.Y;
|
||||||
|
} else {
|
||||||
|
posY2 = (a.Parent.Layer.Background.GraphRectangle.Y + a.Parent.Layer.Background.GraphRectangle.H);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.Options.Label.Line.Width > 0) {
|
||||||
|
a.drawVerticalLine(e.Position.X, posY, posY2, a.Options.Label.Line.Width, a.Options.Label.Line.Colour);
|
||||||
|
}
|
||||||
|
|
||||||
|
const markerRectangle = a.drawCircle(e.Position.X, posY, a.Options.Marker.Width, a.Options.Marker.BorderWidth, e.BorderColour, e.BackColour);
|
||||||
|
|
||||||
|
e.Position = { X: e.Position.X, Y: posY };
|
||||||
|
|
||||||
|
if (a.Parent.ShowMarkerLabel) {
|
||||||
|
const labelRectangle = a.drawText((markerRectangle.X + markerRectangle.W + a.Options.Label.Margin.X), markerRectangle.Y, e.Label, a.Options.Label.Font, a.Options.Label.Colour, "left");
|
||||||
|
|
||||||
|
e.HitBox = a.combineRectangle(markerRectangle, labelRectangle);
|
||||||
|
} else {
|
||||||
|
e.HitBox = markerRectangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.Parent.Debug) a.drawRectangle(e.HitBox, "red");
|
||||||
|
if (a.Parent.Debug) console.log(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
OnMouseDown(e) {
|
||||||
|
if (!a.Parent.Enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY, false);
|
||||||
|
if (event == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.Parent.Debug) console.log(event);
|
||||||
|
|
||||||
|
a.Parent.OnMouseDown(this, e, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
calcMarkerPosition(x, y) {
|
||||||
|
const a = this;
|
||||||
|
const rect = a.Parent.Layer.Background.GraphRectangle;
|
||||||
|
|
||||||
|
let hasMoved = false;
|
||||||
|
let posY = y;
|
||||||
|
for (let i=rect.Y; i<(rect.Y + rect.H); i++)
|
||||||
|
{
|
||||||
|
posY = i;
|
||||||
|
|
||||||
|
var clippedEvent = a.Parent.FindEventsByCoords(x, posY, true);
|
||||||
|
if (clippedEvent == null) {
|
||||||
|
hasMoved = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMoved) {
|
||||||
|
posY = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return posY;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default TimelineForegroundCanvas;
|
380
src/timeline/timeline.js
Normal file
380
src/timeline/timeline.js
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
import TimelineBackgroundCanvas from './timeline-background-canvas.js';
|
||||||
|
import TimelineFlourishCanvas from './timeline-flourish-canvas.js';
|
||||||
|
import TimelineForegroundCanvas from './timeline-foreground-canvas.js';
|
||||||
|
|
||||||
|
|
||||||
|
class Timeline {
|
||||||
|
constructor(el) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Container = document.getElementById(el);
|
||||||
|
a.Padding = {
|
||||||
|
Left: 20,
|
||||||
|
Top: 20,
|
||||||
|
Right: 20,
|
||||||
|
Bottom: 0
|
||||||
|
};
|
||||||
|
a.Size = {
|
||||||
|
W: a.Container.innerWidth || a.Container.clientWidth,
|
||||||
|
H: a.Container.innerHeight || a.Container.clientHeight
|
||||||
|
};
|
||||||
|
a.Layer = {
|
||||||
|
Background: null,
|
||||||
|
Flourish: null,
|
||||||
|
Markers: null
|
||||||
|
};
|
||||||
|
|
||||||
|
a.DateParsePattern = "yyyy-MM-dd";
|
||||||
|
|
||||||
|
a.HotTrack = {
|
||||||
|
Colour: "#F57C00",
|
||||||
|
Width: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
a.Events = [];
|
||||||
|
a.StartDate = a.DateToInternalString(new Date());
|
||||||
|
a.ShowDate = a.StartDate;
|
||||||
|
a.ShowMarkerLabel = true;
|
||||||
|
|
||||||
|
a.Enabled = false;
|
||||||
|
a.Debug = false;
|
||||||
|
a.EnableHotTracking = true;
|
||||||
|
|
||||||
|
a.initialiseComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialiseComponents() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Container.innerHTML = "<canvas></canvas><canvas></canvas><canvas></canvas>";
|
||||||
|
|
||||||
|
const canvasList = a.Container.getElementsByTagName("canvas");
|
||||||
|
|
||||||
|
a.Layer.Background = new TimelineBackgroundCanvas(a, canvasList[0]);
|
||||||
|
a.Layer.Flourish = new TimelineFlourishCanvas(a, canvasList[1]);
|
||||||
|
a.Layer.Markers = new TimelineForegroundCanvas(a, canvasList[2]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get CTX() {
|
||||||
|
return a.Layer.Markers.CTX;
|
||||||
|
}
|
||||||
|
|
||||||
|
get NewEvent() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
Date: "",
|
||||||
|
Label: "",
|
||||||
|
Position: { X: 0, Y: 0 },
|
||||||
|
Events: [],
|
||||||
|
HitBox: null,
|
||||||
|
BorderColour: a.Layer.Markers.Options.Marker.BorderColour,
|
||||||
|
BackColour: a.Layer.Markers.Options.Marker.BackColour
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get NewEventItem() {
|
||||||
|
return {
|
||||||
|
Title: "",
|
||||||
|
Description: "",
|
||||||
|
Link: "",
|
||||||
|
Tag: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get VisibleDays() {
|
||||||
|
const a = this;
|
||||||
|
const clientWidth = (a.Size.W - (a.Padding.Left + a.Padding.Right));
|
||||||
|
|
||||||
|
return Math.floor(clientWidth / (a.XAxis.NoPartPerDay * a.XAxis.HourLineSpace));
|
||||||
|
}
|
||||||
|
|
||||||
|
get VisibleStartDate() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
return a.ConvertToDate(a.ShowDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
get VisibleEndDate() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
let date = a.ConvertToDate(a.ShowDate);
|
||||||
|
date.setDate(date.getDate() + a.VisibleDays);
|
||||||
|
|
||||||
|
// Minus one for lead up
|
||||||
|
date.setDate(date.getDate() - 1);
|
||||||
|
|
||||||
|
return a.DateToString(date, a.DateParsePattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
get VisibleEvents() {
|
||||||
|
const a = this;
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
a.Layer.Background.XAxisPositions.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
get XAxis() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
return a.Layer.Background.Options.XAxis;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AddEvent(date, label, options) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
const _options = Object.assign(a.NewEventItem, options);
|
||||||
|
|
||||||
|
let event = a.FindEvent(date);
|
||||||
|
if (event == null) {
|
||||||
|
let newEvent = a.NewEvent;
|
||||||
|
newEvent.Date = date;
|
||||||
|
|
||||||
|
a.Events.push(newEvent);
|
||||||
|
|
||||||
|
event = a.FindEvent(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (label != null) {
|
||||||
|
event.Label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Events.push(_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
Clear() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.Layer.Background.Clear();
|
||||||
|
a.Layer.Flourish.Clear();
|
||||||
|
a.Layer.Markers.Clear();
|
||||||
|
|
||||||
|
a.StartDate = a.DateToInternalString(new Date());
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Events.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FindDatePosition(date) {
|
||||||
|
const a = this;
|
||||||
|
const points = a.Layer.Background.XAxisPositions;
|
||||||
|
|
||||||
|
for (let i=0; i<points.length; i++) {
|
||||||
|
if (points[i].Date == date){
|
||||||
|
return points[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, addmargin) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
for (let i=0; i<a.Events.length; i++) {
|
||||||
|
const e = a.Events[i].HitBox;
|
||||||
|
if (a.Events[i].HitBox == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x2 = (e.X + e.W);
|
||||||
|
let y2 = (e.Y + e.H);
|
||||||
|
|
||||||
|
if (addmargin) {
|
||||||
|
y2 += a.Layer.Markers.Options.Label.Margin.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((x >= e.X) && (x <= x2) && (y >= e.Y) && (y <= y2)){
|
||||||
|
return a.Events[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Load(startDate) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
a.StartDate = startDate;
|
||||||
|
|
||||||
|
a.Show(startDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
Show(date) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (a.ConvertToDate(date) < a.ConvertToDate(a.StartDate)) {
|
||||||
|
date = a.StartDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.ShowDate = date;
|
||||||
|
a.Enabled = true;
|
||||||
|
|
||||||
|
a.Invalidate(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowNext() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
let date = a.VisibleStartDate;
|
||||||
|
date.setDate(date.getDate() + (a.VisibleDays - 1));
|
||||||
|
|
||||||
|
a.Show(a.DateToInternalString(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowPrevious() {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
let date = a.VisibleStartDate;
|
||||||
|
date.setDate(date.getDate() - (a.VisibleDays - 1));
|
||||||
|
|
||||||
|
a.Show(a.DateToInternalString(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Invalidate(redrawAxis, redrawMarkers) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
if (redrawAxis) a.Layer.Background.Invalidate();
|
||||||
|
|
||||||
|
a.Layer.Flourish.Clear();
|
||||||
|
|
||||||
|
if (redrawMarkers) a.Layer.Markers.Invalidate();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateToInternalString(date) {
|
||||||
|
const a = this;
|
||||||
|
|
||||||
|
return a.DateToString(date, a.DateParsePattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConvertToDate(value) {
|
||||||
|
return new Date(Date.parse(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OnMouseDown(sender, e, event) { /* delegate */ }
|
||||||
|
|
||||||
|
OnMouseMove(sender, e, event) { /* delegate */ }
|
||||||
|
|
||||||
|
OnClick(sender, e, event) { /* delegate */ }
|
||||||
|
|
||||||
|
OnDblClick(sender, e, event) { /* delegate */ }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Timeline;
|
48
webpack.config.js
Normal file
48
webpack.config.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const { version } = require('./package.json');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
|
||||||
|
|
||||||
|
class PrependVersionPlugin {
|
||||||
|
apply(compiler) {
|
||||||
|
compiler.hooks.emit.tapAsync('PrependVersionPlugin', (compilation, callback) => {
|
||||||
|
Object.keys(compilation.assets).forEach((filename) => {
|
||||||
|
if (filename.endsWith('.js')) {
|
||||||
|
const asset = compilation.assets[filename];
|
||||||
|
|
||||||
|
const headerText = `/*!\n * LiteRyzJS/Timeline v${version}\n * Copyright 2023-2024 Ray Lam (https://www.hiimray.co.uk)\n *\n */\n`;
|
||||||
|
|
||||||
|
const newContent = headerText + asset.source();
|
||||||
|
|
||||||
|
compilation.assets[filename] = {
|
||||||
|
source: () => newContent,
|
||||||
|
size: () => newContent.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
timeline: './src/index.js'
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: `[name].dist.js`,
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
library: 'LiteRyzJS',
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
globalObject: 'this'
|
||||||
|
},
|
||||||
|
mode: 'production', // development|production
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.VERSION': JSON.stringify(version)
|
||||||
|
}),
|
||||||
|
new PrependVersionPlugin()
|
||||||
|
]
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user