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

  var grouped_data = google.visualization.data.group(
    proxyDataTable,
    [yearIndex],
    [
      {'column': sschIndex, 'aggregation': google.visualization.data.sum, 
'type':'number'},
      {'column': enrollmentsIndex, 'aggregation': google.visualization.data.
sum, 'type':'number'},
      {'column': crnIndex, 'aggregation': google.visualization.data.count, 
'type':'number'}
    ]
  );

  if ( activeColumns.length === 0 ) { //only run if activeColumns is not 
populated
    const x = grouped_data.getNumberOfColumns();
    for ( let i=0; i < x; i++ ) {
      activeColumns.push(i); //build array of active Columns for 
showing/hiding them in the chart
    }
  }

  var view = buildViewWithSpecifiedColumns(grouped_data, activeColumns);
  chart0.setDataTable(view);
  chart0.draw();
  setChartActionDownload(chart0);

  if ( chartSelectListener == false ) {
    //logic to show/hide columns when clicking on the legend
    google.visualization.events.addListener(chart0, 'select', function () {
      var sel = chart0.getChart().getSelection();
      if ( sel.length > 0 ) { //either a legend item or a chart element has 
been selected
        if ( sel[0].row === null ) { //legend has been clicked
          var selectedCol = sel[0].column;

          //toggle column visibility in the activeColumns array
          var indexInactive = activeColumns.indexOf(selectedCol); //get the 
index in the array for the selected column
          if (indexInactive > -1) { //if it is -1 it was not in the active 
columns array
            activeColumns.splice(indexInactive, 1); //remove if present 
(hide)
          } else {
            insertSorted(activeColumns, selectedCol); //add column if not 
present (show)
          }

          proxyTableReady(); //recursively call this function to redraw the 
chart with selected filters
        }
      } // else //an element in the chart has been deselected
    });

    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
}

//returns a view with hidden columns
//expects a Google Charts dataTable and an array of indexes of the desired 
columns to show
function buildViewWithSpecifiedColumns(dataTable, visibleIndexes) {
  const totalCols = dataTable.getNumberOfColumns();
  const viewColumns = [];

  for (let i = 0; i < totalCols; i++) {
    if (visibleIndexes.includes(i)) {
      viewColumns.push(i);
    } else { //the column should be hidden
      viewColumns.push({
        type: dataTable.getColumnType(i),
        label: dataTable.getColumnLabel(i),
        calc: () => null
      });
    }
  }

  const view = new google.visualization.DataView(dataTable);
  view.setColumns(viewColumns);
  return view;
}
On Wednesday, July 30, 2025 at 2:49:16 PM UTC-5 Alvin Galit wrote:

> Hey Gavin. I think I just see snippets of your code. I can take a closer 
> look if you provide the whole file.
>
> On Wednesday, July 30, 2025 at 11:04:35 AM UTC-7 Gavin Dundee wrote:
>
>> I sent my whole code earlier.  If you did not get it, please let me know.
>>
>> Update:
>> I tried the removeColumn() method, but I still get the same results.  It 
>> is doing the same as the setColumn.  I verified this with the 
>> getViewColumns() method.
>>
>> I also am able to duplicate columns.  If I do this:
>> view.setColumns([0,1,2,2])
>>
>> It does actually produce column 2 twice which makes column 3 not visible 
>> any longer.  It also updates the legend.  I have attached a photo of this 
>> behavior.
>>
>> On Wednesday, July 30, 2025 at 10:17:02 AM UTC-5 Alvin Galit wrote:
>>
>>> Are you able to share your whole code?
>>>
>>> On Wednesday, July 30, 2025 at 8:10:15 AM UTC-7 Gavin Dundee wrote:
>>>
>>>> It really doesn't work.  I truly am puzzled by this.  I am a newbie to 
>>>> this though.
>>>>
>>>> On Wednesday, July 30, 2025 at 9:47:55 AM UTC-5 Alvin Galit wrote:
>>>>
>>>>> [0,1, 2] doesn't work? Index starts at [0] and since you have 3 
>>>>> columns, I thought maybe that should be working. 
>>>>> It makes sense that [0, 1, 3] doesn't work because you only have 3 
>>>>> columns and so the 3rd column is at index [2].
>>>>>
>>>>> On Wednesday, July 30, 2025 at 7:33:55 AM UTC-7 Gavin Dundee wrote:
>>>>>
>>>>>> With array inputs:
>>>>>> [0,1,2]
>>>>>> or
>>>>>> [0,1,3]
>>>>>>
>>>>>> I usually get an error like this:
>>>>>> Invalid column index 3. Should be an integer in the range [0-2].  
>>>>>>
>>>>>> If I set the array to:
>>>>>> [0,1,2,3]
>>>>>> I get all three columns per row.
>>>>>>
>>>>>> I am still at a loss.  Any help would be greatly appreciated! :)
>>>>>>
>>>>>> On Tuesday, July 29, 2025 at 5:02:11 PM UTC-5 Gavin Dundee wrote:
>>>>>>
>>>>>>> I am seeking help.  Here is my datatable setup:
>>>>>>>   var grouped_data = google.visualization.data.group(
>>>>>>>     proxyDataTable,
>>>>>>>     [yearIndex],
>>>>>>>     [
>>>>>>>       {'column': sschIndex, 'aggregation': google.visualization.data
>>>>>>> .sum, 'type':'number'},
>>>>>>>       {'column': enrollmentsIndex, 'aggregation': google.
>>>>>>> visualization.data.sum, 'type':'number'},
>>>>>>>       {'column': crnIndex, 'aggregation': google.visualization.data.
>>>>>>> count, 'type':'number'}
>>>>>>>     ]
>>>>>>>   );
>>>>>>>
>>>>>>> Now I am creating a view:
>>>>>>> var view = new google.visualization.DataView(grouped_data);
>>>>>>>
>>>>>>> Then I draw the view:
>>>>>>> view.setColumns([[0,1],[0,1],[0,1]]);
>>>>>>> chart0.setDataTable(view);
>>>>>>> chart0.draw();
>>>>>>>
>>>>>>> My issue is with setColumns().  There are 3 columns being 
>>>>>>> displayed.  I thought I would be able to use this code:
>>>>>>> view.setColumns([1,2]);
>>>>>>> To display only columns 1 and 2 and not show column 3.  My 
>>>>>>> assumption is that [1,2,3] would show all the columns, but that does 
>>>>>>> not 
>>>>>>> work either.  I am not sure where to go from here I have tried 
>>>>>>> setColumns 
>>>>>>> with many different arrays, but I am just guessing at this point.  Can 
>>>>>>> anyone help determine how I could hide a column using setColumns?
>>>>>>>
>>>>>>> I attached a screenshot of what my chart looks like.
>>>>>>>
>>>>>>> Thanks in advance for any help!!
>>>>>>>
>>>>>>

-- 
You received this message because you are subscribed to the Google Groups 
"Google Visualization API" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/google-visualization-api/8bf1be95-02fb-4f0f-ae69-59d2966207e9n%40googlegroups.com.

Reply via email to