From 48d6ba5007c40870a879d1ba6d58072504c91272 Mon Sep 17 00:00:00 2001 From: Mottie Date: Fri, 6 Feb 2015 22:48:21 -0600 Subject: [PATCH] Charts: Add chart_event option, provide other data formats & add demo --- docs/example-widget-chart.html | 693 +++++++++++++++++++++++++++++++++ docs/index.html | 1 + js/widgets/widget-chart.js | 418 ++++++++++++-------- 3 files changed, 941 insertions(+), 171 deletions(-) create mode 100644 docs/example-widget-chart.html diff --git a/docs/example-widget-chart.html b/docs/example-widget-chart.html new file mode 100644 index 00000000..e7eff14e --- /dev/null +++ b/docs/example-widget-chart.html @@ -0,0 +1,693 @@ + + + + + jQuery plugin: Tablesorter 2.0 - Chart Widget + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+
+
+ +

Notes

+
+
    +
  • This widget will only work in tablesorter version 2.16+ and jQuery version 1.7+.
  • +
  • It only provides data for charts.
  • +
  • Currently it provides data for the following chart libraries: + + More details are available within the named "Setup" sections below. +
  • +
+
+ +

Options

+
+
+ TIP! Click on the link in the function column to reveal full details (or toggle|show|hide all) or double click to update the browser location. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDefaultDescription
'filtered' *Select which rows to include in the chart. +
+

+ This option allows you to select which rows to include in the chart data: +
    +
  • 'a' - Include all rows, even if the table has been filtered or partially hidden by the pager.
  • +
  • 'v' - Include only visible rows, whether they are hidden by the pager or filter.
  • +
  • 'f' - Include only filtered rows, even if the pager is only showing a select few rows.
  • +
+ * Only the first letter is required, but the option will accept the full word (e.g. 'filtered' instead of 'f') +
+
falseUse the columnSelector widget in place of the chart_ignoreColumns option. +
+

+ Set this option to true if using the columnSelector widget. +

+ If using a custom column selector, then set this option to false and use the custom selector to update the chart_ignoreColumns option dynamically. +
+
[ ]Array of zero-based column indexes of columns to ignore. +
+

+ This option is used when the chart_useSelector option is false or if the columnSelector is set to "auto" mode. +

+ Update this option dynamically, if using a custom method to hide/indicate which columns are to be ignored when gathering data for the chart. +
+
[ ]Array of zero-based column indexes of columns that must used parsed data for charting. +
+

+ Parsed data is gathered from the table cache, which is parsed the designated parser for that column. +

+ This option can be updated dynamically. +
+
{ 0: 'string' }Object containing the format needed by the chart for each column. +
+

+ By default, all columns will have their values converted to numbers. Prior to sending the value to tablesorter's formatFloat function, the value will have all non-digit characters stripped out. +

+ Set the zero-based column index of the desired column to 's' (only the first letter is needed) to pass the cell value to the chart as a string. +

+ This option can be updated dynamically. +
+
0Set a zero-based column index for the column to be used as a chart label. +
+

+ The chart label (independent variable) is usually the first array value in each nested array (at least for Google charts). In this demo, it is the Year. +
+
[ [ 0,0 ] ]Sort a specific column of data using the same format as tablesorter's sortList option; but for a single column. +
+

+ Set this value to be an array contained within an array using the following format: [[ column, direction ]] +

+ * Note * It would be best to sort the same column as the chart_labelCol to keep the axis ordered properly; but that is up to you ;) +

+
    +
  • column - zero-based index of the column to sort.
  • +
  • direction - sort direction; 0 indicates an ascending sort, and 1 indicates a descending sort is to be used.
  • +
+ * Note * Anything other than 1 as the sort direction will set an ascending sort. +
+
'chartData'The chart data will be updated when this event is triggered on the table. +
+

+ Trigger a chart data update, then get the data as follows: +
var $table = $('table').trigger('chartData'),
+	data = $table[0].config.chart.data;
+chartData( data ); // custom function to chart the data
+
+
+
+ +

Setup - Google Charts

