Compare commits

..

5 Commits

Author SHA1 Message Date
Ray
06f0a9b4ac Merge pull request 'release/0.2.1' (#2) from release/0.2.1 into master
Reviewed-on: #2
2024-09-07 21:16:00 +00:00
Ray
0b43e4ecd7 Merge pull request 'release/0.2.0' (#1) from release/0.2.0 into master
Reviewed-on: #1
2024-09-07 21:15:28 +00:00
Ray
e279321367 Changed to use gantt chart options class 2024-09-07 22:05:16 +01:00
Ray
fee71b651c Added hot-tracking on flourish layer
Fixed stacking issue with more than two layers
2024-09-07 14:29:34 +01:00
Ray
d05d22afe7 Added multi-layer canvas container 2024-09-02 21:32:54 +01:00
11 changed files with 841 additions and 447 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "LiteRyzJS/Project", "name": "LiteRyzJS/Project",
"version": "0.2.0.508", "version": "0.2.1.114",
"devDependencies": { "devDependencies": {
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"sass": "^1.77.8", "sass": "^1.77.8",

View File

@ -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<displayDays; i++) {
a.DrawVerticalLine((a.Options.DayWidth * i), (a.Options.HeaderRow.Height[0] + a.Options.BorderWidth), (a.Options.HeaderRow.Height[1] - a.Options.BorderWidth), a.Options.BorderColour, {});
}
// Draw horizontal line under dates
a.#drawHorizontalLine(a.HeaderHeight);
// Write dates
for (let i=0; i<displayDays; i++) {
const date = Date.addDays(startDate, i);
const x = (a.Options.DayWidth * i);
// Draw month
if (date.getDate() == 1) {
const size = a.MeasureText(a.Options.DateFont, "#");
const monthPoint = {
X: (x + 2),
Y: Math.half(a.Options.HeaderRow.Height[0] - size.H)
};
a.DrawText(monthPoint.X, monthPoint.Y, date.toCString("MMMM"), a.Options.DateFont, a.Options.DateForeColour);
}
// Draw day
const dateRectangle = {
X: x,
Y: a.Options.HeaderRow.Height[0],
W: a.Options.DayWidth - a.Options.BorderWidth,
H: (a.Options.HeaderRow.Height[1] - a.Options.BorderWidth)
};
a.FillText(dateRectangle, date.getDate(), a.Options.DateFont, a.Options.DateForeColour);
if (a.Debug) {
a.DrawRectangle(dateRectangle, "red", {});
}
}
}
#drawColumnLayout() {
const a = this;
const height = a.Height;
const displayDays = a.Duration + 2;
let startDate = new Date(a.StartDate);
startDate.addDays(-1);
// Write dates
for (let i=0; i<displayDays; i++) {
const date = Date.addDays(startDate, i);
const x = (a.Options.DayWidth * i);
const dateRectangle = {
X: x,
Y: a.HeaderHeight,
W: a.Options.DayWidth - a.Options.BorderWidth,
H: height - a.Options.BorderWidth
};
// Fill background for Saturday and Sunday
if (date.getDay() == 6) {
a.DrawRectangle(dateRectangle, a.Options.Column.SatColour, { FillColour: a.Options.Column.SatColour });
} else if (date.getDay() == 0) {
a.DrawRectangle(dateRectangle, a.Options.Column.SunColour, { FillColour: a.Options.Column.SunColour });
}
// Draw vertical date lines
if (a.StartOfWeek == date.getDay()) {
if (a.Options.ShowStartDayOfWeekLine) {
a.DrawVerticalLine(dateRectangle.X, a.HeaderHeight, dateRectangle.H, a.Options.BorderColour, {});
}
} else {
if (a.Options.ShowDateLines) {
a.DrawVerticalLine(dateRectangle.X, a.HeaderHeight, dateRectangle.H, a.Options.BorderColour, { LineDash: a.Options.BorderDashPattern });
}
}
}
}
#drawRowLayout() {
const a = this;
for (let i=0; i<a.RowCount; i++) {
const rowRectangle = {
X: 0,
Y: (a.HeaderHeight + (a.Options.Row.Height * i)),
W: a.Width,
H: (a.Options.Row.Height - a.Options.BorderWidth)
};
if (a.Options.ShowRowStripes) {
if ((i % 2) == 1) {
a.DrawRectangle(rowRectangle, a.Options.Row.BackColour, { FillColour: a.Options.Row.BackColour });
}
}
if (a.Options.ShowRowLines) {
a.DrawHorizontalLine(0, (rowRectangle.Y + a.Options.Row.Height), rowRectangle.W, a.Options.BorderColour, { LineDash: a.Options.BorderDashPattern });
}
}
}
#drawHorizontalLine(y) {
const a = this;
const width = a.Width;
a.DrawHorizontalLine(0, y, width, this.Options.BorderColour);
}
}
export default BackgroundCanvas;

