/** * BBTimeline * @version v0.1.1.121 beta (2023/10/18 2058) */ class BBTimeline { 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.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.Debug = 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 clientWidth = (a.Size.W - (a.Padding.Left + a.Padding.Right)); const calcdays = Math.floor(clientWidth / (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.Layer.Background.Clear(); a.Layer.Flourish.Clear(); a.Layer.Markers.Clear(); 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= 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); } // initialiseComponents2() { // 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.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) { // 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) // // }; // 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= 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)); } initialiseComponents() { const a = this; a.Container.innerHTML = ""; const canvasList = a.Container.getElementsByTagName("canvas"); a.Layer.Background = new BBTimelineBackgroundCanvas(a, canvasList[0]); a.Layer.Flourish = new BBTimelineCanvas(a, canvasList[1]); a.Layer.Markers = new BBTimelineForegroundCanvas(a, canvasList[2]); } }