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/pandas/common/commonPandas' , 'nbextensions/visualpython/src/pandas/common/pandasGenerator' , 'nbextensions/visualpython/src/common/component/vpSuggestInputText' , 'nbextensions/visualpython/src/common/vpSubsetEditor' ], function (requirejs, $, vpCommon, vpConst, sb, vpFuncJS, libPandas, pdGen, vpSuggestInputText, vpSubsetEditor) { // 옵션 속성 const funcOptProp = { stepCount : 1 , funcName : "Plot" , funcID : "pd_plot" // TODO: ID 규칙 생성 필요 , libID : "pd121" } /** * 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/plot.html", optionLoadCallback, callback, meta); } /** * 본 옵션 처리 위한 클래스 * @param {String} uuid 고유 id */ var PandasPackage = function(uuid) { this.uuid = uuid; // Load html 영역의 uuid. // pandas 함수 this.package = libPandas._PANDAS_FUNCTION[funcOptProp.libID]; // plot 종류 this.plotKind = { 'line': '선', 'bar': '막대', 'barh': '가로막대', 'hist': '히스토그램', 'box': '박스플롯', 'kde': 'Kernel Density Estimation', 'area': 'Area', 'pie': '파이', 'scatter': 'Scatter', 'hexbin': 'Hexbin' } // cmap 종류 this.cmap = [ '', 'viridis', 'plasma', 'inferno', 'magma', 'cividis' , 'Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'tab20b', 'tab20c' ] } /** * 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.showFunctionTitle(); 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/plot.css"); this.bindOptionEvents(); this.bindOptions(); this.bindKindSelector(); this.bindCmapSelector(); this.bindOptionDivider(14); this.subsetEditor = new vpSubsetEditor(this, "i0"); } /** * 선택한 패키지명 입력 */ PandasPackage.prototype.showFunctionTitle = function() { $(this.wrapSelector('.vp_functionName')).text(this.package.name); } /** * Plot 유형 선택하는 폼 구성 */ PandasPackage.prototype.bindKindSelector = function() { var that = this; // 기존 유형 선택하는 select 태그 안보이게 var kindSelector = this.wrapSelector('#kind'); $(kindSelector).closest('tr').hide(); // 메타데이터로 차트유형 선택 var plotType = 'line'; if (this.metadata != undefined && this.metadata.options != undefined) { plotType = this.metadata.options.filter(o => o.id == 'kind')[0].value; } // 차트 선택 $(kindSelector).val(plotType); $(this.wrapSelector(`#vp_plotKind .vp-plot-item[data-kind="${plotType}"]`)).addClass('selected'); // 유형 선택 이벤트 $(this.wrapSelector('.vp-plot-item')).click(function() { // 선택한 플롯 유형 박스 표시 $(this).parent().find('.vp-plot-item').removeClass('selected'); $(this).addClass('selected'); // select 태그 강제 선택 var type = $(this).data('kind'); $(kindSelector).val(type).prop('selected', true); // area plot일 경우 stacked true if (type == 'area') { $(that.wrapSelector('#stacked')).prop('checked', true); } else { $(that.wrapSelector('#stacked')).prop('checked', false); } // pie plot일 경우 subplots true if (type == 'pie') { $(that.wrapSelector('#subplots')).prop('checked', true); } else { $(that.wrapSelector('#subplots')).prop('checked', false); } // scatter, hexbin plot일 경우 x, y 컬럼 필수 입력으로 if (type == 'scatter' || type == 'hexbin') { // that.package = JSON.parse(JSON.stringify(that.package)); that.package = JSON.parse(JSON.stringify(that.package)); var idx = that.package.variable.findIndex(function(item) {return item.name === 'x'}); if (idx > -1) { that.package.variable[idx].required = true; // 라벨 필수 입력 표시 // $(that.wrapSelector('#x')).closest('tr').find('label').text(`* ${that.package.variable[idx].label} (${that.package.variable[idx].name})`); var tag = $(that.wrapSelector('#x')).closest('tr').find('label'); if (!$(tag).hasClass(vpConst.COLOR_FONT_ORANGE)) { $(that.wrapSelector('#x')).closest('tr').find('label').addClass(vpConst.COLOR_FONT_ORANGE); } } var idy = that.package.variable.findIndex(function(item) {return item.name === 'y'}); if (idy > -1) { that.package.variable[idy].required = true; // 라벨 필수 입력 표시 // $(that.wrapSelector('#y')).closest('tr').find('label').text(`* ${that.package.variable[idy].label} (${that.package.variable[idy].name})`); var tag = $(that.wrapSelector('#y')).closest('tr').find('label'); if (!$(tag).hasClass(vpConst.COLOR_FONT_ORANGE)) { $(that.wrapSelector('#y')).closest('tr').find('label').addClass(vpConst.COLOR_FONT_ORANGE); } } } else { that.package = libPandas._PANDAS_FUNCTION[funcOptProp.libID]; // 라벨 재정비 var idx = that.package.variable.findIndex(function(item) {return item.name === 'x'}); if (idx > -1) { // $(that.wrapSelector('#x')).closest('tr').find('label').text(`${that.package.variable[idx].label} (${that.package.variable[idx].name})`); $(that.wrapSelector('#x')).closest('tr').find('label').removeClass(vpConst.COLOR_FONT_ORANGE); } var idy = that.package.variable.findIndex(function(item) {return item.name === 'y'}); if (idy > -1) { // $(that.wrapSelector('#y')).closest('tr').find('label').text(`${that.package.variable[idy].label} (${that.package.variable[idy].name})`); $(that.wrapSelector('#y')).closest('tr').find('label').removeClass(vpConst.COLOR_FONT_ORANGE); } } }); } /** * Plot 색상 팔레트 선택하는 폼 구성 */ PandasPackage.prototype.bindCmapSelector = function() { // 기존 cmap 선택하는 select 태그 안보이게 var cmapSelector = this.wrapSelector('#colormap'); $(cmapSelector).closest('tr').hide(); // cmap 데이터로 팔레트 div 동적 구성 this.cmap.forEach(ctype => { var divColor = document.createElement('div'); $(divColor).attr({ 'class': 'vp-plot-cmap-item', 'data-cmap': ctype, 'data-url': 'pandas/cmap/' + ctype + '.JPG', 'title': ctype }); $(divColor).text(ctype == ''?'None':ctype); // 이미지 url 바인딩 var url = Jupyter.notebook.base_url + vpConst.BASE_PATH + vpConst.RESOURCE_PATH + 'pandas/cmap/' + ctype + '.JPG'; $(divColor).css({ 'background-image' : 'url(' + url + ')' }); var selectedCmap = this.wrapSelector('#vp_selectedCmap'); // 선택 이벤트 등록 $(divColor).click(function() { if (!$(this).hasClass('selected')) { $(this).parent().find('.vp-plot-cmap-item.selected').removeClass('selected'); $(this).addClass('selected'); // 선택된 cmap 이름 표시 $(selectedCmap).text(ctype == ''?'None':ctype); // 선택된 cmap data-caption-id 변경 $(selectedCmap).attr('data-caption-id', ctype); // select 태그 강제 선택 $(cmapSelector).val(ctype).prop('selected', true); } }); $(this.wrapSelector('#vp_plotCmapSelector')).append(divColor); }); // 선택 이벤트 $(this.wrapSelector('.vp-plot-cmap-wrapper')).click(function() { $(this).toggleClass('open'); }); } /** * Pandas Plot 옵션 페이지 분리 * @param {number} div1Count 분리할 기준 숫자 (숫자 기준으로 이후 항목들 분리) */ PandasPackage.prototype.bindOptionDivider = function(div1Count) { var option2 = $(this.wrapSelector('#vp_optionBox table tbody tr')).filter(':gt(2):lt(' + (div1Count) + ')'); $(this.wrapSelector('#vp_graphicsOptionBox table tbody')).append(option2); // useroption to the end $(this.wrapSelector('#vp_optionBox table tbody')).append($(this.wrapSelector('#vp_optionBox table tbody tr:nth-child(2)'))); } /** * 옵션 변경에 따른 이벤트 등록 */ PandasPackage.prototype.bindOptionEvents = function() { var that = this; // 서브플롯 선택에 따라 layout 옵션 show/hide var optLayout = this.wrapSelector('#layout'); $(optLayout).closest('tr').hide(); $(this.wrapSelector('#subplots')).change(function() { var checked = $(this).prop('checked'); if (checked) { $(optLayout).closest('tr').show(); } else { $(optLayout).closest('tr').hide(); } }); // on dataframe change $(document).on('select_suggestvalue change', this.wrapSelector('#i0'), function() { // dataframe columns search pdGen.vp_bindColumnSource(that, this, ['x', 'y']); }); } /** * Pandas 기본 패키지 바인딩 */ PandasPackage.prototype.bindOptions = function() { // HTML 구성 pdGen.vp_showInterface(this); $(this.wrapSelector('#x')).closest('tr').insertBefore('#vp_inputOutputBox tbody tr:last'); $(this.wrapSelector('#y')).closest('tr').insertBefore('#vp_inputOutputBox tbody tr:last'); }; /** * 코드 생성 * @param {boolean} exec 실행여부 */ PandasPackage.prototype.generateCode = function(addCell, exec) { try { var sbCode = new sb.StringBuilder(); // 사용자 옵션 설정값 가져오기 var userOption = new sb.StringBuilder(); // $(this.wrapSelector('#vp_userOptionBox table tbody tr:gt(0):not(:last)')).each(function() { // var key = $(this).find('td:nth-child(1) input').val(); // var val = $(this).find('td:nth-child(2) input').val(); // if (key != '' && val != '') { // userOption.appendFormat(', {0}={1}', key, val); // } else { // throw '사용자 옵션 키/값을 입력하거나 삭제해 주세요.'; // } // }); var package = JSON.parse(JSON.stringify(this.package)); var userOptVal = $(this.wrapSelector('#vp_userOption')).val(); if (userOptVal != '') { package.variable.push({name: 'vp_userOption', value: userOptVal}); userOption.appendFormat(', {0}', userOptVal); } this.package = package; // 코드 생성 var result = pdGen.vp_codeGenerator(this.uuid, package, userOption.toString()); if (result == null) return "BREAK_RUN"; // 코드 생성 중 오류 발생 sbCode.append(result); // cell metadata 작성하기 // pdGen.vp_setCellMetadata(_VP_CODEMD); if (addCell) this.cellExecute(sbCode.toString(), exec); } catch (exmsg) { // 에러 표시 vpCommon.renderAlertModal(exmsg); return "BREAK_RUN"; } return sbCode.toString(); } return { initOption: initOption }; });