Reputation: 77
I'm trying to work on Google Gantt charts and it is working quite well overall. However, some customisation requirements have left me scratching my head.
I want the task labels to have the task names either inside the bars or near the bar depending upon the width, position of the bar. Need to remove the task names from the left section of the y-axis
Also, once the chart becomes bigger, the time range (x-axis) would not be visible as it is placed in the bottom, therefore it would be great if it can be placed at the top.
Is there any way to achieve this customisation ?
Kindly check code snippets mentioned below for reference.
import { Component, OnInit } from '@angular/core';
import { nightcycleDependencies } from './nightcycleData';
declare var google: any;
declare var $ :any;
@Component({
selector: 'app-chart',
templateUrl: './chart.component.html',
styleUrls: ['./chart.component.css']
})
export class ChartComponent implements OnInit {
public allTasks: any[] = [];
public nightcycleDependencies = nightcycleDependencies;
constructor() {
}
ngOnInit() {
this.renderChartForAll();
}
// Render Chart for Summarized View
renderChartForAll() {
google.charts.setOnLoadCallback(() => this.formatDataForAll());
}
// Data format for Summarized View
formatDataForAll() {
this.allTasks = [];
var resource = 'Job Cycles';
const today = new Date();
const start_day = today.getDate();
const end_day = today.getDate();
const month = today.getMonth() + 1;
const year = today.getFullYear();
nightcycleDependencies.forEach((dependency) => {
var finalStart = 9999999999999;
var finalEnd = 0;
var taskId = dependency.source.split(" ").join('');
var taskName = dependency.source;
var dependencies = dependency.parents ? dependency.parents.toString().split(" ").join("") : "null";
dependency.ids.forEach((job) => {
// Date handling
var startTime = new Date(year + '-' + month + '-' + start_day + ' ' + "12:00").getTime()
var startDate = new Date(year + '-' + month + '-' + start_day + ' ' + job.start).getTime()
var endDate = new Date(year + '-' + month + '-' + end_day + ' ' + job.end).getTime()
if (startTime > startDate) {
startDate = new Date(year + '-' + month + '-' + (start_day + 1) + ' ' + job.end).getTime()
endDate = new Date(year + '-' + month + '-' + (end_day + 1) + ' ' + job.end).getTime()
} else if (endDate < startDate) {
endDate = new Date(year + '-' + month + '-' + (end_day + 1) + ' ' + job.end).getTime()
}
if (finalStart > startDate) {
finalStart = startDate
}
if (finalEnd < endDate) {
finalEnd = endDate
}
});
var duration = finalEnd - finalStart
this.allTasks.push([taskId, taskName, taskName, new Date(finalStart), new Date(finalEnd), duration, 100, dependencies])
});
this.drawChart(this.allTasks, '');
}
// Render Chart for Selected Cycle
getCycleBasedData(source) {
google.charts.setOnLoadCallback(() => this.formatCycleBasedData(source));
}
// Data format for Selected Cycle
formatCycleBasedData(source) {
this.allTasks = [];
var resource = source;
var dependencies = [];
var chartSource = [];
var jobsFromOtherSources = [];
// Set The source to loop over
nightcycleDependencies.forEach(dependency => {
if (dependency.source === source) {
chartSource = dependency.ids;
}
});
chartSource.forEach((dependency) => {
var taskId = dependency.id.split(" ").join('');
var taskName = dependency.id;
dependencies = [];
// Date handling
var date = this.formatDate(dependency.start, dependency.end);
// Find out dependencies
dependency.parent.forEach((parent) => {
dependencies.push(parent.id.split(' ').join(''))
})
var finalDependencies = dependencies.toString();
// Get all Parent jobs and push them to task list
jobsFromOtherSources = this.getAllParentJobs(taskName, resource);
jobsFromOtherSources.forEach((otherJob) => {
var jobId = otherJob.id.split(" ").join('');
var jobName = otherJob.id;
var jobSource = otherJob.source;
var jobDate = this.formatDate(otherJob.start, otherJob.end);
this.allTasks.push([jobId, jobName, jobSource, new Date(jobDate[0]), new Date(jobDate[0] + 800000), (jobDate[0] + 800000 - jobDate[0]), 100, null])
})
// Get all Children jobs
// this.getAllChildJobs(taskName, resource);
// Push to the task list
this.allTasks.push([taskId, taskName, resource, new Date(date[0]), new Date(date[1]), (date[1] - date[0]), 100, finalDependencies])
});
this.drawChart(this.allTasks, source);
}
// Function to handle date
formatDate(start, end) {
const today = new Date();
var startTime = 0;
var startDate = 0;
var endDate = 0;
const start_day = today.getDate();
const end_day = today.getDate();
const month = today.getMonth() + 1;
const year = today.getFullYear();
startTime = new Date(year + '-' + month + '-' + start_day + ' ' + "12:00").getTime()
startDate = new Date(year + '-' + month + '-' + start_day + ' ' + start).getTime()
endDate = new Date(year + '-' + month + '-' + end_day + ' ' + end).getTime()
if (startTime > startDate) {
startDate = new Date(year + '-' + month + '-' + (start_day + 1) + ' ' + start).getTime()
endDate = new Date(year + '-' + month + '-' + (end_day + 1) + ' ' + end).getTime()
} else if (endDate < startDate) {
endDate = new Date(year + '-' + month + '-' + (end_day + 1) + ' ' + end).getTime()
}
return [startDate, endDate]
}
// Get all Parent Jobs
getAllParentJobs(name, source) {
var job = {};
var jobSource = '';
var jobsFromOtherSources = [];
nightcycleDependencies.forEach((dependency) => {
if (dependency.source != source) {
jobSource = dependency.source;
dependency.ids.forEach((job) => {
job = job;
job.source = jobSource
job.children.forEach((child) => {
if (child.id === name) {
jobsFromOtherSources.push(job);
}
});
});
}
});
return jobsFromOtherSources;
}
// Get all children jobs
// getAllChildJobs(name, source) {
// }
// Chart configuration
drawChart(allTasks, source) {
var nightcycleData = new google.visualization.DataTable();
nightcycleData.addColumn('string', 'Task ID');
nightcycleData.addColumn('string', 'Task Name');
nightcycleData.addColumn('string', 'Resource');
nightcycleData.addColumn('date', 'Start');
nightcycleData.addColumn('date', 'End');
nightcycleData.addColumn('number', 'Duration');
nightcycleData.addColumn('number', 'Percent Complete');
nightcycleData.addColumn('string', 'Dependencies');
nightcycleData.addRows(this.allTasks);
var trackHeight = 50;
var options = {
height: nightcycleData.getNumberOfRows() * trackHeight + 200,
gantt: {
labelStyle: {
fontName: ["RobotoCondensedRegular"],
fontSize: 15
},
textPosition: 'none',
innerGridTrack: { fill: '#fff3e0' },
innerGridDarkTrack: { fill: '#ffcc80' },
trackHeight: trackHeight
}
};
var chart = new google.visualization.Gantt(source ? document.getElementById(source.split(' ').join('')) : document.getElementById('chart_div'));
chart.draw(nightcycleData, options);
// Event listener which will fire on bar selection
google.visualization.events.addListener(chart, 'select', () => this.getSelectedBarInfo(nightcycleData, chart));
}
// Change chart tab programmatically
getSelectedBarInfo(nightcycleData, chart) {
var selection = chart.getSelection();
if (selection.length) {
var cycle = nightcycleData.getValue(selection[0].row, 2);
this.getCycleBasedData(cycle);
var cycleId = cycle.split(" ").join("");
document.querySelector('.tab-pane.fade.active.show').classList.remove('active', 'show');
document.querySelector('.nav-link.active').classList.remove('active');
document.getElementById(cycleId).classList.add('active', 'show');
document.getElementById('id'+cycleId).classList.add('active', 'show');
}
}
//For Responsiveness on resizing
onResize(event) {
this.drawChart(this.allTasks, event.target.id);
}
}
.btn-holders{
padding:10px;
}
.chart-tabs{
padding: 20px 0px;
}
.chart-btn{
margin: 5px;
}
.chart-item{
float:left;
padding:20px 20px 20px 0px;
list-style: none;
}
.chart-list{
padding: 0px;
}
.chart-anchor{
background-color: #c0b5d0;
padding: 20px;
border-radius: 10px;
color: #0c5460;
}
.chart-anchor.active {
background-color: dodgerblue;
color:#fff;
}
.tab-content>.tab-pane {
height: 1px;
overflow: hidden;
display: block;
visibility: hidden;
}
.tab-content>.active {
height: auto;
overflow: hidden;
visibility: visible;
}
<div class="container-fluid">
<!-- Tabs -->
<div class="chart-tabs">
<ul class="nav nav-pills" role="tablist">
<li class="nav-item">
<a [attr.id]="'idall'" class="nav-link active" data-toggle="pill" href="#all" role="tab">All</a>
</li>
<li class="nav-item" *ngFor="let dependency of nightcycleDependencies" (click)="getCycleBasedData(dependency.source)">
<a [attr.id]="'id'+dependency.source.split(' ').join('')" class="nav-link" data-toggle="pill" [attr.href]="'#'+dependency.source.split(' ').join('')" role="tab">{{dependency.source}}</a>
</li>
</ul>
</div>
<!-- Tab Content -->
<div class="tab-content">
<div id="all" class="tab-pane fade in active show" role="tabpanel">
<div id="chart_div" style="width:100%;" (window:resize)="onResize($event)"></div>
</div>
<div id="DailyProcessing" style="width:100%;" class="tab-pane fade" role="tabpanel">
<div (window:resize)="onResize($event)"></div>
</div>
<div id="TradeMgmtandProcessing" style="width:100%;" class="tab-pane fade" role="tabpanel">
<div (window:resize)="onResize($event)"></div>
</div>
<div id="DailyAnalyticsCycle" style="width:100%;" class="tab-pane fade" role="tabpanel">
<div (window:resize)="onResize($event)"></div>
</div>
<div id="EuropeCycle" style="width:100%;" class="tab-pane fade" role="tabpanel">
<div (window:resize)="onResize($event)"></div>
</div>
<div id="USCycle" style="width:100%;" class="tab-pane fade" role="tabpanel">
<div (window:resize)="onResize($event)"></div>
</div>
</div>
</div>
Upvotes: 0
Views: 2542
Reputation: 77
Nevermind, I got the answer with a little bit of DOM Manipulation:
Add an event listener, which should fire after the chart is ready
google.visualization.events.addListener(chart, 'ready', () => this.addLabelText(chartId, this.allTasks));
The body of the function should look something like this,
addLabelText(chartId, allTasks) {
var toContainer = $('#' + chartId + ' > div > div');
$("#" + chartId + " g:eq(5) rect").each(function ($index) {
if($(this).attr('width') < (allTasks[$index][1].length * 7)) {
if((Number($(this).attr('x')) + Number($(this).attr('width')) + 100) > document.body.clientWidth) {
toContainer.append("<div style='top:" + $(this).attr('y') + "px; left: " + (Number($(this).attr('x')) - (allTasks[$index][1].length * 7)) +
"px; text-align: left;position:absolute;line-height:2; padding-left: 5px; color:#111; font-size:14px;'>" + allTasks[$index][1] + "</div>");
} else {
toContainer.append("<div style='top:" + $(this).attr('y') + "px; left: " + (Number($(this).attr('x')) + Number($(this).attr('width'))) +
"px; text-align: left;position:absolute;line-height:2; padding-left: 5px; color:#111; font-size:14px;'>" + allTasks[$index][1] + "</div>");
}
} else {
toContainer.append("<div style='top:" + $(this).attr('y') + "px; left: " + Number($(this).attr('x')) +
"px; text-align: left;position:absolute;line-height:2; padding-left: 5px; color:#fff; font-size:14px;'>" + allTasks[$index][1] + "</div>");
}
});
}
With a little bit of customization according to your needs, it should work fine.
Upvotes: 1