From 1f90a290746d42a3c39a580396b411313cb8e61b Mon Sep 17 00:00:00 2001 From: minjk-bl Date: Thu, 20 May 2021 12:35:40 +0900 Subject: [PATCH 1/5] #22 - subset preview bug fix --- src/common/component/vpVarSelector.js | 2 +- src/common/vpSubsetEditor.js | 53 +++++++++++++++------------ 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/common/component/vpVarSelector.js b/src/common/component/vpVarSelector.js index ba189f25..b8cbfb15 100644 --- a/src/common/component/vpVarSelector.js +++ b/src/common/component/vpVarSelector.js @@ -137,7 +137,7 @@ define([ // render variable list that.loadVariableList(varList); } catch (ex) { - console.log(ex); + // console.log(ex); } }); } diff --git a/src/common/vpSubsetEditor.js b/src/common/vpSubsetEditor.js index 20727940..10f1badb 100644 --- a/src/common/vpSubsetEditor.js +++ b/src/common/vpSubsetEditor.js @@ -1626,32 +1626,37 @@ define([ if (!this.codepreview) { // var previewTextarea = $('#vp_previewCode')[0]; - var previewTextarea = $(this.wrapSelector('#vp_previewCode'))[0]; - // if (wrappedTextarea) { - // previewTextarea = wrappedTextarea; + // var previewTextarea = $(this.wrapSelector('#vp_previewCode'))[0]; + var previewTextarea = $(this.wrapSelector('textarea'))[0]; + // if (!previewTextarea) { + // previewTextarea = $('#vp_previewCode')[0]; // } - // set codemirror - this.codepreview = codemirror.fromTextArea(previewTextarea, { - mode: { - name: 'python', - version: 3, - singleLineStringErrors: false - }, // text-cell(markdown cell) set to 'htmlmixed' - height: '100%', - width: '100%', - indentUnit: 4, - matchBrackets: true, - readOnly:true, - autoRefresh: true, - // lineWrapping: false, // text-cell(markdown cell) set to true - // indentWithTabs: true, - theme: "ipython", - extraKeys: {"Enter": "newlineAndIndentContinueMarkdownList"}, - scrollbarStyle: "null" - }); - this.setPreview('# Code Preview'); + if (previewTextarea) { + // set codemirror + this.codepreview = codemirror.fromTextArea(previewTextarea, { + mode: { + name: 'python', + version: 3, + singleLineStringErrors: false + }, // text-cell(markdown cell) set to 'htmlmixed' + height: '100%', + width: '100%', + indentUnit: 4, + matchBrackets: true, + readOnly:true, + autoRefresh: true, + // lineWrapping: false, // text-cell(markdown cell) set to true + // indentWithTabs: true, + theme: "ipython", + extraKeys: {"Enter": "newlineAndIndentContinueMarkdownList"}, + scrollbarStyle: "null" + }); + this.setPreview('# Code Preview'); + this.codepreview.refresh(); + } + } else { + this.codepreview.refresh(); } - this.codepreview.refresh(); // reload pandasObject on open this.loadVariables(); From 99d33f99e43a01ec6558f7ae351f5f6b209fa7e6 Mon Sep 17 00:00:00 2001 From: minjk-bl Date: Fri, 21 May 2021 19:54:58 +0900 Subject: [PATCH 2/5] #24 - frame editor prototype --- css/component/common.css | 4 + css/pandas/frameEditor.css | 114 ++++++++++ data/libraries.xml | 7 +- src/pandas/frameEditor.js | 411 +++++++++++++++++++++++++++++++++++++ 4 files changed, 535 insertions(+), 1 deletion(-) create mode 100644 css/pandas/frameEditor.css create mode 100644 src/pandas/frameEditor.js diff --git a/css/component/common.css b/css/component/common.css index cac284ce..8864552f 100644 --- a/css/component/common.css +++ b/css/component/common.css @@ -22,6 +22,10 @@ supported by Chrome, Edge, Opera and Firefox */ } +.vp-button:hover { + background: #F8F8F8; +} + .vp-button.cancel { background: #E5E5E5; } diff --git a/css/pandas/frameEditor.css b/css/pandas/frameEditor.css new file mode 100644 index 00000000..59ff7eb7 --- /dev/null +++ b/css/pandas/frameEditor.css @@ -0,0 +1,114 @@ + +.vp-fe { + display: grid; + grid-row-gap: 5px; +} + +.vp-fe-df-box { + /* margin-bottom: 5px; */ +} + +.vp-fe #vp_feVariable { + width: 125px; + margin-left: 5px; +} + +.vp-fe-df-refresh { + cursor: pointer; +} + +.vp-fe-menu-box { + display: grid; + grid-template-columns: repeat(6, 1fr); +} + +.vp-fe-menu-box .vp-fe-menu-item { + height: 30px; + background: #FFFFFF; + border: 0.25px solid #E4E4E4; + box-sizing: border-box; + box-shadow: 1px 1px 2px rgb(0 0 0 / 10%); + border-radius: 2px; + line-height: 30px; + font-size: 11px; + text-align: center; + color: #696969; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + text-overflow: ellipsis; + /* overflow: hidden; */ + white-space: nowrap; + padding: 0px 5px; +} + +.vp-fe-menu-box .vp-fe-menu-item:hover { + background: #F8F8F8; +} + +.vp-fe-info { + margin: 0px; +} +.vp-fe-info .vp-accordion-header { + background: #F5F5F5; +} + +.vp-fe-table { + height: 350px; + background: var(--border-gray-color); + overflow: auto; +} +.vp-fe-table table { + border-collapse: separate; + margin-top: 0px; + margin-left: 0px; +} +.vp-fe-table thead th { + position: sticky; + top: 0; + background: #FFFFFF; + border-bottom: 1px solid #000000; +} + +.vp-fe-table tbody tr:nth-child(odd) { + background: #F5F5F5; +} +.vp-fe-table tbody tr:nth-child(even) { + background: #FFFFFF; +} + +.vp-fe-table th.selected { + color: var(--hightlight-color); +} + +.vp-fe-table th:hover { + cursor: pointer; + /* background: var(--light-gray-color); */ + /* background: rgba(66, 165, 245, 0.2); */ +} + +/* row hover */ +.vp-fe-table tbody tr:hover { + background-color: rgba(66, 165, 245, 0.2); +} + +/* column hover */ +/* .vp-fe-table thead th:not(:first-child):hover::after { + background: rgba(66, 165, 245, 0.2); + content: ''; + height: 100000px; + left: 0; + position: absolute; + top: -5000px; + width: 100%; + z-index: -1; +} */ + +.vp-fe-table-more { + margin: 5px; +} \ No newline at end of file diff --git a/data/libraries.xml b/data/libraries.xml index 574a765e..4294629e 100644 --- a/data/libraries.xml +++ b/data/libraries.xml @@ -1221,11 +1221,16 @@ 데이터를 차트로 표현하는 함수 pandas/plot.js + + visualpython - pandas - subset + frame editor + pandas/frameEditor.js + visualpython - pandas - subset subset dataframe pandas/subset.js - + visualpython - pandas - input output diff --git a/src/pandas/frameEditor.js b/src/pandas/frameEditor.js new file mode 100644 index 00000000..5c68dd5a --- /dev/null +++ b/src/pandas/frameEditor.js @@ -0,0 +1,411 @@ +define([ + 'require' + , 'jquery' + , 'nbextensions/visualpython/src/common/vpCommon' + , 'nbextensions/visualpython/src/common/constant' + , 'nbextensions/visualpython/src/common/StringBuilder' + , 'nbextensions/visualpython/src/common/vpFuncJS' + , 'nbextensions/visualpython/src/common/kernelApi' +], function (requirejs, $, vpCommon, vpConst, sb, vpFuncJS, kernelApi) { + // 옵션 속성 + const funcOptProp = { + stepCount : 1 + , funcName : "Frame Editor" + , funcID : "pd_frameEditor" + , libID : "pd000" + } + + const VP_FE = 'vp-fe'; + const VP_FE_DF_BOX = 'vp-fe-df-box'; + const VP_FE_DF_REFRESH = 'vp-fe-df-refresh'; + + const VP_FE_MENU_BOX = 'vp-fe-menu-box'; + const VP_FE_MENU_ITEM = 'vp-fe-menu-item'; + + const VP_FE_TABLE = 'vp-fe-table'; + const VP_FE_TABLE_MORE = 'vp-fe-table-more'; + + const TABLE_LINES = 5; + + const FRAME_EDIT_TYPE = { + INIT: 0, + DELETE: 1, + RENAME: 2, + DROP_NA: 3, + DROP_DUP: 4, + ONE_HOT_ENCODING: 5, + SET_IDX: 6, + REPLACE: 7, + + ADD_COL: 8, + ADD_ROW: 9, + SHOW: 10 + } + + /** + * html load 콜백. 고유 id 생성하여 부과하며 js 객체 클래스 생성하여 컨테이너로 전달 + * @param {function} callback 호출자(컨테이너) 의 콜백함수 + */ + var optionLoadCallback = function(callback, meta) { + // document.getElementsByTagName("head")[0].appendChild(link); + // 컨테이너에서 전달된 callback 함수가 존재하면 실행. + if (typeof(callback) === 'function') { + var uuid = vpCommon.getUUID(); + // 최대 10회 중복되지 않도록 체크 + for (var idx = 0; idx < 10; idx++) { + // 이미 사용중인 uuid 인 경우 다시 생성 + if ($(vpConst.VP_CONTAINER_ID).find("." + uuid).length > 0) { + uuid = vpCommon.getUUID(); + } + } + $(vpCommon.wrapSelector(vpCommon.formatString("#{0}", vpConst.OPTION_GREEN_ROOM))).find(vpCommon.formatString(".{0}", vpConst.API_OPTION_PAGE)).addClass(uuid); + + // 옵션 객체 생성 + var pdPackage = new PandasPackage(uuid); + pdPackage.metadata = meta; + + // 옵션 속성 할당. + pdPackage.setOptionProp(funcOptProp); + // html 설정. + pdPackage.initHtml(); + callback(pdPackage); // 공통 객체를 callback 인자로 전달 + } + } + + /** + * html 로드. + * @param {function} callback 호출자(컨테이너) 의 콜백함수 + */ + var initOption = function(callback, meta) { + vpCommon.loadHtml(vpCommon.wrapSelector(vpCommon.formatString("#{0}", vpConst.OPTION_GREEN_ROOM)), "pandas/common/commonEmptyPage.html", optionLoadCallback, callback, meta); + } + + /** + * 본 옵션 처리 위한 클래스 + * @param {String} uuid 고유 id + */ + var PandasPackage = function(uuid) { + this.uuid = uuid; // Load html 영역의 uuid. + // pandas 함수 + this.package = { + input: [ + { name: 'vp_feVariable' } + ] + } + this.state = { + origin_obj: '', + temp_obj: '', + selected: '', + axis: 0, + lines: TABLE_LINES, + steps: [] + } + } + + + /** + * vpFuncJS 에서 상속 + */ + PandasPackage.prototype = Object.create(vpFuncJS.VpFuncJS.prototype); + + /** + * 유효성 검사 + * @returns 유효성 검사 결과. 적합시 true + */ + PandasPackage.prototype.optionValidation = function() { + return true; + + // 부모 클래스 유효성 검사 호출. + // vpFuncJS.VpFuncJS.prototype.optionValidation.apply(this); + } + + + /** + * html 내부 binding 처리 + */ + PandasPackage.prototype.initHtml = function() { + this.loadCss(Jupyter.notebook.base_url + vpConst.BASE_PATH + vpConst.STYLE_PATH + "pandas/commonPandas.css"); + this.loadCss(Jupyter.notebook.base_url + vpConst.BASE_PATH + vpConst.STYLE_PATH + "pandas/frameEditor.css"); + this.renderThis(); + this.bindEvents(); + + this.loadVariableList(); + } + + PandasPackage.prototype.renderThis = function() { + var page = new sb.StringBuilder(); + page.appendFormatLine('
', VP_FE); + // Select DataFrame + page.appendFormatLine('
', VP_FE_DF_BOX); + page.appendFormatLine('', 'vp_feVariable', 'vp-orange-text', 'DataFrame'); + page.appendFormatLine(''); + page.appendFormatLine('', VP_FE_DF_REFRESH, 'fa fa-refresh'); + page.appendLine('
'); + + // Menus + page.appendFormatLine('
', VP_FE_MENU_BOX); + // menu 1. delete + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-delete', FRAME_EDIT_TYPE.DELETE, 'Delete'); + // menu 2. rename + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-rename', FRAME_EDIT_TYPE.RENAME, 'Rename'); + // menu 3. drop + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-drop', FRAME_EDIT_TYPE.DROP_NA, 'Drop'); //TODO: NA & Duplicate selection needed + // menu 4. one-hot encoding + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-ohe', FRAME_EDIT_TYPE.ONE_HOT_ENCODING, 'One-Hot Encoding'); + // menu 5. set/reset index + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-index', FRAME_EDIT_TYPE.SET_IDX, 'Set Index'); + // menu 6. replace + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-replace', FRAME_EDIT_TYPE.REPLACE, 'Replace'); + + page.appendLine('
'); // End of Menus + + // Info Box + var infoBox = this.createOptionContainer('Column Info'); + infoBox.addClass('vp-fe-info'); + // TODO: get selected columns info + infoBox.appendContent('column info'); // value_counts() + page.appendFormatLine('{0}', infoBox.toTagString()); + + // Table + page.appendFormatLine('
', VP_FE_TABLE); + + page.appendLine('
'); // End of Table + + page.appendLine('
'); + + this.setPage(page.toString()); + } + + PandasPackage.prototype.loadVariableList = function() { + var that = this; + // load using kernel + var dataTypes = ['DataFrame']; + kernelApi.searchVarList(dataTypes, function(result) { + try { + var varList = JSON.parse(result); + // render variable list + // replace + $(that.wrapSelector('#vp_feVariable')).replaceWith(function() { + return that.renderVariableList(varList); + }); + $(that.wrapSelector('#vp_feVariable')).trigger('change'); + } catch (ex) { + // console.log(ex); + } + }); + } + + PandasPackage.prototype.renderVariableList = function(varList) { + var tag = new sb.StringBuilder(); + var beforeValue = $(this.wrapSelector('#vp_feVariable')).val(); + tag.appendFormatLine(''); // VP_VS_VARIABLES + return tag.toString(); + } + + PandasPackage.prototype.renderTable = function(renderedText, isHtml=true) { + var tag = new sb.StringBuilder(); + // Table + tag.appendFormatLine('
', VP_FE_TABLE, 'rendered_html'); + if (isHtml) { + var renderedTable = $(renderedText).find('table'); + tag.appendFormatLine('{0}
', renderedTable.html()); + // More button + tag.appendFormatLine('
More...
', VP_FE_TABLE_MORE, 'vp-button'); + } else { + tag.appendFormatLine('
{0}
', renderedText); + } + tag.appendLine('
'); // End of Table + return tag.toString(); + } + + PandasPackage.prototype.loadCode = function(type) { + var that = this; + + var tempObj = this.state.temp_obj; + var orgObj = this.state.origin_obj; + var selectedName = this.state.selected; + var axis = this.state.axis; + var lines = this.state.lines; + + var code = new sb.StringBuilder(); + switch (parseInt(type)) { + case FRAME_EDIT_TYPE.INIT: + code.appendFormatLine('{0} = {1}.copy()', tempObj, orgObj); + break; + case FRAME_EDIT_TYPE.DELETE: + code.appendFormatLine("{0}.drop(['{1}'], axis={2}, inplace=True)", tempObj, selectedName, axis); + break; + case FRAME_EDIT_TYPE.RENAME: + break; + case FRAME_EDIT_TYPE.DROP_NA: + var locObj = ''; + if (axis == 0) { + locObj = vpCommon.formatString('.loc[{0},]', selectedName); + } else { + locObj = vpCommon.formatString('.loc[,{0}]', selectedName); + } + code.appendFormatLine("{0}{1}.dropna(axis={2}, inplace=True)", tempObj, locObj, axis); + break; + case FRAME_EDIT_TYPE.DROP_DUP: + if (axis == 0) { + locObj = vpCommon.formatString('.loc[{0},]', selectedName); + } else { + locObj = vpCommon.formatString('.loc[,{0}]', selectedName); + } + code.appendFormatLine("{0}{1}.drop_duplicates(axis={2}, inplace=True)", tempObj, locObj, axis); + break; + case FRAME_EDIT_TYPE.ONE_HOT_ENCODING: + break; + case FRAME_EDIT_TYPE.SET_IDX: + break; + case FRAME_EDIT_TYPE.REPLACE: + break; + case FRAME_EDIT_TYPE.SHOW: + break; + } + code.appendFormat('{0}.head({1})', tempObj, lines); + + Jupyter.notebook.kernel.execute( + code.toString(), + { + iopub: { + output: function(msg) { + if (msg.content.data) { + var htmlText = String(msg.content.data["text/html"]); + var codeText = String(msg.content.data["text/plain"]); + if (htmlText != 'undefined') { + $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { + return that.renderTable(htmlText); + }); + } else if (codeText != 'undefined') { + // plain text as code + $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { + return that.renderTable(codeText, false); + }); + } else { + $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { + return that.renderTable(''); + }); + } + that.state.steps.push(code.toString()); + } else { + var errorContent = new sb.StringBuilder(); + if (msg.content.ename) { + errorContent.appendFormatLine('
', VP_DS_DATA_ERROR_BOX); + errorContent.appendLine(''); + errorContent.appendFormatLine('' + , VP_DS_DATA_ERROR_BOX_TITLE, msg.content.ename); + if (msg.content.evalue) { + // errorContent.appendLine('
'); + errorContent.appendFormatLine('
{0}
', msg.content.evalue.split('\\n').join('
')); + } + errorContent.appendLine('
'); + } + $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { + return that.renderTable(errorContent); + }); + } + } + } + }, + { silent: false, store_history: true, stop_on_error: true } + ); + } + + + PandasPackage.prototype.bindEvents = function() { + var that = this; + + // select df + $(document).on('change', this.wrapSelector('#vp_feVariable'), function() { + // set temporary df + var origin = $(this).val(); + + // initialize state values + that.state.origin_obj = origin; + that.state.temp_obj = '_vp_temp_' + origin; + that.state.selected = ''; + that.state.axis = 0; + that.state.lines = TABLE_LINES; + that.state.steps = []; + + // load code with temporary df + that.loadCode(FRAME_EDIT_TYPE.INIT); + }); + + // refresh df + $(document).on('click', this.wrapSelector('.vp-fe-df-refresh'), function() { + that.loadVariableList(); + }); + + // select column + $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' thead th'), function() { + $(that.wrapSelector('.' + VP_FE_TABLE + ' th')).removeClass('selected'); + $(this).addClass('selected'); + + that.state.axis = 1; // column + that.state.selected = $(this).text(); + }); + + // select row + $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' tbody th'), function() { + $(that.wrapSelector('.' + VP_FE_TABLE + ' th')).removeClass('selected'); + $(this).addClass('selected'); + + that.state.axis = 0; // index(row) + that.state.selected = $(this).text(); + }); + + // more rows + $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE_MORE), function() { + that.state.lines += TABLE_LINES; + that.loadCode(FRAME_EDIT_TYPE.SHOW); + }); + + // click button + $(document).on('click', this.wrapSelector('.' + VP_FE_MENU_ITEM), function() { + var editType = $(this).attr('data-type'); + that.loadCode(editType); + }); + } + + /** + * 코드 생성 + * @param {boolean} exec 실행여부 + */ + PandasPackage.prototype.generateCode = function(addCell, exec) { + + var sbCode = new sb.StringBuilder(); + + + // 코드 생성 + var result = this.state.steps.join('\n'); + if (result == null) return "BREAK_RUN"; // 코드 생성 중 오류 발생 + sbCode.append(result); + + + if (addCell) this.cellExecute(sbCode.toString(), exec); + + return sbCode.toString(); + } + + return { + initOption: initOption + }; +}); \ No newline at end of file From c5503a915e1284f628dea5507c6f39bd5d221b3d Mon Sep 17 00:00:00 2001 From: minjk-bl Date: Mon, 24 May 2021 17:22:22 +0900 Subject: [PATCH 3/5] #31 - frame editor as popup --- css/{pandas => common}/frameEditor.css | 79 ++++- src/common/vpFrameEditor.js | 437 +++++++++++++++++++++++++ src/pandas/common/commonPandas.js | 23 ++ src/pandas/frameEditor.js | 321 ++---------------- 4 files changed, 560 insertions(+), 300 deletions(-) rename css/{pandas => common}/frameEditor.css (61%) create mode 100644 src/common/vpFrameEditor.js diff --git a/css/pandas/frameEditor.css b/css/common/frameEditor.css similarity index 61% rename from css/pandas/frameEditor.css rename to css/common/frameEditor.css index 59ff7eb7..11371fa2 100644 --- a/css/pandas/frameEditor.css +++ b/css/common/frameEditor.css @@ -1,5 +1,61 @@ - .vp-fe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + z-index: 1200; + + background-color: rgba(0,0,0,.4); +} + +.vp-fe-button { + width: 45px; +} + +.vp-fe-container { + position: relative; + left: 50%; + top: 50%; + transform:translate(-50%, -50%); + + min-width: 400px; + min-height: 400px; + width: 90%; + height: 90%; + + background-color: white; +} + +.vp-fe-title { + height: 30px; + padding: 5px 0px 5px 10px; + + background-color: #EEE; + border: 1px solid #ddd;; + display: flex; + flex-direction: row; + position: relative; + + font-weight: 700; +} + +.vp-fe-close { + position: fixed; + z-index: 3; + right: 5px; + width: 20px; + height: 20px; + line-height: 20px; + top: 5px; + text-align: center; +} + +.vp-fe-body { + width: 100%; + height: 100%; + padding: 10px; display: grid; grid-row-gap: 5px; } @@ -111,4 +167,25 @@ .vp-fe-table-more { margin: 5px; +} + +.vp-fe-info { + width: 100%; +} + +.vp-fe-info-content { + width: 100%; + overflow: auto; +} + +.vp-fe-info-content pre { + width: 300px; + padding:10px; + white-space: pre-wrap; + overflow: auto; + white-space: pre-wrap; /* CSS3*/ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-all; /* Internet Explorer 5.5+ */ } \ No newline at end of file diff --git a/src/common/vpFrameEditor.js b/src/common/vpFrameEditor.js new file mode 100644 index 00000000..d77cab06 --- /dev/null +++ b/src/common/vpFrameEditor.js @@ -0,0 +1,437 @@ +define([ + 'require' + , 'jquery' + , 'nbextensions/visualpython/src/common/constant' + , 'nbextensions/visualpython/src/common/StringBuilder' + , 'nbextensions/visualpython/src/common/vpCommon' + , 'nbextensions/visualpython/src/common/component/vpSuggestInputText' + , 'nbextensions/visualpython/src/pandas/common/pandasGenerator' + , 'nbextensions/visualpython/src/common/component/vpVarSelector' + , 'nbextensions/visualpython/src/common/kernelApi' + + , 'codemirror/lib/codemirror' + , 'codemirror/mode/python/python' + , 'notebook/js/codemirror-ipython' + , 'codemirror/addon/display/placeholder' + , 'codemirror/addon/display/autorefresh' +], function (requirejs, $ + + , vpConst, sb, vpCommon, vpSuggestInputText, pdGen, vpVarSelector, kernelApi + + , codemirror) { + + const VP_FE_BTN = 'vp-fe-btn'; + + const VP_FE = 'vp-fe'; + const VP_FE_CONTAINER = 'vp-fe-container'; + const VP_FE_TITLE = 'vp-fe-title'; + const VP_FE_CLOSE = 'vp-fe-close'; + const VP_FE_BODY = 'vp-fe-body'; + + const VP_FE_DF_BOX = 'vp-fe-df-box'; + const VP_FE_DF_REFRESH = 'vp-fe-df-refresh'; + + const VP_FE_MENU_BOX = 'vp-fe-menu-box'; + const VP_FE_MENU_ITEM = 'vp-fe-menu-item'; + + const VP_FE_TABLE = 'vp-fe-table'; + const VP_FE_TABLE_MORE = 'vp-fe-table-more'; + + const VP_FE_INFO = 'vp-fe-info'; + const VP_FE_INFO_CONTENT = 'vp-fe-info-content'; + + const TABLE_LINES = 5; + + const FRAME_EDIT_TYPE = { + INIT: 0, + DELETE: 1, + RENAME: 2, + DROP_NA: 3, + DROP_DUP: 4, + ONE_HOT_ENCODING: 5, + SET_IDX: 6, + REPLACE: 7, + + ADD_COL: 8, + ADD_ROW: 9, + SHOW: 10 + } + + /** + * @class FrameEditor + * @param {object} pageThis + * @param {string} targetId + * @constructor + */ + var FrameEditor = function(pageThis, targetId) { + this.pageThis = pageThis; + this.targetId = targetId; + this.uuid = vpCommon.getUUID(); + + // state + this.state = { + originObj: '', + tempObj: '', + selected: '', + axis: 0, + lines: TABLE_LINES, + steps: [] + } + + this.bindEvent(); + this.init(); + } + + FrameEditor.prototype.wrapSelector = function(query = '') { + return vpCommon.formatString('.{0}.{1} {2}', VP_FE, this.uuid, query); + } + + FrameEditor.prototype.open = function() { + this.loadVariableList(); + + $(this.wrapSelector()).show(); + } + + FrameEditor.prototype.close = function() { + $(this.wrapSelector()).hide(); + } + + FrameEditor.prototype.init = function() { + this.pageThis.loadCss(Jupyter.notebook.base_url + vpConst.BASE_PATH + vpConst.STYLE_PATH + "common/frameEditor.css"); + + this.renderButton(); + this.renderThis(); + } + + FrameEditor.prototype.initState = function() { + this.state.selected = ''; + this.state.axis = -1; + this.state.lines = TABLE_LINES; + this.state.steps = []; + } + + FrameEditor.prototype.renderButton = function() { + // set button next to input tag + var buttonTag = new sb.StringBuilder(); + buttonTag.appendFormat('' + , VP_FE_BTN, this.uuid, 'vp-button', 'Edit'); + $(this.pageThis.wrapSelector('#' + this.targetId)).parent().append(buttonTag.toString()); + } + + FrameEditor.prototype.renderThis = function() { + var page = new sb.StringBuilder(); + page.appendFormatLine('
', VP_FE, this.uuid); + page.appendFormatLine('
', VP_FE_CONTAINER); + + // title + page.appendFormat('
{1}
' + , VP_FE_TITLE + , 'Frame Editor'); + + // close button + page.appendFormatLine('
' + , VP_FE_CLOSE, 'fa fa-close'); + + // body start + page.appendFormatLine('
', VP_FE_BODY); + + // Select DataFrame + page.appendFormatLine('
', VP_FE_DF_BOX); + page.appendFormatLine('', 'vp_feVariable', 'vp-orange-text', 'DataFrame'); + page.appendFormatLine(''); + page.appendFormatLine('', VP_FE_DF_REFRESH, 'fa fa-refresh'); + page.appendLine('
'); + + // Menus + page.appendFormatLine('
', VP_FE_MENU_BOX); + // menu 1. delete + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-delete', FRAME_EDIT_TYPE.DELETE, 'Delete'); + // menu 2. rename + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-rename', FRAME_EDIT_TYPE.RENAME, 'Rename'); + // menu 3. drop + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-drop', FRAME_EDIT_TYPE.DROP_NA, 'Drop'); //TODO: NA & Duplicate selection needed + // menu 4. one-hot encoding + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-ohe', FRAME_EDIT_TYPE.ONE_HOT_ENCODING, 'One-Hot Encoding'); + // menu 5. set/reset index + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-index', FRAME_EDIT_TYPE.SET_IDX, 'Set Index'); + // menu 6. replace + page.appendFormatLine('
{3}
' + , VP_FE_MENU_ITEM, 'vp-fe-menu-replace', FRAME_EDIT_TYPE.REPLACE, 'Replace'); + + page.appendLine('
'); // End of Menus + + // Table + page.appendFormatLine('
', VP_FE_TABLE); + + page.appendLine('
'); // End of Table + + // Info Box + var infoBox = this.pageThis.createOptionContainer('Info'); + infoBox.addClass(VP_FE_INFO); + // TODO: get selected columns info + infoBox.appendContent(vpCommon.formatString('
info...
', VP_FE_INFO_CONTENT)); // value_counts() + page.appendFormatLine('{0}', infoBox.toTagString()); + + page.appendLine('
'); // VP_FE_BODY + page.appendLine('
'); // VP_FE_CONTAINER + page.appendLine('
'); // VP_FE + + $('#vp-wrapper').append(page.toString()); + $(this.wrapSelector()).hide(); + } + + FrameEditor.prototype.loadVariableList = function() { + var that = this; + // load using kernel + var dataTypes = ['DataFrame']; + kernelApi.searchVarList(dataTypes, function(result) { + try { + var varList = JSON.parse(result); + // render variable list + // replace + $(that.wrapSelector('#vp_feVariable')).replaceWith(function() { + return that.renderVariableList(varList); + }); + $(that.wrapSelector('#vp_feVariable')).trigger('change'); + } catch (ex) { + // console.log(ex); + } + }); + } + + FrameEditor.prototype.renderVariableList = function(varList) { + var tag = new sb.StringBuilder(); + var beforeValue = $(this.wrapSelector('#vp_feVariable')).val(); + tag.appendFormatLine(''); // VP_VS_VARIABLES + return tag.toString(); + } + + FrameEditor.prototype.renderTable = function(renderedText, isHtml=true) { + var tag = new sb.StringBuilder(); + // Table + tag.appendFormatLine('
', VP_FE_TABLE, 'rendered_html'); + if (isHtml) { + var renderedTable = $(renderedText).find('table'); + tag.appendFormatLine('{0}
', renderedTable.html()); + // More button + tag.appendFormatLine('
More...
', VP_FE_TABLE_MORE, 'vp-button'); + } else { + tag.appendFormatLine('
{0}
', renderedText); + } + tag.appendLine('
'); // End of Table + return tag.toString(); + } + + FrameEditor.prototype.loadInfo = function() { + var that = this; + var code = new sb.StringBuilder(); + code.appendFormat("{0}", this.state.tempObj); + if (this.state.selected != '') { + code.appendFormat(".loc[{0},{1}]" + , this.state.axis==0?this.state.selected:":" + , this.state.axis==1?"'"+this.state.selected+"'":":"); + } + code.append(".value_counts()"); + console.log(code.toString()); + kernelApi.executePython(code.toString(), function(result) { + $(that.wrapSelector('.' + VP_FE_INFO_CONTENT)).replaceWith(function() { + return vpCommon.formatString('
{1}
', VP_FE_INFO_CONTENT, result); + }); + }); + } + + FrameEditor.prototype.loadCode = function(type) { + var that = this; + + var tempObj = this.state.tempObj; + var orgObj = this.state.originObj; + var selectedName = this.state.selected; + var axis = this.state.axis; + var lines = this.state.lines; + + var code = new sb.StringBuilder(); + switch (parseInt(type)) { + case FRAME_EDIT_TYPE.INIT: + code.appendFormatLine('{0} = {1}.copy()', tempObj, orgObj); + break; + case FRAME_EDIT_TYPE.DELETE: + code.appendFormatLine("{0}.drop(['{1}'], axis={2}, inplace=True)", tempObj, selectedName, axis); + break; + case FRAME_EDIT_TYPE.RENAME: + break; + case FRAME_EDIT_TYPE.DROP_NA: + var locObj = ''; + if (axis == 0) { + locObj = vpCommon.formatString('.loc[{0},]', selectedName); + } else { + locObj = vpCommon.formatString('.loc[,{0}]', selectedName); + } + code.appendFormatLine("{0}{1}.dropna(axis={2}, inplace=True)", tempObj, locObj, axis); + break; + case FRAME_EDIT_TYPE.DROP_DUP: + if (axis == 0) { + locObj = vpCommon.formatString('.loc[{0},]', selectedName); + } else { + locObj = vpCommon.formatString('.loc[,{0}]', selectedName); + } + code.appendFormatLine("{0}{1}.drop_duplicates(axis={2}, inplace=True)", tempObj, locObj, axis); + break; + case FRAME_EDIT_TYPE.ONE_HOT_ENCODING: + break; + case FRAME_EDIT_TYPE.SET_IDX: + break; + case FRAME_EDIT_TYPE.REPLACE: + break; + case FRAME_EDIT_TYPE.SHOW: + break; + } + code.appendFormat('{0}.head({1})', tempObj, lines); + + Jupyter.notebook.kernel.execute( + code.toString(), + { + iopub: { + output: function(msg) { + if (msg.content.data) { + var htmlText = String(msg.content.data["text/html"]); + var codeText = String(msg.content.data["text/plain"]); + if (htmlText != 'undefined') { + $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { + return that.renderTable(htmlText); + }); + } else if (codeText != 'undefined') { + // plain text as code + $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { + return that.renderTable(codeText, false); + }); + } else { + $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { + return that.renderTable(''); + }); + } + that.loadInfo(); + that.state.steps.push(code.toString()); + } else { + var errorContent = new sb.StringBuilder(); + if (msg.content.ename) { + errorContent.appendFormatLine('
', VP_DS_DATA_ERROR_BOX); + errorContent.appendLine(''); + errorContent.appendFormatLine('' + , VP_DS_DATA_ERROR_BOX_TITLE, msg.content.ename); + if (msg.content.evalue) { + // errorContent.appendLine('
'); + errorContent.appendFormatLine('
{0}
', msg.content.evalue.split('\\n').join('
')); + } + errorContent.appendLine('
'); + } + $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { + return that.renderTable(errorContent); + }); + } + } + } + }, + { silent: false, store_history: true, stop_on_error: true } + ); + } + + + FrameEditor.prototype.bindEvent = function() { + var that = this; + // open popup + $(document).on('click', vpCommon.formatString('.{0}.{1}', VP_FE_BTN, this.uuid), function(event) { + if (!$(this).hasClass('disabled')) { + that.open(); + } + }); + + // close popup + $(document).on('click', this.wrapSelector('.' + VP_FE_CLOSE), function(event) { + that.close(); + + $(vpCommon.formatString('.{0}.{1}', VP_FE_BTN, this.uuid)).remove(); + // vpCommon.removeHeadScript("vpSubsetEditor"); + }); + + // select df + $(document).on('change', this.wrapSelector('#vp_feVariable'), function() { + // set temporary df + var origin = $(this).val(); + + // initialize state values + that.state.originObj = origin; + that.state.tempObj = '_vp_temp_' + origin; + that.initState(); + + // load code with temporary df + that.loadCode(FRAME_EDIT_TYPE.INIT); + that.loadInfo(); + }); + + // refresh df + $(document).on('click', this.wrapSelector('.vp-fe-df-refresh'), function() { + that.loadVariableList(); + }); + + // select column + $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' thead th'), function() { + var hasSelected = $(this).hasClass('selected'); + $(that.wrapSelector('.' + VP_FE_TABLE + ' th')).removeClass('selected'); + if (!hasSelected) { + $(this).addClass('selected'); + that.state.axis = 1; // column + that.state.selected = $(this).text(); + } else { + that.initState(); + $(this).removeClass('selected'); + } + + that.loadInfo(); + }); + + // select row + $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' tbody th'), function() { + var hasSelected = $(this).hasClass('selected'); + $(that.wrapSelector('.' + VP_FE_TABLE + ' th')).removeClass('selected'); + if (!hasSelected) { + $(this).addClass('selected'); + that.state.axis = 0; // index(row) + that.state.selected = $(this).text(); + } else { + that.initState(); + $(this).removeClass('selected'); + } + + that.loadInfo(); + }); + + // more rows + $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE_MORE), function() { + that.state.lines += TABLE_LINES; + that.loadCode(FRAME_EDIT_TYPE.SHOW); + }); + + // click button + $(document).on('click', this.wrapSelector('.' + VP_FE_MENU_ITEM), function() { + var editType = $(this).attr('data-type'); + that.loadCode(editType); + }); + } + + return FrameEditor; +}); \ No newline at end of file diff --git a/src/pandas/common/commonPandas.js b/src/pandas/common/commonPandas.js index 60cd995f..d76f9a44 100644 --- a/src/pandas/common/commonPandas.js +++ b/src/pandas/common/commonPandas.js @@ -5353,6 +5353,29 @@ define([ label: 'Return to' } ] + }, + 'pd126': { + id: 'frame_editor', + name: 'Frame Editor', + library: 'pandas', + description : 'pandas object editor', + code: '${o0} = ${i0}', + input: [ + { + name:'i0', + type:'var', + label: 'Code', + component: 'var_select', + var_type: ['DataFrame'] + } + ], + output: [ + { + name:'o0', + type:'var', + label: 'Return to' + } + ] } } diff --git a/src/pandas/frameEditor.js b/src/pandas/frameEditor.js index 5c68dd5a..af161a6d 100644 --- a/src/pandas/frameEditor.js +++ b/src/pandas/frameEditor.js @@ -6,40 +6,17 @@ define([ , 'nbextensions/visualpython/src/common/StringBuilder' , 'nbextensions/visualpython/src/common/vpFuncJS' , 'nbextensions/visualpython/src/common/kernelApi' -], function (requirejs, $, vpCommon, vpConst, sb, vpFuncJS, kernelApi) { + , 'nbextensions/visualpython/src/pandas/common/commonPandas' + , 'nbextensions/visualpython/src/pandas/common/pandasGenerator' + , 'nbextensions/visualpython/src/common/vpFrameEditor' +], function (requirejs, $, vpCommon, vpConst, sb, vpFuncJS, kernelApi, libPandas, pdGen + , vpFrameEditor) { // 옵션 속성 const funcOptProp = { stepCount : 1 , funcName : "Frame Editor" , funcID : "pd_frameEditor" - , libID : "pd000" - } - - const VP_FE = 'vp-fe'; - const VP_FE_DF_BOX = 'vp-fe-df-box'; - const VP_FE_DF_REFRESH = 'vp-fe-df-refresh'; - - const VP_FE_MENU_BOX = 'vp-fe-menu-box'; - const VP_FE_MENU_ITEM = 'vp-fe-menu-item'; - - const VP_FE_TABLE = 'vp-fe-table'; - const VP_FE_TABLE_MORE = 'vp-fe-table-more'; - - const TABLE_LINES = 5; - - const FRAME_EDIT_TYPE = { - INIT: 0, - DELETE: 1, - RENAME: 2, - DROP_NA: 3, - DROP_DUP: 4, - ONE_HOT_ENCODING: 5, - SET_IDX: 6, - REPLACE: 7, - - ADD_COL: 8, - ADD_ROW: 9, - SHOW: 10 + , libID : "pd126" } /** @@ -77,7 +54,7 @@ define([ * @param {function} callback 호출자(컨테이너) 의 콜백함수 */ var initOption = function(callback, meta) { - vpCommon.loadHtml(vpCommon.wrapSelector(vpCommon.formatString("#{0}", vpConst.OPTION_GREEN_ROOM)), "pandas/common/commonEmptyPage.html", optionLoadCallback, callback, meta); + vpCommon.loadHtml(vpCommon.wrapSelector(vpCommon.formatString("#{0}", vpConst.OPTION_GREEN_ROOM)), "pandas/common/commonPandas.html", optionLoadCallback, callback, meta); } /** @@ -87,19 +64,7 @@ define([ var PandasPackage = function(uuid) { this.uuid = uuid; // Load html 영역의 uuid. // pandas 함수 - this.package = { - input: [ - { name: 'vp_feVariable' } - ] - } - this.state = { - origin_obj: '', - temp_obj: '', - selected: '', - axis: 0, - lines: TABLE_LINES, - steps: [] - } + this.package = libPandas._PANDAS_FUNCTION[funcOptProp.libID]; } @@ -125,265 +90,23 @@ define([ */ PandasPackage.prototype.initHtml = function() { this.loadCss(Jupyter.notebook.base_url + vpConst.BASE_PATH + vpConst.STYLE_PATH + "pandas/commonPandas.css"); - this.loadCss(Jupyter.notebook.base_url + vpConst.BASE_PATH + vpConst.STYLE_PATH + "pandas/frameEditor.css"); - this.renderThis(); - this.bindEvents(); - - this.loadVariableList(); - } - - PandasPackage.prototype.renderThis = function() { - var page = new sb.StringBuilder(); - page.appendFormatLine('
', VP_FE); - // Select DataFrame - page.appendFormatLine('
', VP_FE_DF_BOX); - page.appendFormatLine('', 'vp_feVariable', 'vp-orange-text', 'DataFrame'); - page.appendFormatLine(''); - page.appendFormatLine('', VP_FE_DF_REFRESH, 'fa fa-refresh'); - page.appendLine('
'); - - // Menus - page.appendFormatLine('
', VP_FE_MENU_BOX); - // menu 1. delete - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-delete', FRAME_EDIT_TYPE.DELETE, 'Delete'); - // menu 2. rename - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-rename', FRAME_EDIT_TYPE.RENAME, 'Rename'); - // menu 3. drop - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-drop', FRAME_EDIT_TYPE.DROP_NA, 'Drop'); //TODO: NA & Duplicate selection needed - // menu 4. one-hot encoding - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-ohe', FRAME_EDIT_TYPE.ONE_HOT_ENCODING, 'One-Hot Encoding'); - // menu 5. set/reset index - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-index', FRAME_EDIT_TYPE.SET_IDX, 'Set Index'); - // menu 6. replace - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-replace', FRAME_EDIT_TYPE.REPLACE, 'Replace'); - - page.appendLine('
'); // End of Menus - - // Info Box - var infoBox = this.createOptionContainer('Column Info'); - infoBox.addClass('vp-fe-info'); - // TODO: get selected columns info - infoBox.appendContent('column info'); // value_counts() - page.appendFormatLine('{0}', infoBox.toTagString()); - - // Table - page.appendFormatLine('
', VP_FE_TABLE); - - page.appendLine('
'); // End of Table - - page.appendLine('
'); - - this.setPage(page.toString()); - } - - PandasPackage.prototype.loadVariableList = function() { - var that = this; - // load using kernel - var dataTypes = ['DataFrame']; - kernelApi.searchVarList(dataTypes, function(result) { - try { - var varList = JSON.parse(result); - // render variable list - // replace - $(that.wrapSelector('#vp_feVariable')).replaceWith(function() { - return that.renderVariableList(varList); - }); - $(that.wrapSelector('#vp_feVariable')).trigger('change'); - } catch (ex) { - // console.log(ex); - } - }); - } - - PandasPackage.prototype.renderVariableList = function(varList) { - var tag = new sb.StringBuilder(); - var beforeValue = $(this.wrapSelector('#vp_feVariable')).val(); - tag.appendFormatLine(''); // VP_VS_VARIABLES - return tag.toString(); - } + + this.bindOptions(); - PandasPackage.prototype.renderTable = function(renderedText, isHtml=true) { - var tag = new sb.StringBuilder(); - // Table - tag.appendFormatLine('
', VP_FE_TABLE, 'rendered_html'); - if (isHtml) { - var renderedTable = $(renderedText).find('table'); - tag.appendFormatLine('{0}
', renderedTable.html()); - // More button - tag.appendFormatLine('
More...
', VP_FE_TABLE_MORE, 'vp-button'); - } else { - tag.appendFormatLine('
{0}
', renderedText); - } - tag.appendLine('
'); // End of Table - return tag.toString(); + this.subsetEditor = new vpFrameEditor(this, "i0"); } - - PandasPackage.prototype.loadCode = function(type) { - var that = this; - - var tempObj = this.state.temp_obj; - var orgObj = this.state.origin_obj; - var selectedName = this.state.selected; - var axis = this.state.axis; - var lines = this.state.lines; - var code = new sb.StringBuilder(); - switch (parseInt(type)) { - case FRAME_EDIT_TYPE.INIT: - code.appendFormatLine('{0} = {1}.copy()', tempObj, orgObj); - break; - case FRAME_EDIT_TYPE.DELETE: - code.appendFormatLine("{0}.drop(['{1}'], axis={2}, inplace=True)", tempObj, selectedName, axis); - break; - case FRAME_EDIT_TYPE.RENAME: - break; - case FRAME_EDIT_TYPE.DROP_NA: - var locObj = ''; - if (axis == 0) { - locObj = vpCommon.formatString('.loc[{0},]', selectedName); - } else { - locObj = vpCommon.formatString('.loc[,{0}]', selectedName); - } - code.appendFormatLine("{0}{1}.dropna(axis={2}, inplace=True)", tempObj, locObj, axis); - break; - case FRAME_EDIT_TYPE.DROP_DUP: - if (axis == 0) { - locObj = vpCommon.formatString('.loc[{0},]', selectedName); - } else { - locObj = vpCommon.formatString('.loc[,{0}]', selectedName); - } - code.appendFormatLine("{0}{1}.drop_duplicates(axis={2}, inplace=True)", tempObj, locObj, axis); - break; - case FRAME_EDIT_TYPE.ONE_HOT_ENCODING: - break; - case FRAME_EDIT_TYPE.SET_IDX: - break; - case FRAME_EDIT_TYPE.REPLACE: - break; - case FRAME_EDIT_TYPE.SHOW: - break; + /** + * Pandas 기본 패키지 바인딩 + */ + PandasPackage.prototype.bindOptions = function() { + // HTML 구성 + pdGen.vp_showInterface(this); + // if it has no additional options, remove that box + if (this.package.variable == undefined || this.package.variable.length <= 0) { + $(this.wrapSelector('#vp_optionBox')).closest('div.vp-accordion-container').remove(); } - code.appendFormat('{0}.head({1})', tempObj, lines); - - Jupyter.notebook.kernel.execute( - code.toString(), - { - iopub: { - output: function(msg) { - if (msg.content.data) { - var htmlText = String(msg.content.data["text/html"]); - var codeText = String(msg.content.data["text/plain"]); - if (htmlText != 'undefined') { - $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { - return that.renderTable(htmlText); - }); - } else if (codeText != 'undefined') { - // plain text as code - $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { - return that.renderTable(codeText, false); - }); - } else { - $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { - return that.renderTable(''); - }); - } - that.state.steps.push(code.toString()); - } else { - var errorContent = new sb.StringBuilder(); - if (msg.content.ename) { - errorContent.appendFormatLine('
', VP_DS_DATA_ERROR_BOX); - errorContent.appendLine(''); - errorContent.appendFormatLine('' - , VP_DS_DATA_ERROR_BOX_TITLE, msg.content.ename); - if (msg.content.evalue) { - // errorContent.appendLine('
'); - errorContent.appendFormatLine('
{0}
', msg.content.evalue.split('\\n').join('
')); - } - errorContent.appendLine('
'); - } - $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { - return that.renderTable(errorContent); - }); - } - } - } - }, - { silent: false, store_history: true, stop_on_error: true } - ); - } - - - PandasPackage.prototype.bindEvents = function() { - var that = this; - - // select df - $(document).on('change', this.wrapSelector('#vp_feVariable'), function() { - // set temporary df - var origin = $(this).val(); - - // initialize state values - that.state.origin_obj = origin; - that.state.temp_obj = '_vp_temp_' + origin; - that.state.selected = ''; - that.state.axis = 0; - that.state.lines = TABLE_LINES; - that.state.steps = []; - - // load code with temporary df - that.loadCode(FRAME_EDIT_TYPE.INIT); - }); - - // refresh df - $(document).on('click', this.wrapSelector('.vp-fe-df-refresh'), function() { - that.loadVariableList(); - }); - - // select column - $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' thead th'), function() { - $(that.wrapSelector('.' + VP_FE_TABLE + ' th')).removeClass('selected'); - $(this).addClass('selected'); - - that.state.axis = 1; // column - that.state.selected = $(this).text(); - }); - - // select row - $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' tbody th'), function() { - $(that.wrapSelector('.' + VP_FE_TABLE + ' th')).removeClass('selected'); - $(this).addClass('selected'); - - that.state.axis = 0; // index(row) - that.state.selected = $(this).text(); - }); - - // more rows - $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE_MORE), function() { - that.state.lines += TABLE_LINES; - that.loadCode(FRAME_EDIT_TYPE.SHOW); - }); - - // click button - $(document).on('click', this.wrapSelector('.' + VP_FE_MENU_ITEM), function() { - var editType = $(this).attr('data-type'); - that.loadCode(editType); - }); - } + }; /** * 코드 생성 @@ -395,7 +118,7 @@ define([ // 코드 생성 - var result = this.state.steps.join('\n'); + var result = pdGen.vp_codeGenerator(this.uuid, this.package); if (result == null) return "BREAK_RUN"; // 코드 생성 중 오류 발생 sbCode.append(result); From 07ba8aa1fb63d3c12f5bb444438cd0d2b3d61678 Mon Sep 17 00:00:00 2001 From: minjk-bl Date: Tue, 25 May 2021 18:55:50 +0900 Subject: [PATCH 4/5] #31 - Frame Editor table data converting implemented --- css/common/frameEditor.css | 35 +++++++++- src/common/vpCommon.js | 17 +++++ src/common/vpFrameEditor.js | 131 ++++++++++++++++++++---------------- 3 files changed, 124 insertions(+), 59 deletions(-) diff --git a/css/common/frameEditor.css b/css/common/frameEditor.css index 11371fa2..548c2e15 100644 --- a/css/common/frameEditor.css +++ b/css/common/frameEditor.css @@ -188,4 +188,37 @@ white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-all; /* Internet Explorer 5.5+ */ -} \ No newline at end of file +} + +/** buttons */ +.vp-fe-btn-box { + position: absolute; + bottom: 10px; + right: 10px; +} +.vp-fe-btn-apply { + width: 80px; + height: 30px; + background: #F37704; + border: 0.25px solid #C4C4C4; + box-sizing: border-box; + border-radius: 2px; + text-align: center; + color: #FFFFFF; +} +.vp-fe-btn-apply:hover { + background: var(--hightlight-color); +} +.vp-fe-btn-cancel { + width: 80px; + height: 30px; + background: #E5E5E5; + border: 0.25px solid #C4C4C4; + box-sizing: border-box; + border-radius: 2px; + text-align: center; + color: #696969; +} +.vp-fe-btn-cancel:hover { + background: #ccc; +} diff --git a/src/common/vpCommon.js b/src/common/vpCommon.js index 4ff4984a..26c2c9d6 100644 --- a/src/common/vpCommon.js +++ b/src/common/vpCommon.js @@ -129,6 +129,22 @@ define([ return str; } + /** + * Convert to string format if not numeric + * @param {*} code + * @returns + */ + var convertToStr = function(code) { + if (!$.isNumeric(code)) { + if (code.includes("'")) { + code = `"${code}"`; + } else { + code = `'${code}'`; + } + } + return code; + } + /** * check duplicate variable name * @param {string} varName @@ -338,5 +354,6 @@ define([ // 추가 , kernelExecute: kernelExecute , cellExecute: cellExecute + , convertToStr: convertToStr }; }); \ No newline at end of file diff --git a/src/common/vpFrameEditor.js b/src/common/vpFrameEditor.js index d77cab06..f4bdc275 100644 --- a/src/common/vpFrameEditor.js +++ b/src/common/vpFrameEditor.js @@ -40,7 +40,12 @@ define([ const VP_FE_INFO = 'vp-fe-info'; const VP_FE_INFO_CONTENT = 'vp-fe-info-content'; - const TABLE_LINES = 5; + const VP_FE_BUTTON_BOX = 'vp-fe-btn-box'; + const VP_FE_BUTTON_CANCEL = 'vp-fe-btn-cancel'; + const VP_FE_BUTTON_APPLY = 'vp-fe-btn-apply'; + + // search rows count at once + const TABLE_LINES = 10; const FRAME_EDIT_TYPE = { INIT: 0, @@ -72,7 +77,7 @@ define([ this.state = { originObj: '', tempObj: '', - selected: '', + selected: [], axis: 0, lines: TABLE_LINES, steps: [] @@ -88,7 +93,7 @@ define([ FrameEditor.prototype.open = function() { this.loadVariableList(); - + $(this.wrapSelector()).show(); } @@ -104,7 +109,7 @@ define([ } FrameEditor.prototype.initState = function() { - this.state.selected = ''; + this.state.selected = []; this.state.axis = -1; this.state.lines = TABLE_LINES; this.state.steps = []; @@ -179,6 +184,15 @@ define([ page.appendFormatLine('{0}', infoBox.toTagString()); page.appendLine(''); // VP_FE_BODY + + // apply button + page.appendFormatLine('
', VP_FE_BUTTON_BOX); + page.appendFormatLine('' + , VP_FE_BUTTON_CANCEL, 'Cancel'); + page.appendFormatLine('' + , VP_FE_BUTTON_APPLY, 'Apply'); + page.appendLine('
'); + page.appendLine(''); // VP_FE_CONTAINER page.appendLine(''); // VP_FE @@ -226,8 +240,7 @@ define([ // Table tag.appendFormatLine('
', VP_FE_TABLE, 'rendered_html'); if (isHtml) { - var renderedTable = $(renderedText).find('table'); - tag.appendFormatLine('{0}
', renderedTable.html()); + tag.appendFormatLine('{0}
', renderedText); // More button tag.appendFormatLine('
More...
', VP_FE_TABLE_MORE, 'vp-button'); } else { @@ -242,9 +255,9 @@ define([ var code = new sb.StringBuilder(); code.appendFormat("{0}", this.state.tempObj); if (this.state.selected != '') { - code.appendFormat(".loc[{0},{1}]" - , this.state.axis==0?this.state.selected:":" - , this.state.axis==1?"'"+this.state.selected+"'":":"); + code.appendFormat(".loc[[{0}],[{1}]]" + , this.state.axis==0?this.state.selected.join(','):":" + , this.state.axis==1?this.state.selected.join(','):":"); } code.append(".value_counts()"); console.log(code.toString()); @@ -300,54 +313,56 @@ define([ case FRAME_EDIT_TYPE.SHOW: break; } - code.appendFormat('{0}.head({1})', tempObj, lines); - - Jupyter.notebook.kernel.execute( - code.toString(), - { - iopub: { - output: function(msg) { - if (msg.content.data) { - var htmlText = String(msg.content.data["text/html"]); - var codeText = String(msg.content.data["text/plain"]); - if (htmlText != 'undefined') { - $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { - return that.renderTable(htmlText); - }); - } else if (codeText != 'undefined') { - // plain text as code - $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { - return that.renderTable(codeText, false); - }); - } else { - $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { - return that.renderTable(''); - }); - } - that.loadInfo(); - that.state.steps.push(code.toString()); - } else { - var errorContent = new sb.StringBuilder(); - if (msg.content.ename) { - errorContent.appendFormatLine('
', VP_DS_DATA_ERROR_BOX); - errorContent.appendLine(''); - errorContent.appendFormatLine('' - , VP_DS_DATA_ERROR_BOX_TITLE, msg.content.ename); - if (msg.content.evalue) { - // errorContent.appendLine('
'); - errorContent.appendFormatLine('
{0}
', msg.content.evalue.split('\\n').join('
')); - } - errorContent.appendLine('
'); - } - $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { - return that.renderTable(errorContent); - }); + var addStackCode = code.toString(); + + // search codes //TODO: python 코드로 정리해서 가져올 것 + code.appendFormat("{0}.head({1}).to_json(orient='{2}')", tempObj, lines, 'split'); + + kernelApi.executePython(code.toString(), function(result) { + console.log(result); + try { + // var data = JSON.parse(result); + var data = JSON.parse(result.substr(1,result.length - 2)); + var columnList = data.columns; + var indexList = data.index; + var dataList = data.data; + + // table + var table = new sb.StringBuilder(); + // table.appendFormatLine('', 1, 'dataframe'); + table.appendLine(''); + table.appendLine(''); + columnList && columnList.forEach(col => { + table.appendFormatLine('', vpCommon.convertToStr(col), col); + }); + table.appendLine(''); + table.appendLine(''); + table.appendLine(''); + + dataList && dataList.forEach((row, idx) => { + table.appendLine(''); + table.appendFormatLine('', vpCommon.convertToStr(indexList[idx]), indexList[idx]); + row.forEach(cell => { + if (cell == null) { + cell = 'NaN'; } - } - } - }, - { silent: false, store_history: true, stop_on_error: true } - ); + table.appendFormatLine('', cell); + }); + table.appendLine(''); + }); + table.appendLine(''); + + $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { + return that.renderTable(table.toString()); + }); + // load info + that.loadInfo(); + // add to stack + that.state.steps.push(addStackCode); + } catch (err) { + console.log(err); + } + }); } @@ -391,7 +406,7 @@ define([ // select column $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' thead th'), function() { var hasSelected = $(this).hasClass('selected'); - $(that.wrapSelector('.' + VP_FE_TABLE + ' th')).removeClass('selected'); + $(that.wrapSelector('.' + VP_FE_TABLE + ' tbody th')).removeClass('selected'); if (!hasSelected) { $(this).addClass('selected'); that.state.axis = 1; // column @@ -407,7 +422,7 @@ define([ // select row $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' tbody th'), function() { var hasSelected = $(this).hasClass('selected'); - $(that.wrapSelector('.' + VP_FE_TABLE + ' th')).removeClass('selected'); + $(that.wrapSelector('.' + VP_FE_TABLE + ' thead th')).removeClass('selected'); if (!hasSelected) { $(this).addClass('selected'); that.state.axis = 0; // index(row) From dd4b2ab14c73ea3b9b28ab611993849cf3f9ba88 Mon Sep 17 00:00:00 2001 From: minjk-bl Date: Mon, 31 May 2021 14:54:52 +0900 Subject: [PATCH 5/5] #31 - Edit Frame Editor as popup --- css/common/frameEditor.css | 108 ++++- css/common/subsetEditor.css | 2 +- css/pandas/commonPandas.css | 7 + src/common/vpFrameEditor.js | 592 +++++++++++++++++++++++---- src/pandas/common/commonPandas.js | 2 +- src/pandas/common/pandasGenerator.js | 9 + src/pandas/frameEditor.js | 10 +- 7 files changed, 620 insertions(+), 110 deletions(-) diff --git a/css/common/frameEditor.css b/css/common/frameEditor.css index 548c2e15..05e1d736 100644 --- a/css/common/frameEditor.css +++ b/css/common/frameEditor.css @@ -22,8 +22,8 @@ min-width: 400px; min-height: 400px; - width: 90%; - height: 90%; + width: 95%; + height: 95%; background-color: white; } @@ -41,7 +41,8 @@ font-weight: 700; } -.vp-fe-close { +.vp-fe-close, +.vp-fe-popup-close { position: fixed; z-index: 3; right: 5px; @@ -54,10 +55,31 @@ .vp-fe-body { width: 100%; - height: 100%; + height: calc(100% - 30px); padding: 10px; display: grid; grid-row-gap: 5px; + grid-template-rows: 35px 30px 60% calc(40% - 80px); +} + +/* preview code */ +.vp-fe-preview { + width: 100%; + height: 35px; + background-image: repeating-linear-gradient( to right, var(--grid-line-color) 0, var(--grid-line-color) 0.25px, transparent 1px, transparent 5px ), repeating-linear-gradient( to bottom, var(--grid-line-color) 0, var(--grid-line-color) 0.25px, transparent 1px, transparent 5px ); + background-color: white; + border: 0.25px solid #E4E4E4; +} +.vp-fe-preview textarea { + display: none; +} + +.vp-fe-preview .CodeMirror-code .cm-variable { + background-color: rgba(47, 133, 90, 0.2); +} + +.vp-fe-preview .CodeMirror-code .cm-string { + background-color: rgba(246, 173, 85, 0.2); } .vp-fe-df-box { @@ -74,18 +96,23 @@ } .vp-fe-menu-box { - display: grid; - grid-template-columns: repeat(6, 1fr); + position: fixed; + top: 0; + left: 0; + background: #FFFFFF; + z-index: 70; + /* display: grid; + grid-template-columns: repeat(6, 1fr); */ } .vp-fe-menu-box .vp-fe-menu-item { - height: 30px; + height: 25px; + line-height: 25px; background: #FFFFFF; border: 0.25px solid #E4E4E4; box-sizing: border-box; box-shadow: 1px 1px 2px rgb(0 0 0 / 10%); border-radius: 2px; - line-height: 30px; font-size: 11px; text-align: center; color: #696969; @@ -107,15 +134,42 @@ background: #F8F8F8; } -.vp-fe-info { - margin: 0px; +.vp-fe-menu-sub-box { + display: none; + position: absolute; + left: 100%; } -.vp-fe-info .vp-accordion-header { - background: #F5F5F5; + +.vp-fe-menu-item:hover .vp-fe-menu-sub-box { + display: block; +} + +.vp-fe-popup-box { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + min-width: 400px; + /* min-height: 150px; */ + width: 30%; + height: fit-content; + background-color: white; + z-index: 200; + border: 0.25px solid var(--border-gray-color); + box-shadow: 1px 1px 2px rgb(0 0 0 / 10%); +} + +.vp-fe-popup-body { + padding: 10px; +} + +.vp-fe-popup-button-box { + float: right; + padding: 5px; } .vp-fe-table { - height: 350px; + height: 100%; background: var(--border-gray-color); overflow: auto; } @@ -139,7 +193,8 @@ } .vp-fe-table th.selected { - color: var(--hightlight-color); + /* color: var(--hightlight-color); */ + background: #add3fd; } .vp-fe-table th:hover { @@ -171,15 +226,26 @@ .vp-fe-info { width: 100%; + height: 100%; + margin: 0px; +} + +.vp-fe-info-title { + width: 100%; + height: 20px; + line-height: 20px; + font-weight: bold; + background: #F5F5F5; } .vp-fe-info-content { width: 100%; + height: calc(100% - 20px); overflow: auto; } .vp-fe-info-content pre { - width: 300px; + /* width: 300px; */ padding:10px; white-space: pre-wrap; overflow: auto; @@ -196,7 +262,8 @@ bottom: 10px; right: 10px; } -.vp-fe-btn-apply { +.vp-fe-btn-apply +, .vp-fe-popup-ok { width: 80px; height: 30px; background: #F37704; @@ -206,10 +273,12 @@ text-align: center; color: #FFFFFF; } -.vp-fe-btn-apply:hover { +.vp-fe-btn-apply:hover +, .vp-fe-popup-ok:hover { background: var(--hightlight-color); } -.vp-fe-btn-cancel { +.vp-fe-btn-cancel +, .vp-fe-popup-cancel { width: 80px; height: 30px; background: #E5E5E5; @@ -219,6 +288,7 @@ text-align: center; color: #696969; } -.vp-fe-btn-cancel:hover { +.vp-fe-btn-cancel:hover +, .vp-fe-popup-cancel:hover { background: #ccc; } diff --git a/css/common/subsetEditor.css b/css/common/subsetEditor.css index fdbc95b1..6d07438b 100644 --- a/css/common/subsetEditor.css +++ b/css/common/subsetEditor.css @@ -331,7 +331,7 @@ overflow: auto; } .vp-ds-data-view-box table { - width: 100%; + /* width: 100%; */ height: 100%; } .vp-ds-data-error-box { diff --git a/css/pandas/commonPandas.css b/css/pandas/commonPandas.css index 0209253a..22672e1b 100644 --- a/css/pandas/commonPandas.css +++ b/css/pandas/commonPandas.css @@ -100,6 +100,13 @@ width: 116px; } +#vp-wrapper .vp-textarea { + border: 0.25px solid var(--border-gray-color); + width: 100%; + height: 100px; + margin: 0px; +} + /* 공통 스타일 */ .w100 { width: 100%; } .w90 { width: 90%; } diff --git a/src/common/vpFrameEditor.js b/src/common/vpFrameEditor.js index f4bdc275..130d2273 100644 --- a/src/common/vpFrameEditor.js +++ b/src/common/vpFrameEditor.js @@ -28,16 +28,31 @@ define([ const VP_FE_CLOSE = 'vp-fe-close'; const VP_FE_BODY = 'vp-fe-body'; + const VP_FE_PREVIEW = 'vp-fe-preview'; + const VP_FE_DF_BOX = 'vp-fe-df-box'; const VP_FE_DF_REFRESH = 'vp-fe-df-refresh'; const VP_FE_MENU_BOX = 'vp-fe-menu-box'; const VP_FE_MENU_ITEM = 'vp-fe-menu-item'; + const VP_FE_MENU_SUB_BOX = 'vp-fe-menu-sub-box'; + + const VP_FE_POPUP_BOX = 'vp-fe-popup-box'; + const VP_FE_POPUP_CLOSE = 'vp-fe-popup-close'; + const VP_FE_POPUP_BODY = 'vp-fe-popup-body'; + const VP_FE_POPUP_BUTTON_BOX = 'vp-fe-popup-button-box'; + const VP_FE_POPUP_CANCEL = 'vp-fe-popup-cancel'; + const VP_FE_POPUP_OK = 'vp-fe-popup-ok'; const VP_FE_TABLE = 'vp-fe-table'; + const VP_FE_TABLE_COLUMN = 'vp-fe-table-column'; + const VP_FE_TABLE_ROW = 'vp-fe-table-row'; + const VP_FE_ADD_COLUMN = 'vp-fe-add-column'; + const VP_FE_ADD_ROW = 'vp-fe-add-row'; const VP_FE_TABLE_MORE = 'vp-fe-table-more'; const VP_FE_INFO = 'vp-fe-info'; + const VP_FE_INFO_TITLE = 'vp-fe-info-title'; const VP_FE_INFO_CONTENT = 'vp-fe-info-content'; const VP_FE_BUTTON_BOX = 'vp-fe-btn-box'; @@ -48,18 +63,20 @@ define([ const TABLE_LINES = 10; const FRAME_EDIT_TYPE = { + NONE: -1, INIT: 0, - DELETE: 1, + RENAME: 2, - DROP_NA: 3, - DROP_DUP: 4, - ONE_HOT_ENCODING: 5, - SET_IDX: 6, - REPLACE: 7, - - ADD_COL: 8, - ADD_ROW: 9, - SHOW: 10 + DROP: 3, + DROP_NA: 4, + DROP_DUP: 5, + ONE_HOT_ENCODING: 6, + SET_IDX: 7, + REPLACE: 8, + + ADD_COL: 9, + ADD_ROW: 10, + SHOW: 11 } /** @@ -76,13 +93,16 @@ define([ // state this.state = { originObj: '', - tempObj: '', + tempObj: '_vp', selected: [], axis: 0, lines: TABLE_LINES, - steps: [] + steps: [], + popup: FRAME_EDIT_TYPE.NONE } + this.codepreview = undefined; + this.bindEvent(); this.init(); } @@ -95,6 +115,40 @@ define([ this.loadVariableList(); $(this.wrapSelector()).show(); + + if (!this.codepreview) { + // var previewTextarea = $('#vp_previewCode')[0]; + // var previewTextarea = $(this.wrapSelector('#vp_previewCode'))[0]; + var previewTextarea = $(this.wrapSelector('textarea'))[0]; + // if (!previewTextarea) { + // previewTextarea = $('#vp_previewCode')[0]; + // } + if (previewTextarea) { + // set codemirror + this.codepreview = codemirror.fromTextArea(previewTextarea, { + mode: { + name: 'python', + version: 3, + singleLineStringErrors: false + }, // text-cell(markdown cell) set to 'htmlmixed' + height: '100%', + width: '100%', + indentUnit: 4, + matchBrackets: true, + readOnly:true, + autoRefresh: true, + // lineWrapping: true, // text-cell(markdown cell) set to true + // indentWithTabs: true, + theme: "ipython", + extraKeys: {"Enter": "newlineAndIndentContinueMarkdownList"}, + scrollbarStyle: "null" + }); + this.setPreview('# Code Preview'); + this.codepreview.refresh(); + } + } else { + this.codepreview.refresh(); + } } FrameEditor.prototype.close = function() { @@ -106,6 +160,9 @@ define([ this.renderButton(); this.renderThis(); + + // this.setDraggableBox(); + this.setDraggableColumns(); } FrameEditor.prototype.initState = function() { @@ -123,11 +180,27 @@ define([ $(this.pageThis.wrapSelector('#' + this.targetId)).parent().append(buttonTag.toString()); } + FrameEditor.prototype.setPreview = function(previewCodeStr) { + if (this.codepreview) { + this.codepreview.setValue(previewCodeStr); + this.codepreview.save(); + var that = this; + setTimeout(function() { + that.codepreview.refresh(); + }, 1); + } + } + FrameEditor.prototype.renderThis = function() { var page = new sb.StringBuilder(); page.appendFormatLine('
', VP_FE, this.uuid); page.appendFormatLine('
', VP_FE_CONTAINER); + // menu box + page.appendLine(this.renderMenuBox()); + // input popup + page.appendLine(this.renderInputPopup()); + // title page.appendFormat('
{1}
' , VP_FE_TITLE @@ -140,6 +213,10 @@ define([ // body start page.appendFormatLine('
', VP_FE_BODY); + // preview code board + page.appendFormatLine('
' + , VP_FE_PREVIEW, "vp_fePreviewCode"); + // Select DataFrame page.appendFormatLine('
', VP_FE_DF_BOX); page.appendFormatLine('', 'vp_feVariable', 'vp-orange-text', 'DataFrame'); @@ -148,40 +225,16 @@ define([ page.appendFormatLine('', VP_FE_DF_REFRESH, 'fa fa-refresh'); page.appendLine('
'); - // Menus - page.appendFormatLine('
', VP_FE_MENU_BOX); - // menu 1. delete - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-delete', FRAME_EDIT_TYPE.DELETE, 'Delete'); - // menu 2. rename - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-rename', FRAME_EDIT_TYPE.RENAME, 'Rename'); - // menu 3. drop - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-drop', FRAME_EDIT_TYPE.DROP_NA, 'Drop'); //TODO: NA & Duplicate selection needed - // menu 4. one-hot encoding - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-ohe', FRAME_EDIT_TYPE.ONE_HOT_ENCODING, 'One-Hot Encoding'); - // menu 5. set/reset index - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-index', FRAME_EDIT_TYPE.SET_IDX, 'Set Index'); - // menu 6. replace - page.appendFormatLine('
{3}
' - , VP_FE_MENU_ITEM, 'vp-fe-menu-replace', FRAME_EDIT_TYPE.REPLACE, 'Replace'); - - page.appendLine('
'); // End of Menus - // Table page.appendFormatLine('
', VP_FE_TABLE); page.appendLine('
'); // End of Table // Info Box - var infoBox = this.pageThis.createOptionContainer('Info'); - infoBox.addClass(VP_FE_INFO); - // TODO: get selected columns info - infoBox.appendContent(vpCommon.formatString('
info...
', VP_FE_INFO_CONTENT)); // value_counts() - page.appendFormatLine('{0}', infoBox.toTagString()); + page.appendFormatLine('
', VP_FE_INFO); + page.appendFormatLine('
Info
', VP_FE_INFO_TITLE); + page.appendFormatLine('
content
', VP_FE_INFO_CONTENT); + page.appendLine('
'); // End of VP_FE_INFO page.appendLine('
'); // VP_FE_BODY @@ -200,6 +253,77 @@ define([ $(this.wrapSelector()).hide(); } + FrameEditor.prototype.renderMenuBox = function() { + var page = new sb.StringBuilder(); + // Menus + page.appendFormatLine(''); // End of Menus + return page.toString(); + } + + FrameEditor.prototype.renderInputPopup = function() { + var page = new sb.StringBuilder(); + page.appendFormatLine(''); // End of Popup + return page.toString(); + } + FrameEditor.prototype.loadVariableList = function() { var that = this; // load using kernel @@ -250,79 +374,238 @@ define([ return tag.toString(); } + FrameEditor.prototype.renderAddPage = function(type) { + var content = new sb.StringBuilder(); + content.appendLine('
{1}
{1}{0}
'); + content.appendFormatLine('', type); + content.appendFormatLine(''); + content.appendLine(''); + content.appendFormatLine('
', 'vp-popup-input1'); + content.appendFormatLine('', 'vp-popup-istext1','Text'); + content.appendLine('
', 'vp-popup-input2'); + content.appendFormatLine('', 'vp-popup-istext2','Text'); + content.appendLine('
'); + return content.toString(); + } + + FrameEditor.prototype.renderRenamePage = function() { + var content = new sb.StringBuilder(); + content.appendLine(''); + this.state.selected.forEach((label, idx) => { + content.appendLine(''); + content.appendFormatLine('', label); + content.appendFormatLine(''); + }); + content.appendLine('
', 'vp-popup-input' + idx); + content.appendFormatLine('', 'vp-popup-istext' + idx, 'Text'); + content.appendLine('
'); + return content.toString(); + } + + FrameEditor.prototype.openInputPopup = function(type, width=0, height=0) { + var title = ''; + var content = ''; + + switch (parseInt(type)) { + case FRAME_EDIT_TYPE.ADD_COL: + title = 'Add Column'; + content = this.renderAddPage('column'); + break; + case FRAME_EDIT_TYPE.ADD_ROW: + title = 'Add Row'; + content = this.renderAddPage('row'); + break; + case FRAME_EDIT_TYPE.RENAME: + title = 'Rename'; + content = this.renderRenamePage(); + break; + default: + type = FRAME_EDIT_TYPE.NONE; + break; + } + + this.state.popup = type; + + // set title + $(this.wrapSelector('.' + VP_FE_POPUP_BOX + ' .' + VP_FE_TITLE)).text(title); + // set content + $(this.wrapSelector('.' + VP_FE_POPUP_BODY)).html(content); + + // show popup box + $(this.wrapSelector('.' + VP_FE_POPUP_BOX)).show(); + } + + FrameEditor.prototype.getPopupContent = function() { + var type = this.state.popup; + var content = {}; + switch (parseInt(type)) { + case FRAME_EDIT_TYPE.ADD_COL: + content['name'] = $(this.wrapSelector('.vp-popup-input1')).val(); + content['nameastext'] = $(this.wrapSelector('.vp-popup-istext1')).prop('checked'); + content['value'] = $(this.wrapSelector('.vp-popup-input2')).val(); + content['valueastext'] = $(this.wrapSelector('.vp-popup-istext2')).prop('checked'); + break; + case FRAME_EDIT_TYPE.ADD_ROW: + content['name'] = $(this.wrapSelector('.vp-popup-input1')).val(); + content['nameastext'] = $(this.wrapSelector('.vp-popup-istext1')).prop('checked'); + content['value'] = $(this.wrapSelector('.vp-popup-input2')).val(); + content['valueastext'] = $(this.wrapSelector('.vp-popup-istext2')).prop('checked'); + break; + case FRAME_EDIT_TYPE.RENAME: + this.state.selected.forEach((label, idx) => { + var value = $(this.wrapSelector('.vp-popup-input'+idx)).val(); + var istext = $(this.wrapSelector('.vp-popup-istext'+idx)).prop('checked'); + content[idx] = { + label: label, + value: value, + istext: istext + }; + }); + break; + default: + break; + } + return content; + } + + FrameEditor.prototype.closeInputPopup = function() { + $(this.wrapSelector('.' + VP_FE_POPUP_BOX)).hide(); + } + + FrameEditor.prototype.setDraggableBox = function() { + $('.' + VP_FE_POPUP_BOX).draggable({ + containment: '.' + VP_FE_BODY + }); + } + + FrameEditor.prototype.setDraggableColumns = function() { + + } + FrameEditor.prototype.loadInfo = function() { var that = this; + + // get selected columns/indexes + var selected = []; + $(this.wrapSelector('.' + VP_FE_TABLE + ' th.selected')).each((idx, tag) => { + var name = $(tag).attr('data-label'); + selected.push(name); + }); + this.state.selected = selected; + var code = new sb.StringBuilder(); code.appendFormat("{0}", this.state.tempObj); if (this.state.selected != '') { - code.appendFormat(".loc[[{0}],[{1}]]" - , this.state.axis==0?this.state.selected.join(','):":" - , this.state.axis==1?this.state.selected.join(','):":"); + var rowCode = ':'; + var colCode = ':'; + if (this.state.axis == 0) { + rowCode = '[' + this.state.selected.join(',') + ']'; + } + if (this.state.axis == 1) { + colCode = '[' + this.state.selected.join(',') + ']'; + } + code.appendFormat(".loc[{0},{1}]", rowCode, colCode); } code.append(".value_counts()"); - console.log(code.toString()); kernelApi.executePython(code.toString(), function(result) { $(that.wrapSelector('.' + VP_FE_INFO_CONTENT)).replaceWith(function() { - return vpCommon.formatString('
{1}
', VP_FE_INFO_CONTENT, result); + // return vpCommon.formatString('
{1}
', VP_FE_INFO_CONTENT, result); + return vpCommon.formatString('
{1}
', VP_FE_INFO_CONTENT, result); }); }); } - - FrameEditor.prototype.loadCode = function(type) { - var that = this; + FrameEditor.prototype.getCode = function(type, content={}) { var tempObj = this.state.tempObj; var orgObj = this.state.originObj; - var selectedName = this.state.selected; + + if (!orgObj || orgObj == '') { + // object not selected + + return ''; + } + + var selectedName = this.state.selected.join(','); var axis = this.state.axis; - var lines = this.state.lines; var code = new sb.StringBuilder(); switch (parseInt(type)) { case FRAME_EDIT_TYPE.INIT: - code.appendFormatLine('{0} = {1}.copy()', tempObj, orgObj); + code.appendFormat('{0} = {1}.copy()', tempObj, orgObj); break; - case FRAME_EDIT_TYPE.DELETE: - code.appendFormatLine("{0}.drop(['{1}'], axis={2}, inplace=True)", tempObj, selectedName, axis); + case FRAME_EDIT_TYPE.DROP: + code.appendFormat("{0}.drop([{1}], axis={2}, inplace=True)", tempObj, selectedName, axis); break; case FRAME_EDIT_TYPE.RENAME: + var renameStr = new sb.StringBuilder(); + Object.keys(content).forEach((key, idx) => { + if (idx == 0) { + renameStr.appendFormat("{0}: {1}", content[key].label, convertToStr(content[key].value, content[key].istext)); + } else { + renameStr.appendFormat(", {0}: {1}", content[key].label, convertToStr(content[key].value, content[key].istext)); + } + }); + code.appendFormat("{0}.rename({1}={{2}}, inplace=True)", tempObj, axis==0?'index':'columns', renameStr.toString()); break; case FRAME_EDIT_TYPE.DROP_NA: var locObj = ''; if (axis == 0) { - locObj = vpCommon.formatString('.loc[{0},]', selectedName); + locObj = vpCommon.formatString('.loc[[{0}],:]', selectedName); } else { - locObj = vpCommon.formatString('.loc[,{0}]', selectedName); + locObj = vpCommon.formatString('.loc[:,[{0}]]', selectedName); } - code.appendFormatLine("{0}{1}.dropna(axis={2}, inplace=True)", tempObj, locObj, axis); + code.appendFormat("{0}{1}.dropna(axis={2}, inplace=True)", tempObj, locObj, axis); break; case FRAME_EDIT_TYPE.DROP_DUP: if (axis == 0) { - locObj = vpCommon.formatString('.loc[{0},]', selectedName); + locObj = vpCommon.formatString('.loc[[{0}],:]', selectedName); } else { - locObj = vpCommon.formatString('.loc[,{0}]', selectedName); + locObj = vpCommon.formatString('.loc[:,[{0}]]', selectedName); } - code.appendFormatLine("{0}{1}.drop_duplicates(axis={2}, inplace=True)", tempObj, locObj, axis); + code.appendFormat("{0}{1}.drop_duplicates(axis={2}, inplace=True)", tempObj, locObj, axis); break; case FRAME_EDIT_TYPE.ONE_HOT_ENCODING: break; case FRAME_EDIT_TYPE.SET_IDX: break; case FRAME_EDIT_TYPE.REPLACE: + code.appendFormat("{0}.replace({1}, inplace=True)", tempObj, JSON.stringify(content).replaceAll('"', "'")); + break; + case FRAME_EDIT_TYPE.ADD_COL: + var name = convertToStr(content.name, content.nameastext); + var value = convertToStr(content.value, content.valueastext); + code.appendFormat("{0}[{1}] = {2}", tempObj, name, value); + break; + case FRAME_EDIT_TYPE.ADD_ROW: + var name = convertToStr(content.name, content.nameastext); + var value = convertToStr(content.value, content.valueastext); + code.appendFormat("{0}.loc[{1}] = {2}", tempObj, name, value); break; case FRAME_EDIT_TYPE.SHOW: break; } - var addStackCode = code.toString(); - // search codes //TODO: python 코드로 정리해서 가져올 것 - code.appendFormat("{0}.head({1}).to_json(orient='{2}')", tempObj, lines, 'split'); + return code.toString(); + } + + FrameEditor.prototype.loadCode = function(codeStr) { + var that = this; + + if (code == '') { + return ; + } + + var tempObj = this.state.tempObj; + var lines = this.state.lines; + var code = new sb.StringBuilder(); + code.appendLine(codeStr); + code.appendFormat("{0}.head({1}).to_json(orient='{2}')", tempObj, lines, 'split'); + console.log(code.toString()); kernelApi.executePython(code.toString(), function(result) { - console.log(result); try { - // var data = JSON.parse(result); - var data = JSON.parse(result.substr(1,result.length - 2)); + var data = JSON.parse(result.substr(1,result.length - 2).replaceAll('\\\\', '\\')); + console.log(data); var columnList = data.columns; var indexList = data.index; var dataList = data.data; @@ -333,32 +616,52 @@ define([ table.appendLine(''); table.appendLine(''); columnList && columnList.forEach(col => { - table.appendFormatLine('{1}', vpCommon.convertToStr(col), col); + var colLabel = convertToStr(col, typeof col == 'string'); + var colClass = ''; + if (that.state.axis == 1 && that.state.selected.includes(colLabel)) { + colClass = 'selected'; + } + table.appendFormatLine('{4}', colLabel, 1, VP_FE_TABLE_COLUMN, colClass, col); }); + // add column + table.appendFormatLine('', VP_FE_ADD_COLUMN, 'fa fa-plus'); + table.appendLine(''); table.appendLine(''); table.appendLine(''); dataList && dataList.forEach((row, idx) => { table.appendLine(''); - table.appendFormatLine('{1}', vpCommon.convertToStr(indexList[idx]), indexList[idx]); + var idxName = indexList[idx]; + var idxLabel = convertToStr(idxName, typeof idxName == 'string'); + var idxClass = ''; + if (that.state.axis == 0 && that.state.selected.includes(idxLabel)) { + idxClass = 'selected'; + } + table.appendFormatLine('{4}', idxLabel, 0, VP_FE_TABLE_ROW, idxClass, idxName); row.forEach(cell => { if (cell == null) { cell = 'NaN'; } table.appendFormatLine('{0}', cell); }); + // empty data + // table.appendLine(''); table.appendLine(''); }); + // add row + table.appendLine(''); + table.appendFormatLine('', VP_FE_ADD_ROW, 'fa fa-plus'); table.appendLine(''); - + table.appendLine(''); $(that.wrapSelector('.' + VP_FE_TABLE)).replaceWith(function() { return that.renderTable(table.toString()); }); // load info that.loadInfo(); // add to stack - that.state.steps.push(addStackCode); + that.state.steps.push(codeStr); + that.setPreview(codeStr); } catch (err) { console.log(err); } @@ -390,11 +693,11 @@ define([ // initialize state values that.state.originObj = origin; - that.state.tempObj = '_vp_temp_' + origin; + that.state.tempObj = '_vp'; that.initState(); // load code with temporary df - that.loadCode(FRAME_EDIT_TYPE.INIT); + that.loadCode(that.getCode(FRAME_EDIT_TYPE.INIT)); that.loadInfo(); }); @@ -403,16 +706,64 @@ define([ that.loadVariableList(); }); + // menu on column + $(document).on('contextmenu', this.wrapSelector('.' + VP_FE_TABLE + ' .' + VP_FE_TABLE_COLUMN), function(event) { + event.preventDefault(); + var hasSelected = $(this).hasClass('selected'); + $(that.wrapSelector('.' + VP_FE_TABLE + ' .' + VP_FE_TABLE_ROW)).removeClass('selected'); + // select col/idx + if (!hasSelected) { + $(this).addClass('selected'); + var newAxis = $(this).attr('data-axis'); + that.state.axis = newAxis; + } + + that.loadInfo(); + + // show menu + var thisPos = $(this).position(); + var thisRect = $(this)[0].getBoundingClientRect(); + that.showMenu(thisPos.left, thisPos.top + thisRect.height); + }); + + // menu on row + $(document).on('contextmenu', this.wrapSelector('.' + VP_FE_TABLE + ' .' + VP_FE_TABLE_ROW), function(event) { + event.preventDefault(); + var hasSelected = $(this).hasClass('selected'); + $(that.wrapSelector('.' + VP_FE_TABLE + ' .' + VP_FE_TABLE_COLUMN)).removeClass('selected'); + // select col/idx + if (!hasSelected) { + $(this).addClass('selected'); + var newAxis = $(this).attr('data-axis'); + that.state.axis = newAxis; + } + + that.loadInfo(); + + // show menu + var thisPos = $(this).position(); + var thisRect = $(this)[0].getBoundingClientRect(); + var tblPos = $(that.wrapSelector('.' + VP_FE_TABLE)).position(); + that.showMenu(tblPos.left + thisRect.width, tblPos.top + thisPos.top); + }); + + // hide menu + $(document).on('click', function(evt) { + if (evt.target.id != 'vp_apiblock_menu_box') { + // close menu + that.hideMenu(); + } + }); + // select column - $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' thead th'), function() { + $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' .' + VP_FE_TABLE_COLUMN), function() { var hasSelected = $(this).hasClass('selected'); - $(that.wrapSelector('.' + VP_FE_TABLE + ' tbody th')).removeClass('selected'); + $(that.wrapSelector('.' + VP_FE_TABLE + ' .' + VP_FE_TABLE_ROW)).removeClass('selected'); if (!hasSelected) { $(this).addClass('selected'); - that.state.axis = 1; // column - that.state.selected = $(this).text(); + var newAxis = $(this).attr('data-axis'); + that.state.axis = newAxis; } else { - that.initState(); $(this).removeClass('selected'); } @@ -420,32 +771,103 @@ define([ }); // select row - $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' tbody th'), function() { + $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE + ' .' + VP_FE_TABLE_ROW), function() { var hasSelected = $(this).hasClass('selected'); - $(that.wrapSelector('.' + VP_FE_TABLE + ' thead th')).removeClass('selected'); + $(that.wrapSelector('.' + VP_FE_TABLE + ' .' + VP_FE_TABLE_COLUMN)).removeClass('selected'); if (!hasSelected) { $(this).addClass('selected'); - that.state.axis = 0; // index(row) - that.state.selected = $(this).text(); + var newAxis = $(this).attr('data-axis'); + that.state.axis = newAxis; } else { - that.initState(); $(this).removeClass('selected'); } that.loadInfo(); }); + // add column + $(document).on('click', this.wrapSelector('.' + VP_FE_ADD_COLUMN), function() { + // add column + that.openInputPopup(FRAME_EDIT_TYPE.ADD_COL); + }); + + // add row + $(document).on('click', this.wrapSelector('.' + VP_FE_ADD_ROW), function() { + // add row + that.openInputPopup(FRAME_EDIT_TYPE.ADD_ROW); + }); + // more rows $(document).on('click', this.wrapSelector('.' + VP_FE_TABLE_MORE), function() { that.state.lines += TABLE_LINES; - that.loadCode(FRAME_EDIT_TYPE.SHOW); + that.loadCode(that.getCode(FRAME_EDIT_TYPE.SHOW)); }); - // click button - $(document).on('click', this.wrapSelector('.' + VP_FE_MENU_ITEM), function() { + // click menu item + $(document).on('click', this.wrapSelector('.' + VP_FE_MENU_ITEM), function(event) { + event.stopPropagation(); var editType = $(this).attr('data-type'); - that.loadCode(editType); + switch (parseInt(editType)) { + case FRAME_EDIT_TYPE.ADD_COL: + case FRAME_EDIT_TYPE.ADD_ROW: + case FRAME_EDIT_TYPE.RENAME: + that.openInputPopup(editType); + return; + } + that.loadCode(that.getCode(editType)); }); + + // ok input popup + $(document).on('click', this.wrapSelector('.' + VP_FE_POPUP_OK), function() { + // TODO: ok input popup + that.loadCode(that.getCode(that.state.popup, that.getPopupContent())); + that.closeInputPopup(); + }); + + // cancel input popup + $(document).on('click', this.wrapSelector('.' + VP_FE_POPUP_CANCEL), function() { + that.closeInputPopup(); + }); + + // close input popup + $(document).on('click', this.wrapSelector('.' + VP_FE_POPUP_CLOSE), function() { + that.closeInputPopup(); + }); + + // click cancel + $(document).on('click', this.wrapSelector('.' + VP_FE_BUTTON_CANCEL), function() { + that.close(); + }); + + // click apply + $(document).on('click', this.wrapSelector('.' + VP_FE_BUTTON_APPLY), function() { + $(that.pageThis.wrapSelector('#' + that.targetId)).val(that.state.steps.join('\n')); + $(that.pageThis.wrapSelector('#' + that.targetId)).trigger('frame_apply'); + that.close(); + }); + } + + FrameEditor.prototype.showMenu = function(left, top) { + if (this.state.axis == 0) { + // row + + } else if (this.state.axis == 1) { + // column + + } + $(this.wrapSelector(vpCommon.formatString('.{0}', VP_FE_MENU_BOX))).css({ top: top, left: left }) + $(this.wrapSelector(vpCommon.formatString('.{0}', VP_FE_MENU_BOX))).show(); + } + + FrameEditor.prototype.hideMenu = function() { + $(this.wrapSelector(vpCommon.formatString('.{0}', VP_FE_MENU_BOX))).hide(); + } + + var convertToStr = function(code, isText) { + if (isText) { + code = "'" + code + "'"; + } + return code; } return FrameEditor; diff --git a/src/pandas/common/commonPandas.js b/src/pandas/common/commonPandas.js index d76f9a44..37c0eb60 100644 --- a/src/pandas/common/commonPandas.js +++ b/src/pandas/common/commonPandas.js @@ -5365,7 +5365,7 @@ define([ name:'i0', type:'var', label: 'Code', - component: 'var_select', + component: 'textarea', var_type: ['DataFrame'] } ], diff --git a/src/pandas/common/pandasGenerator.js b/src/pandas/common/pandasGenerator.js index 85946008..0ab6a784 100644 --- a/src/pandas/common/pandasGenerator.js +++ b/src/pandas/common/pandasGenerator.js @@ -179,6 +179,15 @@ define([ vp_generateVarSelect(tag, obj.var_type, obj.value); tblInput.appendChild(tag); break; + case 'textarea': + var textarea = $(``); + // cell metadata test + if (getValue && obj.value != undefined) { + // metadata 에 저장된 값으로 표시 + textarea.val(obj.value); + } + $(tblInput).append(textarea); + break; case 'table': // break; case 'file': diff --git a/src/pandas/frameEditor.js b/src/pandas/frameEditor.js index af161a6d..1295f465 100644 --- a/src/pandas/frameEditor.js +++ b/src/pandas/frameEditor.js @@ -115,12 +115,14 @@ define([ PandasPackage.prototype.generateCode = function(addCell, exec) { var sbCode = new sb.StringBuilder(); - // 코드 생성 - var result = pdGen.vp_codeGenerator(this.uuid, this.package); - if (result == null) return "BREAK_RUN"; // 코드 생성 중 오류 발생 - sbCode.append(result); + var codeValue = $(this.wrapSelector('#i0')).val(); + var returnValue = $(this.wrapSelector('#o0')).val(); + if (returnValue && returnValue != '') { + sbCode.appendFormat('{0} = ', returnValue); + } + sbCode.append(codeValue); if (addCell) this.cellExecute(sbCode.toString(), exec);