tbl_zoom_plot_jqplot.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. // todo: change the axis
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. ** @fileoverview JavaScript functions used on tbl_select.php
  5. **
  6. ** @requires jQuery
  7. ** @requires js/functions.js
  8. **/
  9. /**
  10. ** Display Help/Info
  11. **/
  12. function displayHelp() {
  13. PMA_ajaxShowMessage(PMA_messages['strDisplayHelp'], 10000);
  14. }
  15. /**
  16. ** Extend the array object for max function
  17. ** @param array
  18. **/
  19. Array.max = function (array) {
  20. return Math.max.apply( Math, array );
  21. };
  22. /**
  23. ** Extend the array object for min function
  24. ** @param array
  25. **/
  26. Array.min = function (array) {
  27. return Math.min.apply( Math, array );
  28. };
  29. /**
  30. ** Checks if a string contains only numeric value
  31. ** @param n: String (to be checked)
  32. **/
  33. function isNumeric(n) {
  34. return !isNaN(parseFloat(n)) && isFinite(n);
  35. }
  36. /**
  37. ** Checks if an object is empty
  38. ** @param n: Object (to be checked)
  39. **/
  40. function isEmpty(obj) {
  41. var name;
  42. for (name in obj) {
  43. return false;
  44. }
  45. return true;
  46. }
  47. /**
  48. ** Converts a date/time into timestamp
  49. ** @param val String Date
  50. ** @param type Sring Field type(datetime/timestamp/time/date)
  51. **/
  52. function getTimeStamp(val, type) {
  53. if (type.toString().search(/datetime/i) != -1 || type.toString().search(/timestamp/i) != -1) {
  54. return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', val);
  55. }
  56. else if (type.toString().search(/time/i) != -1) {
  57. return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', '1970-01-01 ' + val);
  58. }
  59. else if (type.toString().search(/date/i) != -1) {
  60. return $.datepicker.parseDate('yy-mm-dd', val);
  61. }
  62. }
  63. /**
  64. ** Classifies the field type into numeric,timeseries or text
  65. ** @param field: field type (as in database structure)
  66. **/
  67. function getType(field) {
  68. if (field.toString().search(/int/i) != -1 || field.toString().search(/decimal/i) != -1
  69. || field.toString().search(/year/i) != -1) {
  70. return 'numeric';
  71. } else if (field.toString().search(/time/i) != -1 || field.toString().search(/date/i) != -1) {
  72. return 'time';
  73. } else {
  74. return 'text';
  75. }
  76. }
  77. /**
  78. ** Converts a categorical array into numeric array
  79. ** @param array categorical values array
  80. **/
  81. function getCord(arr) {
  82. var newCord = [];
  83. var original = $.extend(true, [], arr);
  84. var arr = jQuery.unique(arr).sort();
  85. $.each(original, function(index, value) {
  86. newCord.push(jQuery.inArray(value, arr));
  87. });
  88. return [newCord, arr, original];
  89. }
  90. /**
  91. ** Scrolls the view to the display section
  92. **/
  93. function scrollToChart() {
  94. var x = $('#dataDisplay').offset().top - 100; // 100 provides buffer in viewport
  95. $('html,body').animate({scrollTop: x}, 500);
  96. }
  97. /**
  98. * Unbind all event handlers before tearing down a page
  99. */
  100. AJAX.registerTeardown('tbl_zoom_plot_jqplot.js', function() {
  101. $('#tableid_0').unbind('change');
  102. $('#tableid_1').unbind('change');
  103. $('#tableid_2').unbind('change');
  104. $('#tableid_3').unbind('change');
  105. $('#inputFormSubmitId').unbind('click');
  106. $('#togglesearchformlink').unbind('click');
  107. $("#dataDisplay").find(':input').die('keydown');
  108. $('button.button-reset').unbind('click');
  109. $('div#resizer').unbind('resizestop');
  110. $('div#querychart').unbind('jqplotDataClick');
  111. });
  112. AJAX.registerOnload('tbl_zoom_plot_jqplot.js', function() {
  113. var cursorMode = ($("input[name='mode']:checked").val() == 'edit') ? 'crosshair' : 'pointer';
  114. var currentChart = null;
  115. var searchedDataKey = null;
  116. var xLabel = $('#tableid_0').val();
  117. var yLabel = $('#tableid_1').val();
  118. // will be updated via Ajax
  119. var xType = $('#types_0').val();
  120. var yType = $('#types_1').val();
  121. var dataLabel = $('#dataLabel').val();
  122. var lastX;
  123. var lastY;
  124. var zoomRatio = 1;
  125. // Get query result
  126. var searchedData = jQuery.parseJSON($('#querydata').html());
  127. /**
  128. ** Input form submit on field change
  129. **/
  130. // first column choice corresponds to the X axis
  131. $('#tableid_0').change(function() {
  132. //AJAX request for field type, collation, operators, and value field
  133. $.post('tbl_zoom_select.php',{
  134. 'ajax_request' : true,
  135. 'change_tbl_info' : true,
  136. 'db' : PMA_commonParams.get('db'),
  137. 'table' : PMA_commonParams.get('table'),
  138. 'field' : $('#tableid_0').val(),
  139. 'it' : 0,
  140. 'token' : PMA_commonParams.get('token')
  141. },function(data) {
  142. $('#tableFieldsId tr:eq(1) td:eq(0)').html(data.field_type);
  143. $('#tableFieldsId tr:eq(1) td:eq(1)').html(data.field_collation);
  144. $('#tableFieldsId tr:eq(1) td:eq(2)').html(data.field_operators);
  145. $('#tableFieldsId tr:eq(1) td:eq(3)').html(data.field_value);
  146. xLabel = $('#tableid_0').val();
  147. $('#types_0').val(data.field_type);
  148. xType = data.field_type;
  149. $('#collations_0').val(data.field_collations);
  150. addDateTimePicker();
  151. });
  152. });
  153. // second column choice corresponds to the Y axis
  154. $('#tableid_1').change(function() {
  155. //AJAX request for field type, collation, operators, and value field
  156. $.post('tbl_zoom_select.php',{
  157. 'ajax_request' : true,
  158. 'change_tbl_info' : true,
  159. 'db' : PMA_commonParams.get('db'),
  160. 'table' : PMA_commonParams.get('table'),
  161. 'field' : $('#tableid_1').val(),
  162. 'it' : 1,
  163. 'token' : PMA_commonParams.get('token')
  164. },function(data) {
  165. $('#tableFieldsId tr:eq(3) td:eq(0)').html(data.field_type);
  166. $('#tableFieldsId tr:eq(3) td:eq(1)').html(data.field_collation);
  167. $('#tableFieldsId tr:eq(3) td:eq(2)').html(data.field_operators);
  168. $('#tableFieldsId tr:eq(3) td:eq(3)').html(data.field_value);
  169. yLabel = $('#tableid_1').val();
  170. $('#types_1').val(data.field_type);
  171. yType = data.field_type;
  172. $('#collations_1').val(data.field_collations);
  173. addDateTimePicker();
  174. });
  175. });
  176. $('#tableid_2').change(function() {
  177. //AJAX request for field type, collation, operators, and value field
  178. $.post('tbl_zoom_select.php',{
  179. 'ajax_request' : true,
  180. 'change_tbl_info' : true,
  181. 'db' : PMA_commonParams.get('db'),
  182. 'table' : PMA_commonParams.get('table'),
  183. 'field' : $('#tableid_2').val(),
  184. 'it' : 2,
  185. 'token' : PMA_commonParams.get('token')
  186. },function(data) {
  187. $('#tableFieldsId tr:eq(6) td:eq(0)').html(data.field_type);
  188. $('#tableFieldsId tr:eq(6) td:eq(1)').html(data.field_collation);
  189. $('#tableFieldsId tr:eq(6) td:eq(2)').html(data.field_operators);
  190. $('#tableFieldsId tr:eq(6) td:eq(3)').html(data.field_value);
  191. $('#types_2').val(data.field_type);
  192. $('#collations_2').val(data.field_collations);
  193. addDateTimePicker();
  194. });
  195. });
  196. $('#tableid_3').change(function() {
  197. //AJAX request for field type, collation, operators, and value field
  198. $.post('tbl_zoom_select.php',{
  199. 'ajax_request' : true,
  200. 'change_tbl_info' : true,
  201. 'db' : PMA_commonParams.get('db'),
  202. 'table' : PMA_commonParams.get('table'),
  203. 'field' : $('#tableid_3').val(),
  204. 'it' : 3,
  205. 'token' : PMA_commonParams.get('token')
  206. },function(data) {
  207. $('#tableFieldsId tr:eq(8) td:eq(0)').html(data.field_type);
  208. $('#tableFieldsId tr:eq(8) td:eq(1)').html(data.field_collation);
  209. $('#tableFieldsId tr:eq(8) td:eq(2)').html(data.field_operators);
  210. $('#tableFieldsId tr:eq(8) td:eq(3)').html(data.field_value);
  211. $('#types_3').val(data.field_type);
  212. $('#collations_3').val(data.field_collations);
  213. addDateTimePicker();
  214. });
  215. });
  216. /**
  217. * Input form validation
  218. **/
  219. $('#inputFormSubmitId').click(function() {
  220. if ($('#tableid_0').get(0).selectedIndex == 0 || $('#tableid_1').get(0).selectedIndex == 0) {
  221. PMA_ajaxShowMessage(PMA_messages['strInputNull']);
  222. } else if (xLabel == yLabel) {
  223. PMA_ajaxShowMessage(PMA_messages['strSameInputs']);
  224. }
  225. });
  226. /**
  227. ** Prepare a div containing a link, otherwise it's incorrectly displayed
  228. ** after a couple of clicks
  229. **/
  230. $('<div id="togglesearchformdiv"><a id="togglesearchformlink"></a></div>')
  231. .insertAfter('#zoom_search_form')
  232. // don't show it until we have results on-screen
  233. .hide();
  234. $('#togglesearchformlink')
  235. .html(PMA_messages['strShowSearchCriteria'])
  236. .bind('click', function() {
  237. var $link = $(this);
  238. $('#zoom_search_form').slideToggle();
  239. if ($link.text() == PMA_messages['strHideSearchCriteria']) {
  240. $link.text(PMA_messages['strShowSearchCriteria']);
  241. } else {
  242. $link.text(PMA_messages['strHideSearchCriteria']);
  243. }
  244. // avoid default click action
  245. return false;
  246. });
  247. /**
  248. ** Set dialog properties for the data display form
  249. **/
  250. var buttonOptions = {};
  251. /*
  252. * Handle saving of a row in the editor
  253. */
  254. buttonOptions[PMA_messages['strSave']] = function () {
  255. //Find changed values by comparing form values with selectedRow Object
  256. var newValues = {};//Stores the values changed from original
  257. var sqlTypes = {};
  258. var it = 0;
  259. var xChange = false;
  260. var yChange = false;
  261. var key;
  262. for (key in selectedRow) {
  263. var oldVal = selectedRow[key];
  264. var newVal = ($('#edit_fields_null_id_' + it).prop('checked')) ? null : $('#edit_fieldID_' + it).val();
  265. if (newVal instanceof Array) { // when the column is of type SET
  266. newVal = $('#edit_fieldID_' + it).map(function(){
  267. return $(this).val();
  268. }).get().join(",");
  269. }
  270. if (oldVal != newVal) {
  271. selectedRow[key] = newVal;
  272. newValues[key] = newVal;
  273. if (key == xLabel) {
  274. xChange = true;
  275. searchedData[searchedDataKey][xLabel] = newVal;
  276. } else if (key == yLabel) {
  277. yChange = true;
  278. searchedData[searchedDataKey][yLabel] = newVal;
  279. }
  280. }
  281. var $input = $('#edit_fieldID_' + it);
  282. if ($input.hasClass('bit')) {
  283. sqlTypes[key] = 'bit';
  284. }
  285. it++;
  286. } //End data update
  287. //Update the chart series and replot
  288. if (xChange || yChange) {
  289. //Logic similar to plot generation, replot only if xAxis changes or yAxis changes.
  290. //Code includes a lot of checks so as to replot only when necessary
  291. if (xChange) {
  292. xCord[searchedDataKey] = selectedRow[xLabel];
  293. // [searchedDataKey][0] contains the x value
  294. if (xType == 'numeric') {
  295. series[0][searchedDataKey][0] = selectedRow[xLabel];
  296. } else if (xType == 'time') {
  297. series[0][searchedDataKey][0] =
  298. getTimeStamp(selectedRow[xLabel], $('#types_0').val());
  299. } else {
  300. // todo: text values
  301. }
  302. currentChart.series[0].data = series[0];
  303. // todo: axis changing
  304. currentChart.replot();
  305. }
  306. if (yChange) {
  307. yCord[searchedDataKey] = selectedRow[yLabel];
  308. // [searchedDataKey][1] contains the y value
  309. if (yType == 'numeric') {
  310. series[0][searchedDataKey][1] = selectedRow[yLabel];
  311. } else if (yType == 'time') {
  312. series[0][searchedDataKey][1] =
  313. getTimeStamp(selectedRow[yLabel], $('#types_1').val());
  314. } else {
  315. // todo: text values
  316. }
  317. currentChart.series[0].data = series[0];
  318. // todo: axis changing
  319. currentChart.replot();
  320. }
  321. } //End plot update
  322. //Generate SQL query for update
  323. if (!isEmpty(newValues)) {
  324. var sql_query = 'UPDATE `' + PMA_commonParams.get('table') + '` SET ';
  325. for (key in newValues) {
  326. sql_query += '`' + key + '`=' ;
  327. var value = newValues[key];
  328. // null
  329. if (value == null) {
  330. sql_query += 'NULL, ';
  331. // empty
  332. } else if ($.trim(value) == '') {
  333. sql_query += "'', ";
  334. // other
  335. } else {
  336. // type explicitly identified
  337. if (sqlTypes[key] != null) {
  338. if (sqlTypes[key] == 'bit') {
  339. sql_query += "b'" + value + "', ";
  340. }
  341. // type not explicitly identified
  342. } else {
  343. if (!isNumeric(value)) {
  344. sql_query += "'" + value + "', ";
  345. } else {
  346. sql_query += value + ', ';
  347. }
  348. }
  349. }
  350. }
  351. sql_query = sql_query.substring(0, sql_query.length - 2);
  352. sql_query += ' WHERE ' + PMA_urldecode(searchedData[searchedDataKey]['where_clause']);
  353. //Post SQL query to sql.php
  354. $.post('sql.php', {
  355. 'token' : PMA_commonParams.get('token'),
  356. 'db' : PMA_commonParams.get('db'),
  357. 'ajax_request' : true,
  358. 'sql_query' : sql_query,
  359. 'inline_edit' : false
  360. }, function(data) {
  361. if (data.success == true) {
  362. $('#sqlqueryresults').html(data.sql_query);
  363. $("#sqlqueryresults").trigger('appendAnchor');
  364. } else {
  365. PMA_ajaxShowMessage(data.error, false);
  366. }
  367. }); //End $.post
  368. }//End database update
  369. $("#dataDisplay").dialog('close');
  370. };
  371. buttonOptions[PMA_messages['strCancel']] = function () {
  372. $(this).dialog('close');
  373. };
  374. $("#dataDisplay").dialog({
  375. autoOpen: false,
  376. title: PMA_messages['strDataPointContent'],
  377. modal: true,
  378. buttons: buttonOptions,
  379. width: $('#dataDisplay').width() + 80,
  380. open: function () {
  381. $(this).find('input[type=checkbox]').css('margin', '0.5em');
  382. }
  383. });
  384. /**
  385. * Attach Ajax event handlers for input fields
  386. * in the dialog. Used to submit the Ajax
  387. * request when the ENTER key is pressed.
  388. */
  389. $("#dataDisplay").find(':input').live('keydown', function (e) {
  390. if (e.which === 13) { // 13 is the ENTER key
  391. e.preventDefault();
  392. if (typeof buttonOptions[PMA_messages['strSave']] === 'function') {
  393. buttonOptions[PMA_messages['strSave']].call();
  394. }
  395. }
  396. });
  397. /*
  398. * Generate plot using jqplot
  399. */
  400. if (searchedData != null) {
  401. $('#zoom_search_form')
  402. .slideToggle()
  403. .hide();
  404. $('#togglesearchformlink')
  405. .text(PMA_messages['strShowSearchCriteria']);
  406. $('#togglesearchformdiv').show();
  407. var selectedRow;
  408. var colorCodes = ['#FF0000', '#00FFFF', '#0000FF', '#0000A0', '#FF0080', '#800080', '#FFFF00', '#00FF00', '#FF00FF'];
  409. var series = [];
  410. var xCord = [];
  411. var yCord = [];
  412. var tempX, tempY;
  413. var it = 0;
  414. var xMax; // xAxis extreme max
  415. var xMin; // xAxis extreme min
  416. var yMax; // yAxis extreme max
  417. var yMin; // yAxis extreme min
  418. var options = {
  419. series: [
  420. // for a scatter plot
  421. { showLine: false }
  422. ],
  423. grid: {
  424. drawBorder: false,
  425. shadow: false,
  426. background: 'rgba(0,0,0,0)'
  427. },
  428. axes: {
  429. xaxis: {
  430. label: $('#tableid_0').val(),
  431. labelRenderer: $.jqplot.CanvasAxisLabelRenderer
  432. },
  433. yaxis: {
  434. label: $('#tableid_1').val(),
  435. labelRenderer: $.jqplot.CanvasAxisLabelRenderer
  436. }
  437. },
  438. highlighter: {
  439. show: true,
  440. tooltipAxes: 'y',
  441. yvalues: 2,
  442. // hide the first y value
  443. formatString: '<span class="hide">%s</span>%s'
  444. },
  445. cursor: {
  446. show: true,
  447. zoom: true,
  448. showTooltip: false
  449. }
  450. };
  451. // If data label is not set, do not show tooltips
  452. if (dataLabel == '') {
  453. options.highlighter.show = false;
  454. }
  455. // Classify types as either numeric,time,text
  456. xType = getType(xType);
  457. yType = getType(yType);
  458. // could have multiple series but we'll have just one
  459. series[0] = [];
  460. if (xType == 'time') {
  461. var originalXType = $('#types_0').val();
  462. var format;
  463. if (originalXType == 'date') {
  464. format = '%Y-%m-%d';
  465. }
  466. // todo: does not seem to work
  467. //else if (originalXType == 'time') {
  468. // format = '%H:%M';
  469. //} else {
  470. // format = '%Y-%m-%d %H:%M';
  471. //}
  472. $.extend(options.axes.xaxis, {
  473. renderer:$.jqplot.DateAxisRenderer,
  474. tickOptions: {
  475. formatString: format
  476. }
  477. });
  478. }
  479. if (yType == 'time') {
  480. var originalYType = $('#types_1').val();
  481. var format;
  482. if (originalYType == 'date') {
  483. format = '%Y-%m-%d';
  484. }
  485. $.extend(options.axes.yaxis, {
  486. renderer:$.jqplot.DateAxisRenderer,
  487. tickOptions: {
  488. formatString: format
  489. }
  490. });
  491. }
  492. $.each(searchedData, function(key, value) {
  493. if (xType == 'numeric') {
  494. var xVal = parseFloat(value[xLabel]);
  495. }
  496. if (xType == 'time') {
  497. var xVal = getTimeStamp(value[xLabel], originalXType);
  498. }
  499. if (yType == 'numeric') {
  500. var yVal = parseFloat(value[yLabel]);
  501. }
  502. if (yType == 'time') {
  503. var yVal = getTimeStamp(value[yLabel], originalYType);
  504. }
  505. series[0].push([
  506. xVal,
  507. yVal,
  508. // extra Y values
  509. value[dataLabel], // for highlighter
  510. // (may set an undefined value)
  511. value['where_clause'], // for click on point
  512. key // key from searchedData
  513. ]);
  514. });
  515. // under IE 8, the initial display is mangled; after a manual
  516. // resizing, it's ok
  517. // under IE 9, everything is fine
  518. currentChart = $.jqplot('querychart', series, options);
  519. currentChart.resetZoom();
  520. $('button.button-reset').click(function(event) {
  521. event.preventDefault();
  522. currentChart.resetZoom();
  523. });
  524. $('div#resizer').resizable();
  525. $('div#resizer').bind('resizestop', function(event, ui) {
  526. // make room so that the handle will still appear
  527. $('div#querychart').height($('div#resizer').height() * 0.96);
  528. $('div#querychart').width($('div#resizer').width() * 0.96);
  529. currentChart.replot( {resetAxes: true})
  530. });
  531. $('div#querychart').bind('jqplotDataClick',
  532. function(event, seriesIndex, pointIndex, data) {
  533. searchedDataKey = data[4]; // key from searchedData (global)
  534. var field_id = 0;
  535. var post_params = {
  536. 'ajax_request' : true,
  537. 'get_data_row' : true,
  538. 'db' : PMA_commonParams.get('db'),
  539. 'table' : PMA_commonParams.get('table'),
  540. 'where_clause' : data[3],
  541. 'token' : PMA_commonParams.get('token')
  542. };
  543. $.post('tbl_zoom_select.php', post_params, function(data) {
  544. // Row is contained in data.row_info,
  545. // now fill the displayResultForm with row values
  546. var key;
  547. for (key in data.row_info) {
  548. $field = $('#edit_fieldID_' + field_id);
  549. $field_null = $('#edit_fields_null_id_' + field_id);
  550. if (data.row_info[key] == null) {
  551. $field_null.prop('checked', true);
  552. $field.val('');
  553. } else {
  554. $field_null.prop('checked', false);
  555. if ($field.attr('multiple')) { // when the column is of type SET
  556. $field.val(data.row_info[key].split(','));
  557. } else {
  558. $field.val(data.row_info[key]);
  559. }
  560. }
  561. field_id++;
  562. }
  563. selectedRow = {};
  564. selectedRow = data.row_info;
  565. });
  566. $("#dataDisplay").dialog("open");
  567. }
  568. );
  569. }
  570. });