I'm not sure why it never sent my message with my code attached. I have attached it here. Like I said though, I got it figured out. Column charts cannot have the number of columns hidden. They must be psuedo hidden. I have attached my code here in case you still want to take a look! Thanks for being willing to help. My faith in humanity was boosted because of you, Alvin. <3
var params = new URLSearchParams(window.location.search);
var data = null;
var options = null;
var proxyTable = null;
let proxyDataTable = null;
var chart0 = null;
var filters = [];
//variables for showing/hiding columns
var activeColumns = [];
let chartSelectListener = false; //prevent duplicate listeners from being added
$(function() {
google.charts.load('current', {'packages':['corechart','controls']});
google.charts.setOnLoadCallback( function() {
getData()
.done(function (response) { //runs once response is returned from getData()
let processedData = processData(response); //add new rows (Fall, Spring, Academic Year)
drawChart(processedData);
})
.fail(function(jqXHR, textStatus, errorThrown) {
console.error("Error fetching data:", textStatus, errorThrown);
$('#loading').text("Error loading data."); // Update loading message on error
})
.always(function() {
$('#loading').remove(); // Remove loading indicator once done (success or fail)
});
});
$('#contents').append('<div style="display: none;" id="proxyTable"></div>'); //add the proxyTable to the DOM because it is necessary for Google Charts to operate properly
//make the chart auto resize based on window size
$(window).resize(function() {
if(this.resizeTO) clearTimeout(this.resizeTO);
this.resizeTO = setTimeout(function() {
$(this).trigger('resizeEnd');
}, 500);
});
//redraw graph when window resize is completed
$(window).on('resizeEnd', function(e) {
chart0.draw();
});
});
function getData() {
$('#ef_left').append('<div id="loading" style="width: 100%; text-align: center;">Loading</div>');
const baseDataURL = window.location.pathname.replace(/index.php.*$/, 'index.php/json/');
let data = $.getJSON(baseDataURL + '/data', $.param({ 'func': 'classsections', 'json': '' }));
return data;
}
//add a column for the Period(Academic Year, Fall, or Spring) and Year for filtering and remove null/empty values for any columns with number as the type
function processData(rawData) {
const transformedData = {
cols: rawData.cols.filter(col => col.label !== "Academic Year"),
rows: []
};
//add the two columns to the beginning
transformedData.cols.unshift(
{ id: "", label: "Period", pattern: "", type: "string" },
{ id: "", label: "Year", pattern: "", type: "string" }
);
const academicYearIndex = rawData.cols.findIndex(col => col.label === "Academic Year");
const termIndex = rawData.cols.findIndex(col => col.label === "Term");
rawData.rows.forEach(row => {
const term = row.c[termIndex]?.v || "";
const academicYear = printAYperiod(row.c[academicYearIndex]?.v || "");
const baseRow = row.c.filter((_, idx) => idx !== academicYearIndex);
transformedData.rows.push({
c: [
{ v: "Academic Year", f: null },
{ v: academicYear, f: null },
...baseRow
]
});
if (term.endsWith("70")) {
transformedData.rows.push({
c: [
{ v: "Fall", f: null },
{ v: term.substring(0, 4), f: null },
...baseRow
]
});
}
if (term.endsWith("20")) {
transformedData.rows.push({
c: [
{ v: "Spring", f: null },
{ v: term.substring(0, 4), f: null },
...baseRow
]
});
}
});
//after manipulating the data, make sure to resolve any null values for numbers
//identify numeric column indexes
const numericIndexes = transformedData.cols
.map((col, idx) => col.type === "number" ? idx : -1)
.filter(idx => idx !== -1);
//iterate over all rows
transformedData.rows.forEach(row => {
numericIndexes.forEach(idx => {
const cell = row.c[idx];
const value = cell?.v;
//if cell missing, null, empty string, or invalid — set to 0
if (!cell || cell.v === null || cell.v === "" || isNaN(cell.v)) {
row.c[idx] = { v: 0, f: null };
} else {
//force all to type number just in case any were saved as strings
row.c[idx].v = Number(value);
}
});
});
return transformedData;
}
function drawChart(d) {
data = new google.visualization.DataTable(d); //data is transformed to a Google dataTable
//desired filters (must be a column in the returned data)
const filterList = [
{ name: 'Period', allowMultiple: false, allowNone: false },
{ name: 'Campus', allowMultiple: true, allowNone: true },
{ name: 'College', allowMultiple: true, allowNone: true },
{ name: 'Department', allowMultiple: true, allowNone: true },
{ name: 'Course Level', allowMultiple: true, allowNone: true },
{ name: 'Detail Level', allowMultiple: true, allowNone: true },
{ name: 'Concurrent', allowMultiple: false, allowNone: true }, //don't allow multiple because if both Y and N are selected it is the same as not selecting either one
{ name: 'General Education', allowMultiple: false, allowNone: true } //don't allow multiple because if both Y and N are selected it is the same as not selecting either one
];
//create the array of filters containing the Google controlWrappers
filterList.forEach(filterInfo => {
let filter_id = filterInfo.name.replace(/ /g, "_") + '_filter'; //replace all spaces with _ and append _filter
$("#ef_right").append('<div id="' + filter_id + '"></div>'); //create all filters in the ef_right div
filters.push(new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: filter_id,
options: {
filterColumnLabel: filterInfo.name, //this is the label from your dataTable/the returned column names from the database
ui: {
allowMultiple: filterInfo.allowMultiple,
allowTyping: false,
sortValues: true,
selectedValuesLayout: 'below',
labelStacking: 'vertical',
allowNone: filterInfo.allowNone
}
}
}));
});
//invisible table used for filtering
proxyTable = new google.visualization.ChartWrapper({
'chartType': 'Table',
'containerId': 'proxyTable'
});
//build the chartWrapper for the column graph
options = {
'hAxis': {
'title': null
},
'vAxis': {
'format': '#,###',
'minValue': 0,
'maxValue': 'auto',
'textPosition': 'in',
'title': null
},
//'title': 'Class Sections and SSCH',
'legend': {
'position':'bottom'
},
'chartArea': {
'left':'10',
'right':'10',
'top':'50',
'bottom':'50',
'width':'100%',
'height':'100%',
},
'width':'100%',
'height':450,
'viewWindowMode': 'pretty',
'tooltip': {
'trigger': 'selection',
'isHtml': true
},
'annotations': {
'alwaysOutside': true,
'highContrast': true,
}
};
options = JSON.parse(JSON.stringify(options));
chart0 = new google.visualization.ChartWrapper({
'chartType': 'ColumnChart',
'containerId': 'chart0',
'options': options
});
var dash = new google.visualization.Dashboard(document.getElementById('dashboard'));
dash.bind(filters,proxyTable);
google.visualization.events.addListener(proxyTable,'ready',proxyTableReady); //call proxyTableReady once the proxy table is fully drawn
dash.draw(data); //draw the dashboard with retrieved JSON data which has been converted to a google dataTable
}
function proxyTableReady() {
proxyDataTable = proxyTable.getDataTable(); //retrieve filtered datatable (filters are applied to proxyTable)
//get indexes and then aggregate/group data for graphing
let yearIndex = proxyDataTable.getColumnIndex('Year'); //23
let sschIndex = proxyDataTable.getColumnIndex('SSCH'); //0
let enrollmentsIndex = proxyDataTable.getColumnIndex('ENROLLMENTS'); //1
let crnIndex = proxyDataTable.getColumnIndex('CRN'); //2
});
chartSelectListener = true; //prevent the listener from being added more than once
}
}
//makes the chart downloadable by adding a download button to the tooltip.
function setChartActionDownload(chart) {
chart.getChart().setAction({
'id': chart.getContainerId() + '_tooltip',
'text': 'Download Data',
'action': function() { downloadData(chart); },
'visible': function() { return isDownloadVisible(chart); }
});
}
function downloadData(chart) {
var chartData = chart.getDataTable();
var selection = chart.getChart().getSelection();
var filter_data = {};
//in the future it could limit the download to the single academic year that is clicked on
//filter_data['field'] = chartData.getColumnLabel(selection[0]['column']); //grabs the name of the column the user clicked in Google Charts
//grab the filter options
$(filters).each(function() {
filter_data[this.getOption('filterColumnLabel')] = this.getState()['selectedValues'];
});
window.location.href = 'classsections/csv?' + $.param(filter_data);
/* this whole section will download the data directly from the dataTable
//manually build the header row
let headerRow = [];
for (let i = 0; i < proxyDataTable.getNumberOfColumns(); i++) {
let label = proxyDataTable.getColumnLabel(i);
headerRow.push('"' + label.replace(/"/g, '""') + '"');
}
let csvHeaders = headerRow.join(',') + '\n';
let csvFormattedDataRows = google.visualization.dataTableToCsv(proxyDataTable);
//combine headers and data
let fullCsvContent = csvHeaders + csvFormattedDataRows;
let encodedUri = 'data:application/csv;charset=utf-8,' + encodeURIComponent(fullCsvContent);
//download link creation
let $link = $('<a>')
.attr('href', encodedUri)
.attr('download', 'class_sections_ssch.csv')
.css('display', 'none')
.appendTo('body');
$link[0].click(); //execute the downloading by "clicking" the link
$link.remove(); //clean up the DOM
*/
}
function isDownloadVisible(chart) {
let chartData = chart.getDataTable();
let selection = chart.getChart().getSelection();
if(selection.length > 0) {
if(chartData.getColumnRole(selection[0]['column']) == 'annotation') //prevent the download button from showing on columns with the role of annotation
return false;
else
return true;
} else
return false;
}
//returns the period of years when given the academic year
function printAYperiod(academicYear) {
const endYear = parseInt(academicYear, 10); //convert to integer
if (!isNaN(endYear)) { //check if conversion was successful
const startYear = endYear - 1;
return `${startYear}-${endYear}`;
} else {
return false;
}
}
//inserts a value into an array in numerical order
//the array is assumed to consist of numbers
function insertSorted(arr, val) {
let i = 0;
while (i < arr.length && arr[i] < val) {
i++;
}
arr.splice(i, 0, val); // Insert val at index i