【发布时间】:2020-07-16 18:49:54
【问题描述】:
在我使用类别选择器之前,我有一个行为正确的表。
正确的行为:
- 过滤“性别”
- 点击表格中的一行 - 接收结果
- 对表格中的任何列(如性别)进行排序
- 点击表格中的一行 - 接收结果
问题行为:我希望能够选择一行并接收结果,而不必先对性别列进行排序。
- 过滤“性别”
- 单击表格中的一行 - 失败 - 未返回任何结果
- 对表格中的任何列(如性别)进行排序
- 点击表格中的一行 - 成功 - 你现在得到一个结果
如何在不先对列进行排序的情况下获得成功?
Grazie Mille!
google.charts.load('current', {
'packages': ['corechart', 'table', 'gauge', 'controls', 'charteditor']
});
$(document).ready(function () {
//console.log("ready!");
renderChart_onPageLoad();
});
function renderChart_onPageLoad() {
google.charts.setOnLoadCallback(function () {
//console.log("renderChart_onPageLoad");
drawDashboard();
});
}
function drawDashboard() {
//console.log("drawDashboard");
var data = google.visualization.arrayToDataTable([
['Name', 'RoolNumber', 'Gender', 'Age', 'DonutsEaten'],
['Michael', 1, 'Male', 12, 5],
['Elisa', 2, 'Female', 20, 7],
['Robert', 3, 'Male', 7, 3],
['John', 4, 'Male', 54, 2],
['Jessica', 5, 'Female', 22, 6],
['Aaron', 6, 'Male', 3, 1],
['Margareth', 7, 'Female', 42, 8],
['Miranda', 8, 'Female', 33, 6]
]);
var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard'));
var categoryPicker = new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: 'categoryPicker',
options: {
filterColumnLabel: 'Gender',
ui: {
labelStacking: 'vertical',
allowTyping: false,
allowMultiple: false
}
}
});
var proxyTable = new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'div_proxyTable',
options: {
width: '500px'
}
});
var table = new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'div_table',
options: {
sort: 'event', // <-- set sort to 'event' for table totaling
width: '500px',
allowHtml: true,
page: 'enable',
pageSize: '5',
}
});
//This json contains my settings for later
let json = {
"tableChart": {
"hasTable": true,
"dataView": {
"columns": [
{ "id": "Name" },
{ "id": "RoolNumber" },
{ "id": "Gender" },
{ "id": "Age" },
{ "id": "DonutsEaten" }
]
},
"groupView": {
"hasGroupView": false
},
"totalRow": {
"hasTotalRow": true,
"labelCol": "Total",
"labelColIndex": 0,//This is the column where the words "Grand Total" are stored. It must be text column.
"totalColumns": [
{ "id": "RoolNumber", "type": "number", "function": "sum" }
]
},
"conditionalFormat": {
"hasConditionalFormat": false
},
"options": {},
"hasCSV": false,
"clickGetFunc": null
},
};
google.visualization.events.addOneTimeListener(proxyTable, 'ready', function () {
console.log(".addOneTimeListener(proxyTable, 'ready' - sort");
google.visualization.events.addOneTimeListener(table, 'ready', function () {
console.log(".addOneTimeListener(table, 'ready' - sort");
//#region table - sort: 'event'
google.visualization.events.addListener(table.getChart(), 'sort', function (sender) {
console.log(".addListener(table.getChart(), 'sort' - sorted");
//// update table --> options must include (sort: 'event') for total row to work properly
//// update var grandTotal = tableData.getFilteredRows([{column: 0,
//// update .draw() 'table' references to new name if using for a different table variable
// sort data table according to sort properties
var tableData = table.getDataTable();
var sortIndexes = tableData.getSortedRows({
column: sender.column,
desc: !sender.ascending
});
//#region reposition total row - if required
let totalRow = (json.tableChart.totalRow !== undefined) ? json.tableChart.totalRow : [];
let hasTotalRow = totalRow.hasTotalRow;
if (hasTotalRow) {
// find grand total row
var grandTotal = tableData.getFilteredRows([{
column: json.tableChart.totalRow.labelColIndex, //must be placed in a column which is of type string.
value: json.tableChart.totalRow.labelCol
}]);
if (grandTotal.length > 0) {
// find grand total in sort
var grandTotalSort = sortIndexes.indexOf(grandTotal[0]);
// remove grand total from sort
sortIndexes.splice(grandTotalSort, 1);
// add grand total as first index
sortIndexes.unshift(grandTotal[0]);
}
}
//#endregion
// set table sort arrow
table.setOption('sortAscending', sender.ascending);
table.setOption('sortColumn', sender.column);
// set table view & re-draw table
table.setView({ rows: sortIndexes });
table.draw();
//Table ready then fires
});
//#endregion
});
});
google.visualization.events.addOneTimeListener(proxyTable, 'ready', function () {
console.log(".addOneTimeListener(proxyTable, 'ready' - select");
google.visualization.events.addOneTimeListener(table, 'ready', function () {
console.log(".addOneTimeListener(table, 'ready' - select");
test_gcharts_selectedRowCol(table, json.tableChart.clickGetFunc);//(wrapperName, callback)
});
});
google.visualization.events.addListener(proxyTable, 'ready', function () {
console.log(".addListener(proxyTable, 'ready' - redrawTable()");
redrawTable(json.tableChart);
});
dashboard.bind([categoryPicker], [proxyTable]);
dashboard.draw(data);
//This is a table builder which uses json from above. This is working ok. Contains no listeners.
function redrawTable(tableChart) {
console.log('redrawTable()');
// set defaults for any undefined settings
let dataView = (tableChart.dataView !== undefined) ? tableChart.dataView : [];
let groupView = (tableChart.groupView !== undefined) ? tableChart.groupView : [];
let totalRow = (tableChart.totalRow !== undefined) ? tableChart.totalRow : [];
let conditionalFormat = (tableChart.conditionalFormat !== undefined) ? tableChart.conditionalFormat : [];
// update .draw() 'table' or 'chart' references when using a different or additional chart name
var sourceData = proxyTable.getDataTable().toDataTable().clone();
//console.log('sourceData', sourceData);
//#region create data view - this is used as basis for dataResults
let view = new google.visualization.DataView(sourceData);
//#region create group view - if required
let dataResults_forTable;
let hasGroupView = groupView.hasGroupView;
if (hasGroupView) {
// create keys for grouping
const groupKeys = [];
for (let i = 0; i < groupView.keys.length; i++) {
groupKeys.push(
groupKey_default(view, groupView.keys[i]),
);
};
// create columns for aggregating
const groupColumns = [];
for (let i = 0; i < groupView.columns.length; i++) {
groupColumns.push(
groupColumn_default(view, groupView.columns[i]),
);
};
// create data aggregation
let group = google.visualization.data.group(view, groupKeys, groupColumns);
//console.log('group'); console.log(group);
dataResults_forTable = group.clone();
}
else {
dataResults_forTable = view.toDataTable().clone();
}//END if (hasGroupView) {
//console.log('dataResults_forTable', dataResults_forTable);
//#endregion
//#region create total row - if required
let hasTotalRow = totalRow.hasTotalRow;
if (hasTotalRow) {
let labelCol = totalRow.labelCol;
let labelColIndex = totalRow.labelColIndex;
let totalColumns = totalRow.totalColumns;
//Create groupColumns for total row aggregation calculations
const groupColumns = [];
for (let i = 0; i < totalColumns.length; i++) {
const column = { column: dataResults_forTable.getColumnIndex(totalColumns[i].id), type: 'number' }
switch (totalColumns[i].function) {
case 'sum': column.aggregation = google.visualization.data.sum; break;
case 'count': column.aggregation = google.visualization.data.count; break;
case 'average': column.aggregation = google.visualization.data.avg; break;
case 'min': column.aggregation = google.visualization.data.min; break;
case 'max': column.aggregation = google.visualization.data.avg; break;
default: column.aggregation = google.visualization.data.sum;
}
groupColumns.push(column);
}
let groupTotal = google.visualization.data.group(dataResults_forTable,
// need key column to group on, so we want all rows grouped into 1, then it needs a constant value
[{ column: 0, type: "number", modifier: function () { return 1; } }], groupColumns);
// this code block will run if the filter results in rows available to total. Otherwise the table will present no rows.
if (groupTotal.getNumberOfRows() !== 0) {
let formatDecimal = new google.visualization.NumberFormat({ pattern: '#,###.##' });
for (let i = 1; i < groupTotal.getNumberOfColumns(); i++) { formatDecimal.format(groupTotal, i); }
// create Grand Total row from colToTotal and groupTotal
const gtRow = [];
for (let i = 0; i < dataResults_forTable.getNumberOfColumns(); i++) {
//Build GT Row to match length of dataResults_forTable
gtRow.push(null);
}
//Push words "Grand Total" into it's set position in gtRow - It must go into a column of type string.
gtRow[labelColIndex] = labelCol;
for (let i = 0; i < totalColumns.length; i++) {
//Loop through groupColumns, test setting type for string.
if (totalColumns[i].type === 'string') {
//Convert to number from groupTotal result to string to match the column it's being pushed into
gtRow[dataResults_forTable.getColumnIndex(totalColumns[i].id)] = String(groupTotal.getValue(0, i + 1));
}
else {
//Otherwise push in the number value
gtRow[dataResults_forTable.getColumnIndex(totalColumns[i].id)] = groupTotal.getValue(0, i + 1);
}
}
//console.log('insertRow', insertRow);
// insert complete gtRow with values into row position 0
dataResults_forTable.insertRows(0, [gtRow]);
// add formatting for grand total row to highlight && justify to right if of type number
for (let j = 0; j < dataResults_forTable.getNumberOfColumns(); j++) {
//if statement on column type for left right justification
if (dataResults_forTable.getColumnType(j) === 'number') {
dataResults_forTable.setColumnProperty(j, 'className', 'googleTableTextRight');
dataResults_forTable.setProperty(0, j, 'className', 'googleTableTotalRow googleTableTextRight');//stored in css file
} else {
dataResults_forTable.setProperty(0, j, 'className', 'googleTableTotalRow');//stored in css file
}
}
//console.log('dataResults_forTable', dataResults_forTable);
}//END (groupTotal.getNumberOfRows() !== 0) {
}//END if (hasTotalRow) {
//#endregion
//#region conditional formatting - if required
let hasConditionalFormat = conditionalFormat.hasConditionalFormat;
if (hasConditionalFormat) {
dataResults_forTable = conditionalFormatting_default(dataResults_forTable, conditionalFormat);
}//END if (hasConditionalFormat) {
//#endregion
var finalView_forTable = new google.visualization.DataView(dataResults_forTable);
//console.log('finalView_forTable', finalView_forTable);
// set reset sorting, set dataTable & draw chart
table.setView(null); // reset in case sorting has been used via user click
table.setDataTable(finalView_forTable);//includes any total row
table.draw();
}//END redrawChart()
}
function test_gcharts_selectedRowCol(wrapperName, callback) {
//console.log('wrapperName', wrapperName); console.log('callback', callback)
//NEW - Works with paging active
// initialize page number and size
var page = 0;
var pageSize = 10;
if (wrapperName.getOption('page') === 'enable') {
page = wrapperName.getOption('startPage');
pageSize = wrapperName.getOption('pageSize');
}
test_enableCoordinates(callback);
// page event
google.visualization.events.addListener(wrapperName.getChart(), 'page', function (sender) {
console.log(".addListener(wrapperName.getChart(), 'page' - paged - test_enableCoordinates called");
page = sender.page; // save current page
test_enableCoordinates(callback);
});
// sort event
google.visualization.events.addListener(wrapperName.getChart(), 'sort', function () {
console.log(".addListener(wrapperName.getChart(), 'sort' - sorted - test_enableCoordinates called ")
page = 0; // reset back to first page
test_enableCoordinates(callback);
});
function test_enableCoordinates(callback) {
//console.log('callback enableCoordinates', callback);
//remove event listeners
var container = document.getElementById(wrapperName.getContainerId());
Array.prototype.forEach.call(container.getElementsByTagName('td'), function (cell) {
cell.removeEventListener("click", test_selectCell, false);
});
//add event listeners
var container = document.getElementById(wrapperName.getContainerId());
Array.prototype.forEach.call(container.getElementsByTagName('td'), function (cell) {
cell.addEventListener('click', test_selectCell, false);
});
}
function test_selectCell(sender) {
var cell = sender.target;
var row = cell.closest('tr');
var wrapperDataTable = wrapperName.getDataTable();
var selectedRow = row.rowIndex - 1; // adjust for header row (-1)
selectedRow = (page * pageSize) + selectedRow; // adjust for page number
// Original from whitehat - save sorted info
// This version does not work after a user clicks a column to sort
//var sortInfo = wrapperName.getChart().getSortInfo();
//if (sortInfo.sortedIndexes !== null) {
// selectedRow = sortInfo.sortedIndexes[selectedRow];
//}
// Replaced code to which finds the view row order by .getView then taking the selected row
// and returning the number which is in the .getView result
var sortInfo = wrapperName.getView(); // save sorted info
if (sortInfo !== null) {
selectedRow = sortInfo.rows[selectedRow];
}
var selectedCol = cell.cellIndex;
//var result = "selectedRow: " + selectedRow + " selectedCol: " + selectedCol;
//var ul = document.getElementById("demo");
//var li = document.createElement("li");
//li.innerHTML = result;
//ul.appendChild(li);
var selectedValue = wrapperDataTable.getValue(selectedRow, selectedCol);
var colID = wrapperDataTable.getColumnId(selectedCol);
var colLabel = wrapperDataTable.getColumnLabel(selectedCol);
var result = //removed array brackets
{
"selectedRow": selectedRow,
"selectedCol": selectedCol,
"selectedValue": selectedValue,
"colID": colID,
"colLabel": colLabel
};
//callback(result, wrapperDataTable);
console.log('test_selectCell', result);
return result;
}
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
<div id="dashboard">
<div id="categoryPicker"></div><br />
Proxy Table<br />
<div id="div_proxyTable" style="display:none;"></div><br />
Table<br />
<div id="div_table"></div><br /><br />
</div>
================多个仪表板场景======================
提供的解决方案适用于单个 drawDashboard() 场景。然而,它在我想在接下来讨论的多个 drawDashboard() 场景中分解。
我将在此页面中添加额外的 drawDashboard() 函数。
它们还需要相同的功能并保持相互独立。
有没有办法在没有全局变量的情况下容纳多个drawDashboard() 函数?
我是否将 listenerPage 和 listenerSort 移动到特定的 drawDashboard() 函数中以自包含?
我希望只维护一个库函数test_gcharts_selectedRowCol(),而不是复制它以添加额外的drawDashboard()?
https://jsfiddle.net/cmill/pvj23nfk/
在运行时,drawDashboard_A() 以 listenerPage 开头,listenerSort = null。然后它将为每个执行.addListener 例程。
那么当drawDashboard_B() 运行时,listenerPage 和listenerSort 不再为空,而是由drawDashboard_A() 的传递设置。
排序和点击事件可以根据需要完美运行并返回结果。
不良行为:
-
点击
drawDashboard_A()中的第2页 -
点击一行 - 失败 - 没有结果。
-
点击
drawDashboard_B()中的第2页 -
点击一行 - PASS - 接收结果。
【问题讨论】:
标签: javascript charts google-visualization