+
+ The data used by Google charts is an array of arrays in this format: +
[
+	[ "Year", "Sales", "Expenses" ],
+	[ "2004", 1000, 400  ],
+	[ "2005", 1170, 460  ],
+	[ "2006", 660,  1120 ],
+	[ "2007", 1030, 540  ]
+]
+ Access the data as follows: +
var $table = $('table').trigger('chartData'),
+	options = { /* set up options here */ },
+	rawdata = $table[0].config.chart.data,
+	data = google.visualization.arrayToDataTable( rawdata ),
+	// bar chart example
+	chart = new google.visualization.BarChart( $('#chart')[0] );
+chart.draw(data, options);
+
+
+ +

Setup - Highcharts

+
+ The data used by Highcharts is an array of objects in this format: +
// categories
+[ '2004', '2005', '2006', '2007' ]
+
+// series
+[{
+	name: 'Sales',
+	data: [ 1000, 1170, 660, 1030 ]
+}, {
+	name: 'Expenses',
+	data: [ 400, 460, 1120, 540 ]
+}]
+ Access the data as follows: +
var $table = $('table').trigger('chartData');
+
+$('#chart').highcharts({
+	chart: { type: 'column' },
+	xAxis: { categories: $table[0].config.chart.categories },
+	series: $table[0].config.chart.series
+});
+
+
+ +

Setup - FusionCharts

+
+ The data used by FusionCharts is an array of objects in this format: +
// category
+[
+	{"label": "2004"},
+	{"label": "2005"},
+	{"label": "2006"},
+	{"label": "2007"}
+]
+// dataset
+[{
+	"seriesname": "Sales",
+	"data": [
+		{"value": "1000"},
+		{"value": "1170"},
+		{"value": "660"},
+		{"value": "1030"}
+	]
+},{
+	"seriesname": "Expenses",
+	"data": [
+		{"value": "400"},
+		{"value": "600"},
+		{"value": "1120"},
+		{"value": "540"}
+	]
+}]
+ Access the data as follows: +
var $table = $('table');
+$table.trigger('chartData');
+
+FusionCharts.ready(function () {
+	var analysisChart = new FusionCharts({
+		dataFormat: 'json',
+		dataSource: {
+			"chart": {
+				// ...
+			},
+			"categories": [{
+				"category": $table[0].config.chart.category
+			}],
+			"dataset": $table[0].config.chart.dataset,
+		},
+		// other options
+	}).render();
+});
+
+ +
+

+ +

Google Charts Demo

+
+
+

+
+ + + + + + + + +
+

+
+ + + + + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + +
YearSalesExpensesProfit
2004$ 1,000$ 400$ 600
2005$ 1,170$ 460$ 710
2006$ 660$ 1,120($ 460)
2007$ 1,030$ 540$ 490
+ + +
+
+
+ +
+ + +
+ +
+ + +
+ +
+
+
+
+ +

Page Header

+
+
<!-- Google charts -->
+<script src="//www.google.com/jsapi"></script>
+
+<!-- jQuery -->
+<script src="js/jquery-latest.min.js"></script>
+
+<!-- Demo stuff -->
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css">
+<!-- buttons -->
+<link rel="stylesheet" href="css/bootstrap.min.css">
+<script src="js/bootstrap.min.js"></script>
+
+<!-- Tablesorter: required -->
+<link rel="stylesheet" href="../css/theme.blue.css">
+<script src="../js/jquery.tablesorter.js"></script>
+<script src="../js/widgets/widget-chart.js"></script>
+
+<!-- Tablesorter: optional -->
+<script src="../js/jquery.tablesorter.widgets.js"></script>
+<script src="../js/widgets/widget-cssStickyHeaders.js"></script>
+<script src="../js/widgets/widget-columnSelector.js"></script>
+<script src="../js/widgets/widget-pager.js"></script>
+
+ +

Javascript

+
+

+	
+ +

CSS

+
+

+	
+ +

HTML

+
+

