Last active
February 4, 2016 11:22
-
-
Save blink1073/184601e653a0e4aace9f to your computer and use it in GitHub Desktop.
QGrid IPython Widget
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"\n", | |
"#qgrid - SlickGrid in IPython notebook\n", | |
"---" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Qgrid is an IPython extension which uses a javascript library called SlickGrid to render pandas DataFrames within an IPython notebook. It's being developed for use in [Quantopian's hosted research environment]( https://www.quantopian.com/research?utm_source=github&utm_medium=web&utm_campaign=qgrid-nbviewer), and this notebook demonstrates the current state of the project." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Overview\n", | |
"* [SlickGrid](https://github.com/mleibman/SlickGrid) is an an advanced javascript grid which allows users to scroll, sort, \n", | |
"and filter hundreds of thousands of rows with extreme responsiveness. \n", | |
"* [Pandas](https://github.com/pydata/pandas) is a powerful data analysis / manipulation library for Python, and DataFrames are the primary way of storing and manipulating two-dimensional data in pandas.\n", | |
"\n", | |
"[Qgrid](https://github.com/quantopian/qgrid) renders pandas DataFrames as SlickGrids, which enables users to explore the entire contents of a DataFrame using intuitive sorting and filtering controls. It's designed to be used within IPython notebook, and it's also fully functional when rendered in [nbviewer](http://nbviewer.ipython\n", | |
".org/github/quantopian/qgrid/blob/master/qgrid_demo.ipynb).\n", | |
"\n", | |
"This Demo adds an IPython Widget for a QGrid, which allows us to interact with the dataframe within the notebook." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Installation using 'pip install'\n", | |
"##### Qgrid is a python module so you can install it using pip:\n", | |
"\n", | |
"```pip install git+https://github.com/quantopian/qgrid```\n", | |
"\n", | |
"##### Import it into your namespace like you would for any other python module:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 110, | |
"metadata": { | |
"collapsed": false | |
}, | |
"outputs": [], | |
"source": [ | |
"import qgrid" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Demo - Interacting with a DataFrame returned by Yahoo Finance\n", | |
"##### First, lets create a sample DataFrame using pandas 'get_data_yahoo' function:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 111, | |
"metadata": { | |
"collapsed": false | |
}, | |
"outputs": [], | |
"source": [ | |
"import pandas as pd\n", | |
"import numpy as np\n", | |
"randn = np.random.randn\n", | |
"\n", | |
"pd.set_option('display.max_rows', 8)\n", | |
"\n", | |
"from pandas.io.data import get_data_yahoo\n", | |
"spy = get_data_yahoo(\n", | |
" symbols='SPY',\n", | |
" start=pd.Timestamp('2011-01-01'),\n", | |
" end=pd.Timestamp('2014-01-01'),\n", | |
" adjust_price=True,\n", | |
")\n", | |
"\n", | |
"# use an integer index so we can add rows\n", | |
"spy = spy.reset_index()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 136, | |
"metadata": { | |
"collapsed": false | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"application/javascript": [ | |
"require([\"widgets/js/widget\", \"widgets/js/manager\"], function(widget, manager){\n", | |
"\n", | |
" /**\n", | |
" * Date input handler.\n", | |
" *\n", | |
" * Adapted from https://github.com/mleibman/SlickGrid/blob/master/slick.editors.js\n", | |
" * MIT License, Copyright (c) 2010 Michael Leibman\n", | |
" */\n", | |
" function DateEditor(args) {\n", | |
" var $input;\n", | |
" var defaultValue;\n", | |
" var scope = this;\n", | |
" var calendarOpen = false;\n", | |
" this.init = function () {\n", | |
" $input = $(\"<INPUT type=text class='editor-text' />\");\n", | |
" $input.appendTo(args.container);\n", | |
" $input.focus().select();\n", | |
" $input.datepicker({\n", | |
" showOn: \"button\",\n", | |
" buttonImageOnly: true,\n", | |
" beforeShow: function () {\n", | |
" calendarOpen = true\n", | |
" },\n", | |
" onClose: function () {\n", | |
" calendarOpen = false\n", | |
" }\n", | |
" });\n", | |
"\n", | |
" $input.width($input.width() - 18);\n", | |
" };\n", | |
"\n", | |
" this.destroy = function () {\n", | |
" $.datepicker.dpDiv.stop(true, true);\n", | |
" $input.datepicker(\"hide\");\n", | |
" $input.datepicker(\"destroy\");\n", | |
" $input.remove();\n", | |
" };\n", | |
"\n", | |
" this.show = function () {\n", | |
" if (calendarOpen) {\n", | |
" $.datepicker.dpDiv.stop(true, true).show();\n", | |
" }\n", | |
" };\n", | |
"\n", | |
" this.hide = function () {\n", | |
" if (calendarOpen) {\n", | |
" $.datepicker.dpDiv.stop(true, true).hide();\n", | |
" }\n", | |
" };\n", | |
"\n", | |
" this.position = function (position) {\n", | |
" if (!calendarOpen) {\n", | |
" return;\n", | |
" }\n", | |
" $.datepicker.dpDiv\n", | |
" .css(\"top\", position.top + 30)\n", | |
" .css(\"left\", position.left);\n", | |
" };\n", | |
"\n", | |
" this.focus = function () {\n", | |
" $input.focus();\n", | |
" };\n", | |
"\n", | |
" this.loadValue = function (item) {\n", | |
" defaultValue = item[args.column.field];\n", | |
" $input.val(defaultValue);\n", | |
" $input[0].defaultValue = defaultValue;\n", | |
" $input.select();\n", | |
" };\n", | |
"\n", | |
" this.serializeValue = function () {\n", | |
" return $input.val();\n", | |
" };\n", | |
"\n", | |
" this.applyValue = function (item, state) {\n", | |
" item[args.column.field] = state;\n", | |
" };\n", | |
"\n", | |
" this.isValueChanged = function () {\n", | |
" return (!($input.val() == \"\" && defaultValue == null)) && ($input.val() != defaultValue);\n", | |
" };\n", | |
"\n", | |
" this.validate = function () {\n", | |
" return {\n", | |
" valid: true,\n", | |
" msg: null\n", | |
" };\n", | |
" };\n", | |
"\n", | |
" this.init();\n", | |
" }\n", | |
"\n", | |
" /**\n", | |
" * Text input handler.\n", | |
" *\n", | |
" * From https://github.com/mleibman/SlickGrid/blob/master/slick.editors.js\n", | |
" * MIT License, Copyright (c) 2010 Michael Leibman\n", | |
" */\n", | |
" function TextEditor(args) {\n", | |
" var $input;\n", | |
" var defaultValue;\n", | |
" var scope = this;\n", | |
"\n", | |
" this.init = function () {\n", | |
" $input = $(\"<INPUT type=text class='editor-text' />\")\n", | |
" .appendTo(args.container)\n", | |
" .bind(\"keydown.nav\", function (e) {\n", | |
" if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {\n", | |
" e.stopImmediatePropagation();\n", | |
" }\n", | |
" })\n", | |
" .focus()\n", | |
" .select();\n", | |
" };\n", | |
"\n", | |
" this.destroy = function () {\n", | |
" $input.remove();\n", | |
" };\n", | |
"\n", | |
" this.focus = function () {\n", | |
" $input.focus();\n", | |
" };\n", | |
"\n", | |
" this.getValue = function () {\n", | |
" return $input.val();\n", | |
" };\n", | |
"\n", | |
" this.setValue = function (val) {\n", | |
" $input.val(val);\n", | |
" };\n", | |
"\n", | |
" this.loadValue = function (item) {\n", | |
" defaultValue = item[args.column.field] || \"\";\n", | |
" $input.val(defaultValue);\n", | |
" $input[0].defaultValue = defaultValue;\n", | |
" $input.select();\n", | |
" };\n", | |
"\n", | |
" this.serializeValue = function () {\n", | |
" return $input.val();\n", | |
" };\n", | |
"\n", | |
" this.applyValue = function (item, state) {\n", | |
" item[args.column.field] = state;\n", | |
" };\n", | |
"\n", | |
" this.isValueChanged = function () {\n", | |
" return (!($input.val() == \"\" && defaultValue == null)) && ($input.val() != defaultValue);\n", | |
" };\n", | |
"\n", | |
" this.validate = function () {\n", | |
" if (args.column.validator) {\n", | |
" var validationResults = args.column.validator($input.val());\n", | |
" if (!validationResults.valid) {\n", | |
" return validationResults;\n", | |
" }\n", | |
" }\n", | |
"\n", | |
" return {\n", | |
" valid: true,\n", | |
" msg: null\n", | |
" };\n", | |
" };\n", | |
"\n", | |
" this.init();\n", | |
" }\n", | |
" \n", | |
" /**\n", | |
" * Validator for numeric cells.\n", | |
" */\n", | |
" function validateNumber(value) {\n", | |
" if (isNaN(value)) {\n", | |
" return {\n", | |
" valid: false,\n", | |
" msg: \"Please enter a valid integer\"\n", | |
" };\n", | |
" }\n", | |
" return {\n", | |
" valid: true,\n", | |
" msg: null\n", | |
" };\n", | |
" }\n", | |
"\n", | |
" var grid;\n", | |
"\n", | |
" var QGridView = widget.DOMWidgetView.extend({\n", | |
"\n", | |
" render: function() {\n", | |
" var that = this;\n", | |
" var cdn_base_url = \"https://cdn.rawgit.com/quantopian/qgrid/6988858671b6f80c7d1987e07ebff3dc810d20ce/qgrid/qgridjs/\";\n", | |
" \n", | |
" // Load the custom css\n", | |
" if ($(\"#dg-css\").length == 0){\n", | |
" $(\"head\").append([\n", | |
" \"<link href='\" + cdn_base_url + \"/lib/slick.grid.css' rel='stylesheet'>\",\n", | |
" \"<link href='\" + cdn_base_url + \"/lib/slick-default-theme.css' rel='stylesheet'>\",\n", | |
" \"<link href='http://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.4/css/jquery-ui.min.css' rel='stylesheet'>\",\n", | |
" \"<link id='dg-css' href='\" + cdn_base_url + \"/qgrid.css' rel='stylesheet'>\"\n", | |
" ]);\n", | |
" }\n", | |
" \n", | |
" // set the custom script load paths\n", | |
" var path_dictionary = {\n", | |
" jquery_drag: cdn_base_url + \"/lib/jquery.event.drag-2.2\",\n", | |
" slick_core: cdn_base_url + \"/lib/slick.core.2.2\",\n", | |
" slick_data_view: cdn_base_url + \"/lib/slick.dataview.2.2\",\n", | |
" slick_grid: cdn_base_url + \"/lib/slick.grid.2.2\",\n", | |
" data_grid: cdn_base_url + \"/qgrid\",\n", | |
" date_filter: cdn_base_url + \"/qgrid.datefilter\",\n", | |
" slider_filter: cdn_base_url + \"/qgrid.sliderfilter\",\n", | |
" filter_base: cdn_base_url + \"/qgrid.filterbase\",\n", | |
" handlebars: \"https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.min\"\n", | |
" };\n", | |
"\n", | |
" var existing_config = require.s.contexts._.config;\n", | |
" if (!existing_config.paths['underscore']){\n", | |
" path_dictionary['underscore'] = \"https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min\";\n", | |
" }\n", | |
"\n", | |
" if (!existing_config.paths['moment']){\n", | |
" path_dictionary['moment'] = \"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment.min\";\n", | |
" }\n", | |
"\n", | |
" if (!existing_config.paths['jqueryui']){\n", | |
" path_dictionary['jqueryui'] = \"https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/jquery-ui.min\";\n", | |
" }\n", | |
"\n", | |
" require.config({\n", | |
" paths: path_dictionary\n", | |
" });\n", | |
" \n", | |
" if (typeof jQuery === 'function') {\n", | |
" define('jquery', function() { return jQuery; });\n", | |
" }\n", | |
" \n", | |
" /**\n", | |
" * Load the required scripts and create the grid.\n", | |
" */\n", | |
" require([\n", | |
" 'jquery',\n", | |
" 'jquery_drag',\n", | |
" 'slick_core',\n", | |
" 'slick_data_view'\n", | |
" ],\n", | |
" function() {\n", | |
" require(['slick_grid'], function() {\n", | |
" require([\"data_grid\"], function(dgrid) {\n", | |
" that.setupQGrid(dgrid);\n", | |
" });\n", | |
" });\n", | |
" });\n", | |
" },\n", | |
"\n", | |
" /**\n", | |
" * Set up our QGrid and event handlers.\n", | |
" */\n", | |
" setupQGrid: function(dgrid) {\n", | |
" var that = this;\n", | |
" // set up the divs and styles\n", | |
" this.$el.addClass('q-grid-container');\n", | |
" var table = this.$el.append('div');\n", | |
" table.addClass('q-grid');\n", | |
" var width = this.el.clientWidth.toString();\n", | |
" this.el.setAttribute(\"style\", \"max-width:\" + width + \"px;\");\n", | |
"\n", | |
" // create the table\n", | |
" var df = JSON.parse(this.model.get('_df_json'));\n", | |
" var column_types = JSON.parse(this.model.get('_column_types_json'));\n", | |
" \n", | |
" grid = new dgrid.QGrid(table[0], df, column_types);\n", | |
" grid.initialize_slick_grid();\n", | |
"\n", | |
" // set up editing\n", | |
" var editable = this.model.get('editable');\n", | |
" if (!editable) {\n", | |
" return;\n", | |
" }\n", | |
"\n", | |
" var sgrid = grid.slick_grid;\n", | |
" sgrid.setOptions({'editable': true, \n", | |
" 'autoEdit': false});\n", | |
" var columns = sgrid.getColumns();\n", | |
" for (var i = 1; i < columns.length; i++) {\n", | |
" if (columns[i].type === 'date') {\n", | |
" columns[i].editor = DateEditor;\n", | |
" } else {\n", | |
" columns[i].editor = TextEditor;\n", | |
" }\n", | |
" if (columns[i].type === 'number') {\n", | |
" columns[i].validator = validateNumber;\n", | |
" } \n", | |
" }\n", | |
" sgrid.setColumns(columns);\n", | |
"\n", | |
" // set up callbacks\n", | |
" sgrid.onCellChange.subscribe(function(e, args) {\n", | |
" var column = columns[args.cell].name;\n", | |
" var msg = {'row': args.row, 'column': column,\n", | |
" 'value': args.item[column], 'type': 'cell_change'};\n", | |
" that.sendMsg(msg);\n", | |
" });\n", | |
"\n", | |
" // subscribe to incoming messages from the QGridWidget\n", | |
" this.model.on('change:py_msg', this.handleMsg, this);\n", | |
" },\n", | |
" \n", | |
" /**\n", | |
" * Handle messages from the QGridWidget.\n", | |
" */\n", | |
" handleMsg: function() {\n", | |
" var sgrid = grid.slick_grid;\n", | |
" var msg = this.model.get('py_msg');\n", | |
" msg = JSON.parse(msg);\n", | |
"\n", | |
" if (msg.type === 'remove_row') {\n", | |
" var row = sgrid.getActiveCell().row;\n", | |
" var data = sgrid.getData().getItem(row); \n", | |
" grid.data_view.deleteItem(data.id);\n", | |
" msg = {'type': 'remove_row', 'row': row, 'id': data.id};\n", | |
" this.sendMsg(msg);\n", | |
"\n", | |
" } else if (msg.type === 'add_row') {\n", | |
" var dd = sgrid.getData();\n", | |
" dd.addItem(msg);\n", | |
" dd.refresh();\n", | |
" }\n", | |
" },\n", | |
"\n", | |
" /**\n", | |
" * Send a message to the QGridWidget.\n", | |
" */\n", | |
" sendMsg: function(msg) {\n", | |
" // add a unique id and send the message\n", | |
" var s = [];\n", | |
" var hexDigits = \"0123456789ABCDEF\";\n", | |
" for (var i = 0; i < 32; i++) {\n", | |
" s[i] = hexDigits.charAt(Math.floor(Math.random() * 0x10));\n", | |
" }\n", | |
" msg.uid = s.join(\"\");\n", | |
" this.model.set('js_msg', JSON.stringify(msg));\n", | |
" this.touch();\n", | |
" }\n", | |
" });\n", | |
"\n", | |
" manager.WidgetManager.register_widget_view('QGridView', QGridView);\n", | |
"});" | |
], | |
"text/plain": [ | |
"<IPython.core.display.Javascript object>" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"%%javascript\n", | |
"require([\"widgets/js/widget\", \"widgets/js/manager\"], function(widget, manager){\n", | |
"\n", | |
" /**\n", | |
" * Date input handler.\n", | |
" *\n", | |
" * Adapted from https://github.com/mleibman/SlickGrid/blob/master/slick.editors.js\n", | |
" * MIT License, Copyright (c) 2010 Michael Leibman\n", | |
" */\n", | |
" function DateEditor(args) {\n", | |
" var $input;\n", | |
" var defaultValue;\n", | |
" var scope = this;\n", | |
" var calendarOpen = false;\n", | |
" this.init = function () {\n", | |
" $input = $(\"<INPUT type=text class='editor-text' />\");\n", | |
" $input.appendTo(args.container);\n", | |
" $input.focus().select();\n", | |
" $input.datepicker({\n", | |
" showOn: \"button\",\n", | |
" buttonImageOnly: true,\n", | |
" beforeShow: function () {\n", | |
" calendarOpen = true\n", | |
" },\n", | |
" onClose: function () {\n", | |
" calendarOpen = false\n", | |
" }\n", | |
" });\n", | |
"\n", | |
" $input.width($input.width() - 18);\n", | |
" };\n", | |
"\n", | |
" this.destroy = function () {\n", | |
" $.datepicker.dpDiv.stop(true, true);\n", | |
" $input.datepicker(\"hide\");\n", | |
" $input.datepicker(\"destroy\");\n", | |
" $input.remove();\n", | |
" };\n", | |
"\n", | |
" this.show = function () {\n", | |
" if (calendarOpen) {\n", | |
" $.datepicker.dpDiv.stop(true, true).show();\n", | |
" }\n", | |
" };\n", | |
"\n", | |
" this.hide = function () {\n", | |
" if (calendarOpen) {\n", | |
" $.datepicker.dpDiv.stop(true, true).hide();\n", | |
" }\n", | |
" };\n", | |
"\n", | |
" this.position = function (position) {\n", | |
" if (!calendarOpen) {\n", | |
" return;\n", | |
" }\n", | |
" $.datepicker.dpDiv\n", | |
" .css(\"top\", position.top + 30)\n", | |
" .css(\"left\", position.left);\n", | |
" };\n", | |
"\n", | |
" this.focus = function () {\n", | |
" $input.focus();\n", | |
" };\n", | |
"\n", | |
" this.loadValue = function (item) {\n", | |
" defaultValue = item[args.column.field];\n", | |
" $input.val(defaultValue);\n", | |
" $input[0].defaultValue = defaultValue;\n", | |
" $input.select();\n", | |
" };\n", | |
"\n", | |
" this.serializeValue = function () {\n", | |
" return $input.val();\n", | |
" };\n", | |
"\n", | |
" this.applyValue = function (item, state) {\n", | |
" item[args.column.field] = state;\n", | |
" };\n", | |
"\n", | |
" this.isValueChanged = function () {\n", | |
" return (!($input.val() == \"\" && defaultValue == null)) && ($input.val() != defaultValue);\n", | |
" };\n", | |
"\n", | |
" this.validate = function () {\n", | |
" return {\n", | |
" valid: true,\n", | |
" msg: null\n", | |
" };\n", | |
" };\n", | |
"\n", | |
" this.init();\n", | |
" }\n", | |
"\n", | |
" /**\n", | |
" * Text input handler.\n", | |
" *\n", | |
" * From https://github.com/mleibman/SlickGrid/blob/master/slick.editors.js\n", | |
" * MIT License, Copyright (c) 2010 Michael Leibman\n", | |
" */\n", | |
" function TextEditor(args) {\n", | |
" var $input;\n", | |
" var defaultValue;\n", | |
" var scope = this;\n", | |
"\n", | |
" this.init = function () {\n", | |
" $input = $(\"<INPUT type=text class='editor-text' />\")\n", | |
" .appendTo(args.container)\n", | |
" .bind(\"keydown.nav\", function (e) {\n", | |
" if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {\n", | |
" e.stopImmediatePropagation();\n", | |
" }\n", | |
" })\n", | |
" .focus()\n", | |
" .select();\n", | |
" };\n", | |
"\n", | |
" this.destroy = function () {\n", | |
" $input.remove();\n", | |
" };\n", | |
"\n", | |
" this.focus = function () {\n", | |
" $input.focus();\n", | |
" };\n", | |
"\n", | |
" this.getValue = function () {\n", | |
" return $input.val();\n", | |
" };\n", | |
"\n", | |
" this.setValue = function (val) {\n", | |
" $input.val(val);\n", | |
" };\n", | |
"\n", | |
" this.loadValue = function (item) {\n", | |
" defaultValue = item[args.column.field] || \"\";\n", | |
" $input.val(defaultValue);\n", | |
" $input[0].defaultValue = defaultValue;\n", | |
" $input.select();\n", | |
" };\n", | |
"\n", | |
" this.serializeValue = function () {\n", | |
" return $input.val();\n", | |
" };\n", | |
"\n", | |
" this.applyValue = function (item, state) {\n", | |
" item[args.column.field] = state;\n", | |
" };\n", | |
"\n", | |
" this.isValueChanged = function () {\n", | |
" return (!($input.val() == \"\" && defaultValue == null)) && ($input.val() != defaultValue);\n", | |
" };\n", | |
"\n", | |
" this.validate = function () {\n", | |
" if (args.column.validator) {\n", | |
" var validationResults = args.column.validator($input.val());\n", | |
" if (!validationResults.valid) {\n", | |
" return validationResults;\n", | |
" }\n", | |
" }\n", | |
"\n", | |
" return {\n", | |
" valid: true,\n", | |
" msg: null\n", | |
" };\n", | |
" };\n", | |
"\n", | |
" this.init();\n", | |
" }\n", | |
" \n", | |
" /**\n", | |
" * Validator for numeric cells.\n", | |
" */\n", | |
" function validateNumber(value) {\n", | |
" if (isNaN(value)) {\n", | |
" return {\n", | |
" valid: false,\n", | |
" msg: \"Please enter a valid integer\"\n", | |
" };\n", | |
" }\n", | |
" return {\n", | |
" valid: true,\n", | |
" msg: null\n", | |
" };\n", | |
" }\n", | |
"\n", | |
" var grid;\n", | |
"\n", | |
" var QGridView = widget.DOMWidgetView.extend({\n", | |
"\n", | |
" render: function() {\n", | |
" var that = this;\n", | |
" var cdn_base_url = \"https://cdn.rawgit.com/quantopian/qgrid/6988858671b6f80c7d1987e07ebff3dc810d20ce/qgrid/qgridjs/\";\n", | |
" \n", | |
" // Load the custom css\n", | |
" if ($(\"#dg-css\").length == 0){\n", | |
" $(\"head\").append([\n", | |
" \"<link href='\" + cdn_base_url + \"/lib/slick.grid.css' rel='stylesheet'>\",\n", | |
" \"<link href='\" + cdn_base_url + \"/lib/slick-default-theme.css' rel='stylesheet'>\",\n", | |
" \"<link href='http://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.4/css/jquery-ui.min.css' rel='stylesheet'>\",\n", | |
" \"<link id='dg-css' href='\" + cdn_base_url + \"/qgrid.css' rel='stylesheet'>\"\n", | |
" ]);\n", | |
" }\n", | |
" \n", | |
" // set the custom script load paths\n", | |
" var path_dictionary = {\n", | |
" jquery_drag: cdn_base_url + \"/lib/jquery.event.drag-2.2\",\n", | |
" slick_core: cdn_base_url + \"/lib/slick.core.2.2\",\n", | |
" slick_data_view: cdn_base_url + \"/lib/slick.dataview.2.2\",\n", | |
" slick_grid: cdn_base_url + \"/lib/slick.grid.2.2\",\n", | |
" data_grid: cdn_base_url + \"/qgrid\",\n", | |
" date_filter: cdn_base_url + \"/qgrid.datefilter\",\n", | |
" slider_filter: cdn_base_url + \"/qgrid.sliderfilter\",\n", | |
" filter_base: cdn_base_url + \"/qgrid.filterbase\",\n", | |
" handlebars: \"https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.min\"\n", | |
" };\n", | |
"\n", | |
" var existing_config = require.s.contexts._.config;\n", | |
" if (!existing_config.paths['underscore']){\n", | |
" path_dictionary['underscore'] = \"https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min\";\n", | |
" }\n", | |
"\n", | |
" if (!existing_config.paths['moment']){\n", | |
" path_dictionary['moment'] = \"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment.min\";\n", | |
" }\n", | |
"\n", | |
" if (!existing_config.paths['jqueryui']){\n", | |
" path_dictionary['jqueryui'] = \"https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/jquery-ui.min\";\n", | |
" }\n", | |
"\n", | |
" require.config({\n", | |
" paths: path_dictionary\n", | |
" });\n", | |
" \n", | |
" if (typeof jQuery === 'function') {\n", | |
" define('jquery', function() { return jQuery; });\n", | |
" }\n", | |
" \n", | |
" /**\n", | |
" * Load the required scripts and create the grid.\n", | |
" */\n", | |
" require([\n", | |
" 'jquery',\n", | |
" 'jquery_drag',\n", | |
" 'slick_core',\n", | |
" 'slick_data_view'\n", | |
" ],\n", | |
" function() {\n", | |
" require(['slick_grid'], function() {\n", | |
" require([\"data_grid\"], function(dgrid) {\n", | |
" that.setupQGrid(dgrid);\n", | |
" });\n", | |
" });\n", | |
" });\n", | |
" },\n", | |
"\n", | |
" /**\n", | |
" * Set up our QGrid and event handlers.\n", | |
" */\n", | |
" setupQGrid: function(dgrid) {\n", | |
" var that = this;\n", | |
" // set up the divs and styles\n", | |
" this.$el.addClass('q-grid-container');\n", | |
" var table = this.$el.append('div');\n", | |
" table.addClass('q-grid');\n", | |
" var width = this.el.clientWidth.toString();\n", | |
" this.el.setAttribute(\"style\", \"max-width:\" + width + \"px;\");\n", | |
"\n", | |
" // create the table\n", | |
" var df = JSON.parse(this.model.get('_df_json'));\n", | |
" var column_types = JSON.parse(this.model.get('_column_types_json'));\n", | |
" \n", | |
" grid = new dgrid.QGrid(table[0], df, column_types);\n", | |
" grid.initialize_slick_grid();\n", | |
"\n", | |
" // set up editing\n", | |
" var editable = this.model.get('editable');\n", | |
" if (!editable) {\n", | |
" return;\n", | |
" }\n", | |
"\n", | |
" var sgrid = grid.slick_grid;\n", | |
" sgrid.setOptions({'editable': true, \n", | |
" 'autoEdit': false});\n", | |
" var columns = sgrid.getColumns();\n", | |
" for (var i = 1; i < columns.length; i++) {\n", | |
" if (columns[i].type === 'date') {\n", | |
" columns[i].editor = DateEditor;\n", | |
" } else {\n", | |
" columns[i].editor = TextEditor;\n", | |
" }\n", | |
" if (columns[i].type === 'number') {\n", | |
" columns[i].validator = validateNumber;\n", | |
" } \n", | |
" }\n", | |
" sgrid.setColumns(columns);\n", | |
"\n", | |
" // set up callbacks\n", | |
" sgrid.onCellChange.subscribe(function(e, args) {\n", | |
" var column = columns[args.cell].name;\n", | |
" var msg = {'row': args.row, 'column': column,\n", | |
" 'value': args.item[column], 'type': 'cell_change'};\n", | |
" that.sendMsg(msg);\n", | |
" });\n", | |
"\n", | |
" // subscribe to incoming messages from the QGridWidget\n", | |
" this.model.on('change:py_msg', this.handleMsg, this);\n", | |
" },\n", | |
" \n", | |
" /**\n", | |
" * Handle messages from the QGridWidget.\n", | |
" */\n", | |
" handleMsg: function() {\n", | |
" var sgrid = grid.slick_grid;\n", | |
" var msg = this.model.get('py_msg');\n", | |
" msg = JSON.parse(msg);\n", | |
"\n", | |
" if (msg.type === 'remove_row') {\n", | |
" var row = sgrid.getActiveCell().row;\n", | |
" var data = sgrid.getData().getItem(row); \n", | |
" grid.data_view.deleteItem(data.id);\n", | |
" msg = {'type': 'remove_row', 'row': row, 'id': data.id};\n", | |
" this.sendMsg(msg);\n", | |
"\n", | |
" } else if (msg.type === 'add_row') {\n", | |
" var dd = sgrid.getData();\n", | |
" dd.addItem(msg);\n", | |
" dd.refresh();\n", | |
" }\n", | |
" },\n", | |
"\n", | |
" /**\n", | |
" * Send a message to the QGridWidget.\n", | |
" */\n", | |
" sendMsg: function(msg) {\n", | |
" // add a unique id and send the message\n", | |
" var s = [];\n", | |
" var hexDigits = \"0123456789ABCDEF\";\n", | |
" for (var i = 0; i < 32; i++) {\n", | |
" s[i] = hexDigits.charAt(Math.floor(Math.random() * 0x10));\n", | |
" }\n", | |
" msg.uid = s.join(\"\");\n", | |
" this.model.set('js_msg', JSON.stringify(msg));\n", | |
" this.touch();\n", | |
" }\n", | |
" });\n", | |
"\n", | |
" manager.WidgetManager.register_widget_view('QGridView', QGridView);\n", | |
"});\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 137, | |
"metadata": { | |
"collapsed": false | |
}, | |
"outputs": [], | |
"source": [ | |
"import json\n", | |
"from IPython.html import widgets\n", | |
"from IPython.display import display, Javascript\n", | |
"from IPython.utils.traitlets import Unicode, Integer, Instance, Bool, List\n", | |
"from pandas import DataFrame\n", | |
"import uuid\n", | |
"\n", | |
"class QGridWidget(widgets.DOMWidget):\n", | |
" _view_name = Unicode('QGridView', sync=True)\n", | |
" _df_json = Unicode('', sync=True)\n", | |
" _column_types_json = Unicode('', sync=True)\n", | |
" _loop_guard = Bool(False)\n", | |
" _index_name = Unicode('')\n", | |
" js_msg = Unicode('', sync=True)\n", | |
" py_msg = Unicode('', sync=True)\n", | |
" \n", | |
" df = Instance(DataFrame)\n", | |
" editable = Bool(True, sync=True)\n", | |
"\n", | |
" def _df_changed(self):\n", | |
" \"\"\"Build the Data Table for the DataFrame.\"\"\"\n", | |
" if self._loop_guard:\n", | |
" return\n", | |
" display(Javascript(JAVASCRIPT))\n", | |
" df = self.df.copy()\n", | |
" \n", | |
" if not df.index.name:\n", | |
" df.index.name = 'Index'\n", | |
"\n", | |
" if type(df.index) == pd.core.index.MultiIndex:\n", | |
" df.reset_index(inplace=True)\n", | |
" else:\n", | |
" df.insert(0, df.index.name, df.index)\n", | |
" \n", | |
" self._index_name = df.index.name\n", | |
"\n", | |
" tc = dict(np.typecodes)\n", | |
" for key in np.typecodes.keys():\n", | |
" if \"All\" in key:\n", | |
" del tc[key]\n", | |
"\n", | |
" column_types = []\n", | |
" for col_name, dtype in df.dtypes.iteritems():\n", | |
" column_type = {'field': col_name}\n", | |
" for type_name, type_codes in tc.items():\n", | |
" if dtype.kind in type_codes:\n", | |
" column_type['type'] = type_name\n", | |
" break\n", | |
" column_types.append(column_type)\n", | |
" self._column_types_json = json.dumps(column_types)\n", | |
"\n", | |
" precision = pd.get_option('display.precision') - 1\n", | |
" \n", | |
" self._df_json = df.to_json(\n", | |
" orient='records',\n", | |
" date_format='iso',\n", | |
" double_precision=precision,\n", | |
" )\n", | |
" \n", | |
" def _js_msg_changed(self):\n", | |
" \"\"\"Handle incoming messages from the QGridView\"\"\"\n", | |
" data = json.loads(self.js_msg)\n", | |
"\n", | |
" if data['type'] == 'remove_row':\n", | |
" self._loop_guard = True\n", | |
" if data['row'] == 0:\n", | |
" self.df = self.df[1:]\n", | |
" self.df = pd.concat((self.df[:data['row']], self.df[data['row'] + 1:]))\n", | |
" self._loop_guard = False\n", | |
"\n", | |
" elif data['type'] == 'cell_change':\n", | |
" try:\n", | |
" self.df.set_value(self.df.index[data['row']], data['column'],\n", | |
" data['value'])\n", | |
" except ValueError:\n", | |
" pass\n", | |
" \n", | |
" def add_row(self, value=None):\n", | |
" \"\"\"Append a row at the end of the dataframe.\"\"\"\n", | |
" df = self.df\n", | |
" if not df.index.is_integer():\n", | |
" msg = 'alert(\"Cannot add a row a table with a non-integer index\")'\n", | |
" display(Javascript(msg))\n", | |
" return\n", | |
" last = df.loc[df.index[-1], :]\n", | |
" last.name += 1\n", | |
" self._loop_guard = True\n", | |
" self.df = self.df.append(last)\n", | |
" self._loop_guard = False\n", | |
" precision = pd.get_option('display.precision') - 1\n", | |
" row_data = last.to_json(date_format='iso',\n", | |
" double_precision=precision)\n", | |
" msg = json.loads(row_data)\n", | |
" msg[self._index_name] = str(last.name)\n", | |
" msg['id'] = str(last.name)\n", | |
" msg['type'] = 'add_row'\n", | |
" self._send_msg(msg)\n", | |
" \n", | |
" def _send_msg(self, msg):\n", | |
" \"\"\"Send a message to the QGridView\"\"\"\n", | |
" msg['uid'] = uuid.uuid4().hex\n", | |
" self.py_msg = json.dumps(msg)\n", | |
" \n", | |
" def remove_row(self, value):\n", | |
" \"\"\"Remove the current row from the table\"\"\"\n", | |
" self._send_msg({'type': 'remove_row'})" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 138, | |
"metadata": { | |
"collapsed": false | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"/Users/ssilvester/anaconda/lib/python3.4/site-packages/pandas/core/internals.py:956: FutureWarning: comparison to `None` will result in an elementwise object comparison in the future.\n", | |
" return self._try_coerce_result(func(values, other))\n" | |
] | |
}, | |
{ | |
"data": { | |
"application/javascript": [ | |
"\n", | |
"\n", | |
"require([\"widgets/js/widget\", \"widgets/js/manager\"], function(widget, manager){\n", | |
"\n", | |
" var SlickGridView = widget.DOMWidgetView.extend({\n", | |
" \n", | |
" render: function(){\n", | |
" var that = this;\n", | |
" \n", | |
" function DateEditor(args) {\n", | |
" var $input;\n", | |
" var defaultValue;\n", | |
" var scope = this;\n", | |
" var calendarOpen = false;\n", | |
"\n", | |
" this.init = function () {\n", | |
" $input = $(\"<INPUT type=text class='editor-text' />\");\n", | |
" $input.appendTo(args.container);\n", | |
" $input.focus().select();\n", | |
" $input.datepicker({\n", | |
" showOn: \"button\",\n", | |
" buttonImageOnly: true,\n", | |
" beforeShow: function () {\n", | |
" calendarOpen = true\n", | |
" },\n", | |
" onClose: function () {\n", | |
" calendarOpen = false\n", | |
" }\n", | |
" });\n", | |
" $input.width($input.width() - 18);\n", | |
" };\n", | |
"\n", | |
" this.destroy = function () {\n", | |
" $.datepicker.dpDiv.stop(true, true);\n", | |
" $input.datepicker(\"hide\");\n", | |
" $input.datepicker(\"destroy\");\n", | |
" $input.remove();\n", | |
" };\n", | |
"\n", | |
" this.show = function () {\n", | |
" if (calendarOpen) {\n", | |
" $.datepicker.dpDiv.stop(true, true).show();\n", | |
" }\n", | |
" };\n", | |
"\n", | |
" this.hide = function () {\n", | |
" if (calendarOpen) {\n", | |
" $.datepicker.dpDiv.stop(true, true).hide();\n", | |
" }\n", | |
" };\n", | |
"\n", | |
" this.position = function (position) {\n", | |
" if (!calendarOpen) {\n", | |
" return;\n", | |
" }\n", | |
" $.datepicker.dpDiv\n", | |
" .css(\"top\", position.top + 30)\n", | |
" .css(\"left\", position.left);\n", | |
" };\n", | |
"\n", | |
" this.focus = function () {\n", | |
" $input.focus();\n", | |
" };\n", | |
"\n", | |
" this.loadValue = function (item) {\n", | |
" defaultValue = item[args.column.field];\n", | |
" $input.val(defaultValue);\n", | |
" $input[0].defaultValue = defaultValue;\n", | |
" $input.select();\n", | |
" };\n", | |
"\n", | |
" this.serializeValue = function () {\n", | |
" return $input.val();\n", | |
" };\n", | |
"\n", | |
" this.applyValue = function (item, state) {\n", | |
" item[args.column.field] = state;\n", | |
" };\n", | |
"\n", | |
" this.isValueChanged = function () {\n", | |
" return (!($input.val() == \"\" && defaultValue == null)) && ($input.val() != defaultValue);\n", | |
" };\n", | |
"\n", | |
" this.validate = function () {\n", | |
" return {\n", | |
" valid: true,\n", | |
" msg: null\n", | |
" };\n", | |
" };\n", | |
"\n", | |
" this.init();\n", | |
" }\n", | |
" \n", | |
" function TextEditor(args) {\n", | |
" var $input;\n", | |
" var defaultValue;\n", | |
" var scope = this;\n", | |
"\n", | |
" this.init = function () {\n", | |
" $input = $(\"<INPUT type=text class='editor-text' />\")\n", | |
" .appendTo(args.container)\n", | |
" .bind(\"keydown.nav\", function (e) {\n", | |
" if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {\n", | |
" e.stopImmediatePropagation();\n", | |
" }\n", | |
" })\n", | |
" .focus()\n", | |
" .select();\n", | |
" };\n", | |
"\n", | |
" this.destroy = function () {\n", | |
" $input.remove();\n", | |
" };\n", | |
"\n", | |
" this.focus = function () {\n", | |
" $input.focus();\n", | |
" };\n", | |
"\n", | |
" this.getValue = function () {\n", | |
" return $input.val();\n", | |
" };\n", | |
"\n", | |
" this.setValue = function (val) {\n", | |
" $input.val(val);\n", | |
" };\n", | |
"\n", | |
" this.loadValue = function (item) {\n", | |
" defaultValue = item[args.column.field] || \"\";\n", | |
" $input.val(defaultValue);\n", | |
" $input[0].defaultValue = defaultValue;\n", | |
" $input.select();\n", | |
" };\n", | |
"\n", | |
" this.serializeValue = function () {\n", | |
" return $input.val();\n", | |
" };\n", | |
"\n", | |
" this.applyValue = function (item, state) {\n", | |
" item[args.column.field] = state;\n", | |
" };\n", | |
"\n", | |
" this.isValueChanged = function () {\n", | |
" return (!($input.val() == \"\" && defaultValue == null)) && ($input.val() != defaultValue);\n", | |
" };\n", | |
"\n", | |
" this.validate = function () {\n", | |
" if (args.column.validator) {\n", | |
" var validationResults = args.column.validator($input.val());\n", | |
" if (!validationResults.valid) {\n", | |
" return validationResults;\n", | |
" }\n", | |
" }\n", | |
"\n", | |
" return {\n", | |
" valid: true,\n", | |
" msg: null\n", | |
" };\n", | |
" };\n", | |
"\n", | |
" this.init();\n", | |
" }\n", | |
" \n", | |
" var cdn_base_url = \"https://cdn.rawgit.com/quantopian/qgrid/6988858671b6f80c7d1987e07ebff3dc810d20ce/qgrid/qgridjs/\";\n", | |
" \n", | |
" if ($(\"#dg-css\").length == 0){\n", | |
" $(\"head\").append([\n", | |
" \"<link href='\" + cdn_base_url + \"/lib/slick.grid.css' rel='stylesheet'>\",\n", | |
" \"<link href='\" + cdn_base_url + \"/lib/slick-default-theme.css' rel='stylesheet'>\",\n", | |
" \"<link href='http://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.4/css/jquery-ui.min.css' rel='stylesheet'>\",\n", | |
" \"<link id='dg-css' href='\" + cdn_base_url + \"/qgrid.css' rel='stylesheet'>\"\n", | |
" ]);\n", | |
" }\n", | |
" \n", | |
" var path_dictionary = {\n", | |
" jquery_drag: cdn_base_url + \"/lib/jquery.event.drag-2.2\",\n", | |
" slick_core: cdn_base_url + \"/lib/slick.core.2.2\",\n", | |
" slick_data_view: cdn_base_url + \"/lib/slick.dataview.2.2\",\n", | |
" slick_grid: cdn_base_url + \"/lib/slick.grid.2.2\",\n", | |
" data_grid: cdn_base_url + \"/qgrid\",\n", | |
" date_filter: cdn_base_url + \"/qgrid.datefilter\",\n", | |
" slider_filter: cdn_base_url + \"/qgrid.sliderfilter\",\n", | |
" filter_base: cdn_base_url + \"/qgrid.filterbase\",\n", | |
" handlebars: \"https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.min\"\n", | |
" };\n", | |
"\n", | |
" var existing_config = require.s.contexts._.config;\n", | |
" if (!existing_config.paths['underscore']){\n", | |
" path_dictionary['underscore'] = \"https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min\";\n", | |
" }\n", | |
"\n", | |
" if (!existing_config.paths['moment']){\n", | |
" path_dictionary['moment'] = \"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment.min\";\n", | |
" }\n", | |
"\n", | |
" if (!existing_config.paths['jqueryui']){\n", | |
" path_dictionary['jqueryui'] = \"https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/jquery-ui.min\";\n", | |
" }\n", | |
"\n", | |
" require.config({\n", | |
" paths: path_dictionary\n", | |
" });\n", | |
" \n", | |
" if (typeof jQuery === 'function') {\n", | |
" define('jquery', function() { return jQuery; });\n", | |
" }\n", | |
" \n", | |
" function validateNumber(value) {\n", | |
" if (isNaN(value)) {\n", | |
" return {\n", | |
" valid: false,\n", | |
" msg: \"Please enter a valid integer\"\n", | |
" };\n", | |
" }\n", | |
" return {\n", | |
" valid: true,\n", | |
" msg: null\n", | |
" };\n", | |
" };\n", | |
" \n", | |
" function setupDGrid(dgrid) {\n", | |
" // set up the divs and styles\n", | |
" that.$el.addClass('q-grid-container');\n", | |
" var table = that.$el.append('div');\n", | |
" table.addClass('q-grid');\n", | |
" var width = that.el.clientWidth.toString();\n", | |
" that.el.setAttribute(\"style\", \"max-width:\" + width + \"px;\");\n", | |
"\n", | |
" // create the table\n", | |
" var df = JSON.parse(that.model.get('_df_json'));\n", | |
" var column_types = JSON.parse(that.model.get('_column_types_json'));\n", | |
" var grid = new dgrid.QGrid(table[0], df, column_types);\n", | |
" grid.initialize_slick_grid();\n", | |
"\n", | |
" // set up editing\n", | |
" var editable = that.model.get('editable');\n", | |
" if (!editable) {\n", | |
" return;\n", | |
" }\n", | |
"\n", | |
" var sgrid = grid.slick_grid;\n", | |
" sgrid.setOptions({'editable': true, \n", | |
" 'autoEdit': false});\n", | |
" var columns = sgrid.getColumns();\n", | |
" for (var i = 0; i < columns.length; i++) {\n", | |
" if (columns[i].type === 'date') {\n", | |
" columns[i].editor = DateEditor;\n", | |
" } else {\n", | |
" columns[i].editor = TextEditor;\n", | |
" }\n", | |
" if (columns[i].type === 'number') {\n", | |
" columns[i].validator = validateNumber;\n", | |
" } \n", | |
" }\n", | |
" sgrid.setColumns(columns);\n", | |
"\n", | |
" // set up callbacks\n", | |
" sgrid.onCellChange.subscribe(function(e, args) {\n", | |
" var column = columns[args.cell].name;\n", | |
" var msg = {'row': args.row, 'column': column,\n", | |
" 'value': args.item[column], 'type': 'cell_change'};\n", | |
" sendMsg(msg);\n", | |
" });\n", | |
" \n", | |
" function sendMsg(msg) {\n", | |
" // add a unique id and send the message\n", | |
" var s = [];\n", | |
" var hexDigits = \"0123456789ABCDEF\";\n", | |
" for (var i = 0; i < 32; i++) {\n", | |
" s[i] = hexDigits.charAt(Math.floor(Math.random() * 0x10));\n", | |
" }\n", | |
" msg.uid = s.join(\"\");\n", | |
" that.model.set('js_msg', JSON.stringify(msg));\n", | |
" that.touch();\n", | |
" }\n", | |
" \n", | |
" function handleMsg() {\n", | |
" var msg = that.model.get('py_msg');\n", | |
" msg = JSON.parse(msg);\n", | |
" \n", | |
" if (msg.type === 'remove_row') {\n", | |
" var row = sgrid.getActiveCell().row;\n", | |
" var data = sgrid.getData().getItem(row); \n", | |
" grid.data_view.deleteItem(data.id);\n", | |
" msg = {'type': 'remove_row', 'row': row, 'id': data.id};\n", | |
" sendMsg(msg);\n", | |
"\n", | |
" } else if (msg.type === 'add_row') {\n", | |
" var dd = sgrid.getData();\n", | |
" dd.addItem(msg);\n", | |
" dd.refresh();\n", | |
" }\n", | |
" }\n", | |
" that.model.on('change:py_msg', handleMsg, that);\n", | |
" };\n", | |
" \n", | |
" require([\n", | |
" 'jquery',\n", | |
" 'jquery_drag',\n", | |
" 'slick_core',\n", | |
" 'slick_data_view'\n", | |
" ],\n", | |
" function() {\n", | |
" require(['slick_grid'], function() {\n", | |
" require([\"data_grid\"], function(dgrid) {\n", | |
" setupDGrid(dgrid);\n", | |
" });\n", | |
" });\n", | |
" });\n", | |
" }\n", | |
" });\n", | |
" manager.WidgetManager.register_widget_view('SlickGridView', SlickGridView);\n", | |
"});" | |
], | |
"text/plain": [ | |
"<IPython.core.display.Javascript object>" | |
] | |
}, | |
"metadata": {}, | |
"output_type": "display_data" | |
} | |
], | |
"source": [ | |
"from IPython.html.widgets import Button, HBox\n", | |
"from IPython.display import display\n", | |
"\n", | |
"# create a visualization for the dataframe\n", | |
"grid = QGridWidget(df=spy)\n", | |
"\n", | |
"add_row = Button(description=\"Add Row\")\n", | |
"add_row.on_click(grid.add_row)\n", | |
"\n", | |
"rem_row = Button(description=\"Remove Row\")\n", | |
"rem_row.on_click(grid.remove_row)\n", | |
"\n", | |
"display(HBox((add_row, rem_row)), grid)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 122, | |
"metadata": { | |
"collapsed": false, | |
"scrolled": false | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
" Date Open High Low Close Volume \\\n", | |
"0 2011-01-03 115.648405 116.460708 114.726575 115.958727 138725200 \n", | |
"1 2011-01-04 116.214283 100.000000 115.173803 115.894838 137409700 \n", | |
"3 2011-01-06 116.542855 116.670633 115.922218 116.269042 122519000 \n", | |
"4 2011-01-07 116.424201 116.615867 115.137295 116.040867 156034600 \n", | |
"5 2011-01-10 115.529756 116.059125 115.182925 115.894838 122401700 \n", | |
"\n", | |
" Adj_Ratio \n", | |
"0 0.912701 \n", | |
"1 0.912701 \n", | |
"3 0.912701 \n", | |
"4 0.912701 \n", | |
"5 0.912701 " | |
] | |
}, | |
"execution_count": 122, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"grid.df.head()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": true | |
}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.4.3" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 0 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment