tbl_zoom_plot_jqplot.js 22 KB

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