Sanjay79
Sanjay79

Reputation: 77

Google Gantt charts axis customization [Labels and Date/Time]

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.

Gantt chart

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

Answers (1)

Sanjay79
Sanjay79

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

Related Questions