View File

@ -0,0 +1,80 @@
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();
}
});
a.el.addEventListener('mouseout', function (e) {
a.XPos = -1;
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;

View File

@ -0,0 +1,157 @@
import Canvas from '../references/canvas.js';
class ForegroundCanvas extends Canvas {
Options = null;
Debug = false;
StartDate = new Date();
Tasks = [];
constructor(el) {
super(el);
}
get HeaderHeight() {
const a = this;
return a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1];
}
Load(startDate, tasks) {
const a = this;
a.StartDate = new Date(startDate);
a.Tasks = tasks;
}
Invalidate() {
const a = this;
if (a.Options == null) {
return;
}
if (a.StartDate == null) {
return;
}
a.#drawTasks();
a.#drawConnectorLines();
}
#drawTasks() {
const a = this;
for (let i=0; i<a.Tasks.length; i++) {
let style = a.Options.Row.Task;
if (a.Tasks[i].IsCollated == true) {
style = a.Options.Row.CollatedTask;
} else if (a.Tasks[i].CollatedTaskID != null) {
style = a.Options.Row.ChildTask;
} else if (a.Tasks[i].PredecessorTaskID == null) {
style = a.Options.Row.OrphanTask;
}
const rectangle = a.#getTaskRectangle(a.Tasks[i], style);
if (a.Tasks[i].Duration <= 0) {
a.DrawDiamond(rectangle, style.BorderColour, { FillColour: style.FillColour });
} else {
a.DrawRectangle(rectangle, style.BorderColour, { FillColour: style.FillColour });
}
}
}
#drawConnectorLines() {
const a = this;
for (let i=0; i<a.Tasks.length; i++) {
if (a.Tasks[i].PredecessorTaskID == null) {
continue;
}
if (a.Tasks[i].PredecessorTaskID == null) {
continue;
}
const style = ((a.Tasks[i].IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
const rectangle = a.#getTaskRectangle(a.Tasks[i], style);
const paddingX = (a.Options.Row.CollatedTask.Height + a.Options.BorderWidth);
const offsetX = (rectangle.X + paddingX);
const predecessorTask = a.Tasks.first("ID", a.Tasks[i].PredecessorTaskID);
if (predecessorTask == null) {
continue;
}
const predecessorStyle = ((predecessorTask.IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
const predecessorRectangle = a.#getTaskRectangle(predecessorTask, predecessorStyle);
let points = [];
points.push({ X: (predecessorRectangle.X + predecessorRectangle.W), Y: (predecessorRectangle.Y + Math.half(predecessorRectangle.H)) });
points.push({ X: offsetX, Y: (predecessorRectangle.Y + Math.half(predecessorRectangle.H)) });
points.push({ X: offsetX, Y: (rectangle.Y - paddingX) });
points.push({ X: offsetX, Y: rectangle.Y });
a.DrawLines(points, a.Options.Line.Colour, { LineWidth: a.Options.Line.Width });
const arrowRectangle = {
X: (offsetX - Math.half(a.Options.Line.ArrowSize)),
Y: (rectangle.Y - a.Options.Line.ArrowSize),
W: a.Options.Line.ArrowSize,
H: a.Options.Line.ArrowSize
}
a.DrawArrowS(arrowRectangle, a.Options.Line.Colour, { FillColour: a.Options.Line.Colour });
}
}
#getTaskRectangle(task, style) {
const a = this;
const i = (task.Order - 1);
const rowRectangle = {
X: 0,
Y: (a.HeaderHeight + (a.Options.Row.Height * i)),
W: a.Width,
H: (a.Options.Row.Height - a.Options.BorderWidth)
};
if (a.Debug) {
a.DrawRectangle(rowRectangle, "red", {});
}
if (task.Duration <= 0) {
let result = {
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, task.StartDate) + 1)),
Y: (a.HeaderHeight + (a.Options.Row.Height * i) + Math.half(rowRectangle.H - style.Height)),
W: style.Height,
H: style.Height
};
result.X += Math.half(a.Options.DayWidth - (style.Height + a.Options.BorderWidth));
return result;
} else {
return {
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, task.StartDate) + 1)),
Y: (a.HeaderHeight + (a.Options.Row.Height * i) + Math.half(rowRectangle.H - style.Height)),
W: (a.Options.DayWidth * task.Duration),
H: style.Height
};
}
}
}
export default ForegroundCanvas;

