WIP: Gantt chart for project
This commit is contained in:
parent
d30d60ae91
commit
db458dc3a0
|
@ -7,8 +7,8 @@ Array.prototype.countMany=function(...a){let b=0;a.forEach(c=>{b+=this.count(c.p
|
|||
Array.prototype.joinIfNotNullOrWhitespace=function(a){let b="";for(let c=0;c<this.length;c++)String.isNullOrWhitespace(this[c])||(String.isNullOrWhitespace(b)||(b+=a),b+=this[c]);return b};Array.prototype.first=function(a,b){for(let c=0;c<this.length;c++)if("undefined"!=typeof this[c][a]&&this[c][a]==b)return this[c];return null};Array.prototype.forEachTree=function(a,b){for(let c=0;c<this.length&&!1!==b(this[c])&&(0>=this[c][a].length||!1!==this[c][a].forEachTree(a,b));c++);};
|
||||
Array.prototype.indexes=function(a,b){let c=[];for(let d=0;d<this.length;d++)"undefined"!=typeof this[d][a]&&this[d][a]==b&&c.push(d);return c};Array.prototype.orderBy=function(a){this.sort(function(b,c){return b[a]<c[a]?-1:b[a]>c[a]?1:0});return this};Array.prototype.orderByDesc=function(a){this.sort(function(b,c){return b[a]<c[a]?1:b[a]>c[a]?-1:0});return this};
|
||||
Array.prototype.remove=function(a){let b=[];for(let c=0;c<this.length;c++)this[c]==a&&b.push(c);for(a=b.length-1;0<=a;a--)this.removeAt(b[a]);return this};Array.prototype.removeAt=function(a){if(0>a||a>=this.length)return this;this.splice(a,1);return this};Array.prototype.removeRange=function(a){for(let b=0;b<a.length;b++)this.remove(a[b]);return this};Array.prototype.select=function(a,b){let c=[];for(let d=0;d<this.length;d++)"undefined"!=typeof this[d][a]&&this[d][a]==b&&c.push(this[d]);return c};
|
||||
Array.prototype.selectMany=function(...a){let b=this;a.forEach(c=>{b=b.select(c.propName,c.value)});return b};Array.prototype.toList=function(a){let b=[];this.forEach(c=>{b.push(c[a])});return b};Array.prototype.sortTree=function(a,b){this.orderBy(b);for(let c=0;c<this.length;c++)0>=this[c][a].length||this[c][a].orderBy(b)};Boolean.isFalse=function(a){return String.isNullOrUndefined(a)?!0:a.toString().containsCI("false","f","y","0","x")};Boolean.isTrue=function(a){return String.isNullOrUndefined(a)?!1:a.toString().containsCI("true","t","n","1","o")};Boolean.ifTrue=function(a,b,c){return Boolean.isTrue(a)?b:c};Date.addDays=function(a,b){a=new Date(a);a.addDays(b);return a};Date.addMonths=function(a,b){a=new Date(a);a.addMonths(b);return a};Date.addYears=function(a,b){a=new Date(a);a.addYears(b);return a};Date.diffDays=function(a,b){return(b.getTime()-a.getTime())/864E5};Date.today=function(){let a=new Date;a.setHours(0);a.setMinutes(0);a.setSeconds(0);a.setMilliseconds(0);return a};Date.prototype.addDays=function(a){this.setDate(this.getDate()+parseInt(a))};
|
||||
Date.prototype.addMonths=function(a){this.setMonth(this.getMonth()+parseInt(a))};Date.prototype.addYears=function(a){this.setFullYear(this.getFullYear()+parseInt(a))};
|
||||
Array.prototype.selectMany=function(...a){let b=this;a.forEach(c=>{b=b.select(c.propName,c.value)});return b};Array.prototype.toList=function(a){let b=[];this.forEach(c=>{b.push(c[a])});return b};Array.prototype.sortTree=function(a,b){this.orderBy(b);for(let c=0;c<this.length;c++)0>=this[c][a].length||this[c][a].orderBy(b)};Boolean.isFalse=function(a){return String.isNullOrUndefined(a)?!0:a.toString().containsCI("false","f","y","0","x")};Boolean.isTrue=function(a){return String.isNullOrUndefined(a)?!1:a.toString().containsCI("true","t","n","1","o")};Boolean.ifTrue=function(a,b,c){return Boolean.isTrue(a)?b:c};Date.addDays=function(a,b){a=new Date(a);a.addDays(b);return a};Date.addMonths=function(a,b){a=new Date(a);a.addMonths(b);return a};Date.addYears=function(a,b){a=new Date(a);a.addYears(b);return a};Date.max=function(a,b){return null==a?b:null==b?a:new Date(a)<=new Date(b)?new Date(b):new Date(a)};Date.min=function(a,b){return null==a?b:null==b?a:new Date(a)<=new Date(b)?new Date(a):new Date(b)};Date.diffDays=function(a,b){return Math.ceil(((new Date(b)).getTime()-(new Date(a)).getTime())/864E5)};
|
||||
Date.today=function(){let a=new Date;a.setHours(0);a.setMinutes(0);a.setSeconds(0);a.setMilliseconds(0);return a};Date.prototype.addDays=function(a){this.setDate(this.getDate()+parseInt(a))};Date.prototype.addMonths=function(a){this.setMonth(this.getMonth()+parseInt(a))};Date.prototype.addYears=function(a){this.setFullYear(this.getFullYear()+parseInt(a))};
|
||||
Date.prototype.toCString=function(a){a=a.replace("fffffff",this.getMilliseconds().toString().padStart(7,"0"));a=a.replace("ffffff",this.getMilliseconds().toString().padStart(6,"0"));a=a.replace("fffff",this.getMilliseconds().toString().padStart(5,"0"));a=a.replace("yyyy",this.getFullYear().toString().padStart(4,"0"));a=a.replace("MMMM","{1}");a=a.replace("dddd","{2}");a=a.replace("ffff",this.getMilliseconds().toString().padStart(4,"0"));a=a.replace("yyy",this.getFullYear().toString().padStart(3,"0"));
|
||||
a=a.replace("MMM","{3}");a=a.replace("ddd","{4}");a=a.replace("fff",this.getMilliseconds().toString().padStart(3,"0"));a=a.replace("zzz","");a=a.replace("yy",this.getFullYear().toString().slice(-2));a=a.replace("MM",(this.getMonth()+1).toString().padStart(2,"0"));a=a.replace("dd",this.getDate().toString().padStart(2,"0"));a=a.replace("HH",this.getHours().toString().padStart(2,"0"));a=a.replace("hh",(12<this.getHours()?this.getHours()-12:this.getHours()).toString().padStart(2,"0"));a=a.replace("mm",
|
||||
this.getMinutes().toString().padStart(2,"0"));a=a.replace("ss",this.getSeconds().toString().padStart(2,"0"));a=a.replace("ff",this.getMilliseconds().toString().padStart(2,"0"));a=a.replace("tt","{5}");a=a.replace("zz","");a=a.replace("y",this.getFullYear().toString());a=a.replace("M",(this.getMonth()+1).toString());a=a.replace("d",this.getDate().toString());a=a.replace("H",this.getHours().toString());a=a.replace("h",(12<this.getHours()?this.getHours()-12:this.getHours()).toString());a=a.replace("m",
|
||||
|
|
235
canvas.js
235
canvas.js
|
@ -1,6 +1,10 @@
|
|||
class Canvas {
|
||||
_container = null;
|
||||
_flowContainer = null;
|
||||
_canvasContainer = null;
|
||||
_ctx = null;
|
||||
|
||||
_autoSize = true;
|
||||
_padding = {
|
||||
Top: 0,
|
||||
Right: 0,
|
||||
|
@ -25,8 +29,18 @@ class Canvas {
|
|||
return;
|
||||
}
|
||||
|
||||
a._container.innerHTML = "<canvas></canvas>";
|
||||
a._container.innerHTML = "<div class='border'><canvas></canvas></div>";
|
||||
|
||||
if (a._container != null) {
|
||||
a._container.style.width = "100%";
|
||||
a._container.style.height = "100%";
|
||||
|
||||
a._flowContainer = a._container.getElementsByTagName("div")[0];
|
||||
a._canvasContainer = a._flowContainer.getElementsByTagName("canvas")[0];
|
||||
a._ctx = a._canvasContainer.getContext("2d");
|
||||
}
|
||||
|
||||
a.AutoSize = a.AutoSize;
|
||||
a.Width = a.Width;
|
||||
a.Height = a.Height;
|
||||
|
||||
|
@ -34,6 +48,59 @@ class Canvas {
|
|||
}
|
||||
|
||||
|
||||
get AutoSize() {
|
||||
return this._autoSize;
|
||||
}
|
||||
|
||||
set AutoSize(value) {
|
||||
const a = this;
|
||||
|
||||
a._autoSize = value;
|
||||
|
||||
if (a._flowContainer != null) {
|
||||
if (value == true) {
|
||||
a._flowContainer.style.overflow = "hidden";
|
||||
} else {
|
||||
a._flowContainer.style.overflowX = ((a.Width >= a.ClientWidth) ? "hidden" : "auto");
|
||||
a._flowContainer.style.overflowY = ((a.Height >= a.ClientHeight) ? "hidden" : "auto");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get ClientHeight() {
|
||||
const a = this;
|
||||
|
||||
if (a._canvasContainer == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a.#getHeight(a._canvasContainer);
|
||||
}
|
||||
|
||||
set ClientHeight(value) {
|
||||
const a = this;
|
||||
|
||||
if (a._canvasContainer != null) a._canvasContainer.style.height = value + "px";
|
||||
if (a._ctx != null) a._ctx.canvas.height = value;
|
||||
}
|
||||
|
||||
get ClientWidth() {
|
||||
const a = this;
|
||||
|
||||
if (a._canvasContainer == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return a.#getWidth(a._canvasContainer);
|
||||
}
|
||||
|
||||
set ClientWidth(value) {
|
||||
const a = this;
|
||||
|
||||
if (a._canvasContainer != null) a._canvasContainer.style.width = value + "px";
|
||||
if (a._ctx != null) a._ctx.canvas.width = value;
|
||||
}
|
||||
|
||||
get Height() {
|
||||
const a = this;
|
||||
|
||||
|
@ -41,25 +108,18 @@ class Canvas {
|
|||
return 0;
|
||||
}
|
||||
|
||||
const h = (a._container.offsetHeight || a._container.innerHeight || a._container.clientHeight);
|
||||
|
||||
return h;
|
||||
return a.#getHeight(a._container);
|
||||
}
|
||||
|
||||
set Height(value) {
|
||||
const a = this;
|
||||
|
||||
if (a._container == null) {
|
||||
return;
|
||||
if (a._container != null) a._container.style.height = value + "px";
|
||||
if (a._flowContainer != null) a._flowContainer.style.height = value + "px";
|
||||
|
||||
if (a.AutoSize == true) {
|
||||
a.ClientHeight = value;
|
||||
}
|
||||
|
||||
a._container.style.height = value + "px";
|
||||
|
||||
const canvas = a._container.getElementsByTagName("canvas")[0];
|
||||
canvas.style.height = value + "px";
|
||||
|
||||
a._ctx = canvas.getContext("2d");
|
||||
a._ctx.canvas.height = value;
|
||||
}
|
||||
|
||||
get Width() {
|
||||
|
@ -69,25 +129,18 @@ class Canvas {
|
|||
return 0;
|
||||
}
|
||||
|
||||
const w = (a._container.offsetWidth || a._container.innerWidth || a._container.clientWidth);
|
||||
|
||||
return w;
|
||||
return a.#getWidth(a._container);
|
||||
}
|
||||
|
||||
set Width(value) {
|
||||
const a = this;
|
||||
|
||||
if (a._container == null) {
|
||||
return;
|
||||
if (a._container != null) a._container.style.width = value + "px";
|
||||
if (a._flowContainer != null) a._flowContainer.style.width = value + "px";
|
||||
|
||||
if (a.AutoSize == true) {
|
||||
a.ClientWidth = value;
|
||||
}
|
||||
|
||||
a._container.style.width = value + "px";
|
||||
|
||||
const canvas = a._container.getElementsByTagName("canvas")[0];
|
||||
canvas.style.width = value + "px";
|
||||
|
||||
a._ctx = canvas.getContext("2d");
|
||||
a._ctx.canvas.width = value;
|
||||
}
|
||||
|
||||
get Size() {
|
||||
|
@ -157,23 +210,143 @@ class Canvas {
|
|||
return rectangle;
|
||||
}
|
||||
|
||||
MeasureText(font, value) {
|
||||
// DrawLine(x1, y1, x2, y2, width, colour) {
|
||||
// const a = this;
|
||||
|
||||
// a.CTX.beginPath();
|
||||
// a.CTX.moveTo(x1, y1);
|
||||
// a.CTX.lineTo(x2, (y2 - width));
|
||||
// a.CTX.lineWidth = width;
|
||||
// a.CTX.strokeStyle = colour;
|
||||
// a.CTX.stroke();
|
||||
// }
|
||||
|
||||
DrawHorizontalLine(x, y, width, penColour, penWidth) {
|
||||
const a = this;
|
||||
|
||||
if (a._ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
y -= 0.5;
|
||||
// y -= penWidth;
|
||||
|
||||
a._ctx.beginPath();
|
||||
a._ctx.strokeStyle = penColour;
|
||||
a._ctx.lineWidth = penWidth;
|
||||
a._ctx.moveTo(x, y);
|
||||
a._ctx.lineTo((x + width), y);
|
||||
a._ctx.stroke();
|
||||
}
|
||||
|
||||
DrawText(x, y, text, font, foreColour, align) {
|
||||
const a = this;
|
||||
|
||||
a._ctx.font = font;
|
||||
a._ctx.fillStyle = foreColour;
|
||||
a._ctx.textAlign = align;
|
||||
a._ctx.textBaseline = "top";
|
||||
|
||||
a._ctx.fillText(text, x, y);
|
||||
}
|
||||
|
||||
DrawVerticalLine(x, y, height, penColour, penWidth) {
|
||||
const a = this;
|
||||
|
||||
if (a._ctx == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
x -= 0.5;
|
||||
y -= penWidth;
|
||||
|
||||
a._ctx.beginPath();
|
||||
a._ctx.strokeStyle = penColour;
|
||||
a._ctx.lineWidth = penWidth;
|
||||
a._ctx.moveTo(x, y);
|
||||
a._ctx.lineTo(x, (y + height));
|
||||
a._ctx.stroke();
|
||||
}
|
||||
|
||||
FillText(rectangle, text, font, foreColour, textAlign, verticalAlign) {
|
||||
const a = this;
|
||||
|
||||
a._ctx.font = font;
|
||||
a._ctx.fillStyle = foreColour;
|
||||
a._ctx.textAlign = textAlign;
|
||||
a._ctx.textBaseline = verticalAlign;
|
||||
a._ctx.textBaseline = "top";
|
||||
|
||||
const size = a.MeasureText(font, text);
|
||||
|
||||
let x = rectangle.X + Math.half(rectangle.W);
|
||||
// let y = rectangle.Y + Math.half(rectangle.H);
|
||||
let y = rectangle.Y;
|
||||
|
||||
// switch (textAlign) {
|
||||
// case "center":
|
||||
// x += Math.half(rectangle.W - size.W);
|
||||
// break;
|
||||
// case "right":
|
||||
// x += (rectangle.W - size.W);
|
||||
// break;
|
||||
// case "left":
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
|
||||
switch (verticalAlign) {
|
||||
case "center":
|
||||
case "middle":
|
||||
y += Math.half((rectangle.H - size.H));
|
||||
break;
|
||||
case "bottom":
|
||||
y += (rectangle.H - size.H);
|
||||
break;
|
||||
case "top":
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
a._ctx.fillText(text, x, y);
|
||||
}
|
||||
|
||||
Invalidate() {
|
||||
const a = this;
|
||||
|
||||
a.AutoSize = a.AutoSize;
|
||||
a.Width = a.Width;
|
||||
a.Height = a.Height;
|
||||
}
|
||||
|
||||
MeasureText(font, value) {
|
||||
const a = this;
|
||||
|
||||
if (a._ctx == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
a._ctx.font = font;
|
||||
|
||||
const size = a._ctx.measureText(value);
|
||||
|
||||
return {
|
||||
X: a.half(size.width),
|
||||
Y: a.half(size.fontBoundingBoxAscent),
|
||||
W: size.width,
|
||||
H: size.fontBoundingBoxAscent
|
||||
H: Math.max(size.fontBoundingBoxDescent, size.fontBoundingBoxAscent)
|
||||
};
|
||||
}
|
||||
|
||||
#getWidth(el) {
|
||||
const result = (el.offsetWidth || el.innerWidth || el.clientWidth);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#getHeight(el) {
|
||||
const result = (el.offsetHeight || el.innerHeight || el.clientHeight);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -22,8 +22,32 @@ Date.addYears = function (date, years) {
|
|||
return result;
|
||||
};
|
||||
|
||||
Date.max = function (date1, date2) {
|
||||
if (date1 == null) {
|
||||
return date2;
|
||||
}
|
||||
|
||||
if (date2 == null) {
|
||||
return date1;
|
||||
}
|
||||
|
||||
return ((new Date(date1) <= new Date(date2)) ? new Date(date2) : new Date(date1));
|
||||
};
|
||||
|
||||
Date.min = function (date1, date2) {
|
||||
if (date1 == null) {
|
||||
return date2;
|
||||
}
|
||||
|
||||
if (date2 == null) {
|
||||
return date1;
|
||||
}
|
||||
|
||||
return ((new Date(date1) <= new Date(date2)) ? new Date(date1) : new Date(date2));
|
||||
};
|
||||
|
||||
Date.diffDays = function (date1, date2) {
|
||||
return (date2.getTime() - date1.getTime()) / (1000 * 3600 * 24);
|
||||
return Math.ceil((new Date(date2).getTime() - new Date(date1).getTime()) / (1000 * 3600 * 24));
|
||||
};
|
||||
|
||||
Date.today = function () {
|
||||
|
|
|
@ -3,8 +3,10 @@ class RyzGanttChart extends Canvas {
|
|||
super(el);
|
||||
|
||||
const a = this;
|
||||
|
||||
a.Options = Object.assign(a.DefaultOptions, options);
|
||||
// a.Debug = true;
|
||||
a.Debug = false;
|
||||
a.Project = null;
|
||||
|
||||
a.#initialiseComponents();
|
||||
}
|
||||
|
@ -18,9 +20,12 @@ class RyzGanttChart extends Canvas {
|
|||
|
||||
get DefaultOptions() {
|
||||
return {
|
||||
DayWidth: 100,
|
||||
DayWidth: 32,
|
||||
RowHeight: 28,
|
||||
HeaderRowHeight: 40
|
||||
HeaderRowHeight: 42,
|
||||
DateFont: "8pt sans-serif",
|
||||
DateForeColour: "#636363",
|
||||
BorderColour: "#B8B8B8"
|
||||
|
||||
};
|
||||
}
|
||||
|
@ -50,22 +55,56 @@ class RyzGanttChart extends Canvas {
|
|||
return;
|
||||
}
|
||||
|
||||
a.Project = project;
|
||||
|
||||
const tasks = project.ExportTasks();
|
||||
const width = (project.Duration * a.Options.DayWidth);
|
||||
const width = ((project.Duration + 2) * a.Options.DayWidth);
|
||||
const height = (tasks.length * a.Options.RowHeight) + a.Options.HeaderRowHeight;
|
||||
|
||||
console.log(tasks.length);
|
||||
console.log(height);
|
||||
a.AutoSize = false;
|
||||
a.ClientWidth = width;
|
||||
a.ClientHeight = height;
|
||||
|
||||
a.Width = width;
|
||||
a.Height = height;
|
||||
a.Invalidate();
|
||||
|
||||
|
||||
// a.Invalidate();
|
||||
a.#drawAxis();
|
||||
}
|
||||
|
||||
// SetOptions(options) {
|
||||
#drawAxis() {
|
||||
const a = this;
|
||||
|
||||
// }
|
||||
const width = a.ClientWidth;
|
||||
const height = a.ClientHeight;
|
||||
const borderWidth = 1;
|
||||
const displayDays = a.Project.Duration + 2;
|
||||
|
||||
let startDate = new Date(a.Project.StartDate);
|
||||
startDate.addDays(-1);
|
||||
|
||||
// Draw vertical lines
|
||||
for (let i=1; i<displayDays; i++) {
|
||||
a.DrawVerticalLine((a.Options.DayWidth * i), 22, 19, a.Options.BorderColour, borderWidth);
|
||||
}
|
||||
|
||||
a.DrawHorizontalLine(0, 21, width, a.Options.BorderColour, borderWidth);
|
||||
a.DrawHorizontalLine(0, 41, width, a.Options.BorderColour, borderWidth);
|
||||
|
||||
// Write dates
|
||||
for (let i=0; i<displayDays; i++) {
|
||||
const date = Date.addDays(startDate, i);
|
||||
const rectangle = {
|
||||
X: (a.Options.DayWidth * i),
|
||||
Y: 21,
|
||||
W: a.Options.DayWidth - borderWidth,
|
||||
H: 19
|
||||
};
|
||||
|
||||
if (a.Debug) a.DrawRectangle(rectangle, "red", 1);
|
||||
|
||||
a.FillText(rectangle, date.getDate(), a.Options.DateFont, a.Options.DateForeColour, "center", "middle");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -41,10 +41,10 @@ body {
|
|||
<div class="column" id="taskGrid1">
|
||||
|
||||
</div>
|
||||
<div class="column" style="overflow: auto;">
|
||||
<div class="column" style="padding-left: 3px">
|
||||
|
||||
<!-- <div style="width:100%; height: 100%; overflow: scroll;"> -->
|
||||
<div id="ganttChart1" style="width:100%; height: 100%;"></div>
|
||||
<div id="ganttChart1"></div>
|
||||
<!-- </div> -->
|
||||
|
||||
</div>
|
||||
|
|
10
ryzproj.css
10
ryzproj.css
|
@ -2,6 +2,7 @@
|
|||
border-spacing: 0px;
|
||||
border-collapse: separate;
|
||||
cursor: default;
|
||||
padding: 0px 0px 30px 0px
|
||||
}
|
||||
|
||||
.ryz-project .b {
|
||||
|
@ -66,4 +67,13 @@
|
|||
background-color: #D1F2C7;
|
||||
color: #3E7138;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.border {
|
||||
border-color: #B8B8B8;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
}
|
|
@ -46,16 +46,12 @@ class RyzProject {
|
|||
|
||||
let result = new Date(a.Project.StartDate);
|
||||
|
||||
a.Tasks.forEach(e => {
|
||||
a.ExportTasks().forEach(e => {
|
||||
if (e.FinishDate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.FinishDate <= result) {
|
||||
return;
|
||||
}
|
||||
|
||||
result = new Date(e.FinishDate);
|
||||
result = Date.max(result, e.FinishDate);
|
||||
});
|
||||
|
||||
return result;
|
||||
|
|
Loading…
Reference in New Issue