tbl_zoom_plot_jqplot.js 22 KB

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