View File

@ -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;

View File

@ -1,20 +1,29 @@
import Canvas from '../references/canvas.js'; 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';
import FlourishCanvas from './flourish-canvas.js';
import './project.scss'; import './project.scss';
class GanttChart { class GanttChart {
Options = null;
CanvasContainer = null;
#debug = false;
Tasks = [];
StartDate = null;
Duration = 30;
StartOfWeek = 1;
constructor(el, options) { constructor(el, options) {
const a = this; const a = this;
a.Canvas = new Canvas(el); a.Options = Object.assign(new GanttChartOptions(), options);
a.Options = Object.assign(a.DefaultOptions, options); a.CanvasContainer = new CanvasContainer(el);
a.Debug = false;
a.Tasks = [];
a.StartDate = null;
a.Duration = 30;
a.StartOfWeek = 1;
a.#initialiseComponents(); a.#initialiseComponents();
} }
@ -22,92 +31,37 @@ class GanttChart {
#initialiseComponents() { #initialiseComponents() {
const a = this; const a = this;
if (!a.Canvas.el.classList.contains("literyzjs-project")) { // Add background canvas layer
a.Canvas.el.classList.add("literyzjs-project"); const layer1 = a.CanvasContainer.AddLayer();
const backCanvasLayer = new BackgroundCanvas(layer1);
backCanvasLayer.Options = a.Options;
a.CanvasContainer.Layer.push(backCanvasLayer);
// Add foreground canvas layer
const layer2 = a.CanvasContainer.AddLayer();
const foreCanvasLayer = new ForegroundCanvas(layer2);
foreCanvasLayer.Options = a.Options;
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");
} }
if (a.Canvas.FlowContainer.classList.contains("border")) {
a.Canvas.FlowContainer.classList.remove("border");
}
if (!a.Canvas.FlowContainer.classList.contains("gantt-chart")) {
a.Canvas.FlowContainer.classList.add("gantt-chart");
}
}
get DefaultOptions() {
return {
DayWidth: 24,
HeaderRow: {
Height: [ 20, 21 ]
},
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
},
DateFont: "7pt sans-serif",
DateForeColour: "#636363",
BorderWidth: 1,
BorderColour: "#B8B8B8",
BorderDashPattern: [1, 1],
MinimumRowCount: 6,
ShowDateLines: true,
ShowStartDayOfWeekLine: true,
ShowRowLines: true,
ShowRowStripes: true
};
}
get HeaderRow1Rectangle() {
return {
X: 0,
Y: 0,
W: this.Canvas.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.Canvas.Width,
H: (this.Options.HeaderRow.Height[1] - this.Options.BorderWidth)
};
}
get HeaderHeight() { get HeaderHeight() {
const a = this; const a = this;
@ -115,43 +69,64 @@ class GanttChart {
return a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1]; return a.Options.HeaderRow.Height[0] + a.Options.HeaderRow.Height[1];
} }
get Debug() {
return this.#debug;
}
set Debug(value) {
const a = this;
a.#debug = value;
a.CanvasContainer.Layer.forEach(e => {
if (typeof(e.Debug) == "undefined") {
return;
}
e.Debug = value;
});
}
Clear() { Clear() {
const a = this; const a = this;
a.Canvas.Clear(); a.CanvasContainer.Clear();
a.StartDate = new Date(); a.StartDate = new Date();
a.Duration = 30; a.Duration = 30;
a.StartOfWeek = 1; // 1 = Monday a.StartOfWeek = 1; // 1 = Monday
a.Tasks = []; 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.CanvasContainer.Layer[2].Load();
a.Invalidate(); a.Invalidate();
} }
Invalidate() { Invalidate() {
const a = this; const a = this;
a.Canvas.Clear(); a.CanvasContainer.Clear();
const width = ((a.Duration + 2) * a.Options.DayWidth); const width = ((a.Duration + 2) * a.Options.DayWidth);
const height = (Math.max(a.Tasks.length, a.Options.MinimumRowCount) * a.Options.Row.Height) + a.HeaderHeight; 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.CanvasContainer.Invalidate();
a.#drawChartHeader();
a.#drawColumnLayout();
a.#drawRowLayout();
a.#drawTasks();
a.#drawConnectorLines();
} }
Load(project) { Load(project) {
const a = this; const a = this;
a.Canvas.Clear(); a.CanvasContainer.Clear();
if (project == null) { if (project == null) {
return; return;
@ -162,244 +137,14 @@ class GanttChart {
a.StartOfWeek = project.Project.StartOfWeek; a.StartOfWeek = project.Project.StartOfWeek;
a.Tasks = project.ExportTasks(); 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.CanvasContainer.Layer[2].Load();
a.Invalidate(); 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<displayDays; i++) {
a.Canvas.DrawVerticalLine((a.Options.DayWidth * i), (a.Options.HeaderRow.Height[0] + a.Options.BorderWidth), (a.Options.HeaderRow.Height[1] - a.Options.BorderWidth), a.Options.BorderColour, {});
}
// Draw horizontal line under dates
a.#drawHorizontalLine(a.HeaderHeight);
// Write dates
for (let i=0; i<displayDays; i++) {
const date = Date.addDays(startDate, i);
const x = (a.Options.DayWidth * i);
// Draw month
if (date.getDate() == 1) {
const size = a.Canvas.MeasureText(a.Options.DateFont, "#");
const monthPoint = {
X: (x + 2),
Y: Math.half(a.Options.HeaderRow.Height[0] - size.H)
};
a.Canvas.DrawText(monthPoint.X, monthPoint.Y, date.toCString("MMMM"), a.Options.DateFont, a.Options.DateForeColour);
}
// Draw day
const dateRectangle = {
X: x,
Y: a.Options.HeaderRow.Height[0],
W: a.Options.DayWidth - a.Options.BorderWidth,
H: (a.Options.HeaderRow.Height[1] - a.Options.BorderWidth)
};
a.Canvas.FillText(dateRectangle, date.getDate(), a.Options.DateFont, a.Options.DateForeColour);
if (a.Debug) {
a.Canvas.DrawRectangle(dateRectangle, "red", {});
}
}
}
#drawColumnLayout() {
const a = this;
const height = a.Canvas.Height;
const displayDays = a.Duration + 2;
let startDate = new Date(a.StartDate);
startDate.addDays(-1);
// Write dates
for (let i=0; i<displayDays; i++) {
const date = Date.addDays(startDate, i);
const x = (a.Options.DayWidth * i);
const dateRectangle = {
X: x,
Y: a.HeaderHeight,
W: a.Options.DayWidth - a.Options.BorderWidth,
H: height - a.Options.BorderWidth
};
// Fill background for Saturday and Sunday
if (date.getDay() == 6) {
a.Canvas.DrawRectangle(dateRectangle, a.Options.Column.SatColour, { FillColour: a.Options.Column.SatColour });
} else if (date.getDay() == 0) {
a.Canvas.DrawRectangle(dateRectangle, a.Options.Column.SunColour, { FillColour: a.Options.Column.SunColour });
}
// Draw vertical date lines
if (a.StartOfWeek == date.getDay()) {
if (a.Options.ShowStartDayOfWeekLine) {
a.Canvas.DrawVerticalLine(dateRectangle.X, a.HeaderHeight, dateRectangle.H, a.Options.BorderColour, {});
}
} else {
if (a.Options.ShowDateLines) {
a.Canvas.DrawVerticalLine(dateRectangle.X, a.HeaderHeight, dateRectangle.H, a.Options.BorderColour, { LineDash: a.Options.BorderDashPattern });
}
}
}
}
#drawRowLayout() {
const a = this;
for (let i=0; i<a.Tasks.length; i++) {
const rowRectangle = {
X: 0,
Y: (a.HeaderHeight + (a.Options.Row.Height * i)),
W: a.Canvas.Width,
H: (a.Options.Row.Height - a.Options.BorderWidth)
};
if (a.Options.ShowRowStripes) {
if ((i % 2) == 1) {
a.Canvas.DrawRectangle(rowRectangle, a.Options.Row.BackColour, { FillColour: a.Options.Row.BackColour });
}
}
if (a.Options.ShowRowLines) {
a.Canvas.DrawHorizontalLine(0, (rowRectangle.Y + a.Options.Row.Height), rowRectangle.W, a.Options.BorderColour, { LineDash: a.Options.BorderDashPattern });
}
}
}
#drawTasks() {
const a = this;
for (let i=0; i<a.Tasks.length; i++) {
let style = a.Options.Row.Task;
if (a.Tasks[i].IsCollated == true) {
style = a.Options.Row.CollatedTask;
} else if (a.Tasks[i].CollatedTaskID != null) {
style = a.Options.Row.ChildTask;
} else if (a.Tasks[i].PredecessorTaskID == null) {
style = a.Options.Row.OrphanTask;
}
const rectangle = a.#getTaskRectangle(a.Tasks[i], style);
if (a.Tasks[i].Duration <= 0) {
a.Canvas.DrawDiamond(rectangle, style.BorderColour, { FillColour: style.FillColour });
} else {
a.Canvas.DrawRectangle(rectangle, style.BorderColour, { FillColour: style.FillColour });
}
}
}
#drawConnectorLines() {
const a = this;
for (let i=0; i<a.Tasks.length; i++) {
if (a.Tasks[i].PredecessorTaskID == null) {
continue;
}
if (a.Tasks[i].PredecessorTaskID == null) {
continue;
}
const style = ((a.Tasks[i].IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
const rectangle = a.#getTaskRectangle(a.Tasks[i], style);
const paddingX = (a.Options.Row.CollatedTask.Height + a.Options.BorderWidth);
const offsetX = (rectangle.X + paddingX);
const predecessorTask = a.Tasks.first("ID", a.Tasks[i].PredecessorTaskID);
if (predecessorTask == null) {
continue;
}
const predecessorStyle = ((predecessorTask.IsCollated == true) ? a.Options.Row.CollatedTask : a.Options.Row.Task);
const predecessorRectangle = a.#getTaskRectangle(predecessorTask, predecessorStyle);
let points = [];
points.push({ X: (predecessorRectangle.X + predecessorRectangle.W), Y: (predecessorRectangle.Y + Math.half(predecessorRectangle.H)) });
points.push({ X: offsetX, Y: (predecessorRectangle.Y + Math.half(predecessorRectangle.H)) });
points.push({ X: offsetX, Y: (rectangle.Y - paddingX) });
points.push({ X: offsetX, Y: rectangle.Y });
a.Canvas.DrawLines(points, a.Options.Line.Colour, { LineWidth: a.Options.Line.Width });
const arrowRectangle = {
X: (offsetX - Math.half(a.Options.Line.ArrowSize)),
Y: (rectangle.Y - a.Options.Line.ArrowSize),
W: a.Options.Line.ArrowSize,
H: a.Options.Line.ArrowSize
}
a.Canvas.DrawArrowS(arrowRectangle, a.Options.Line.Colour, { FillColour: a.Options.Line.Colour });
}
}
#drawHorizontalLine(y) {
const a = this;
const width = a.Canvas.Width;
a.Canvas.DrawHorizontalLine(0, y, width, this.Options.BorderColour);
}
#getTaskRectangle(task, style) {
const a = this;
const i = (task.Order - 1);
const rowRectangle = {
X: 0,
Y: (a.HeaderHeight + (a.Options.Row.Height * i)),
W: a.Canvas.Width,
H: (a.Options.Row.Height - a.Options.BorderWidth)
};
if (a.Debug) {
a.Canvas.DrawRectangle(rowRectangle, "red", {});
}
if (task.Duration <= 0) {
let result = {
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, task.StartDate) + 1)),
Y: (a.HeaderHeight + (a.Options.Row.Height * i) + Math.half(rowRectangle.H - style.Height)),
W: style.Height,
H: style.Height
};
result.X += Math.half(a.Options.DayWidth - (style.Height + a.Options.BorderWidth));
return result;
} else {
return {
X: (a.Options.DayWidth * (Date.diffDays(a.StartDate, task.StartDate) + 1)),
Y: (a.HeaderHeight + (a.Options.Row.Height * i) + Math.half(rowRectangle.H - style.Height)),
W: (a.Options.DayWidth * task.Duration),
H: style.Height
};
}
}
} }

