/* * Project Name : Visual Python * Description : GUI-based Python code generator * File Name : Plotly.js * Author : Black Logic * Note : Visualization > Plotly * License : GNU GPLv3 with Visual Python special exception * Date : 2022. 05. 16 * Change Date : */ //============================================================================ // [CLASS] Plotly //============================================================================ define([ 'text!vp_base/html/m_visualize/plotly.html!strip', 'css!vp_base/css/m_visualize/plotly.css', 'vp_base/js/com/com_String', 'vp_base/js/com/com_generatorV2', 'vp_base/js/com/com_util', 'vp_base/js/com/component/PopupComponent', 'vp_base/js/com/component/SuggestInput', 'vp_base/js/com/component/FileNavigation', 'vp_base/js/com/component/DataSelector', 'vp_base/data/m_visualize/plotlyLibrary', ], function(ptHTML, ptCss, com_String, com_generator, com_util, PopupComponent, SuggestInput, FileNavigation, DataSelector, PLOTLY_LIBRARIES) { class Plotly extends PopupComponent { _init() { super._init(); this.config.size = { width: 1064, height: 550 }; this.config.installButton = true; this.config.importButton = true; this.config.dataview = false; this.config.checkModules = ['px']; this.state = { chartType: 'scatter', data: '', x: '', y: '', z: '', x_start: '', x_end: '', values: '', names: '', parents: '', color: '', sort: '', userOption: '', title: '', x_label: '', y_label: '', userCode: '', autoRefresh: true, ...this.state } /** * Plotly.express functions * --- * Basics: scatter, line, area, bar, funnel, timeline * Part-of-Whole: pie, sunburst, treemap, icicle, funnel_area * 1D Distributions: histogram, box, violin, strip, ecdf * 2D Distributions: density_heatmap, density_contour * Matrix or Image Input: imshow * 3-Dimensional: scatter_3d, line_3d * Multidimensional: scatter_matrix, parallel_coordinates, parallel_categories * Tile Maps: scatter_mapbox, line_mapbox, choropleth_mapbox, density_mapbox * Outline Maps: scatter_geo, line_geo, choropleth * Polar Charts: scatter_polar, line_polar, bar_polar * Ternary Charts: scatter_ternary, line_ternary */ this.chartConfig = PLOTLY_LIBRARIES; this.chartTypeList = { 'Basics': [ 'scatter', 'line', 'area', 'bar', 'funnel', 'timeline' ], 'Part-of-Whole': [ 'pie', 'sunburst', 'treemap', 'funnel_area' ], // 'icicle' removed '1D Distributions': [ 'histogram', 'box', 'violin', 'strip' ], // 'ecdf' removed '2D Distributions': [ 'density_heatmap', 'density_contour' ], 'Matrix or Image Input': [ 'imshow' ], // '3-Dimensional': [ 'scatter_3d', 'line_3d' ], // 'Multidimensional': [ 'scatter_matrix', 'parallel_coordinates', 'parallel_categories' ], // 'Tile Maps': [ 'scatter_mapbox', 'line_mapbox', 'choropleth_mapbox', 'density_mapbox' ], // 'Outline Maps': [ 'scatter_geo', 'line_geo', 'choropleth' ], // 'Polar Charts': [ 'scatter_polar', 'line_polar', 'bar_polar' ], // 'Ternary Charts': [ 'scatter_ternary', 'line_ternary' ], } } _bindEvent() { super._bindEvent(); let that = this; // change tab $(this.wrapSelector('.vp-tab-item')).on('click', function() { let type = $(this).data('type'); // data / wordcloud / plot $(that.wrapSelector('.vp-tab-bar .vp-tab-item')).removeClass('vp-focus'); $(this).addClass('vp-focus'); $(that.wrapSelector('.vp-tab-page-box > .vp-tab-page')).hide(); $(that.wrapSelector(com_util.formatString('.vp-tab-page[data-type="{0}"]', type))).show(); }); // change chart type $(this.wrapSelector('#chartType')).on('change', function() { let chartType = $(this).val(); $(that.wrapSelector('.pt-option')).hide(); if (chartType === 'density_heatmap' || chartType === 'density_contour') { // show x, y, z $(that.wrapSelector('#x')).closest('.pt-option').show(); $(that.wrapSelector('#y')).closest('.pt-option').show(); $(that.wrapSelector('#z')).closest('.pt-option').show(); if (chartType === 'density_contour') { $(that.wrapSelector('#color')).closest('.pt-option').show(); } } else if (chartType === 'timeline') { $(that.wrapSelector('#x_start')).closest('.pt-option').show(); $(that.wrapSelector('#x_end')).closest('.pt-option').show(); $(that.wrapSelector('#y')).closest('.pt-option').show(); $(that.wrapSelector('#color')).closest('.pt-option').show(); } else if (chartType === 'pie' || chartType === 'funnel_area') { // show values, names $(that.wrapSelector('#values')).closest('.pt-option').show(); $(that.wrapSelector('#names')).closest('.pt-option').show(); $(that.wrapSelector('#color')).closest('.pt-option').show(); } else if (chartType === 'sunburst' || chartType === 'treemap' || chartType === 'icicle') { // show values, names, parents $(that.wrapSelector('#values')).closest('.pt-option').show(); $(that.wrapSelector('#names')).closest('.pt-option').show(); $(that.wrapSelector('#color')).closest('.pt-option').show(); $(that.wrapSelector('#parents')).closest('.pt-option').show(); } else { // show x, y $(that.wrapSelector('#x')).closest('.pt-option').show(); $(that.wrapSelector('#y')).closest('.pt-option').show(); $(that.wrapSelector('#color')).closest('.pt-option').show(); } $(that.wrapSelector('#sort')).closest('.pt-option').show(); }); // use data or not $(this.wrapSelector('#setXY')).on('change', function() { let setXY = $(this).prop('checked'); if (setXY == false) { // set Data $(that.wrapSelector('#data')).prop('disabled', false); $(that.wrapSelector('#x')).closest('.vp-ds-box').replaceWith(''); $(that.wrapSelector('#x_start')).closest('.vp-ds-box').replaceWith(''); $(that.wrapSelector('#x_end')).closest('.vp-ds-box').replaceWith(''); $(that.wrapSelector('#y')).closest('.vp-ds-box').replaceWith(''); $(that.wrapSelector('#z')).closest('.vp-ds-box').replaceWith(''); $(that.wrapSelector('#values')).closest('.vp-ds-box').replaceWith(''); $(that.wrapSelector('#names')).closest('.vp-ds-box').replaceWith(''); $(that.wrapSelector('#parents')).closest('.vp-ds-box').replaceWith(''); $(that.wrapSelector('#color')).closest('.vp-ds-box').replaceWith(''); } else { // set X Y indivisually // disable data selection $(that.wrapSelector('#data')).prop('disabled', true); $(that.wrapSelector('#data')).val(''); that.state.data = ''; that.state.x = ''; that.state.x_start = ''; that.state.x_end = ''; that.state.y = ''; that.state.z = ''; that.state.values = ''; that.state.names = ''; that.state.parents = ''; that.state.color = ''; let dataSelectorX = new DataSelector({ pageThis: that, id: 'x' }); $(that.wrapSelector('#x')).replaceWith(dataSelectorX.toTagString()); let dataSelectorXStart = new DataSelector({ pageThis: that, id: 'x_start' }); $(that.wrapSelector('#x_start')).replaceWith(dataSelectorXStart.toTagString()); let dataSelectorXEnd = new DataSelector({ pageThis: that, id: 'x_end' }); $(that.wrapSelector('#x_end')).replaceWith(dataSelectorXEnd.toTagString()); let dataSelectorY = new DataSelector({ pageThis: that, id: 'y' }); $(that.wrapSelector('#y')).replaceWith(dataSelectorY.toTagString()); let dataSelectorZ = new DataSelector({ pageThis: that, id: 'z' }); $(that.wrapSelector('#z')).replaceWith(dataSelectorZ.toTagString()); let dataSelectorValues = new DataSelector({ pageThis: that, id: 'values' }); $(that.wrapSelector('#values')).replaceWith(dataSelectorValues.toTagString()); let dataSelectorNames = new DataSelector({ pageThis: that, id: 'names' }); $(that.wrapSelector('#names')).replaceWith(dataSelectorNames.toTagString()); let dataSelectorParents = new DataSelector({ pageThis: that, id: 'parents' }); $(that.wrapSelector('#parents')).replaceWith(dataSelectorParents.toTagString()); let dataSelectorColor = new DataSelector({ pageThis: that, id: 'color' }); $(that.wrapSelector('#color')).replaceWith(dataSelectorColor.toTagString()); } }); // load preview // preview refresh $(this.wrapSelector('#previewRefresh')).on('click', function() { that.loadPreview(); }); $(document).off('change', this.wrapSelector('.vp-state')); $(document).on('change', this.wrapSelector('.vp-state'), function(evt) { that._saveSingleState($(this)[0]); if (that.state.autoRefresh) { that.loadPreview(); } evt.stopPropagation(); }); } templateForBody() { let page = $(ptHTML); let that = this; // chart types let chartTypeTag = new com_String(); Object.keys(this.chartTypeList).forEach(chartCategory => { let chartOptionTag = new com_String(); that.chartTypeList[chartCategory].forEach(opt => { let optConfig = that.chartConfig[opt]; let selectedFlag = ''; if (opt == that.state.chartType) { selectedFlag = 'selected'; } chartOptionTag.appendFormatLine('', opt, selectedFlag, opt); }) chartTypeTag.appendFormatLine('{1}', chartCategory, chartOptionTag.toString()); }); $(page).find('#chartType').html(chartTypeTag.toString()); // chart variable let dataSelector = new DataSelector({ type: 'data', pageThis: this, id: 'data', select: function(value, dtype) { that.state.dtype = dtype; if (dtype == 'DataFrame') { $(that.wrapSelector('#x')).prop('disabled', false); $(that.wrapSelector('#x_start')).prop('disabled', false); $(that.wrapSelector('#x_end')).prop('disabled', false); $(that.wrapSelector('#y')).prop('disabled', false); $(that.wrapSelector('#z')).prop('disabled', false); $(that.wrapSelector('#values')).prop('disabled', false); $(that.wrapSelector('#names')).prop('disabled', false); $(that.wrapSelector('#parents')).prop('disabled', false); $(that.wrapSelector('#color')).prop('disabled', false); // bind column source using selected dataframe com_generator.vp_bindColumnSource(that, 'data', [ 'x', 'x_start', 'x_end', 'y', 'z', 'values', 'names', 'parents', 'color' ], 'select', true, true); } else { $(that.wrapSelector('#x')).prop('disabled', true); $(that.wrapSelector('#x_start')).prop('disabled', true); $(that.wrapSelector('#x_end')).prop('disabled', true); $(that.wrapSelector('#y')).prop('disabled', true); $(that.wrapSelector('#z')).prop('disabled', true); $(that.wrapSelector('#values')).prop('disabled', true); $(that.wrapSelector('#names')).prop('disabled', true); $(that.wrapSelector('#parents')).prop('disabled', true); $(that.wrapSelector('#color')).prop('disabled', true); } }, finish: function(value, dtype) { that.state.dtype = dtype; if (dtype == 'DataFrame') { $(that.wrapSelector('#x')).prop('disabled', false); $(that.wrapSelector('#x_start')).prop('disabled', false); $(that.wrapSelector('#x_end')).prop('disabled', false); $(that.wrapSelector('#y')).prop('disabled', false); $(that.wrapSelector('#z')).prop('disabled', false); $(that.wrapSelector('#values')).prop('disabled', false); $(that.wrapSelector('#names')).prop('disabled', false); $(that.wrapSelector('#parents')).prop('disabled', false); $(that.wrapSelector('#color')).prop('disabled', false); // bind column source using selected dataframe com_generator.vp_bindColumnSource(that, 'data', [ 'x', 'x_start', 'x_end', 'y', 'z', 'values', 'names', 'parents', 'color' ], 'select', true, true); } else { $(that.wrapSelector('#x')).prop('disabled', true); $(that.wrapSelector('#x_start')).prop('disabled', true); $(that.wrapSelector('#x_end')).prop('disabled', true); $(that.wrapSelector('#y')).prop('disabled', true); $(that.wrapSelector('#z')).prop('disabled', true); $(that.wrapSelector('#values')).prop('disabled', true); $(that.wrapSelector('#names')).prop('disabled', true); $(that.wrapSelector('#parents')).prop('disabled', true); $(that.wrapSelector('#color')).prop('disabled', true); } } }); $(page).find('#data').replaceWith(dataSelector.toTagString()); $(page).find('.pt-option').hide(); if (this.state.chartType === 'density_heatmap' || this.state.chartType === 'density_contour') { // show x, y, z $(page).find('#x').closest('.pt-option').show(); $(page).find('#y').closest('.pt-option').show(); $(page).find('#z').closest('.pt-option').show(); if (this.state.chartType === 'density_contour') { $(page).find('#color').closest('.pt-option').show(); } } else if (this.state.chartType === 'timeline') { // show x_start, x_end, y $(page).find('#x_start').closest('.pt-option').show(); $(page).find('#x_end').closest('.pt-option').show(); $(page).find('#y').closest('.pt-option').show(); $(page).find('#color').closest('.pt-option').show(); } else if (this.state.chartType === 'pie' || this.state.chartType === 'funnel_area') { // show values, names $(page).find('#values').closest('.pt-option').show(); $(page).find('#names').closest('.pt-option').show(); $(page).find('#color').closest('.pt-option').show(); } else if (this.state.chartType === 'sunburst' || this.state.chartType === 'treemap' || this.state.chartType === 'icicle') { // show values, names, parents $(page).find('#values').closest('.pt-option').show(); $(page).find('#names').closest('.pt-option').show(); $(page).find('#color').closest('.pt-option').show(); $(page).find('#parents').closest('.pt-option').show(); } else { // show x, y $(page).find('#x').closest('.pt-option').show(); $(page).find('#y').closest('.pt-option').show(); $(page).find('#color').closest('.pt-option').show(); } $(page).find('#sort').closest('.pt-option').show(); //================================================================ // Load state //================================================================ Object.keys(this.state).forEach(key => { let tag = $(page).find('#' + key); let tagName = $(tag).prop('tagName'); // returns with UpperCase let value = that.state[key]; if (value == undefined) { return; } switch(tagName) { case 'INPUT': let inputType = $(tag).prop('type'); if (inputType == 'text' || inputType == 'number' || inputType == 'hidden') { $(tag).val(value); break; } if (inputType == 'checkbox') { $(tag).prop('checked', value); break; } break; case 'TEXTAREA': case 'SELECT': default: $(tag).val(value); break; } }); return page; } render() { super.render(); let that = this; // Add style $(this.wrapSelector('.vp-popup-body-top-bar')).css({ 'position': 'absolute', 'left': 'calc(50% - 250px)' }); $(this.wrapSelector('.vp-popup-codeview-box')).css({ 'height': '200px' }); // load code tab - code mirror let userCodeKey = 'userCode'; let userCodeTarget = this.wrapSelector('#' + userCodeKey); this.codeArea = this.initCodemirror({ key: userCodeKey, selector: userCodeTarget, events: [{ key: 'blur', callback: function(instance, evt) { // save its state instance.save(); that.state[userCodeKey] = $(userCodeTarget).val(); // refresh preview that.loadPreview(); } }] }); this.loadPreview(); } loadPreview() { let that = this; let code = this.generateCode(true); that.checkAndRunModules(true).then(function() { // show variable information on clicking variable vpKernel.execute(code).then(function(resultObj) { let { result, type, msg } = resultObj; if (msg.content.data) { var textResult = msg.content.data["text/plain"]; var htmlResult = msg.content.data["text/html"]; var imgResult = msg.content.data["image/png"]; $(that.wrapSelector('#vp_ptPreview')).html(''); if (htmlResult != undefined) { // 1. HTML tag $(that.wrapSelector('#vp_ptPreview')).append(htmlResult); } else if (imgResult != undefined) { // 2. Image data (base64) var imgTag = ''; $(that.wrapSelector('#vp_ptPreview')).append(imgTag); } else if (textResult != undefined) { // 3. Text data var preTag = document.createElement('pre'); $(preTag).text(textResult); $(that.wrapSelector('#vp_ptPreview')).html(preTag); } } else { var errorContent = ''; if (msg.content.ename) { errorContent = com_util.templateForErrorBox(msg.content.ename, msg.content.evalue); } $(that.wrapSelector('#vp_ptPreview')).html(errorContent); vpLog.display(VP_LOG_TYPE.ERROR, msg.content.ename, msg.content.evalue, msg.content); } }).catch(function(resultObj) { let { msg } = resultObj; var errorContent = ''; if (msg.content.ename) { errorContent = com_util.templateForErrorBox(msg.content.ename, msg.content.evalue); } $(that.wrapSelector('#vp_ptPreview')).html(errorContent); vpLog.display(VP_LOG_TYPE.ERROR, msg.content.ename, msg.content.evalue, msg.content); }); }); } generateInstallCode() { return ['!pip install plotly']; } generateImportCode() { var code = new com_String(); code.appendLine('import plotly.express as px'); // need to be installed code.appendLine('import plotly'); code.append('plotly.offline.init_notebook_mode(connected=True)'); return [code.toString()]; } generateCode(preview=false) { /** * Plotly is not showing sometimes... * import plotly * plotly.offline.init_notebook_mode(connected=True) */ let { chartType, data, x, y, color, setXY, sort, userOption, userCode, title, x_label, y_label } = this.state; let code = new com_String(); let config = this.chartConfig[chartType]; let etcOptionCode = []; // add title if (title != '') { etcOptionCode.push(com_util.formatString("title='{0}'", title)); } let labelOptions = []; // add x_label if (x_label != '') { if (setXY === true) { // replace 'x' to x_label labelOptions.push(com_util.formatString("'x': '{0}'", x_label)); } else { // if x is selected if (x != '') { // replace x column name to x_label labelOptions.push(com_util.formatString("{0}: '{1}'", x, x_label)); } else { // replace 'index' to x_label labelOptions.push(com_util.formatString("'index': '{0}'", x_label)); } } } // add y_label if (y_label != '') { if (setXY === true) { // replace 'y' to y_label labelOptions.push(com_util.formatString("'y': '{0}'", y_label)); } else { // if y is selected if (y != '') { // replace y column name to y_label labelOptions.push(com_util.formatString("{0}: '{1}'", y, y_label)); } else { // replace 'index' to y_label labelOptions.push(com_util.formatString("'index': '{0}'", y_label)); } } } if (labelOptions.length > 0) { etcOptionCode.push(com_util.formatString("labels={ {0} }", labelOptions.join(', '))); } // add user option if (userOption != '') { etcOptionCode.push(userOption); } if (preview === true) { // set preview size let width = 450; let height = 400; // let width = $(this.wrapSelector('#vp_ptPreview')).width(); // let height = $(this.wrapSelector('#vp_ptPreview')).height(); // console.log(width, height); etcOptionCode.push(com_util.formatString('width={0}, height={1}', width, height)); // no auto-import for preview this.config.checkModules = []; } else { this.config.checkModules = ['px']; } let generatedCode = com_generator.vp_codeGenerator(this, config, this.state , etcOptionCode.length > 0? ', ' + etcOptionCode.join(', '): ''); code.appendFormatLine("fig = {0}", generatedCode); // sort code if (sort && sort != '') { code.appendFormatLine("fig.update_xaxes(categoryorder='{0}')", sort); } if (userCode && userCode != '') { code.appendLine(userCode); } code.append('fig.show()'); return code.toString(); } } return Plotly; });