+	
+ +
+ + + + + + + + diff --git a/docs/index.html b/docs/index.html index b3828179..129f81fc 100644 --- a/docs/index.html +++ b/docs/index.html @@ -470,6 +470,7 @@
  • Beta Align Character Widget (v2.15.8).
  • Build Table Widget (v2.11; v2.16).
  • +
  • Beta Chart Widget (v2.18.5).
  • Columns Highlight widget (v2.0.17)
  • Beta Column Selector widget (v2.15; v2.18.5).
  • Content Editable widget (v2.9; v2.18.0).
  • diff --git a/js/widgets/widget-chart.js b/js/widgets/widget-chart.js index 4851d058..10a57b2d 100644 --- a/js/widgets/widget-chart.js +++ b/js/widgets/widget-chart.js @@ -1,200 +1,276 @@ -/* Chart widget (beta) for TableSorter 1/27/2015 (v2.19.0) +/* Chart widget (beta) for TableSorter 2/7/2015 (v2.19.0) * Requires tablesorter v2.8+ and jQuery 1.7+ */ - /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ - ;(function($){ -"use strict"; + 'use strict'; -var ts = $.tablesorter, + var ts = $.tablesorter, -chart = ts.chart = { + // temp variables + chart_cols = [], + chart_headers = [], + // google charts + chart_rows = [], + chart_data = [], + // highcharts + chart_categories = [], + chart_series = [], + // fusioncharts + chart_category = [], + chart_dataset = [], - event: 'chartData', + chart = ts.chart = { - chartdata: [], - cols: [], - headers: [], - rows: [], + // regex used to strip out non-digit values before sending + // the string to the $.tablesorter.formatFloat function + nonDigit : /[^\d,.\-()]/g, - init: function(c, wo) { - c.$table - .off(chart.event) - .on(chart.event, function() { - chart.getCols(c, wo); - chart.getData(c, wo); - }); - }, - - getCols: function(c, wo) { - chart.cols = []; - - for(var i = 0; i < c.columns; i++) { - if (wo.chart_useSelector && - $.inArray('columnSelector', c.widgets) !== -1 && - c.selector.auto === false - ) { - if ((c.selector.states[i] === true && $.inArray(i, wo.chart_ignoreColumns) === -1) || - i == wo.chart_labelCol || - i == wo.chart_sort[0][0] - ) { - chart.cols.push(i); - } - } else { - if ($.inArray(i, wo.chart_ignoreColumns) === -1 || - i == wo.chart_labelCol || - i == wo.chart_sort[0][0] - ) { - chart.cols.push(i); - } - } - } - }, - - getData: function(c, wo) { - chart.getHeaders(c, wo); - chart.getRows(c, wo); - - chart.chartdata = [ - chart.headers - ]; - - $.each(chart.rows, function(k, row) { - chart.chartdata.push(row); - }); - - c.chart = { - data: chart.chartdata - }; - }, - - getHeaders: function(c, wo) { - chart.headers = []; - chart.headers.push(c.headerContent[wo.chart_labelCol]); - $.each(chart.cols, function(k, col) { - if (col == wo.chart_labelCol) { - return true; - } - - chart.headers.push(c.headerContent[col]); - }); - }, - - getRows: function(c, wo) { - var $table = c.$table; - // the cache may not have a zero index if there are any - // "info-only" tbodies above the main tbody - var cache = c.cache[0].normalized; - var rows = []; - chart.rows = []; - - $.each(cache, function(k, v) { - var $tr = v[c.columns].$row; - var cells = $tr.find('td'); - var row = []; - if ((wo.chart_incRows == 'visible' && $tr.is(':visible')) || - (wo.chart_incRows == 'filtered' && !$tr.hasClass(wo.filter_filteredRow || 'filtered')) || - (wo.chart_incRows != 'visible' && wo.chart_incRows != 'filtered') - ) { - // Add all cols (don't mess up indx for sorting) - for(var i = 0; i < c.columns; i++) { - if ($.inArray(k, wo.chart_parsed) !== -1) { - row.push(v[i]); - } else { - row.push($(cells[i]).text().trim()); + init: function(c, wo) { + c.$table + .off(wo.chart_event) + .on(wo.chart_event, function() { + if (this.hasInitialized) { + // refresh "c" variable in case options are updated dynamically + var c = this.config; + chart.getCols(c, c.widgetOptions); + chart.getData(c, c.widgetOptions); } - } + }); + }, - rows.push(row); - } - }); + getCols: function(c, wo) { + var i; + chart_cols = []; + chart_series = []; + chart_dataset = []; - // sort based on chart_sort - rows.sort(function(a, b) { - if (wo.chart_sort[0][1] == 1) { - return $.tablesorter.sortNatural(b[wo.chart_sort[0][0]], a[wo.chart_sort[0][0]]); - } - - return $.tablesorter.sortNatural(a[wo.chart_sort[0][0]], b[wo.chart_sort[0][0]]); - }); - - $.each(rows, function(kk, vv) { - var row = []; - var label = vv[wo.chart_labelCol]; - - row.push(String(label)); - - $.each(vv, function(k, cell) { - if (k == wo.chart_labelCol) { - return true; - } - - var thiscell = false; - - if (wo.chart_useSelector && - $.inArray('columnSelector', c.widgets) !== -1 && - c.selector.auto === false - ) { - if (c.selector.states[k] === true && - $.inArray(k, wo.chart_ignoreColumns) === -1 - ) { - thiscell = cell; + for ( i = 0; i < c.columns; i++ ) { + if ( wo.chart_useSelector && ts.hasWidget( c.table, 'columnSelector' ) && !c.selector.auto ) { + if ( ( c.selector.states[i] && $.inArray(i, wo.chart_ignoreColumns) < 0 ) || + i === wo.chart_labelCol || i === wo.chart_sort[0][0] ) { + chart_cols.push(i); } } else { - if ($.inArray(k, wo.chart_ignoreColumns) === -1) { - thiscell = cell; + if ( $.inArray(i, wo.chart_ignoreColumns) < 0 || i === wo.chart_labelCol || i === wo.chart_sort[0][0] ) { + chart_cols.push(i); } } + } + }, - if (thiscell !== false) { - if (wo.chart_layout[row.length] == 'string') { - row.push(String(thiscell)); - } else { - row.push(parseFloat(thiscell)); + getData: function(c, wo) { + chart.getHeaders(c, wo); + chart.getRows(c, wo); + + /* == Google data == + array of arrays (Google charts) + [ + [ "Year", "Sales", "Expenses" ], + [ "2004", 1000, 400 ], + [ "2005", 1170, 460 ], + [ "2006", 660, 1120 ], + [ "2007", 1030, 540 ] + ] + + == Highcharts == + categories -> [ '2004', '2005', '2006', '2007' ] + series -> [{ + name: 'Sales', + data: [ 1000, 1170, 660, 1030 ] + }, { + name: 'Expenses', + data: [ 400, 460, 1120, 540 ] + }] + + == Fusioncharts + "categories": [{ + "category": [ + {"label": "2004"}, + {"label": "2005"}, + {"label": "2006"}, + {"label": "2007"} + ] + }], + "dataset": [ + { + "seriesname": "Sales", + "data": [ + {"value": "1000"}, + {"value": "1170"}, + {"value": "660"}, + {"value": "1030"} + ] + },{ + "seriesname": "Expenses", + "data": [ + {"value": "400"}, + {"value": "600"}, + {"value": "1120"}, + {"value": "540"} + ] + } + ] + */ + + chart_data = [ chart_headers ]; + $.each(chart_rows, function(k, row) { + chart_data.push(row); + }); + + c.chart = { + // google + data: chart_data, + // highcharts + categories: chart_categories, + series: chart_series, + // FusionCharts + category: chart_category, + dataset: chart_dataset + }; + }, + + getHeaders: function(c, wo) { + var text; + chart_headers = []; + chart_series = []; + chart_dataset = []; + chart_headers.push( c.headerContent[wo.chart_labelCol] ); + $.each(chart_cols, function(k, col) { + if (col === wo.chart_labelCol) { + return true; + } + text = c.headerContent[col]; + chart_headers.push( text ); + chart_series.push( { name: text, data: [] } ); + chart_dataset.push( { seriesname: text, data: [] } ); + }); + }, + + getRows: function(c, wo) { + // the cache may not have a zero index if there are any "info-only" tbodies above the main tbody + var cache = c.cache[0].normalized, + rows = []; + chart_rows = []; + chart_categories = []; + chart_category = []; + + $.each(cache, function(indx, rowVal) { + var i, txt, + $tr = rowVal[c.columns].$row, + $cells = $tr.children('th,td'), + row = []; + if ( + (/v/i.test(wo.chart_incRows) && $tr.is(':visible')) || + (/f/i.test(wo.chart_incRows) && !$tr.hasClass(wo.filter_filteredRow || 'filtered')) || + (!/(v|f)/i.test(wo.chart_incRows)) + ) { + // Add all cols (don't mess up indx for sorting) + for (i = 0; i < c.columns; i++) { + if ( $.inArray(indx, wo.chart_parsed) >= 0 ) { + row.push( rowVal[i] ); + } else { + txt = $cells[i].getAttribute( c.textAttribute ) || $cells[i].textContent || $cells.eq(i).text(); + row.push( $.trim( txt ) ); + } } + rows.push(row); } }); - chart.rows.push(row); - }); - }, + // sort based on chart_sort + rows.sort(function(a, b) { + if ( wo.chart_sort[0][1] === 1 ) { + return ts.sortNatural( b[wo.chart_sort[0][0]], a[wo.chart_sort[0][0]] ); + } + return ts.sortNatural( a[wo.chart_sort[0][0]], b[wo.chart_sort[0][0]] ); + }); - remove: function(c) { - c.$table.off(chart.event); - } -}; + $.each(rows, function(i, rowVal) { + var value, + objIndex = 0, + row = [], + label = rowVal[wo.chart_labelCol]; -ts.addWidget({ - id: 'chart', - options: { - // all, visible or filtered - chart_incRows: 'filtered', - // prefer columnSelector for ignoreColumns - chart_useSelector: false, - // columns to ignore [0, 1,... ] (zero-based index) - chart_ignoreColumns: [], - // Use parsed data instead of cell.text() - chart_parsed: [], - // data output layout, int is default - chart_layout: { - // first element is a string, all others will be float - 0: 'string' + row.push( '' + label ); + + $.each(rowVal, function(indx, cellValue) { + var tempVal; + if (indx === wo.chart_labelCol) { + chart_categories.push( cellValue ); + chart_category.push({ 'label': cellValue }); + return true; + } + value = false; + if ( wo.chart_useSelector && ts.hasWidget( c.table, 'columnSelector' ) && !c.selector.auto ) { + if ( c.selector.states[indx] && $.inArray(indx, wo.chart_ignoreColumns) < 0 ) { + value = '' + cellValue; + } + } else { + if ($.inArray(indx, wo.chart_ignoreColumns) < 0) { + value = '' + cellValue; + } + } + + if (value !== false) { + if ( /s/i.test( '' + wo.chart_layout[row.length] ) ) { + row.push( value ); + chart_series[objIndex].data.push( value ); + chart_dataset[objIndex].data.push( value ); + } else { + // using format float, after stripping out all non-digit values + tempVal = ts.formatFloat( value.replace( chart.nonDigit, '' ), c.table ); + tempVal = isNaN(tempVal) ? value : tempVal; + // if tempVal ends up being an empty string, fall back to value + row.push( tempVal ); + chart_series[objIndex].data.push( tempVal ); + chart_dataset[objIndex].data.push( { value : tempVal } ); + } + objIndex++; + } + }); + chart_rows.push(row); + }); }, - // Set the label column - chart_labelCol: 0, - // data sort, shoudl always be first row, might want [[0,1]] - chart_sort: [[0,0]] - }, - init: function(table, thisWidget, c, wo) { - chart.init(c, wo); - }, + remove: function(c) { + c.$table.off(chart.event); + } - remove: function(table, c, wo) { - chart.remove(c); - } -}); + }; + + ts.addWidget({ + id: 'chart', + options: { + // (a)ll, (v)isible or (f)iltered - only the first letter is needed + chart_incRows: 'filtered', + // prefer columnSelector for ignoreColumns + chart_useSelector: false, + // columns to ignore [0, 1,... ] (zero-based index) + chart_ignoreColumns: [], + // Use parsed data instead of cell.text() + chart_parsed: [], + // data output layout, float is default + chart_layout: { + // first element is a string, all others will be float + 0: 'string' + }, + // Set the label column + chart_labelCol: 0, + // data sort, should always be first row, might want [[0,1]] + chart_sort: [[0,0]], + // event to trigger get updated data + chart_event: 'chartData' + }, + + init: function(table, thisWidget, c, wo) { + chart.init(c, wo); + }, + + remove: function(table, c, wo) { + chart.remove(c); + } + }); })(jQuery);