View File

@ -9,7 +9,7 @@ class Project {
const a = this; const a = this;
const _options = Object.assign(a.DefaultOptions, options); const _options = Object.assign(a.DefaultOptions, options);
a.Debug = true; a.Debug = false;
a.Project = _options; a.Project = _options;
a.Tasks = []; a.Tasks = [];
} }

View File

@ -117,17 +117,25 @@
} }
.gantt-chart { }
.gantt-chart {
border-color: #B8B8B8; border-color: #B8B8B8;
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
overflow: hidden; overflow: hidden;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: #CDCDCD #F0F0F0; scrollbar-color: #CDCDCD #F0F0F0;
}
.gantt-chart.scroll-x {
&.scroll-x {
overflow-x: scroll !important; overflow-x: scroll !important;
} }
canvas {
display: block;
position: relative;
}
} }

View File

@ -0,0 +1,229 @@
class CanvasContainer {
constructor(el) {
const a = this;
a.el = el;
a.Layer = [];
a.BorderWidth = 2;
a.ScrollbarWidth = 11;
a.Padding = {
Top: 0,
Right: 0,
Bottom: 0,
Left: 0
};
a.EnableDock = true;
a.#initialiseComponents();
}
#initialiseComponents() {
const a = this;
a.ClearLayers();
// a.Invalidate();
// a.Clear();
}
get FlowContainer() {
return this.el.getElementsByTagName("div")[0];
}
get Height() {
const a = this;
if (a.Layer.length <= 0) {
return 0;
}
return Document.getHeight(a.Layer[0].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.CanvasContext != null) {
// a.CanvasContext.canvas.height = value;
// }
a.Layer.forEach((e, 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 = "<div class='border'></div>";
}
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();
}
});
let offsetTop = 0;
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);
offsetTop += h;
a.Layer[i].el.style.top = "-" + offsetTop + "px";
});
}
}
export default CanvasContainer;

