From d05d22afe75c6c4d7526cfd25efa2d4f3ad16e75 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 2 Sep 2024 21:32:54 +0100 Subject: [PATCH 1/3] Added multi-layer canvas container --- package.json | 2 +- src/project/background-canvas.js | 204 +++++++++++++++++ src/project/foreground-canvas.js | 157 +++++++++++++ src/project/gantt-chart.js | 349 ++++++----------------------- src/project/project.js | 2 +- src/project/project.scss | 28 ++- src/references/canvas-container.js | 225 +++++++++++++++++++ src/references/canvas.js | 107 +-------- src/references/extensions.dist.js | 4 +- 9 files changed, 683 insertions(+), 395 deletions(-) create mode 100644 src/project/background-canvas.js create mode 100644 src/project/foreground-canvas.js create mode 100644 src/references/canvas-container.js diff --git a/package.json b/package.json index 0e371c3..917d7a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "LiteRyzJS/Project", - "version": "0.2.0.508", + "version": "0.2.1.114", "devDependencies": { "css-loader": "^7.1.2", "sass": "^1.77.8", diff --git a/src/project/background-canvas.js b/src/project/background-canvas.js new file mode 100644 index 0000000..21fb050 --- /dev/null +++ b/src/project/background-canvas.js @@ -0,0 +1,204 @@ +import Canvas from '../references/canvas.js'; + + +class BackgroundCanvas extends Canvas { + Options = null; + Debug = false; + + StartDate = new Date(); + Duration = 30; + StartOfWeek = 1; + RowCount = 8; + + + constructor(el) { + super(el); + } + + + get HeaderRow1Rectangle() { + return { + X: 0, + Y: 0, + W: this.Width, + H: (this.Options.HeaderRow.Height[0] - this.Options.BorderWidth) + }; + } + + get HeaderRow2Rectangle() { + const a = this; + + return { + X: 0, + Y: this.Options.HeaderRow.Height[0], + W: this.Width, + H: (this.Options.HeaderRow.Height[1] - this.Options.BorderWidth) + }; + } + + get HeaderHeight() { + const a = this; + + return a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1]; + } + + + Load(startDate, duration, startOfWeek, rowCount) { + const a = this; + + a.StartDate = new Date(startDate); + a.Duration = duration; + a.StartOfWeek = startOfWeek; // 1 = Monday + a.RowCount = rowCount; + } + + Invalidate() { + const a = this; + + if (a.Options == null) { + return; + } + + if (a.StartDate == null) { + return; + } + + a.#drawChartHeader(); + a.#drawColumnLayout(); + a.#drawRowLayout(); + } + + #drawChartHeader() { + const a = this; + + const displayDays = a.Duration + 2; + + if (a.Debug) { + a.DrawRectangle(a.HeaderRow1Rectangle, "red", {}); + a.DrawRectangle(a.HeaderRow2Rectangle, "orange", {}); + } + + let startDate = new Date(a.StartDate); + startDate.addDays(-1); + + // Draw horizontal line under months + a.#drawHorizontalLine(a.Options.HeaderRow.Height[0]); + + // Draw vertical lines for dates + for (let i=1; i { + if (typeof(e.Debug) == "undefined") { + return; + } + + e.Debug = value; + }); + + } + Clear() { const a = this; - a.Canvas.Clear(); + a.CanvasContainer.Clear(); a.StartDate = new Date(); a.Duration = 30; a.StartOfWeek = 1; // 1 = Monday a.Tasks = []; + // Invalidate canvas (background, foreground) + a.CanvasContainer.Layer[0].Load(new Date(), a.Duration, a.StartOfWeek, a.Tasks.length); + a.CanvasContainer.Layer[1].Load(new Date(), a.Tasks); + a.Invalidate(); } Invalidate() { const a = this; - a.Canvas.Clear(); + a.CanvasContainer.Clear(); const width = ((a.Duration + 2) * a.Options.DayWidth); const height = (Math.max(a.Tasks.length, a.Options.MinimumRowCount) * a.Options.Row.Height) + a.HeaderHeight; - a.Canvas.Size = { W: width, H: height }; + a.CanvasContainer.Size = { W: width, H: height }; - a.Canvas.Invalidate(); - - a.#drawChartHeader(); - a.#drawColumnLayout(); - a.#drawRowLayout(); - a.#drawTasks(); - a.#drawConnectorLines(); + a.CanvasContainer.Invalidate(); } Load(project) { const a = this; - a.Canvas.Clear(); + a.CanvasContainer.Clear(); if (project == null) { return; @@ -162,244 +174,13 @@ class GanttChart { a.StartOfWeek = project.Project.StartOfWeek; a.Tasks = project.ExportTasks(); + // Invalidate canvas (background, foreground) + a.CanvasContainer.Layer[0].Load(new Date(project.StartDate), project.Duration, project.Project.StartOfWeek, a.Tasks.length); + a.CanvasContainer.Layer[1].Load(new Date(project.StartDate), a.Tasks); + a.Invalidate(); } - #drawChartHeader() { - const a = this; - - const displayDays = a.Duration + 2; - - if (a.Debug) { - a.Canvas.DrawRectangle(a.HeaderRow1Rectangle, "red", {}); - a.Canvas.DrawRectangle(a.HeaderRow2Rectangle, "orange", {}); - } - - let startDate = new Date(a.StartDate); - startDate.addDays(-1); - - // Draw horizontal line under months - a.#drawHorizontalLine(a.Options.HeaderRow.Height[0]); - - // Draw vertical lines for dates - for (let i=1; i { + e.Height = value; + }); + } + + get Width() { + const a = this; + + if (a.Layer.length <= 0) { + return 0; + } + + return Document.getWidth(a.Layer[0].el); + } + + set Width(value) { + const a = this; + + // if (a.el != null) { + // a.el.style.width = value + "px"; + // } + + if (a.FlowContainer != null) { + a.FlowContainer.style.width = value + "px"; + } + + // if (a.CanvasContainer != null) { + // a.CanvasContainer.style.width = value + "px"; + // } + + // if (a.CanvasContext != null) { + // a.CanvasContext.canvas.width = value; + // } + + a.Layer.forEach((e, i) => { + e.Width = value; + }); + } + + get Size() { + return { + W: this.Width, + H: this.Height + }; + } + + set Size(value) { + this.Width = value.W; + this.Height = value.H; + } + + get ClientRectangle() { + const a = this; + + return { + X: a.Padding.Left, + Y: a.Padding.Top, + W: (a.Width - (a.Padding.Left + a.Padding.Right)), + H: (a.Height - (a.Padding.Top + a.Padding.Bottom)) + }; + } + + get Rectangle() { + const a = this; + + return { + X: 0, + Y: 0, + W: a.Width, + H: a.Height + }; + } + + + Clear() { + const a = this; + + // if (a.CanvasContext == null) { + // return; + // } + + // a.CanvasContext.clearRect(0, 0, a.CanvasContext.canvas.width, a.CanvasContext.canvas.height); + + a.Layer.forEach((e, i) => { + e.Clear(); + }); + } + + ClearLayers() { + const a = this; + + if (a.el != null) { + a.el.innerHTML = "
"; + } + + a.Layer = []; + + a.Invalidate(); + } + + AddLayer() { + const a = this; + + if (a.el == null) { + return; + } + + const divList = a.el.getElementsByTagName("div"); + if (divList.length <= 0) { + return; + } + + // Add another canvas element + Document.appendHtml(divList[0], "canvas"); + + const canvasList = a.el.getElementsByTagName("canvas"); + const result = canvasList[(canvasList.length - 1)]; + + return result; + } + + Invalidate() { + const a = this; + + if (a.EnableDock) { + a.FlowContainer.style.width = "100%"; + } + + const w = (a.Layer.length <= 0 ? 0 : Document.getWidth(a.Layer[0].el)); + const h = (a.Layer.length <= 0 ? 0 : Document.getHeight(a.Layer[0].el)); + + if (w > (Document.getWidth(a.FlowContainer) + (a.BorderWidth * 2))) { + a.FlowContainer.classList.add("scroll-x"); + a.FlowContainer.style.height = (h + a.ScrollbarWidth) + "px"; + } else { + a.FlowContainer.classList.remove("scroll-x"); + a.FlowContainer.style.height = h + "px"; + } + + a.Layer.forEach(e => { + if (typeof(e.Invalidate) != "undefined") { + e.Invalidate(); + } + }); + + a.Layer.forEach((e, i) => { + if (i <= 0) { + return; + } + + // Correct positon because of position-relative to keep container overflow working + const h = Document.getHeight(a.Layer[(i - 1)].el); + + a.Layer[i].el.style.top = "-" + h + "px"; + }); + } + +} + + +export default CanvasContainer; \ No newline at end of file diff --git a/src/references/canvas.js b/src/references/canvas.js index 450907f..d689794 100644 --- a/src/references/canvas.js +++ b/src/references/canvas.js @@ -1,19 +1,10 @@ class Canvas { - constructor(el) { const a = this; a.el = el; a.BorderWidth = 2; - a.ScrollbarWidth = 11; - a.Padding = { - Top: 0, - Right: 0, - Bottom: 0, - Left: 0 - }; - a.EnableDock = true; a.#initialiseComponents(); } @@ -25,48 +16,30 @@ class Canvas { return; } - a.el.innerHTML = "
"; - - a.Invalidate(); a.Clear(); + a.Invalidate(); } - get FlowContainer() { - return this.el.getElementsByTagName("div")[0]; - } - - get CanvasContainer() { - return this.FlowContainer.getElementsByTagName("canvas")[0]; - } - get CanvasContext() { - return this.CanvasContainer.getContext("2d"); + return this.el.getContext("2d"); } get Height() { const a = this; - if (a.CanvasContainer == null) { + if (a.el == null) { return 0; } - return a.#getHeight(a.CanvasContainer); + return Document.getHeight(a.el); } set Height(value) { const a = this; - // if (a.el != null) { - // a.el.style.height = value + "px"; - // } - - if (a.FlowContainer != null) { - a.FlowContainer.style.height = value + "px"; - } - - if (a.CanvasContainer != null) { - a.CanvasContainer.style.height = value + "px"; + if (a.el != null) { + a.el.style.height = value + "px"; } if (a.CanvasContext != null) { @@ -77,26 +50,18 @@ class Canvas { get Width() { const a = this; - if (a.CanvasContainer == null) { + if (a.el == null) { return 0; } - return a.#getWidth(a.CanvasContainer); + return Document.getWidth(a.el); } set Width(value) { const a = this; - // if (a.el != null) { - // a.el.style.width = value + "px"; - // } - - if (a.FlowContainer != null) { - a.FlowContainer.style.width = value + "px"; - } - - if (a.CanvasContainer != null) { - a.CanvasContainer.style.width = value + "px"; + if (a.el != null) { + a.el.style.width = value + "px"; } if (a.CanvasContext != null) { @@ -116,17 +81,6 @@ class Canvas { this.Height = value.H; } - get ClientRectangle() { - const a = this; - - return { - X: a.Padding.Left, - Y: a.Padding.Top, - W: (a.Width - (a.Padding.Left + a.Padding.Right)), - H: (a.Height - (a.Padding.Top + a.Padding.Bottom)) - }; - } - get Rectangle() { const a = this; @@ -466,23 +420,6 @@ class Canvas { a.CanvasContext.fillText(text, x, y); } - Invalidate() { - const a = this; - - if (a.EnableDock) { - a.FlowContainer.style.width = "100%"; - } - - if (a.#getWidth(a.CanvasContainer) > (a.#getWidth(a.FlowContainer) + (a.BorderWidth * 2))) { - a.FlowContainer.classList.add("scroll-x"); - a.FlowContainer.style.height = (a.#getHeight(a.CanvasContainer) + a.ScrollbarWidth) + "px"; - } else { - a.FlowContainer.classList.remove("scroll-x"); - a.FlowContainer.style.height = a.#getHeight(a.CanvasContainer) + "px"; - } - - } - MeasureText(font, value) { const a = this; @@ -500,30 +437,6 @@ class Canvas { }; } - #getWidth(el) { - if (el == null) { - return 0; - } - - if (typeof(el) == "undefined") { - return 0; - } - - return (el.offsetWidth || el.innerWidth || el.clientWidth); - } - - #getHeight(el) { - if (el == null) { - return 0; - } - - if (typeof(el) == "undefined") { - return 0; - } - - return (el.offsetHeight || el.innerHeight || el.clientHeight); - } - } diff --git a/src/references/extensions.dist.js b/src/references/extensions.dist.js index 2b5bb91..fcc14b6 100644 --- a/src/references/extensions.dist.js +++ b/src/references/extensions.dist.js @@ -1,6 +1,6 @@ /*! - * Part of LiteRyzJS v0.1.0.301 + * Part of LiteRyzJS v0.1.0.397 * Copyright 2023-2024 Ray Lam (https://www.hiimray.co.uk) * */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.LiteRyzJS=e():t.LiteRyzJS=e()}(this,(()=>(()=>{var t={814:()=>{Array.isEmpty=function(t){return"array"!=Object.getDataType(t)||t.length<=0},Array.toFlatten=function(t,e,r){for(let n=0;n0&&Array.toFlatten(t[n][e],e,r)},Array.prototype.addRange=function(t){return null==t||t.forEach((t=>{this.push(t)})),this},Array.prototype.any=function(t,e){return this.count(t,e)>0},Array.prototype.copy=function(){return JSON.parse(JSON.stringify(this))},Array.prototype.count=function(t,e){let r=0;for(let n=0;n{e+=this.count(t.propName,t.value)})),e},Array.prototype.create=function(t,e){let r=[];for(let n=0;n=this.length?this.push(e):this.splice(t,0,e),this},Array.prototype.joinIfNotNullOrWhitespace=function(t){let e="";for(let r=0;rr[t]?1:0})),this},Array.prototype.orderByDesc=function(t){return this.sort((function(e,r){return e[t]r[t]?-1:0})),this},Array.prototype.remove=function(t){let e=[];for(let r=0;r=0;t--)this.removeAt(e[t]);return this},Array.prototype.removeAt=function(t){return t<0||t>=this.length||this.splice(t,1),this},Array.prototype.removeRange=function(t){for(let e=0;e{e=e.select(t.propName,t.value)})),e},Array.prototype.toList=function(t){let e=[];return this.forEach((r=>{null!=typeof r[t]&&e.push(r[t])})),e},Array.prototype.sortTree=function(t,e){this.orderBy(e);for(let r=0;r{Boolean.isFalse=function(t){return!!String.isNullOrUndefined(t)||t.toString().containsCI("false","f","y","0","x")},Boolean.isTrue=function(t){return!String.isNullOrUndefined(t)&&t.toString().containsCI("true","t","n","1","o")},Boolean.ifTrue=function(t,e,r){return Boolean.isTrue(t)?e:r}},579:()=>{Date.addDays=function(t,e){let r=new Date(t);return r.addDays(e),r},Date.addMonths=function(t,e){let r=new Date(t);return r.addMonths(e),r},Date.addYears=function(t,e){let r=new Date(t);return r.addYears(e),r},Date.max=function(t,e){return null==t?e:null==e?t:new Date(t)<=new Date(e)?new Date(e):new Date(t)},Date.min=function(t,e){return null==t?e:null==e?t:new Date(t)<=new Date(e)?new Date(t):new Date(e)},Date.diffDays=function(t,e){return Math.ceil((new Date(e).getTime()-new Date(t).getTime())/864e5)},Date.today=function(){let t=new Date;return t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0),t},Date.prototype.addDays=function(t){this.setDate(this.getDate()+parseInt(t))},Date.prototype.addMonths=function(t){this.setMonth(this.getMonth()+parseInt(t))},Date.prototype.addYears=function(t){this.setFullYear(this.getFullYear()+parseInt(t))},Date.prototype.toCString=function(t){let e=t;return e=e.replace("fffffff",this.getMilliseconds().toString().padStart(7,"0")),e=e.replace("ffffff",this.getMilliseconds().toString().padStart(6,"0")),e=e.replace("fffff",this.getMilliseconds().toString().padStart(5,"0")),e=e.replace("yyyy",this.getFullYear().toString().padStart(4,"0")),e=e.replace("MMMM","{1}"),e=e.replace("dddd","{2}"),e=e.replace("ffff",this.getMilliseconds().toString().padStart(4,"0")),e=e.replace("yyy",this.getFullYear().toString().padStart(3,"0")),e=e.replace("MMM","{3}"),e=e.replace("ddd","{4}"),e=e.replace("fff",this.getMilliseconds().toString().padStart(3,"0")),e=e.replace("zzz",""),e=e.replace("yy",this.getFullYear().toString().slice(-2)),e=e.replace("MM",(this.getMonth()+1).toString().padStart(2,"0")),e=e.replace("dd",this.getDate().toString().padStart(2,"0")),e=e.replace("HH",this.getHours().toString().padStart(2,"0")),e=e.replace("hh",(this.getHours()>12?this.getHours()-12:this.getHours()).toString().padStart(2,"0")),e=e.replace("mm",this.getMinutes().toString().padStart(2,"0")),e=e.replace("ss",this.getSeconds().toString().padStart(2,"0")),e=e.replace("ff",this.getMilliseconds().toString().padStart(2,"0")),e=e.replace("tt","{5}"),e=e.replace("zz",""),e=e.replace("y",this.getFullYear().toString()),e=e.replace("M",(this.getMonth()+1).toString()),e=e.replace("d",this.getDate().toString()),e=e.replace("H",this.getHours().toString()),e=e.replace("h",(this.getHours()>12?this.getHours()-12:this.getHours()).toString()),e=e.replace("m",this.getMinutes().toString()),e=e.replace("s",this.getSeconds().toString()),e=e.replace("z",""),e=e.replace("t","{6}"),e=e.replace("Z",""),e=e.replace("{1}",this.toLocaleString("default",{month:"long"})),e=e.replace("{2}",this.toLocaleString("default",{weekday:"long"})),e=e.replace("{3}",this.toLocaleString("default",{month:"short"})),e=e.replace("{4}",this.toLocaleString("default",{weekday:"short"})),e=e.replace("{5}",this.getHours()>=12?"PM":"AM"),e=e.replace("{6}",this.getHours()>=12?"P":"A"),e}},874:()=>{Document.ready=async function(t){!async function(){"loading"!==document.readyState?t():document.addEventListener("DOMContentLoaded",(function(){t()}))}()}},705:()=>{Math.randomN=function(t,e){return t=Math.ceil(t),e=Math.floor(e),Math.floor(Math.random()*(e-t)+t)},Math.average=function(t){let e=0;return t.forEach((t=>{e+=parseFloat(t)})),e/t.length},Math.avg=function(...t){let e=0;return t.forEach((t=>{e+=parseFloat(t)})),e/t.length},Math.half=function(t){return t/2}},428:()=>{Object.isNullOrUndefined=function(t){return void 0===t||null==t},Object.getDataType=function(t){return String.isNullOrUndefined(t)?"null":"object"==typeof t?Array.isArray(t)?"array":"object":typeof t}},930:()=>{String.isNullOrUndefined=function(t){return void 0===t||null==t},String.isNullOrWhitespace=function(t){return!!String.isNullOrUndefined(t)||("string"==typeof t?t.trim().length<=0:t.toString().trim().length<=0)},String.joinIfNotNullOrWhitespace=function(t,...e){let r="";for(let n=0;n$1")},String.prototype.toTitleCase=function(){let t=this;return t=t.replace(/([A-Z]{1})/g," $1"),t=t.trim(),t=t.charAt(0).toUpperCase()+t.substr(1),t},String.prototype.getFilename=function(){return this.substring(this.lastIndexOf("/")+1)}},323:()=>{Window.goToTop=function(){Window.scrollTo(0,0)},Window.fragment={get:function(){if(!window.location.hash)return null;const t=window.location.hash.indexOf("?");return t<0?window.location.hash.substring(1):window.location.hash.substring(1,t)},getQuery:function(){if(!window.location.hash)return null;let t=window.location.hash;const e=t.indexOf("?");if(e<0)return null;t=hasQueryString.substring(e+1);const r=new URLSearchParams(t),n={};for(const[t,e]of r.entries())n[t]=e;return n},clear:function(){location.hash="",history.replaceState("","",location.pathname)}}}},e={};function r(n){var o=e[n];if(void 0!==o)return o.exports;var i=e[n]={exports:{}};return t[n](i,i.exports,r),i.exports}r.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return r.d(e,{a:e}),e},r.d=(t,e)=>{for(var n in e)r.o(e,n)&&!r.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var n={};return(()=>{"use strict";r.r(n),r(814),r(523),r(579),r(874),r(705),r(428),r(930),r(323)})(),n})())); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.LiteRyzJS=e():t.LiteRyzJS=e()}(this,(()=>(()=>{var t={814:()=>{Array.isEmpty=function(t){return"array"!=Object.getDataType(t)||t.length<=0},Array.toFlatten=function(t,e,r){for(let n=0;n0&&Array.toFlatten(t[n][e],e,r)},Array.prototype.addRange=function(t){return null==t||t.forEach((t=>{this.push(t)})),this},Array.prototype.all=function(t,e){for(let r=0;r{e+=this.count(t.propName,t.value)})),e},Array.prototype.create=function(t,e){let r=[];for(let n=0;n=this.length?"":this[t]},Array.prototype.index=function(t,e){const r=this.indexes(t,e);return r.length<=0?null:r[0]},Array.prototype.indexes=function(t,e){let r=[];for(let n=0;n=this.length?this.push(e):this.splice(t,0,e),this},Array.prototype.joinIfNotNullOrWhitespace=function(t){let e="";for(let r=0;rr[t]?1:0})),this},Array.prototype.orderByDesc=function(t){return this.sort((function(e,r){return e[t]r[t]?-1:0})),this},Array.prototype.remove=function(t){let e=[];for(let r=0;r=0;t--)this.removeAt(e[t]);return this},Array.prototype.removeAt=function(t){return t<0||t>=this.length||this.splice(t,1),this},Array.prototype.removeRange=function(t){for(let e=0;e{e=e.select(t.propName,t.value)})),e},Array.prototype.toList=function(t){let e=[];return this.forEach((r=>{null!=typeof r[t]&&e.push(r[t])})),e},Array.prototype.sortTree=function(t,e){this.orderBy(e);for(let r=0;r{Boolean.isFalse=function(t){return!!String.isNullOrUndefined(t)||t.toString().containsCI("false","f","y","0","x")},Boolean.isTrue=function(t){return!String.isNullOrUndefined(t)&&t.toString().containsCI("true","t","n","1","o")},Boolean.ifTrue=function(t,e,r){return Boolean.isTrue(t)?e:r}},579:()=>{Date.addDays=function(t,e){let r=new Date(t);return r.addDays(e),r},Date.addMonths=function(t,e){let r=new Date(t);return r.addMonths(e),r},Date.addYears=function(t,e){let r=new Date(t);return r.addYears(e),r},Date.max=function(t,e){return null==t?e:null==e?t:new Date(t)<=new Date(e)?new Date(e):new Date(t)},Date.min=function(t,e){return null==t?e:null==e?t:new Date(t)<=new Date(e)?new Date(t):new Date(e)},Date.diffDays=function(t,e){return Math.ceil((new Date(e).getTime()-new Date(t).getTime())/864e5)},Date.today=function(){let t=new Date;return t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0),t},Date.prototype.addDays=function(t){this.setDate(this.getDate()+parseInt(t))},Date.prototype.addMonths=function(t){this.setMonth(this.getMonth()+parseInt(t))},Date.prototype.addYears=function(t){this.setFullYear(this.getFullYear()+parseInt(t))},Date.prototype.toCString=function(t){let e=t;return e=e.replace("fffffff",this.getMilliseconds().toString().padStart(7,"0")),e=e.replace("ffffff",this.getMilliseconds().toString().padStart(6,"0")),e=e.replace("fffff",this.getMilliseconds().toString().padStart(5,"0")),e=e.replace("yyyy",this.getFullYear().toString().padStart(4,"0")),e=e.replace("MMMM","{1}"),e=e.replace("dddd","{2}"),e=e.replace("ffff",this.getMilliseconds().toString().padStart(4,"0")),e=e.replace("yyy",this.getFullYear().toString().padStart(3,"0")),e=e.replace("MMM","{3}"),e=e.replace("ddd","{4}"),e=e.replace("fff",this.getMilliseconds().toString().padStart(3,"0")),e=e.replace("zzz",""),e=e.replace("yy",this.getFullYear().toString().slice(-2)),e=e.replace("MM",(this.getMonth()+1).toString().padStart(2,"0")),e=e.replace("dd",this.getDate().toString().padStart(2,"0")),e=e.replace("HH",this.getHours().toString().padStart(2,"0")),e=e.replace("hh",(this.getHours()>12?this.getHours()-12:this.getHours()).toString().padStart(2,"0")),e=e.replace("mm",this.getMinutes().toString().padStart(2,"0")),e=e.replace("ss",this.getSeconds().toString().padStart(2,"0")),e=e.replace("ff",this.getMilliseconds().toString().padStart(2,"0")),e=e.replace("tt","{5}"),e=e.replace("zz",""),e=e.replace("y",this.getFullYear().toString()),e=e.replace("M",(this.getMonth()+1).toString()),e=e.replace("d",this.getDate().toString()),e=e.replace("H",this.getHours().toString()),e=e.replace("h",(this.getHours()>12?this.getHours()-12:this.getHours()).toString()),e=e.replace("m",this.getMinutes().toString()),e=e.replace("s",this.getSeconds().toString()),e=e.replace("z",""),e=e.replace("t","{6}"),e=e.replace("Z",""),e=e.replace("{1}",this.toLocaleString("default",{month:"long"})),e=e.replace("{2}",this.toLocaleString("default",{weekday:"long"})),e=e.replace("{3}",this.toLocaleString("default",{month:"short"})),e=e.replace("{4}",this.toLocaleString("default",{weekday:"short"})),e=e.replace("{5}",this.getHours()>=12?"PM":"AM"),e=e.replace("{6}",this.getHours()>=12?"P":"A"),e}},874:()=>{Document.ready=async function(t){!async function(){"loading"!==document.readyState?t():document.addEventListener("DOMContentLoaded",(function(){t()}))}()},Document.getWidth=function(t){return null==t||void 0===t?0:t.offsetWidth||t.innerWidth||t.clientWidth},Document.getHeight=function(t){return null==t||void 0===t?0:t.offsetHeight||t.innerHeight||t.clientHeight},Document.appendHtml=function(t,e){const r=document.createElement(e);t.appendChild(r)},Document.addClass=function(t,e){t.classList.contains(e)||t.classList.add(e)},Document.removeClass=function(t,e){t.classList.contains(e)&&t.classList.remove(e)}},705:()=>{Math.randomN=function(t,e){return t=Math.ceil(t),e=Math.floor(e),Math.floor(Math.random()*(e-t)+t)},Math.average=function(t){let e=0;return t.forEach((t=>{e+=parseFloat(t)})),e/t.length},Math.avg=function(...t){let e=0;return t.forEach((t=>{e+=parseFloat(t)})),e/t.length},Math.half=function(t){return t/2}},428:()=>{Object.isNullOrUndefined=function(t){return void 0===t||null==t},Object.getDataType=function(t){return String.isNullOrUndefined(t)?"null":"object"==typeof t?Array.isArray(t)?"array":"object":typeof t}},930:()=>{String.isNullOrUndefined=function(t){return void 0===t||null==t},String.isNullOrWhitespace=function(t){return!!String.isNullOrUndefined(t)||("string"==typeof t?t.trim().length<=0:t.toString().trim().length<=0)},String.joinIfNotNullOrWhitespace=function(t,...e){let r="";for(let n=0;n$1")},String.prototype.toTitleCase=function(){let t=this;return t=t.replace(/([A-Z]{1})/g," $1"),t=t.trim(),t=t.charAt(0).toUpperCase()+t.substr(1),t},String.prototype.getFilename=function(){return this.substring(this.lastIndexOf("/")+1)}},323:()=>{Window.goToTop=function(){Window.scrollTo(0,0)},Window.fragment={get:function(){if(!window.location.hash)return null;const t=window.location.hash.indexOf("?");return t<0?window.location.hash.substring(1):window.location.hash.substring(1,t)},getQuery:function(){if(!window.location.hash)return null;let t=window.location.hash;const e=t.indexOf("?");if(e<0)return null;t=hasQueryString.substring(e+1);const r=new URLSearchParams(t),n={};for(const[t,e]of r.entries())n[t]=e;return n},clear:function(){location.hash="",history.replaceState("","",location.pathname)}}}},e={};function r(n){var o=e[n];if(void 0!==o)return o.exports;var i=e[n]={exports:{}};return t[n](i,i.exports,r),i.exports}r.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return r.d(e,{a:e}),e},r.d=(t,e)=>{for(var n in e)r.o(e,n)&&!r.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var n={};return(()=>{"use strict";r.r(n),r(814),r(523),r(579),r(874),r(705),r(428),r(930),r(323)})(),n})())); \ No newline at end of file -- 2.45.2 From fee71b651c4ef36dc52e7a616a0f81cd4cab3b21 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 7 Sep 2024 14:29:34 +0100 Subject: [PATCH 2/3] Added hot-tracking on flourish layer Fixed stacking issue with more than two layers --- src/project/flourish-canvas.js | 75 ++++++++++++++++++++++++++++++ src/project/gantt-chart.js | 18 ++++++- src/references/canvas-container.js | 6 ++- 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/project/flourish-canvas.js diff --git a/src/project/flourish-canvas.js b/src/project/flourish-canvas.js new file mode 100644 index 0000000..4413838 --- /dev/null +++ b/src/project/flourish-canvas.js @@ -0,0 +1,75 @@ +import Canvas from '../references/canvas.js'; + + +class FlourishCanvas extends Canvas { + Options = null; + Debug = false; + + XPos = -1 + + + constructor(el) { + super(el); + } + + + get HeaderHeight() { + const a = this; + + return a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1]; + } + + + Load() { + const a = this; + + a.el.addEventListener('mousemove', function (e) { + // Hottracking + if (a.Options.EnableHotTracking) { + const point = { X: e.offsetX, Y: e.offsetY }; + + if (a.Debug) { + console.log(point); + } + + a.XPos = point.X; + + a.Invalidate(); + } + + }); + + } + + Invalidate() { + const a = this; + + if (a.Options == null) { + return; + } + + a.Clear(); + + if (a.XPos < 0) { + return; + } + + a.#drawVerticalLine(a.XPos); + } + + #drawVerticalLine(x) { + const a = this; + const h = this.Height - a.HeaderHeight; + + if (a.Options.HotTrackLine.Width > 1) { + x += Math.half(a.Options.HotTrackLine.Width); + } + + a.DrawVerticalLine(x, a.HeaderHeight, h, a.Options.HotTrackLine.Colour, { LineWidth: a.Options.HotTrackLine.Width }); + } + + +} + + +export default FlourishCanvas; \ No newline at end of file diff --git a/src/project/gantt-chart.js b/src/project/gantt-chart.js index fde94f0..eb20c3e 100644 --- a/src/project/gantt-chart.js +++ b/src/project/gantt-chart.js @@ -1,6 +1,7 @@ import CanvasContainer from '../references/canvas-container.js'; import BackgroundCanvas from './background-canvas.js'; import ForegroundCanvas from './foreground-canvas.js'; +import FlourishCanvas from './flourish-canvas.js'; import './project.scss'; @@ -44,11 +45,19 @@ class GanttChart { a.CanvasContainer.Layer.push(foreCanvasLayer); + // Add flourish canvas layer + const layer3 = a.CanvasContainer.AddLayer(); + + const flourishCanvasLayer = new FlourishCanvas(layer3); + flourishCanvasLayer.Options = a.Options; + + a.CanvasContainer.Layer.push(flourishCanvasLayer); + + // Invalidate every canvas a.CanvasContainer.Invalidate(); Document.removeClass(a.CanvasContainer.FlowContainer, "border"); Document.addClass(a.CanvasContainer.FlowContainer, "gantt-chart"); - } @@ -91,11 +100,16 @@ class GanttChart { Width: 1, ArrowSize: 5 }, + HotTrackLine: { + Colour: "#D04437", + Width: 1 + }, DateFont: "7pt sans-serif", DateForeColour: "#636363", BorderWidth: 1, BorderColour: "#B8B8B8", BorderDashPattern: [1, 1], + EnableHotTracking: true, MinimumRowCount: 6, ShowDateLines: true, ShowStartDayOfWeekLine: true, @@ -143,6 +157,7 @@ class GanttChart { // Invalidate canvas (background, foreground) a.CanvasContainer.Layer[0].Load(new Date(), a.Duration, a.StartOfWeek, a.Tasks.length); a.CanvasContainer.Layer[1].Load(new Date(), a.Tasks); + a.CanvasContainer.Layer[2].Load(); a.Invalidate(); } @@ -177,6 +192,7 @@ class GanttChart { // Invalidate canvas (background, foreground) a.CanvasContainer.Layer[0].Load(new Date(project.StartDate), project.Duration, project.Project.StartOfWeek, a.Tasks.length); a.CanvasContainer.Layer[1].Load(new Date(project.StartDate), a.Tasks); + a.CanvasContainer.Layer[2].Load(); a.Invalidate(); } diff --git a/src/references/canvas-container.js b/src/references/canvas-container.js index 7ae170e..f8133dd 100644 --- a/src/references/canvas-container.js +++ b/src/references/canvas-container.js @@ -207,6 +207,8 @@ class CanvasContainer { } }); + let offsetTop = 0; + a.Layer.forEach((e, i) => { if (i <= 0) { return; @@ -215,7 +217,9 @@ class CanvasContainer { // Correct positon because of position-relative to keep container overflow working const h = Document.getHeight(a.Layer[(i - 1)].el); - a.Layer[i].el.style.top = "-" + h + "px"; + offsetTop += h; + + a.Layer[i].el.style.top = "-" + offsetTop + "px"; }); } -- 2.45.2 From e2793213673e9f8184d34b7a832f930736b8d087 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 7 Sep 2024 22:05:16 +0100 Subject: [PATCH 3/3] Changed to use gantt chart options class --- src/project/flourish-canvas.js | 7 +++- src/project/gantt-chart-options.js | 58 ++++++++++++++++++++++++++ src/project/gantt-chart.js | 66 ++++-------------------------- 3 files changed, 71 insertions(+), 60 deletions(-) create mode 100644 src/project/gantt-chart-options.js diff --git a/src/project/flourish-canvas.js b/src/project/flourish-canvas.js index 4413838..136afe2 100644 --- a/src/project/flourish-canvas.js +++ b/src/project/flourish-canvas.js @@ -39,6 +39,12 @@ class FlourishCanvas extends Canvas { }); + a.el.addEventListener('mouseout', function (e) { + a.XPos = -1; + + a.Invalidate(); + }); + } Invalidate() { @@ -68,7 +74,6 @@ class FlourishCanvas extends Canvas { a.DrawVerticalLine(x, a.HeaderHeight, h, a.Options.HotTrackLine.Colour, { LineWidth: a.Options.HotTrackLine.Width }); } - } diff --git a/src/project/gantt-chart-options.js b/src/project/gantt-chart-options.js new file mode 100644 index 0000000..a017d67 --- /dev/null +++ b/src/project/gantt-chart-options.js @@ -0,0 +1,58 @@ +class GanttChartOptions { + DayWidth = 24; + HeaderRow = { + Height: [ 20, 20 ] + }; + Row = { + Height: 28, + BackColour: "rgba(235, 235, 235, 0.3)", + Task: { + Height: 14, + BorderColour: "#555555", + FillColour: "#D8EEDB" + }, + OrphanTask: { + Height: 14, + BorderColour: "#555555", + FillColour: "#9CC2E6" + }, + ChildTask: { + Height: 14, + BorderColour: "#555555", + FillColour: "#FFF5C1" + }, + CollatedTask: { + Height: 6, + BorderColour: "#555555", + FillColour: "#555555" + } + }; + Column = { + SatColour: "rgba(233, 237, 239, 0.8)", + SunColour: "rgba(233, 237, 239, 0.9)" + }; + Line = { + Colour: "#555555", + Width: 1, + ArrowSize: 5 + }; + HotTrackLine = { + Colour: "#D04437", + Width: 1 + }; + DateFont = "7pt sans-serif"; + DateForeColour = "#636363"; + BorderWidth = 1; + BorderColour = "#B8B8B8"; + BorderDashPattern = [1, 1]; + EnableHotTracking = true; + MinimumRowCount = 6; + ShowDateLines = true; + ShowStartDayOfWeekLine = true; + ShowRowLines = true; + ShowRowStripes = true; + +} + + +export default GanttChartOptions; \ No newline at end of file diff --git a/src/project/gantt-chart.js b/src/project/gantt-chart.js index eb20c3e..3a41e29 100644 --- a/src/project/gantt-chart.js +++ b/src/project/gantt-chart.js @@ -1,3 +1,5 @@ +import GanttChartOptions from './gantt-chart-options.js'; + import CanvasContainer from '../references/canvas-container.js'; import BackgroundCanvas from './background-canvas.js'; import ForegroundCanvas from './foreground-canvas.js'; @@ -20,7 +22,7 @@ class GanttChart { constructor(el, options) { const a = this; - a.Options = Object.assign(a.DefaultOptions, options); + a.Options = Object.assign(new GanttChartOptions(), options); a.CanvasContainer = new CanvasContainer(el); a.#initialiseComponents(); @@ -61,63 +63,6 @@ class GanttChart { } - get DefaultOptions() { - return { - DayWidth: 24, - HeaderRow: { - Height: [ 20, 20 ] - }, - Row: { - Height: 28, - BackColour: "rgba(235, 235, 235, 0.3)", - Task: { - Height: 14, - BorderColour: "#555555", - FillColour: "#D8EEDB" - }, - OrphanTask: { - Height: 14, - BorderColour: "#555555", - FillColour: "#9CC2E6" - }, - ChildTask: { - Height: 14, - BorderColour: "#555555", - FillColour: "#FFF5C1" - }, - CollatedTask: { - Height: 6, - BorderColour: "#555555", - FillColour: "#555555" - } - }, - Column: { - SatColour: "rgba(233, 237, 239, 0.8)", - SunColour: "rgba(233, 237, 239, 0.9)", - }, - Line: { - Colour: "#555555", - Width: 1, - ArrowSize: 5 - }, - HotTrackLine: { - Colour: "#D04437", - Width: 1 - }, - DateFont: "7pt sans-serif", - DateForeColour: "#636363", - BorderWidth: 1, - BorderColour: "#B8B8B8", - BorderDashPattern: [1, 1], - EnableHotTracking: true, - MinimumRowCount: 6, - ShowDateLines: true, - ShowStartDayOfWeekLine: true, - ShowRowLines: true, - ShowRowStripes: true - }; - } - get HeaderHeight() { const a = this; @@ -170,7 +115,10 @@ class GanttChart { const width = ((a.Duration + 2) * a.Options.DayWidth); const height = (Math.max(a.Tasks.length, a.Options.MinimumRowCount) * a.Options.Row.Height) + a.HeaderHeight; - a.CanvasContainer.Size = { W: width, H: height }; + a.CanvasContainer.Size = { + W: width, + H: height + }; a.CanvasContainer.Invalidate(); } -- 2.45.2