View File

@ -1,19 +1,10 @@
class Canvas { class Canvas {
constructor(el) { constructor(el) {
const a = this; const a = this;
a.el = el; a.el = el;
a.BorderWidth = 2; a.BorderWidth = 2;
a.ScrollbarWidth = 11;
a.Padding = {
Top: 0,
Right: 0,
Bottom: 0,
Left: 0
};
a.EnableDock = true;
a.#initialiseComponents(); a.#initialiseComponents();
} }
@ -25,48 +16,30 @@ class Canvas {
return; return;
} }
a.el.innerHTML = "<div class='border'><canvas></canvas></div>";
a.Invalidate();
a.Clear(); a.Clear();
a.Invalidate();
} }
get FlowContainer() {
return this.el.getElementsByTagName("div")[0];
}
get CanvasContainer() {
return this.FlowContainer.getElementsByTagName("canvas")[0];
}
get CanvasContext() { get CanvasContext() {
return this.CanvasContainer.getContext("2d"); return this.el.getContext("2d");
} }
get Height() { get Height() {
const a = this; const a = this;
if (a.CanvasContainer == null) { if (a.el == null) {
return 0; return 0;
} }
return a.#getHeight(a.CanvasContainer); return Document.getHeight(a.el);
} }
set Height(value) { set Height(value) {
const a = this; const a = this;
// if (a.el != null) { if (a.el != null) {
// a.el.style.height = value + "px"; 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.CanvasContext != null) { if (a.CanvasContext != null) {
@ -77,26 +50,18 @@ class Canvas {
get Width() { get Width() {
const a = this; const a = this;
if (a.CanvasContainer == null) { if (a.el == null) {
return 0; return 0;
} }
return a.#getWidth(a.CanvasContainer); return Document.getWidth(a.el);
} }
set Width(value) { set Width(value) {
const a = this; const a = this;
// if (a.el != null) { if (a.el != null) {
// a.el.style.width = value + "px"; 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) { if (a.CanvasContext != null) {
@ -116,17 +81,6 @@ class Canvas {
this.Height = value.H; 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() { get Rectangle() {
const a = this; const a = this;
@ -466,23 +420,6 @@ class Canvas {
a.CanvasContext.fillText(text, x, y); 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) { MeasureText(font, value) {
const a = this; 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);
}
} }

File diff suppressed because one or more lines are too long