diff --git a/css/boardFrame.css b/css/boardFrame.css index 0614f1da..33b38983 100644 --- a/css/boardFrame.css +++ b/css/boardFrame.css @@ -239,39 +239,39 @@ background-color: rgb(253, 177, 133); } .vp-block.visualization .vp-block-header { - background-color: rgb(249, 227, 214); + background-color: #E8ECD0; } .vp-block.visualization.vp-focus .vp-block-header, .vp-block.visualization.vp-focus-child .vp-block-header { - background-color: rgb(253, 177, 133); + background-color: #C6CE94; } .vp-block.machine_learning .vp-block-header { - background-color: #E8ECD0; + background-color: #E5EEF8; } .vp-block.machine_learning.vp-focus .vp-block-header, .vp-block.machine_learning.vp-focus-child .vp-block-header { - background-color: #C6CE94; + background-color: #BFD5F0; } .vp-block.logic-define .vp-block-header { - background-color: rgb(213, 231, 222); + /* background-color: rgb(213, 231, 222); */ } .vp-block.logic-define.vp-focus .vp-block-header, .vp-block.logic-define.vp-focus-child .vp-block-header { - background-color: rgb(138, 214, 176); + /* background-color: rgb(138, 214, 176); */ } .vp-block.logic-control .vp-block-header { - background-color: rgb(253, 239, 221); + /* background-color: rgb(253, 239, 221); */ } .vp-block.logic-control.vp-focus .vp-block-header, .vp-block.logic-control.vp-focus-child .vp-block-header { - background-color: rgb(255, 207, 115); + /* background-color: rgb(255, 207, 115); */ } .vp-block.library .vp-block-header { - background-color: rgb(249, 227, 214); + /* background-color: rgb(249, 227, 214); */ } .vp-block.library.vp-focus .vp-block-header, .vp-block.library.vp-focus-child .vp-block-header { - background-color: rgb(253, 177, 133); + /* background-color: rgb(253, 177, 133); */ } /* block button group */ .vp-block-button-group { diff --git a/css/m_ml/fitPredict.css b/css/m_ml/fitPredict.css new file mode 100644 index 00000000..b14cb17a --- /dev/null +++ b/css/m_ml/fitPredict.css @@ -0,0 +1,46 @@ +.vp-model-select-box { + grid-column-gap: 5px; +} +.vp-ins-select-title { + font-weight: bold; + color: var(--font-hightlight); + padding: 5px 5px 5px 0px; +} +.vp-ins-select-container input.vp-ins-search { + width: 100%; +} +.vp-ins-select-container .vp-ins-search-icon { + position: absolute; + color: #C4C4C4; + right: 7px; + top: 7px; +} +.vp-ins-select-box { + margin-top: 5px; + border: 0.25px solid var(--border-gray-color); +} +.vp-ins-select-list { + height: 145px; + width: 100%; + list-style: none; + margin: 0px; + padding: 0px 5px 0px 5px; + color: #696969; + overflow: auto; +} +.vp-ins-select-item { + padding-top: 3px; + padding-bottom: 3px; + cursor: pointer; + border-bottom: 0.25px solid var(--light-gray-color); +} +.vp-ins-select-item.selected { + color: var(--font-hightlight); + background: var(--light-gray-color); +} +.vp-ins-select-item:hover { + background: var(--light-gray-color); +} +.vp-ins-parameter-box { + grid-column: 1/3; +} diff --git a/css/m_ml/modelInfo.css b/css/m_ml/modelInfo.css new file mode 100644 index 00000000..871aec8d --- /dev/null +++ b/css/m_ml/modelInfo.css @@ -0,0 +1,43 @@ +.vp-ins-select-title { + font-weight: bold; + color: var(--font-hightlight); + padding: 5px 5px 5px 0px; +} +.vp-ins-select-container input.vp-ins-search { + width: 100%; +} +.vp-ins-select-container .vp-ins-search-icon { + position: absolute; + color: #C4C4C4; + right: 7px; + top: 7px; +} +.vp-ins-select-box { + margin-top: 5px; + border: 0.25px solid var(--border-gray-color); +} +.vp-ins-select-list { + height: 145px; + width: 100%; + list-style: none; + margin: 0px; + padding: 0px 5px 0px 5px; + color: #696969; + overflow: auto; +} +.vp-ins-select-item { + padding-top: 3px; + padding-bottom: 3px; + cursor: pointer; + border-bottom: 0.25px solid var(--light-gray-color); +} +.vp-ins-select-item.selected { + color: var(--font-hightlight); + background: var(--light-gray-color); +} +.vp-ins-select-item:hover { + background: var(--light-gray-color); +} +.vp-ins-parameter-box { + grid-column: 1/3; +} diff --git a/css/m_apps/chart.css b/css/m_visualize/chart.css similarity index 100% rename from css/m_apps/chart.css rename to css/m_visualize/chart.css diff --git a/css/m_visualize/seaborn.css b/css/m_visualize/seaborn.css index 36f84855..cdf36ffa 100644 --- a/css/m_visualize/seaborn.css +++ b/css/m_visualize/seaborn.css @@ -34,20 +34,24 @@ width: 100%; } .vp-tab-page-box.plot { - height: calc(100% - 30px); + /* height: calc(100% - 30px); */ + min-height: 352px; align-content: baseline; } .vp-chart-plot-box { + +} +.vp-chart-body { display: grid; grid-template-columns: calc(50% - 8px) calc(50% - 8px); grid-row-gap: 5px; grid-column-gap: 15px; align-items: baseline; align-content: center; + height: 100%; } .vp-chart-left-box { - display: grid; - grid-row-gap: 3px; + } .vp-chart-left-box > label { margin-bottom: 0px; @@ -66,4 +70,14 @@ .vp-chart-preview-box { min-height: 150px; width: 100%; + height: calc(100% - 30px); +} + +.vp-tab-page label { + margin-bottom: 0px; +} +.vp-chart-setting-footer { + position: absolute; + left: 20px; + bottom: 15px; } \ No newline at end of file diff --git a/css/menuFrame.css b/css/menuFrame.css index f8bcdbf3..54b7882f 100644 --- a/css/menuFrame.css +++ b/css/menuFrame.css @@ -230,13 +230,16 @@ background: #E56139; } .vp-menuitem.apps.vp-color-apps5 { - background: #BEB727; + background: #97AA4E; } .vp-menuitem.apps.vp-color-apps6 { - background: #91A541; + background: #88B4E9; } .vp-menuitem.apps.vp-color-apps7 { - background: #718E41; + background: #6C9BD1; +} +.vp-menuitem.apps.vp-color-apps8 { + background: #578BC7; } .vp-menuitem.apps.vp-color-preparing { background: var(--gray-color); @@ -252,14 +255,14 @@ } /* MenuItem - Logic */ .vp-menuitem.logic-define { - background-color: rgb(213, 231, 222); + /* background-color: rgb(213, 231, 222); */ } .vp-menuitem.logic-control { - background-color: rgb(253, 239, 221); + /* background-color: rgb(253, 239, 221); */ } /* MenuItem - Library */ .vp-menuitem.library { - background-color: rgb(249, 227, 214); + /* background-color: rgb(249, 227, 214); */ } /* Task Bar */ diff --git a/css/popupComponent.css b/css/popupComponent.css index 3fff2c90..389cca3d 100644 --- a/css/popupComponent.css +++ b/css/popupComponent.css @@ -65,7 +65,7 @@ font-family: 'AppleSDGothicNeo'; color: var(--font-hightlight); } -.vp-popup-toggle { +.vp-popup-maximize { position: absolute; width: 15px; height: 20px; @@ -77,6 +77,31 @@ cursor: pointer; color: var(--gray-color); } +.vp-popup-return { + position: absolute; + width: 15px; + height: 20px; + top: 4px; + right: 32px; + z-index: 3; + line-height: 20px; + text-align: center; + cursor: pointer; + color: var(--gray-color); + display: none; +} +.vp-popup-toggle { + position: absolute; + width: 15px; + height: 20px; + top: 4px; + right: 58px; + z-index: 3; + line-height: 20px; + text-align: center; + cursor: pointer; + color: var(--gray-color); +} .vp-popup-close { position: absolute; width: 20px; @@ -94,6 +119,9 @@ padding: 15px; overflow: auto; } +.vp-popup-content { + min-height: calc(100% - 30px); +} .vp-popup-footer { position: relative; height: 50px; diff --git a/css/root.css b/css/root.css index 567a75ef..6048662e 100644 --- a/css/root.css +++ b/css/root.css @@ -336,6 +336,9 @@ hr.vp-extra-menu-line { width: 10px !important; } /* temporary margin */ +.mt5 { + margin-top: 5px; +} .mb5 { margin-bottom: 5px; } diff --git a/data/libraries.json b/data/libraries.json index 22985d2d..eefddfce 100644 --- a/data/libraries.json +++ b/data/libraries.json @@ -2860,14 +2860,14 @@ "file": "m_matplotlib/import" }, { - "id": "mp_plot", + "id": "mp_chart", "type": "function", "level": 2, "name": "Create chart", "path": "visualpython - library - matplotlib - plot", "desc": "", "tag": "MATPLOTLIB, PLOT, CHART", - "file": "m_matplotlib/plot" + "file": "m_visualize/Chart" }, { "id": "mp_figure", @@ -3047,20 +3047,6 @@ "icon": "apps/apps_markdown.svg" } }, - { - "id" : "visualize_chart", - "type" : "function", - "level": 1, - "name" : "Chart", - "tag" : "MATPLOTLIB,CHART,VISUALIZATION,VISUALIZE", - "path" : "visualpython - visualization - matplotlib", - "desc" : "Matplotlib chart creation", - "file" : "m_apps/Chart", - "apps" : { - "color": 3, - "icon": "apps/apps_chart.svg" - } - }, { "id" : "apps_pdf", "type" : "function", @@ -3071,7 +3057,7 @@ "desc" : "PDF", "file" : "m_apps/PDF", "apps" : { - "color": 4, + "color": 3, "icon": "apps/apps_pymupdf.svg" } }, @@ -3091,6 +3077,46 @@ } ] }, + { + "id" : "pkg_visualize", + "type" : "package", + "level": 0, + "name" : "Visualization", + "path" : "visualpython - visualization", + "desc" : "Visualization modules", + "open" : true, + "grid" : true, + "item" : [ + { + "id" : "visualize_chartStyle", + "type" : "function", + "level": 1, + "name" : "Chart Style", + "tag" : "CHART STYLE SETTING,IMPORT CHART,VISUALIZATION,VISUALIZE", + "path" : "visualpython - visualization - chartsstyle", + "desc" : "Chart style setting", + "file" : "m_visualize/ChartSetting", + "apps" : { + "color": 5, + "icon": "apps/apps_style.svg" + } + }, + { + "id" : "visualize_seaborn", + "type" : "function", + "level": 1, + "name" : "Seaborn", + "tag" : "SEABORN,CHART,VISUALIZATION,VISUALIZE", + "path" : "visualpython - visualization - seaborn", + "desc" : "Seaborn chart creation", + "file" : "m_visualize/Seaborn", + "apps" : { + "color": 5, + "icon": "apps/apps_visualize.svg" + } + } + ] + }, { "id" : "pkg_ml", "type" : "package", @@ -3111,10 +3137,24 @@ "desc" : "Data sets for machine learning", "file" : "m_ml/DataSets", "apps" : { - "color": 5, + "color": 6, "icon": "apps/apps_dataset.svg" } }, + { + "id" : "ml_dataSplit", + "type" : "function", + "level": 1, + "name" : "Data Split", + "tag" : "DATA SPLIT,MACHINE LEARNING,ML", + "path" : "visualpython - machine_learning - data split", + "desc" : "Data split for machine learning", + "file" : "m_ml/dataSplit", + "apps" : { + "color": 6, + "icon": "apps/apps_datasplit.svg" + } + }, { "id" : "ml_dataPrep", "type" : "function", @@ -3125,22 +3165,22 @@ "desc" : "Data preparation for machine learning", "file" : "m_ml/DataPrep", "apps" : { - "color": 5, + "color": 6, "icon": "apps/apps_dataprep.svg" } }, { - "id" : "ml_dataSplit", + "id" : "ml_autoML", "type" : "function", "level": 1, - "name" : "Data Split", - "tag" : "DATA SPLIT,MACHINE LEARNING,ML", - "path" : "visualpython - machine_learning - data split", - "desc" : "Data split for machine learning", - "file" : "m_ml/dataSplit", + "name" : "AutoML", + "tag" : "AUTO ML,MODEL,MACHINE LEARNING,ML", + "path" : "visualpython - machine_learning - automl", + "desc" : "AutoML model for machine learning", + "file" : "m_ml/AutoML", "apps" : { - "color": 5, - "icon": "apps/apps_datasplit.svg" + "color": 6, + "icon": "apps/apps_automl.svg" } }, { @@ -3153,7 +3193,7 @@ "desc" : "Regression model for machine learning", "file" : "m_ml/Regression", "apps" : { - "color": 5, + "color": 7, "icon": "apps/apps_regression.svg" } }, @@ -3167,7 +3207,7 @@ "desc" : "Classification model for machine learning", "file" : "m_ml/Classification", "apps" : { - "color": 6, + "color": 7, "icon": "apps/apps_classification.svg" } }, @@ -3181,7 +3221,7 @@ "desc" : "Clustering model for machine learning", "file" : "m_ml/Clustering", "apps" : { - "color": 6, + "color": 7, "icon": "apps/apps_clustering.svg" } }, @@ -3195,22 +3235,36 @@ "desc" : "Dimension reduction model for machine learning", "file" : "m_ml/DimensionReduction", "apps" : { - "color": 6, + "color": 7, "icon": "apps/apps_dimension.svg" } }, { - "id" : "ml_autoML", + "id" : "ml_fitPredict", "type" : "function", "level": 1, - "name" : "AutoML", - "tag" : "AUTO ML,MODEL,MACHINE LEARNING,ML", - "path" : "visualpython - machine_learning - automl", - "desc" : "AutoML model for machine learning", - "file" : "m_ml/AutoML", + "name" : "Fit/Predict", + "tag" : "FIT,PREDICT,MACHINE LEARNING,ML", + "path" : "visualpython - machine_learning - fit_predict", + "desc" : "Model fit/predict for machine learning", + "file" : "m_ml/FitPredict", "apps" : { - "color": 6, - "icon": "apps/apps_automl.svg" + "color": 8, + "icon": "apps/apps_fit.svg" + } + }, + { + "id" : "ml_modelInfo", + "type" : "function", + "level": 1, + "name" : "Model Info", + "tag" : "MODEL INFO,INFORMATION,MACHINE LEARNING,ML", + "path" : "visualpython - machine_learning - model_info", + "desc" : "Model information for machine learning", + "file" : "m_ml/ModelInfo", + "apps" : { + "color": 8, + "icon": "apps/apps_predict.svg" } }, { @@ -3223,7 +3277,7 @@ "desc" : "Performance evaluation for machine learning", "file" : "m_ml/evaluation", "apps" : { - "color": 7, + "color": 8, "icon": "apps/apps_evaluate.svg" } } diff --git a/data/libraries_new.json b/data/libraries_old.json similarity index 98% rename from data/libraries_new.json rename to data/libraries_old.json index 6fc6b0c5..22985d2d 100644 --- a/data/libraries_new.json +++ b/data/libraries_old.json @@ -3047,6 +3047,20 @@ "icon": "apps/apps_markdown.svg" } }, + { + "id" : "visualize_chart", + "type" : "function", + "level": 1, + "name" : "Chart", + "tag" : "MATPLOTLIB,CHART,VISUALIZATION,VISUALIZE", + "path" : "visualpython - visualization - matplotlib", + "desc" : "Matplotlib chart creation", + "file" : "m_apps/Chart", + "apps" : { + "color": 3, + "icon": "apps/apps_chart.svg" + } + }, { "id" : "apps_pdf", "type" : "function", @@ -3057,7 +3071,7 @@ "desc" : "PDF", "file" : "m_apps/PDF", "apps" : { - "color": 3, + "color": 4, "icon": "apps/apps_pymupdf.svg" } }, @@ -3077,74 +3091,6 @@ } ] }, - { - "id" : "pkg_visualize", - "type" : "package", - "level": 0, - "name" : "Visualization", - "path" : "visualpython - visualization", - "desc" : "Visualization modules", - "open" : true, - "grid" : true, - "item" : [ - { - "id" : "visualize_chartStyle", - "type" : "function", - "level": 1, - "name" : "Chart Style", - "tag" : "CHART STYLE SETTING,IMPORT CHART,VISUALIZATION,VISUALIZE", - "path" : "visualpython - visualization - chartsstyle", - "desc" : "Chart style setting", - "file" : "m_visualize/ChartSetting", - "apps" : { - "color": 1, - "icon": "apps/apps_style.svg" - } - }, - { - "id" : "pd_plot", - "type" : "function", - "level": 1, - "name" : "Pandas Plot", - "tag" : "PANDAS PLOT,PANDAS", - "path" : "visualpython - library - pandas - plot", - "desc" : "Pandas plot creation", - "file" : "m_library/m_pandas/plot", - "apps" : { - "color": 1, - "icon": "apps/apps_chart.svg" - } - }, - { - "id" : "visualize_chart", - "type" : "function", - "level": 1, - "name" : "Matplotlib", - "tag" : "MATPLOTLIB,CHART,VISUALIZATION,VISUALIZE", - "path" : "visualpython - visualization - matplotlib", - "desc" : "Matplotlib chart creation", - "file" : "m_apps/Chart", - "apps" : { - "color": 1, - "icon": "apps/apps_chart.svg" - } - }, - { - "id" : "visualize_seaborn", - "type" : "function", - "level": 1, - "name" : "Seaborn", - "tag" : "SEABORN,CHART,VISUALIZATION,VISUALIZE", - "path" : "visualpython - visualization - seaborn", - "desc" : "Seaborn chart creation", - "file" : "m_visualize/Seaborn", - "apps" : { - "color": 1, - "icon": "apps/apps_chart.svg" - } - } - ] - }, { "id" : "pkg_ml", "type" : "package", diff --git a/html/m_ml/evaluation.html b/html/m_ml/evaluation.html index c08859b7..73129fb6 100644 --- a/html/m_ml/evaluation.html +++ b/html/m_ml/evaluation.html @@ -1,23 +1,29 @@
-
- - - - - - - - +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
- +
@@ -33,13 +39,29 @@ - - +
+ + +
+ + +
- - - + +
+ + +
+
+ + +
+ + +
diff --git a/html/m_ml/fitPredict.html b/html/m_ml/fitPredict.html new file mode 100644 index 00000000..f80d7559 --- /dev/null +++ b/html/m_ml/fitPredict.html @@ -0,0 +1,46 @@ + +
+
+
Model
+ +
+ +
+
    + +
+
+ +
+
    + +
+
+
+
+
+ + +
+
Action
+
+ + + +
+
+
    + +
+
+
+
+
+
Options
+
+ +
+
+
+ \ No newline at end of file diff --git a/html/m_ml/model.html b/html/m_ml/model.html index 4c26c1c4..739aa2e0 100644 --- a/html/m_ml/model.html +++ b/html/m_ml/model.html @@ -1,9 +1,8 @@
- +
+ +
@@ -24,27 +23,5 @@
-
-
- - -
-
-
- -
-
-
- -
- -
\ No newline at end of file diff --git a/html/m_ml/modelInfo.html b/html/m_ml/modelInfo.html new file mode 100644 index 00000000..58abd6eb --- /dev/null +++ b/html/m_ml/modelInfo.html @@ -0,0 +1,46 @@ + +
+
+
Model
+ +
+ +
+
    + +
+
+ +
+
    + +
+
+
+
+
+ + +
+
Info
+
+ + + +
+
+
    + +
+
+
+
+
+
Options
+
+ +
+
+
+ \ No newline at end of file diff --git a/html/m_apps/chart.html b/html/m_visualize/chart.html similarity index 100% rename from html/m_apps/chart.html rename to html/m_visualize/chart.html diff --git a/html/m_visualize/seaborn.html b/html/m_visualize/seaborn.html index a8301186..8b3cc9b9 100644 --- a/html/m_visualize/seaborn.html +++ b/html/m_visualize/seaborn.html @@ -1,6 +1,6 @@ - -
+ + -
-
- NOTE: Options selected from this tab only apply to subplot 1. -
- - - - -
-
- - -
-
- - -
-
- - + + + +
+ + +
+
+
+ + +
+
+ + +
+
+ + + + +
+
-
-
-
- Chart Preview - - - - - - +
+
+ Chart Preview + + + + + + + - -
-
- -
- +
+
+ +
+ +
+
+
+ +
+ \ No newline at end of file diff --git a/html/popupComponent.html b/html/popupComponent.html index 531b89dd..c606ebc1 100644 --- a/html/popupComponent.html +++ b/html/popupComponent.html @@ -46,6 +46,8 @@ + +
diff --git a/img/apps/apps_visualize.svg b/img/apps/apps_visualize.svg new file mode 100644 index 00000000..28711332 --- /dev/null +++ b/img/apps/apps_visualize.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/img/max_window.svg b/img/max_window.svg new file mode 100644 index 00000000..b2aa45e8 --- /dev/null +++ b/img/max_window.svg @@ -0,0 +1,3 @@ + + + diff --git a/img/min_window.svg b/img/min_window.svg new file mode 100644 index 00000000..f7578948 --- /dev/null +++ b/img/min_window.svg @@ -0,0 +1,4 @@ + + + + diff --git a/js/board/Block.js b/js/board/Block.js index 24d4c2d0..725eb9c9 100644 --- a/js/board/Block.js +++ b/js/board/Block.js @@ -199,10 +199,11 @@ define([ this.focusItem(); } + // Deprecated: show markdown as other DA blocks // if markdown, set its height to fit-content - if (this.id == 'apps_markdown') { - $(this.wrapSelector()).addClass('vp-block-markdown'); - } + // if (this.id == 'apps_markdown') { + // $(this.wrapSelector()).addClass('vp-block-markdown'); + // } // if viewDepthNumber, show it let viewDepthNumber = this.prop.parent.state.viewDepthNumber; @@ -304,9 +305,10 @@ define([ if (this._getMenuGroupRootType() == 'logic') { header = this.task.generateCode(); } - if (this.id == 'apps_markdown') { - header = this.task.getPreview(); - } + // Deprecated: show markdown as other blocks + // if (this.id == 'apps_markdown') { + // header = this.task.getPreview(); + // } this.state.header = header; return header; } diff --git a/js/com/com_Config.js b/js/com/com_Config.js index 127ac6e4..70a38466 100644 --- a/js/com/com_Config.js +++ b/js/com/com_Config.js @@ -448,6 +448,10 @@ define([ return Config.ML_DATA_TYPES; } + getMLCategories() { + return Object.keys(Config.ML_DATA_DICT); + } + } //======================================================================== @@ -482,38 +486,38 @@ define([ * Data types using for searching model variables */ Config.ML_DATA_DICT = { + 'Data Preparation': [ + /** Encoding */ + 'OneHotEncoder', 'LabelEncoder', 'OrdinalEncoder', 'TargetEncoder', 'SMOTE', + /** Scaling */ + 'StandardScaler', 'RobustScaler', 'MinMaxScaler', 'Normalizer', 'FunctionTransformer', 'PolynomialFeatures', 'KBinsDiscretizer', + /** ETC */ + 'ColumnTransformer' + ], 'Regression': [ 'LinearRegression', 'Ridge', 'Lasso', 'ElasticNet', 'SVR', 'DecisionTreeRegressor', 'RandomForestRegressor', 'GradientBoostingRegressor', 'XGBRegressor', 'LGBMRegressor', 'CatBoostRegressor', ], 'Classification': [ 'LogisticRegression', 'BernoulliNB', 'MultinomialNB', 'GaussianNB', 'SVC', 'DecisionTreeClassifier', 'RandomForestClassifier', 'GradientBoostingClassifier', 'XGBClassifier', 'LGBMClassifier', 'CatBoostClassifier', ], - 'Auto ML': [ - 'AutoSklearnRegressor', 'AutoSklearnClassifier', 'TPOTRegressor', 'TPOTClassifier' - ], 'Clustering': [ 'KMeans', 'AgglomerativeClustering', 'GaussianMixture', 'DBSCAN', ], 'Dimension Reduction': [ 'PCA', 'LinearDiscriminantAnalysis', 'TruncatedSVD', 'NMF', 'TSNE' ], - 'Data Preparation': [ - /** Encoding */ - 'OneHotEncoder', 'LabelEncoder', 'OrdinalEncoder', 'TargetEncoder', 'SMOTE', - /** Scaling */ - 'StandardScaler', 'RobustScaler', 'MinMaxScaler', 'Normalizer', 'FunctionTransformer', 'PolynomialFeatures', 'KBinsDiscretizer', - /** ETC */ - 'ColumnTransformer' + 'Auto ML': [ + 'AutoSklearnRegressor', 'AutoSklearnClassifier', 'TPOTRegressor', 'TPOTClassifier' ] }; Config.ML_DATA_TYPES = [ + ...Config.ML_DATA_DICT['Data Preparation'], ...Config.ML_DATA_DICT['Regression'], ...Config.ML_DATA_DICT['Classification'], - ...Config.ML_DATA_DICT['Auto ML'], ...Config.ML_DATA_DICT['Clustering'], ...Config.ML_DATA_DICT['Dimension Reduction'], - ...Config.ML_DATA_DICT['Data Preparation'] + ...Config.ML_DATA_DICT['Auto ML'] ]; return Config; diff --git a/js/com/com_generatorV2.js b/js/com/com_generatorV2.js index 4881a316..a8256500 100644 --- a/js/com/com_generatorV2.js +++ b/js/com/com_generatorV2.js @@ -656,12 +656,13 @@ define([ * @param {object} target * @param {array} columnInputIdList * @param {string} tagType input / select (tag type) + * @param {array/boolean} columnWithEmpty boolean array or value to decide whether select tag has empty space * Usage : * $(document).on('change', this.wrapSelector('#dataframe_tag_id'), function() { - * pdGen.vp_bindColumnSource(that.wrapSelector(), this, ['column_input_id']); + * pdGen.vp_bindColumnSource(that.wrapSelector(), this, ['column_input_id'], 'select', [true, true, true]); * }); */ - var vp_bindColumnSource = function(selector, target, columnInputIdList, tagType="input") { + var vp_bindColumnSource = function(selector, target, columnInputIdList, tagType="input", columnWithEmpty=false) { var varName = ''; if ($(target).length > 0) { varName = $(target).val(); @@ -701,8 +702,22 @@ define([ let { result, type, msg } = resultObj; var varResult = JSON.parse(result); + // check if it needs to add empty option + let addEmpty = false; + if (Array.isArray(columnWithEmpty)) { + addEmpty = columnWithEmpty[idx]; + } else { + addEmpty = columnWithEmpty; + } + if (addEmpty == true) { + varResult = [ + {value: '', label: ''}, + ...varResult + ] + } + // columns using suggestInput - columnInputIdList && columnInputIdList.forEach(columnInputId => { + columnInputIdList && columnInputIdList.forEach((columnInputId, idx) => { let defaultValue = $(selector + ' #' + columnInputId).val(); if (defaultValue == null || defaultValue == undefined) { defaultValue = ''; @@ -724,6 +739,7 @@ define([ 'id': columnInputId, 'class': 'vp-select vp-state' }); + // make tag varResult.forEach(listVar => { var option = document.createElement('option'); $(option).attr({ diff --git a/js/com/component/ModelEditor.js b/js/com/component/ModelEditor.js index 9b6fa738..2eee09cf 100644 --- a/js/com/component/ModelEditor.js +++ b/js/com/component/ModelEditor.js @@ -883,8 +883,13 @@ define([ }); } + /** + * Show Model Editor + * @param {*} showType all / action / info + */ show() { $(this.wrapSelector()).show(); + this.reload(); } diff --git a/js/com/component/PopupComponent.js b/js/com/component/PopupComponent.js index 4c5dbac7..548adffe 100644 --- a/js/com/component/PopupComponent.js +++ b/js/com/component/PopupComponent.js @@ -251,12 +251,44 @@ define([ }); // Toggle operation (minimize) $(this.wrapSelector('.vp-popup-toggle')).on('click', function(evt) { - // that.toggle(); $(that.eventTarget).trigger({ type: 'close_option_page', component: that }); }); + // Maximize operation + $(this.wrapSelector('.vp-popup-maximize')).on('click', function(evt) { + // save position + that.config.position = $(that.wrapSelector()).position(); + // save size + that.config.size = { + width: $(that.wrapSelector()).width(), + height: $(that.wrapSelector()).height() + } + // maximize popup + $(that.wrapSelector()).css({ + width: '100%', + height: '100%', + top: 0, + left: 0 + }); + // show / hide buttons + $(this).hide(); + $(that.wrapSelector('.vp-popup-return')).show(); + }); + // Return operation + $(this.wrapSelector('.vp-popup-return')).on('click', function(evt) { + // return size + $(that.wrapSelector()).css({ + width: that.config.size.width + 'px', + height: that.config.size.height + 'px', + top: that.config.position.top, + left: that.config.position.left + }); + // show / hide buttons + $(this).hide(); + $(that.wrapSelector('.vp-popup-maximize')).show(); + }); // Click install package $(this.wrapSelector('#popupInstall')).on('click', function() { @@ -297,45 +329,7 @@ define([ // save state values $(document).on('change', this.wrapSelector('.vp-state'), function() { - let id = $(this)[0].id; - let customKey = $(this).data('key'); - let tagName = $(this).prop('tagName'); // returns with UpperCase - let newValue = ''; - switch(tagName) { - case 'INPUT': - let inputType = $(this).prop('type'); - if (inputType == 'text' || inputType == 'number' || inputType == 'hidden') { - newValue = $(this).val(); - break; - } - if (inputType == 'checkbox') { - newValue = $(this).prop('checked'); - break; - } - break; - case 'TEXTAREA': - case 'SELECT': - default: - newValue = $(this).val(); - if (!newValue) { - newValue = ''; - } - break; - } - - // if custom key is available, use it - if (customKey && customKey != '') { - // allow custom key until level 2 - let customKeys = customKey.split('.'); - if (customKeys.length == 2) { - that.state[customKeys[0]][customKeys[1]] = newValue; - } else { - that.state[customKey] = newValue; - } - } else { - that.state[id] = newValue; - } - vpLog.display(VP_LOG_TYPE.DEVELOP, 'saved state : ' + id+ '/'+tagName+'/'+newValue); + that._saveSingleState($(this)[0]); }); // Click buttons @@ -423,7 +417,7 @@ define([ }); // click on data selector input filter - $(this.wrapSelector('.vp-data-selector ')).on('click', function(evt) { + $(this.wrapSelector('.vp-data-selector')).on('click', function(evt) { }); } @@ -458,8 +452,14 @@ define([ } _bindResizable() { + let that = this; $(this.wrapSelector()).resizable({ - handles: 'all' + handles: 'all', + start: function(evt, ui) { + // show / hide buttons + $(that.wrapSelector('.vp-popup-return')).hide(); + $(that.wrapSelector('.vp-popup-maximize')).show(); + } }); } @@ -618,46 +618,50 @@ define([ /** Implementation needed */ let that = this; $(this.wrapSelector('.vp-state')).each((idx, tag) => { - let id = tag.id; - let customKey = $(tag).data('key'); - let tagName = $(tag).prop('tagName'); // returns with UpperCase - let newValue = ''; - switch(tagName) { - case 'INPUT': - let inputType = $(tag).prop('type'); - if (inputType == 'text' || inputType == 'number' || inputType == 'hidden') { - newValue = $(tag).val(); - break; - } - if (inputType == 'checkbox') { - newValue = $(tag).prop('checked'); - break; - } - break; - case 'TEXTAREA': - case 'SELECT': - default: + that._saveSingleState(tag); + }); + vpLog.display(VP_LOG_TYPE.DEVELOP, 'savedState', that.state); + } + + _saveSingleState(tag) { + let id = tag.id; + let customKey = $(tag).data('key'); + let tagName = $(tag).prop('tagName'); // returns with UpperCase + let newValue = ''; + switch(tagName) { + case 'INPUT': + let inputType = $(tag).prop('type'); + if (inputType == 'text' || inputType == 'number' || inputType == 'hidden') { newValue = $(tag).val(); - if (!newValue) { - newValue = ''; - } break; - } - - // if custom key is available, use it - if (customKey && customKey != '') { - // allow custom key until level 2 - let customKeys = customKey.split('.'); - if (customKeys.length == 2) { - that.state[customKeys[0]][customKeys[1]] = newValue; - } else { - that.state[customKey] = newValue; } + if (inputType == 'checkbox') { + newValue = $(tag).prop('checked'); + break; + } + break; + case 'TEXTAREA': + case 'SELECT': + default: + newValue = $(tag).val(); + if (!newValue) { + newValue = ''; + } + break; + } + + // if custom key is available, use it + if (customKey && customKey != '') { + // allow custom key until level 2 + let customKeys = customKey.split('.'); + if (customKeys.length == 2) { + this.state[customKeys[0]][customKeys[1]] = newValue; } else { - that.state[id] = newValue; + this.state[customKey] = newValue; } - }); - vpLog.display(VP_LOG_TYPE.DEVELOP, 'savedState', that.state); + } else { + this.state[id] = newValue; + } } run(execute=true, addcell=true) { diff --git a/js/m_ml/FitPredict.js b/js/m_ml/FitPredict.js new file mode 100644 index 00000000..1366c913 --- /dev/null +++ b/js/m_ml/FitPredict.js @@ -0,0 +1,685 @@ +/* + * Project Name : Visual Python + * Description : GUI-based Python code generator + * File Name : FitPredict.js + * Author : Black Logic + * Note : Model fit / predict + * License : GNU GPLv3 with Visual Python special exception + * Date : 2022. 04. 20 + * Change Date : + */ + +//============================================================================ +// [CLASS] FitPredict +//============================================================================ +define([ + 'text!vp_base/html/m_ml/fitPredict.html!strip', + 'css!vp_base/css/m_ml/fitPredict.css', + 'vp_base/js/com/com_util', + 'vp_base/js/com/com_String', + 'vp_base/js/com/com_generatorV2', + 'vp_base/data/m_ml/mlLibrary', + 'vp_base/js/com/component/PopupComponent', + 'vp_base/js/com/component/VarSelector2', + 'vp_base/js/com/component/SuggestInput' +], function(msHtml, msCss, com_util, com_String, com_generator, ML_LIBRARIES, PopupComponent, VarSelector2, SuggestInput) { + + /** + * FitPredict + */ + class FitPredict extends PopupComponent { + _init() { + super._init(); + this.config.sizeLevel = 2; + this.config.dataview = false; + + this.state = { + // model selection + category: 'All', + model: '', + modelType: '', + method: '', + action: {}, + optionConfig: {}, + userOption: '', + ...this.state + } + + // categories : Data Preparation / Regression / Classification / Clustering / Dimension Reduction / Auto ML + this.modelCategories = [ + 'All', + ...vpConfig.getMLCategories() + ] + + this.modelConfig = ML_LIBRARIES; + + this.loaded = false; + + } + + _bindEvent() { + super._bindEvent(); + /** Implement binding events */ + var that = this; + + // click category + $(this.wrapSelector('.vp-ins-select-list.category .vp-ins-select-item')).on('click', function() { + let category = $(this).data('var-name'); + + that.state.category = category; + + $(that.wrapSelector('.vp-ins-select-list.category .vp-ins-select-item')).removeClass('selected'); + $(this).addClass('selected'); + + // load model list for this category + that.loadModelList(category); + }); + } + + templateForBody() { + let page = $(msHtml); + + let that = this; + + //================================================================ + // Model selection + //================================================================ + // set model category list + let modelCategoryTag = new com_String(); + this.modelCategories.forEach(category => { + let selected = ''; + if (category == that.state.category) { + selected = 'selected'; + } + modelCategoryTag.appendFormatLine('
  • {5}
  • ', + 'vp-ins-select-item', selected, category, 'category', category, category); + }); + $(page).find('.vp-ins-select-list.category').html(modelCategoryTag.toString()); + + //================================================================ + // 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; + } + + templateForOption(modelType) { + let optionConfig = this.modelConfig[modelType]; + let state = this.state; + + let optBox = new com_String(); + // render tag + optionConfig.options.forEach(opt => { + optBox.appendFormatLine('' + , opt.name, opt.name, com_util.optionToLabel(opt.name)); + let content = com_generator.renderContent(this, opt.component[0], opt, state); + optBox.appendLine(content[0].outerHTML); + }); + // render user option + optBox.appendFormatLine('', 'userOption', 'User option'); + optBox.appendFormatLine('', + 'userOption', 'key=value, ...', this.state.userOption); + return optBox.toString(); + } + + render() { + super.render(); + + this.loadModelList(this.state.category); + + this.reload(); + } + + loadModelList(category='') { + // reset page + try { + $(this.wrapSelector('.vp-ins-search')).autocomplete("destroy"); + } catch { ; } + $(this.wrapSelector('.vp-ins-select-list.action')).html(''); + $(this.wrapSelector('.vp-ins-parameter-box')).html(''); + + if (category == 'All') { + category = ''; + } + // set model list + let that = this; + let modelOptionTag = new com_String(); + vpKernel.getModelList(category).then(function(resultObj) { + let { result } = resultObj; + var modelList = JSON.parse(result); + modelList && modelList.forEach(model => { + let selectFlag = ''; + if (model.varName == that.state.model) { + selectFlag = 'selected'; + } + modelOptionTag.appendFormatLine('
  • {5} ({6})
  • ', + 'vp-ins-select-item', selectFlag, model.varName, model.varType, model.varName, model.varName, model.varType); + }); + $(that.wrapSelector('.vp-ins-select-list.model')).html(modelOptionTag.toString()); + + // click model + $(that.wrapSelector('.vp-ins-select-list.model .vp-ins-select-item')).on('click', function() { + let model = $(this).data('var-name'); + let modelType = $(this).data('var-type'); + + that.state.model = model; + that.state.modelType = modelType; + + $(that.wrapSelector('.vp-ins-select-list.model .vp-ins-select-item')).removeClass('selected'); + $(this).addClass('selected'); + + that.reload(); + }); + }); + } + + reload() { + // reset option page + try { + $(this.wrapSelector('.vp-ins-search')).autocomplete("destroy"); + } catch { ; } + $(this.wrapSelector('.vp-ins-select-list.action')).html(''); + $(this.wrapSelector('.vp-ins-parameter-box')).html(''); + + let model = this.state.model; + let modelType = this.state.modelType; + + let actions = this.getAction(modelType); + this.state.action = { ...actions }; + + var actListTag = new com_String(); + + Object.keys(actions).forEach(actKey => { + let titleText = actions[actKey].description; + if (actions[actKey].name != actions[actKey].label) { + titleText = actions[actKey].name + ': ' + titleText; + } + actListTag.appendFormatLine('
  • {4}
  • ', + 'vp-ins-select-item', actKey, 'action', titleText, actions[actKey].label); + }); + + $(this.wrapSelector('.vp-ins-select-list.action')).html(actListTag.toString()); + + let that = this; + // action search suggest + var suggestInput = new SuggestInput(); + suggestInput.addClass('vp-input'); + suggestInput.addClass('vp-ins-search'); + suggestInput.setPlaceholder("Search Action"); + suggestInput.setSuggestList(function () { return Object.keys(actions); }); + suggestInput.setSelectEvent(function (value, item) { + $(this.wrapSelector()).val(value); + $(that.wrapSelector('.vp-ins-type.action')).val(value); + + $(that.wrapSelector('.vp-ins-select-item[data-var-name="' + value + '"]')).click(); + }); + $(that.wrapSelector('.vp-ins-search')).replaceWith(function () { + return suggestInput.toTagString(); + }); + + // bind event + // click option + $(this.wrapSelector('.vp-ins-select-list.action .vp-ins-select-item')).on('click', function() { + let name = $(this).data('var-name'); + let type = $(this).data('var-type'); + + that.renderOptionPage(type, name); + }); + + // load once on initializing page + if (this.loaded == false) { + let { modelEditorType, modelEditorName } = this.state; + if (modelEditorType != '' && modelEditorName != '') { + // render option page for saved state + that.renderOptionPage(modelEditorType, modelEditorName); + } + // set loaded true + this.loaded = true; + } + } + + /** + * Render option page for selected option + * @param {String} type action / info + * @param {String} name option name (ex. fit/predict/...) + */ + renderOptionPage(type, name) { + if (this.state[type] != undefined && this.state[type][name] != undefined) { + let optionConfig = this.state[type][name]; + let optBox = new com_String(); + // render tag + optionConfig && optionConfig.options && optionConfig.options.forEach(opt => { + let label = opt.name; + if (opt.label != undefined) { + label = opt.label; + } + // fix label + label = com_util.optionToLabel(label); + optBox.appendFormatLine('' + , opt.name, opt.name, label); + let content = com_generator.renderContent(this, opt.component[0], opt, this.state); + optBox.appendLine(content[0].outerHTML); + }); + // replace option box + $(this.wrapSelector('.vp-ins-parameter-box')).html(optBox.toString()); + + this.state.optionConfig = optionConfig; + + // add selection + let typeClass = '.vp-ins-select-list.' + type; + let nameClass = '.vp-ins-select-item[data-var-name="' + name + '"]'; + $(this.wrapSelector(typeClass + ' ' + '.vp-ins-select-item')).removeClass('selected'); + $(this.wrapSelector(typeClass + ' ' + nameClass)).addClass('selected'); + // set state + $(this.wrapSelector('#modelEditorType')).val(type); + $(this.wrapSelector('#modelEditorName')).val(name); + this.state.modelEditorType = type; + this.state.modelEditorName = name; + } + } + + generateCode() { + let { model } = this.state; + let code = new com_String(); + let replaceDict = {'${model}': model}; + + if (this.state.optionConfig.import != undefined) { + code.appendLine(this.state.optionConfig.import); + code.appendLine(); + } + let modelCode = com_generator.vp_codeGenerator(this, this.state.optionConfig, this.state); + if (modelCode) { + Object.keys(replaceDict).forEach(key => { + modelCode = modelCode.replace(key, replaceDict[key]); + }); + code.append(modelCode); + + let allocateIdx = modelCode.indexOf(' = '); + if (allocateIdx >= 0) { + let allocateCode = modelCode.substr(0, allocateIdx); + code.appendLine(); + code.append(allocateCode); + } + } + + return code.toString(); + } + + getModelCategory(modelType) { + let mlDict = vpConfig.getMLDataDict(); + let keys = Object.keys(mlDict); + let modelCategory = ''; + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + if (mlDict[key].includes(modelType)) { + modelCategory = key; + break; + } + } + return modelCategory; + } + + getAction(modelType) { + let category = this.getModelCategory(modelType); + let defaultActions = { + 'fit': { + name: 'fit', + label: 'Fit', + code: '${model}.fit(${fit_featureData}, ${fit_targetData})', + description: 'Perform modeling from features, or distance matrix.', + options: [ + { name: 'fit_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X_train' }, + { name: 'fit_targetData', label: 'Target Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'y_train' } + ] + }, + 'predict': { + name: 'predict', + label: 'Predict', + code: '${pred_allocate} = ${model}.predict(${pred_featureData})', + description: 'Predict the closest target data X belongs to.', + options: [ + { name: 'pred_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X_test' }, + { name: 'pred_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'pred' } + ] + }, + 'predict_proba': { + name: 'predict_proba', + label: 'Predict probability', + code: '${pred_prob_allocate} = ${model}.predict_proba(${pred_prob_featureData})', + description: 'Predict class probabilities for X.', + options: [ + { name: 'pred_prob_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X_test' }, + { name: 'pred_prob_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'pred' } + ] + }, + 'transform': { + name: 'transform', + label: 'Transform', + code: '${trans_allocate} = ${model}.transform(${trans_featureData})', + description: 'Apply dimensionality reduction to X.', + options: [ + { name: 'trans_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'trans_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'trans' } + ] + } + }; + let actions = {}; + switch (category) { + case 'Data Preparation': + actions = { + 'fit': { + name: 'fit', + label: 'Fit', + code: '${model}.fit(${fit_featureData})', + description: 'Fit Encoder/Scaler to X.', + options: [ + { name: 'fit_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' } + ] + }, + 'fit_transform': { + name: 'fit_transform', + label: 'Fit and transform', + code: '${fit_trans_allocate} = ${model}.fit_transform(${fit_trans_featureData})', + description: 'Fit Encoder/Scaler to X, then transform X.', + options: [ + { name: 'fit_trans_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'fit_trans_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'trans' } + ] + }, + 'transform': { + ...defaultActions['transform'], + description: 'Transform labels to normalized encoding.' + } + } + + if (modelType != 'ColumnTransformer') { + actions = { + ...actions, + 'inverse_transform': { + name: 'inverse_transform', + label: 'Inverse transform', + code: '${inverse_allocate} = ${model}.inverse_transform(${inverse_featureData})', + description: 'Transform binary labels back to multi-class labels.', + options: [ + { name: 'inverse_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'inverse_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'inv_trans' } + ] + } + } + } + break; + case 'Regression': + actions = { + 'fit': defaultActions['fit'], + 'predict': defaultActions['predict'] + } + break; + case 'Classification': + actions = { + 'fit': defaultActions['fit'], + 'predict': defaultActions['predict'], + 'predict_proba': defaultActions['predict_proba'], + } + if (['LogisticRegression', 'SVC', 'GradientBoostingClassifier'].includes(modelType)) { + actions = { + ...actions, + 'decision_function': { + name: 'decision_function', + label: 'Decision function', + code: '${dec_allocate} = ${model}.decision_function(${dec_featureData})', + description: 'Compute the decision function of X.', + options: [ + { name: 'dec_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'dec_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable' } + ] + } + } + } + break; + case 'Auto ML': + actions = { + 'fit': defaultActions['fit'], + 'predict': defaultActions['predict'], + 'fit_predict': { + name: 'fit_predict', + label: 'Fit and predict', + code: '${fit_pred_allocate} = ${model}.fit_predict(${fit_pred_featureData})', + description: 'Fit and predict.', + options: [ + { name: 'fit_pred_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'fit_pred_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'pred' } + ] + }, + 'predict_proba': defaultActions['predict_proba'] + } + break; + case 'Clustering': + if (modelType == 'AgglomerativeClustering' + || modelType == 'DBSCAN') { + actions = { + 'fit': { + name: 'fit', + label: 'Fit', + code: '${model}.fit(${fit_featureData})', + description: 'Perform clustering from features, or distance matrix.', + options: [ + { name: 'fit_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' } + ] + }, + 'fit_predict': { + name: 'fit_predict', + label: 'Fit and predict', + code: '${fit_pred_allocate} = ${model}.fit_predict(${fit_pred_featureData})', + description: 'Compute clusters from a data or distance matrix and predict labels.', + options: [ + { name: 'fit_pred_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'fit_pred_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'pred' } + ] + } + } + break; + } + actions = { + 'fit': { + name: 'fit', + label: 'Fit', + code: '${model}.fit(${fit_featureData})', + description: 'Compute clustering.', + options: [ + { name: 'fit_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' } + ] + }, + 'predict': { + name: 'predict', + label: 'Predict', + code: '${pred_allocate} = ${model}.predict(${pred_featureData})', + description: 'Predict the closest target data X belongs to.', + options: [ + { name: 'pred_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'pred_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'pred' } + ] + }, + 'fit_predict': { + name: 'fit_predict', + label: 'Fit and predict', + code: '${fit_pred_allocate} = ${model}.fit_predict(${fit_pred_featureData})', + description: 'Compute cluster centers and predict cluster index for each sample.', + options: [ + { name: 'fit_pred_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'fit_pred_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'pred' } + ] + } + } + if (modelType == 'KMeans') { + actions = { + ...actions, + 'fit_transform': { + name: 'fit_transform', + label: 'Fit and transform', + code: '${fit_trans_allocate} = ${model}.fit_transform(${fit_trans_featureData})', + description: 'Compute clustering and transform X to cluster-distance space.', + options: [ + { name: 'fit_trans_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'fit_trans_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'trans' } + ] + }, + 'transform': { + name: 'transform', + label: 'Transform', + code: '${trans_allocate} = ${model}.transform(${trans_featureData})', + description: 'Transform X to a cluster-distance space.', + options: [ + { name: 'trans_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'trans_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'trans' } + ] + } + } + } + break; + case 'Dimension Reduction': + if (modelType == 'TSNE') { + actions = { + 'fit': { + name: 'fit', + label: 'Fit', + code: '${model}.fit(${fit_featureData})', + description: 'Fit X into an embedded space.', + options: [ + { name: 'fit_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' } + ] + }, + 'fit_transform': { + name: 'fit_transform', + label: 'Fit and transform', + code: '${fit_trans_allocate} = ${model}.fit_transform(${fit_trans_featureData})', + description: 'Fit X into an embedded space and return that transformed output.', + options: [ + { name: 'fit_trans_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'fit_trans_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'trans' } + ] + } + } + break; + } + if (modelType == 'LinearDiscriminantAnalysis') { // LDA + actions = { + 'fit': { + name: 'fit', + label: 'Fit', + code: '${model}.fit(${fit_featureData}, ${fit_targetData})', + description: 'Fit the Linear Discriminant Analysis model.', + options: [ + { name: 'fit_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'fit_targetData', label: 'Target Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'y' } + ] + }, + 'fit_transform': { + name: 'fit_transform', + label: 'Fit and transform', + code: '${fit_trans_allocate} = ${model}.fit_transform(${fit_trans_featureData}${fit_trans_targetData})', + description: 'Fit to data, then transform it.', + options: [ + { name: 'fit_trans_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'fit_trans_targetData', label: 'Target Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'y' }, + { name: 'fit_trans_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'trans' } + ] + }, + 'predict': { + name: 'predict', + label: 'Predict', + code: '${pred_allocate} = ${model}.predict(${pred_featureData})', + description: 'Predict class labels for samples in X.', + options: [ + { name: 'pred_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'pred_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'pred' } + ] + }, + 'transform': { + name: 'transform', + label: 'Transform', + code: '${trans_allocate} = ${model}.transform(${trans_featureData})', + description: 'Project data to maximize class separation.', + options: [ + { name: 'trans_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'trans_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'trans' } + ] + } + } + break; + } + actions = { + 'fit': { + name: 'fit', + label: 'Fit', + code: '${model}.fit(${fit_featureData})', + description: 'Fit X into an embedded space.', + options: [ + { name: 'fit_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' } + ] + }, + 'fit_transform': { + name: 'fit_transform', + label: 'Fit and transform', + code: '${fit_trans_allocate} = ${model}.fit_transform(${fit_trans_featureData})', + description: 'Fit the model with X and apply the dimensionality reduction on X.', + options: [ + { name: 'fit_trans_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'fit_trans_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'trans' } + ] + }, + 'inverse_transform': { + name: 'inverse_transform', + label: 'Inverse transform', + code: '${inverse_allocate} = ${model}.inverse_transform(${inverse_featureData})', + description: 'Transform data back to its original space.', + options: [ + { name: 'inverse_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'inverse_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'inv_trans' } + ] + }, + 'transform': { + name: 'transform', + label: 'Transform', + code: '${trans_allocate} = ${model}.transform(${trans_featureData})', + description: 'Apply dimensionality reduction to X.', + options: [ + { name: 'trans_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'trans_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'trans' } + ] + } + } + break; + } + return actions; + } + + } + + return FitPredict; +}); \ No newline at end of file diff --git a/js/m_ml/ModelInfo.js b/js/m_ml/ModelInfo.js new file mode 100644 index 00000000..2d945da3 --- /dev/null +++ b/js/m_ml/ModelInfo.js @@ -0,0 +1,654 @@ +/* + * Project Name : Visual Python + * Description : GUI-based Python code generator + * File Name : ModelInfo.js + * Author : Black Logic + * Note : Model information + * License : GNU GPLv3 with Visual Python special exception + * Date : 2022. 04. 20 + * Change Date : + */ + +//============================================================================ +// [CLASS] ModelInfo +//============================================================================ +define([ + 'text!vp_base/html/m_ml/modelInfo.html!strip', + 'css!vp_base/css/m_ml/modelInfo.css', + 'vp_base/js/com/com_util', + 'vp_base/js/com/com_String', + 'vp_base/js/com/com_generatorV2', + 'vp_base/data/m_ml/mlLibrary', + 'vp_base/js/com/component/PopupComponent', + 'vp_base/js/com/component/VarSelector2', + 'vp_base/js/com/component/SuggestInput' +], function(msHtml, msCss, com_util, com_String, com_generator, ML_LIBRARIES, PopupComponent, VarSelector2, SuggestInput) { + + /** + * ModelInfo + */ + class ModelInfo extends PopupComponent { + _init() { + super._init(); + this.config.sizeLevel = 2; + this.config.dataview = false; + + this.state = { + // model selection + category: 'All', + model: '', + modelType: '', + method: '', + info: {}, + optionConfig: {}, + userOption: '', + ...this.state + } + + // categories : Data Preparation / Regression / Classification / Clustering / Dimension Reduction / Auto ML + this.modelCategories = [ + 'All', + ...vpConfig.getMLCategories() + ] + + this.modelConfig = ML_LIBRARIES; + + this.loaded = false; + + } + + _bindEvent() { + super._bindEvent(); + /** Implement binding events */ + var that = this; + + // click category + $(this.wrapSelector('.vp-ins-select-list.category .vp-ins-select-item')).on('click', function() { + let category = $(this).data('var-name'); + + that.state.category = category; + + $(that.wrapSelector('.vp-ins-select-list.category .vp-ins-select-item')).removeClass('selected'); + $(this).addClass('selected'); + + // load model list for this category + that.loadModelList(category); + }); + } + + templateForBody() { + let page = $(msHtml); + + let that = this; + + //================================================================ + // Model selection + //================================================================ + // set model category list + let modelCategoryTag = new com_String(); + this.modelCategories.forEach(category => { + let selected = ''; + if (category == that.state.category) { + selected = 'selected'; + } + modelCategoryTag.appendFormatLine('
  • {5}
  • ', + 'vp-ins-select-item', selected, category, 'category', category, category); + }); + $(page).find('.vp-ins-select-list.category').html(modelCategoryTag.toString()); + + //================================================================ + // 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; + } + + templateForOption(modelType) { + let optionConfig = this.modelConfig[modelType]; + let state = this.state; + + let optBox = new com_String(); + // render tag + optionConfig.options.forEach(opt => { + optBox.appendFormatLine('' + , opt.name, opt.name, com_util.optionToLabel(opt.name)); + let content = com_generator.renderContent(this, opt.component[0], opt, state); + optBox.appendLine(content[0].outerHTML); + }); + // render user option + optBox.appendFormatLine('', 'userOption', 'User option'); + optBox.appendFormatLine('', + 'userOption', 'key=value, ...', this.state.userOption); + return optBox.toString(); + } + + render() { + super.render(); + + this.loadModelList(this.state.category); + + this.reload(); + } + + loadModelList(category='') { + // reset page + try { + $(this.wrapSelector('.vp-ins-search')).autocomplete("destroy"); + } catch { ; } + $(this.wrapSelector('.vp-ins-select-list.info')).html(''); + $(this.wrapSelector('.vp-ins-parameter-box')).html(''); + + if (category == 'All') { + category = ''; + } + // set model list + let that = this; + let modelOptionTag = new com_String(); + vpKernel.getModelList(category).then(function(resultObj) { + let { result } = resultObj; + var modelList = JSON.parse(result); + modelList && modelList.forEach(model => { + let selectFlag = ''; + if (model.varName == that.state.model) { + selectFlag = 'selected'; + } + modelOptionTag.appendFormatLine('
  • {5} ({6})
  • ', + 'vp-ins-select-item', selectFlag, model.varName, model.varType, model.varName, model.varName, model.varType); + }); + $(that.wrapSelector('.vp-ins-select-list.model')).html(modelOptionTag.toString()); + + // click model + $(that.wrapSelector('.vp-ins-select-list.model .vp-ins-select-item')).on('click', function() { + let model = $(this).data('var-name'); + let modelType = $(this).data('var-type'); + + that.state.model = model; + that.state.modelType = modelType; + + $(that.wrapSelector('.vp-ins-select-list.model .vp-ins-select-item')).removeClass('selected'); + $(this).addClass('selected'); + + that.reload(); + }); + }); + } + + reload() { + // reset option page + try { + $(this.wrapSelector('.vp-ins-search')).autocomplete("destroy"); + } catch { ; } + $(this.wrapSelector('.vp-ins-select-list.info')).html(''); + $(this.wrapSelector('.vp-ins-parameter-box')).html(''); + + let model = this.state.model; + let modelType = this.state.modelType; + + let infos = this.getInfo(modelType); + this.state.info = { ...infos }; + + var infoListTag = new com_String(); + + Object.keys(infos).forEach(infoKey => { + let titleText = infos[infoKey].description; + if (infos[infoKey].name != infos[infoKey].label) { + titleText = infos[infoKey].name + ': ' + titleText; + } + infoListTag.appendFormatLine('
  • {4}
  • ', + 'vp-ins-select-item', infoKey, 'info', titleText, infos[infoKey].label); + }); + + $(this.wrapSelector('.vp-ins-select-list.info')).html(infoListTag.toString()); + + let that = this; + // info search suggest + let suggestInput = new SuggestInput(); + suggestInput.addClass('vp-input'); + suggestInput.addClass('vp-ins-search'); + suggestInput.setPlaceholder("Search information"); + suggestInput.setSuggestList(function () { return Object.keys(infos); }); + suggestInput.setSelectEvent(function (value, item) { + $(this.wrapSelector()).val(value); + $(that.wrapSelector('.vp-ins-type.info')).val(value); + + $(that.wrapSelector('.vp-ins-select-item[data-var-name="' + value + '"]')).click(); + }); + $(that.wrapSelector('.vp-ins-search')).replaceWith(function () { + return suggestInput.toTagString(); + }); + + // bind event + // click option + $(this.wrapSelector('.vp-ins-select-list.info .vp-ins-select-item')).on('click', function() { + let name = $(this).data('var-name'); + let type = $(this).data('var-type'); + + that.renderOptionPage(type, name); + }); + + // load once on initializing page + if (this.loaded == false) { + let { modelEditorType, modelEditorName } = this.state; + if (modelEditorType != '' && modelEditorName != '') { + // render option page for saved state + that.renderOptionPage(modelEditorType, modelEditorName); + } + // set loaded true + this.loaded = true; + } + } + + /** + * Render option page for selected option + * @param {String} type action / info + * @param {String} name option name (ex. fit/predict/...) + */ + renderOptionPage(type, name) { + if (this.state[type] != undefined && this.state[type][name] != undefined) { + let optionConfig = this.state[type][name]; + let optBox = new com_String(); + // render tag + optionConfig && optionConfig.options && optionConfig.options.forEach(opt => { + let label = opt.name; + if (opt.label != undefined) { + label = opt.label; + } + // fix label + label = com_util.optionToLabel(label); + optBox.appendFormatLine('' + , opt.name, opt.name, label); + let content = com_generator.renderContent(this, opt.component[0], opt, this.state); + optBox.appendLine(content[0].outerHTML); + }); + // replace option box + $(this.wrapSelector('.vp-ins-parameter-box')).html(optBox.toString()); + + this.state.optionConfig = optionConfig; + + // add selection + let typeClass = '.vp-ins-select-list.' + type; + let nameClass = '.vp-ins-select-item[data-var-name="' + name + '"]'; + $(this.wrapSelector(typeClass + ' ' + '.vp-ins-select-item')).removeClass('selected'); + $(this.wrapSelector(typeClass + ' ' + nameClass)).addClass('selected'); + // set state + $(this.wrapSelector('#modelEditorType')).val(type); + $(this.wrapSelector('#modelEditorName')).val(name); + this.state.modelEditorType = type; + this.state.modelEditorName = name; + } + } + + generateCode() { + let { model } = this.state; + let code = new com_String(); + let replaceDict = {'${model}': model}; + + if (this.state.optionConfig.import != undefined) { + code.appendLine(this.state.optionConfig.import); + code.appendLine(); + } + let modelCode = com_generator.vp_codeGenerator(this, this.state.optionConfig, this.state); + if (modelCode) { + Object.keys(replaceDict).forEach(key => { + modelCode = modelCode.replace(key, replaceDict[key]); + }); + code.append(modelCode); + + let allocateIdx = modelCode.indexOf(' = '); + if (allocateIdx >= 0) { + let allocateCode = modelCode.substr(0, allocateIdx); + code.appendLine(); + code.append(allocateCode); + } + } + + return code.toString(); + } + + getModelCategory(modelType) { + let mlDict = vpConfig.getMLDataDict(); + let keys = Object.keys(mlDict); + let modelCategory = ''; + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + if (mlDict[key].includes(modelType)) { + modelCategory = key; + break; + } + } + return modelCategory; + } + + getInfo(modelType) { + let category = this.getModelCategory(modelType); + let infos = {}; + let defaultInfos = { + 'score': { + name: 'score', + label: 'Score', + code: '${score_allocate} = ${model}.score(${score_featureData}, ${score_targetData})', + description: '', + options: [ + { name: 'score_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'score_targetData', label: 'Target Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'y' }, + { name: 'score_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'scores' } + ] + }, + 'get_params': { + name: 'get_params', + label: 'Get parameters', + code: '${param_allocate} = ${model}.get_params(${deep})', + description: 'Get parameters for this estimator.', + options: [ + { name: 'deep', component: ['bool_select'], default: 'True', usePair: true }, + { name: 'param_allocate', label: 'Allocate to', component: ['input'], value: 'params' } + ] + }, + 'permutation_importance': { + name: 'permutation_importance', + label: 'Permutation importance', + import: 'from sklearn.inspection import permutation_importance', + code: '${importance_allocate} = permutation_importance(${model}, ${importance_featureData}, ${importance_targetData}${scoring}${random_state}${etc})', + description: 'Permutation importance for feature evaluation.', + options: [ + { name: 'importance_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X_train' }, + { name: 'importance_targetData', label: 'Target Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'y_train' }, + { name: 'scoring', component: ['input'], usePair: true }, + { name: 'random_state', component: ['input_number'], placeholder: '123', usePair: true }, + { name: 'importance_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'importances' } + ] + } + } + switch (category) { + case 'Data Preparation': + if (modelType == 'OneHotEncoder') { + infos = { + 'categories_': { // TODO: + name: 'categories_', + label: 'Categories', + code: '${categories_allocate} = ${model}.categories_', + description: 'The categories of each feature determined during fitting', + options: [ + { name: 'categories_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'categories' } + ] + }, + 'get_feature_names_out': { + name: 'get_feature_names_out', + label: 'Get feature names', + code: '${feature_names_allocate} = ${model}.get_feature_names_out()', + description: 'Get output feature names.', + options: [ + { name: 'feature_names_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'features' } + ] + } + } + } + if (modelType == 'LabelEncoder') { + infos = { + 'classes_': { + name: 'classes_', + label: 'Classes', + code: '${classes_allocate} = ${model}.classes_', + description: 'Holds the label for each class.', + options: [ + { name: 'classes_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'classes' } + ] + } + } + } + if (modelType == 'KBinsDiscretizer') { + infos = { + 'bin_edges': { // TODO: + name: 'bin_edges', + label: 'Bin edges', + code: '${bin_edges_allocate} = ${model}.bin_edges_', + description: 'The edges of each bin. Contain arrays of varying shapes', + options: [ + { name: 'bin_edges_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'bin_edges' } + ] + } + } + } + if (modelType == 'ColumnTransformer') { + infos = { + 'transformers_': { + name: 'transformers_', + label: 'Transformers_', + code: '${transformers_allocate} = ${model}.transformers_', + description: 'The collection of fitted transformers as tuples of (name, fitted_transformer, column).', + options: [ + { name: 'transformers_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'classes' } + ] + }, + 'get_feature_names_out': { + name: 'get_feature_names_out', + label: 'Get feature names', + code: '${feature_names_allocate} = ${model}.get_feature_names_out()', + description: 'Get output feature names.', + options: [ + { name: 'feature_names_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'features' } + ] + } + } + } + infos = { + ...infos, + 'get_params': defaultInfos['get_params'] + } + break; + case 'Regression': + infos = { + 'score': { + ...defaultInfos['score'], + description: 'Return the coefficient of determination of the prediction.' + }, + 'cross_val_score': { + name: 'cross_val_score', + label: 'Cross validation score', + import: 'from sklearn.model_selection import cross_val_score', + code: '${cvs_allocate} = cross_val_score(${model}, ${cvs_featureData}, ${cvs_targetData}${scoring}${cv})', + description: 'Evaluate a score by cross-validation.', + options: [ + { name: 'cvs_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'cvs_targetData', label: 'Target Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'y' }, + { name: 'scoring', component: ['option_select'], usePair: true, type: 'text', + options: [ + '', + 'explained_variance', 'max_error', 'neg_mean_absolute_error', 'neg_mean_squared_error', 'neg_root_mean_squared_error', + 'neg_mean_squared_log_error', 'neg_median_absolute_error', 'r2', 'neg_mean_poisson_deviance', 'neg_mean_gamma_deviance', + 'neg_mean_absolute_percentage_error' + ] }, + { name: 'cv', label: 'Cross Validation', component: ['input_number'], placeholder: '1 ~ 10', default: 5, usePair: true }, + { name: 'cvs_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'scores' } + ] + }, + 'permutation_importance': defaultInfos['permutation_importance'], + 'Coefficient': { + name: 'coef_', + label: 'Coefficient', + code: '${coef_allocate} = ${model}.coef_', + description: 'Weights assigned to the features.', + options: [ + { name: 'coef_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'coef' } + ] + }, + 'Intercept': { + name: 'intercept_', + label: 'Intercept', + code: '${intercept_allocate} = ${model}.intercept_', + description: 'Constants in decision function.', + options: [ + { name: 'intercept_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'intercepts' } + ] + } + } + break; + case 'Classification': + infos = { + 'score': { + ...defaultInfos['score'], + description: 'Return the mean accuracy on the given test data and labels.' + }, + 'cross_val_score': { + name: 'cross_val_score', + label: 'Cross validation score', + import: 'from sklearn.model_selection import cross_val_score', + code: '${cvs_allocate} = cross_val_score(${model}, ${cvs_featureData}, ${cvs_targetData}${scoring}${cv})', + description: 'Evaluate a score by cross-validation.', + options: [ + { name: 'cvs_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'cvs_targetData', label: 'Target Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'y' }, + { name: 'scoring', component: ['option_select'], usePair: true, type: 'text', + options: [ + '', + 'accuracy', 'balanced_accuracy', 'top_k_accuracy', 'average_precision', 'neg_brier_score', + 'f1', 'f1_micro', 'f1_macro', 'f1_weighted', 'f1_samples', 'neg_log_loss', 'precision', 'recall', 'jaccard', + 'roc_auc', 'roc_auc_ovr', 'roc_auc_ovo', 'roc_auc_ovr_weighted', 'roc_auc_ovo_weighted' + ] }, + { name: 'cv', label: 'Cross Validation', component: ['input_number'], placeholder: '1 ~ 10', default: 5, usePair: true }, + { name: 'cvs_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'scores' } + ] + }, + 'permutation_importance': defaultInfos['permutation_importance'] + } + break; + case 'Auto ML': + infos = { + 'score': { + ...defaultInfos['score'], + description: 'Return the mean accuracy on the given test data and labels.' + }, + 'get_params': { + ...defaultInfos['get_params'] + } + } + break; + case 'Clustering': + infos = { + 'get_params': { + ...defaultInfos['get_params'] + } + } + + if (modelType == 'KMeans') { + infos = { + ...infos, + 'score': { + ...defaultInfos['score'], + description: 'Return the mean accuracy on the given test data and labels.' + }, + 'cluster_centers_': { + name: 'cluster_centers', + label: 'Cluster centers', + code: '${centers_allocate} = ${model}.cluster_centers_', + description: 'Coordinates of cluster centers.', + options: [ + { name: 'centers_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'cluster_centers' } + ] + } + } + } + + if (modelType == 'AgglomerativeClustering') { + infos = { + ...infos, + 'Dendrogram': { // FIXME: + name: 'dendrogram', + label: 'Dendrogram', + code: "# import\nfrom scipy.cluster.hierarchy import dendrogram, ward\n\nlinkage_array = ward(${dendro_data})\ndendrogram(linkage_array, p=3, truncate_mode='level', no_labels=True)\nplt.show()", + description: 'Draw a dendrogram', + options: [ + { name: 'dendro_data', label: 'Data', component: ['var_select'], var_type: ['DataFrame'] } + ] + } + } + } + + if (modelType == 'GaussianMixture') { + infos = { + ...infos, + 'score': { + ...defaultInfos['score'], + description: 'Compute the per-sample average log-likelihood of the given data X.' + } + } + } + break; + case 'Dimension Reduction': + if (modelType == 'LDA') { + infos = { + 'score': { + name: 'score', + label: 'Score', + code: '${score_allocate} = ${model}.score(${score_featureData}, ${score_targetData})', + description: 'Return the average log-likelihood of all samples.', + options: [ + { name: 'score_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'score_targetData', label: 'Target Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'y' }, + { name: 'score_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'scores' } + ] + } + } + break; + } + if (modelType == 'PCA') { + infos = { + 'explained_variance_ratio_': { + name: 'explained_variance_ratio_', + label: 'Explained variance ratio', + code: '${ratio_allocate} = ${model}.explained_variance_ratio_', + description: 'Percentage of variance explained by each of the selected components.', + options: [ + { name: 'ratio_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'ratio' } + ] + } + } + } + infos = { + ...infos, + 'score': { + name: 'score', + label: 'Score', + code: '${score_allocate} = ${model}.score(${score_featureData})', + description: 'Return the average log-likelihood of all samples.', + options: [ + { name: 'score_featureData', label: 'Feature Data', component: ['var_select'], var_type: ['DataFrame', 'Series'], value: 'X' }, + { name: 'score_allocate', label: 'Allocate to', component: ['input'], placeholder: 'New variable', value: 'scores' } + ] + } + } + break; + } + return infos; + } + + } + + return ModelInfo; +}); \ No newline at end of file diff --git a/js/m_ml/evaluation.js b/js/m_ml/evaluation.js index 41ab3be5..26ea1eed 100644 --- a/js/m_ml/evaluation.js +++ b/js/m_ml/evaluation.js @@ -41,8 +41,12 @@ define([ confusion_matrix: true, report: true, accuracy: false, precision: false, recall: false, f1_score: false, roc_curve: false, auc: false, + model: '', // clustering - silhouetteScore: true, ari: false, nm: false, + clusteredIndex: 'clusters', + silhouetteScore: true, ari: false, nmi: false, + featureData2: 'X', + targetData2: 'y', ...this.state } } @@ -62,13 +66,33 @@ define([ let modelType = $(this).val(); that.state.modelType = modelType; + $(that.wrapSelector('.vp-upper-box')).hide(); + $(that.wrapSelector('.vp-upper-box.' + modelType)).show(); + $(that.wrapSelector('.vp-eval-box')).hide(); $(that.wrapSelector('.vp-eval-'+modelType)).show(); - if (modelType == 'clf') { + if (modelType == 'rgs') { + // Regression + + } else if (modelType == 'clf') { // Classification - model selection - if (that.checkToShowModel() == true) { - $(that.wrapSelector('.vp-ev-model')).show(); + if (that.checkToShowModel('roc-auc') == true) { + $(that.wrapSelector('.vp-ev-model.roc-auc')).prop('disabled', false); + } else { + $(that.wrapSelector('.vp-ev-model.roc-auc')).prop('disabled', true); + } + } else { + // Clustering + if (that.checkToShowModel('silhouette') == true) { + $(that.wrapSelector('.vp-ev-model.silhouette')).prop('disabled', false); + } else { + $(that.wrapSelector('.vp-ev-model.silhouette')).prop('disabled', true); + } + if (that.checkToShowModel('ari-nmi') == true) { + $(that.wrapSelector('.vp-ev-model.ari-nmi')).prop('disabled', false); + } else { + $(that.wrapSelector('.vp-ev-model.ari-nmi')).prop('disabled', true); } } }); @@ -76,12 +100,13 @@ define([ // open model selection show $(this.wrapSelector('.vp-eval-check')).on('change', function() { let checked = $(this).prop('checked'); + let type = $(this).data('type'); if (checked) { - $(that.wrapSelector('.vp-ev-model')).show(); + $(that.wrapSelector('.vp-ev-model.' + type)).prop('disabled', false); } else { - if (that.checkToShowModel() == false) { - $(that.wrapSelector('.vp-ev-model')).hide(); + if (that.checkToShowModel(type) == false) { + $(that.wrapSelector('.vp-ev-model.' + type)).prop('disabled', true); } } }); @@ -91,8 +116,8 @@ define([ * Check if anything checked available ( > 0) * @returns */ - checkToShowModel() { - let checked = $(this.wrapSelector('.vp-eval-check:checked')).length; + checkToShowModel(type) { + let checked = $(this.wrapSelector('.vp-eval-check[data-type="' + type + '"]:checked')).length; if (checked > 0) { return true; } @@ -118,6 +143,25 @@ define([ varSelector.setValue(this.state.targetData); $(page).find('#targetData').replaceWith(varSelector.toTagString()); + // Clustering - data selection + varSelector = new VarSelector2(this.wrapSelector(), ['DataFrame', 'list', 'str']); + varSelector.setComponentID('clusteredIndex'); + varSelector.addClass('vp-state vp-input'); + varSelector.setValue(this.state.clusteredIndex); + $(page).find('#clusteredIndex').replaceWith(varSelector.toTagString()); + + varSelector = new VarSelector2(this.wrapSelector(), ['DataFrame', 'list', 'str']); + varSelector.setComponentID('featureData2'); + varSelector.addClass('vp-state vp-input vp-ev-model silhouette'); + varSelector.setValue(this.state.featureData2); + $(page).find('#featureData2').replaceWith(varSelector.toTagString()); + + varSelector = new VarSelector2(this.wrapSelector(), ['DataFrame', 'list', 'str']); + varSelector.setComponentID('targetData2'); + varSelector.addClass('vp-state vp-input vp-ev-model ari-nmi'); + varSelector.setValue(this.state.targetData2); + $(page).find('#targetData2').replaceWith(varSelector.toTagString()); + // model // set model list let modelOptionTag = new com_String(); @@ -169,14 +213,25 @@ define([ } }); - if (this.state.modelType == 'clf') { - if (this.state.roc_curve == true || this.state.auc == true) { - $(page).find('.vp-ev-model').show(); - } else { - $(page).find('.vp-ev-model').hide(); + $(page).find('.vp-upper-box').hide(); + $(page).find('.vp-upper-box.' + this.state.modelType).show(); + + if (this.state.modelType == 'rgs') { + // Regression + + } else if (this.state.modelType == 'clf') { + // Classification + if (this.state.roc_curve == false && this.state.auc == false) { + $(page).find('.vp-ev-model.roc-auc').prop('disabled', true); } } else { - $(page).find('.vp-ev-model').hide(); + // Clustering + if (this.state.silhouetteScore == false) { + $(page).find('.vp-ev-model.silhouette').prop('disabled', true); + } + if (this.state.ari == false && this.state.nmi == false) { + $(page).find('.vp-ev-model.ari-nmi').prop('disabled', true); + } } return page; @@ -197,7 +252,8 @@ define([ // regression coefficient, intercept, r_squared, mae, mape, rmse, scatter_plot, // clustering - sizeOfClusters, silhouetteScore, ari, nm + sizeOfClusters, silhouetteScore, ari, nmi, + clusteredIndex, featureData2, targetData2 } = this.state; //==================================================================== @@ -317,19 +373,19 @@ define([ if (silhouetteScore) { code = new com_String(); code.appendLine("# Silhouette score"); - code.appendFormat("print(f'Silhouette score: {metrics.cluster.silhouette_score({0}, {1})}')", targetData, predictData); + code.appendFormat("print(f'Silhouette score: {metrics.cluster.silhouette_score({0}, {1})}')", featureData2, clusteredIndex); codeCells.push(code.toString()); } if (ari) { code = new com_String(); - code.appendLine("# ARI"); - code.appendFormat("print(f'ARI: {metrics.cluster.adjusted_rand_score({0}, {1})}')", targetData, predictData); + code.appendLine("# ARI(Adjusted Rand score)"); + code.appendFormat("print(f'ARI: {metrics.cluster.adjusted_rand_score({0}, {1})}')", targetData2, clusteredIndex); codeCells.push(code.toString()); } - if (nm) { + if (nmi) { code = new com_String(); - code.appendLine("# NM"); - code.appendFormat("print(f'NM: {metrics.cluster.normalized_mutual_info_score({0}, {1})}')", targetData, predictData); + code.appendLine("# NMI(Normalized Mutual Info Score)"); + code.appendFormat("print(f'NM: {metrics.cluster.normalized_mutual_info_score({0}, {1})}')", targetData2, clusteredIndex); codeCells.push(code.toString()); } } diff --git a/js/m_apps/Chart.js b/js/m_visualize/Chart.js similarity index 99% rename from js/m_apps/Chart.js rename to js/m_visualize/Chart.js index 76ed858e..53c4a62d 100644 --- a/js/m_apps/Chart.js +++ b/js/m_visualize/Chart.js @@ -13,8 +13,8 @@ // [CLASS] Chart //============================================================================ define([ - 'text!vp_base/html/m_apps/chart.html!strip', - 'css!vp_base/css/m_apps/chart.css', + 'text!vp_base/html/m_visualize/chart.html!strip', + 'css!vp_base/css/m_visualize/chart.css', 'vp_base/js/com/com_String', 'vp_base/js/com/com_Const', 'vp_base/js/com/com_util', diff --git a/js/m_visualize/ChartSetting.js b/js/m_visualize/ChartSetting.js index ebcda860..a41ef089 100644 --- a/js/m_visualize/ChartSetting.js +++ b/js/m_visualize/ChartSetting.js @@ -31,7 +31,7 @@ define([ this.state = { figureWidth: 12, figureHeight: 8, - styleSheet: '', + styleSheet: 'seaborn-darkgrid', fontName: '', fontSize: 10, ...this.state diff --git a/js/m_visualize/Seaborn.js b/js/m_visualize/Seaborn.js index 8135a90c..4e509b1d 100644 --- a/js/m_visualize/Seaborn.js +++ b/js/m_visualize/Seaborn.js @@ -29,7 +29,7 @@ define([ super._init(); this.config.dataview = false; - this.config.sizeLevel = 3; + this.config.size = { width: 1064, height: 550 }; this.state = { chartType: 'scatterplot', @@ -39,10 +39,27 @@ define([ figColumn: 0, shareX: false, shareY: false, + setXY: false, data: '', x: '', y: '', hue: '', + // info options + title: '', + x_label: '', + y_label: '', + useLegend: 'False', + legendPos: '', + // style options + useGrid: 'False', + useMarker: 'False', + markerStyle: '', + // setting options + x_limit_from: '', + x_limit_to: '', + y_limit_from: '', + y_limit_to: '', + // preview options useSampling: true, sampleCount: 30, autoRefresh: true, @@ -52,17 +69,50 @@ define([ this.chartConfig = CHART_LIBRARIES; this.chartTypeList = { 'Relational': [ 'scatterplot', 'lineplot' ], - 'Distributions': [ 'histplot', 'kdeplot', 'ecdfplot', 'rugplot' ], // FIXME: ecdf : no module + 'Distributions': [ 'histplot', 'kdeplot', 'rugplot' ], 'Categorical': [ 'stripplot', 'swarmplot', 'boxplot', 'violinplot', 'pointplot', 'barplot' ], - 'ETC': [ ] + // 'ETC': [ ] } + + this.legendPosList = [ + 'best', 'upper right', 'upper left', 'lower left', 'lower right', + 'center left', 'center right', 'lower center', 'upper center', 'center' + ]; + + this.markerList = [ + // 'custom': { label: 'Custom', value: 'marker' }, + { label: ' ', value: ' ', title: 'select marker style'}, + { label: '.', value: '.', title: 'point' }, + { label: ',', value: ',', title: 'pixel' }, + { label: 'o', value: 'o', title: 'circle' }, + { label: '▼', value: 'v', title: 'triangle_down' }, + { label: '▲', value: '^', title: 'triangle_up' }, + { label: '◀', value: '<', title: 'triangle_left' }, + { label: '▶', value: '>', title: 'triangle_right' }, + { label: '┬', value: '1', title: 'tri_down' }, + { label: '┵', value: '2', title: 'tri_up' }, + { label: '┤', value: '3', title: 'tri_left' }, + { label: '├', value: '4', title: 'tri_right' }, + { label: 'octagon', value: '8', title: 'octagon' }, + { label: 'square', value: 's', title: 'square' }, + { label: 'pentagon', value: 'p', title: 'pentagon' }, + { label: 'filled plus', value: 'P', title: 'plus (filled)' }, + { label: 'star', value: '*', title: 'star' }, + { label: 'hexagon1', value: 'h', title: 'hexagon1' }, + { label: 'hexagon2', value: 'H', title: 'hexagon2' }, + { label: 'plus', value: '+', title: 'plus' }, + { label: 'x', value: 'x', title: 'x' }, + { label: 'filled x', value: 'X', title: 'x (filled)' }, + { label: 'diamond', value: 'D', title: 'diamond' }, + { label: 'thin diamond', value: 'd', title: 'thin_diamond' } + ] } _bindEvent() { - super._bindEvent(); - let that = this; + super._bindEvent(); + // setting popup $(this.wrapSelector('#chartSetting')).on('click', function() { // show popup box @@ -88,30 +138,74 @@ define([ // change tab $(this.wrapSelector('.vp-tab-item')).on('click', function() { let level = $(this).parent().data('level'); - let type = $(this).data('type'); // info / element / figure + let type = $(this).data('type'); // data / info / element / figure $(that.wrapSelector(com_util.formatString('.vp-tab-bar.{0} .vp-tab-item', level))).removeClass('vp-focus'); $(this).addClass('vp-focus'); - $(that.wrapSelector(com_util.formatString('.vp-tab-page-box.{0} .vp-tab-page', level))).hide(); + $(that.wrapSelector(com_util.formatString('.vp-tab-page-box.{0} > .vp-tab-page', level))).hide(); $(that.wrapSelector(com_util.formatString('.vp-tab-page[data-type="{0}"]', type))).show(); }); - - // bind column by dataframe - $(document).on('change', this.wrapSelector('#data'), function() { - com_generator.vp_bindColumnSource(that.wrapSelector(), this, ['x', 'y', 'hue'], 'select'); + + // 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-vs-box').replaceWith(''); + $(that.wrapSelector('#y')).closest('.vp-vs-box').replaceWith(''); + $(that.wrapSelector('#hue')).closest('.vp-vs-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.y = ''; + that.state.hue = ''; + + let varSelectorX = new VarSelector2(that.wrapSelector(), ['DataFrame', 'Series', 'list']); + varSelectorX.setComponentID('x'); + varSelectorX.addClass('vp-state vp-input'); + varSelectorX.setValue(that.state.x); + $(that.wrapSelector('#x')).replaceWith(varSelectorX.toTagString()); + + let varSelectorY = new VarSelector2(that.wrapSelector(), ['DataFrame', 'Series', 'list']); + varSelectorY.setComponentID('y'); + varSelectorY.addClass('vp-state vp-input'); + varSelectorY.setValue(that.state.y); + $(that.wrapSelector('#y')).replaceWith(varSelectorY.toTagString()); + + let varSelectorHue = new VarSelector2(that.wrapSelector(), ['DataFrame', 'Series', 'list']); + varSelectorHue.setComponentID('hue'); + varSelectorHue.addClass('vp-state vp-input'); + varSelectorHue.setValue(that.state.hue); + $(that.wrapSelector('#hue')).replaceWith(varSelectorHue.toTagString()); + } }); // preview refresh $(this.wrapSelector('#previewRefresh')).on('click', function() { that.loadPreview(); }); - $(this.wrapSelector('.vp-state')).on('change', function() { - if (that.state.autoRefresh && that.state.data != '') { + // auto refresh + $(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(); }); - + + // set preview size + $(this.wrapSelector('#previewSize')).on('change', function() { + that.loadPreview(); + }); + } templateForBody() { @@ -141,9 +235,50 @@ define([ let varSelector = new VarSelector2(this.wrapSelector(), ['DataFrame', 'Series', 'list']); varSelector.setComponentID('data'); varSelector.addClass('vp-state vp-input'); - varSelector.setValue(this.state.featureData); + varSelector.setValue(this.state.data); + varSelector.setSelectEvent(function (value, item) { + $(this.wrapSelector()).val(value); + that.state.dtype = item.dtype; + + if (item.dtype == 'DataFrame') { + $(that.wrapSelector('#x')).prop('disabled', false); + $(that.wrapSelector('#y')).prop('disabled', false); + $(that.wrapSelector('#hue')).prop('disabled', false); + + // bind column source using selected dataframe + com_generator.vp_bindColumnSource(that.wrapSelector(), $(that.wrapSelector('#data')), ['x', 'y', 'hue'], 'select', true); + } else { + $(that.wrapSelector('#x')).prop('disabled', true); + $(that.wrapSelector('#y')).prop('disabled', true); + $(that.wrapSelector('#hue')).prop('disabled', true); + } + }); $(page).find('#data').replaceWith(varSelector.toTagString()); + // legend position + let legendPosTag = new com_String(); + this.legendPosList.forEach(pos => { + let selectedFlag = ''; + if (pos == that.state.legendPos) { + selectedFlag = 'selected'; + } + legendPosTag.appendFormatLine('', + pos, selectedFlag, pos, pos == 'best'?' (default)':''); + }); + $(page).find('#legendPos').html(legendPosTag.toString()); + + // marker style + let markerTag = new com_String(); + this.markerList.forEach(marker => { + let selectedFlag = ''; + if (marker.value == that.state.markerStyle) { + selectedFlag = 'selected'; + } + markerTag.appendFormatLine('', + marker.value, marker.title, selectedFlag, marker.label); + }); + $(page).find('#markerStyle').html(markerTag.toString()); + // preview sample count let sampleCountList = [30, 50, 100, 300, 500, 700, 1000]; let sampleCountTag = new com_String(); @@ -157,23 +292,60 @@ define([ }); $(page).find('#sampleCount').html(sampleCountTag.toString()); + //================================================================ + // 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; } templateForSettingBox() { - return `
    - -
    - - + return `
    + +
    + + +
    + + + + + +
    - - - - - - -
    `; + + `; } render() { @@ -191,10 +363,10 @@ define([ // set size $(this.wrapSelector('.vp-inner-popup-box')).css({ width: 400, height: 260}); - this.renderImportOptions(); + this.bindSettingBox(); } - renderImportOptions() { + bindSettingBox() { //==================================================================== // Stylesheet suggestinput //==================================================================== @@ -213,6 +385,7 @@ define([ suggestInput.setComponentID('styleSheet'); suggestInput.setSuggestList(function() { return varList; }); suggestInput.setPlaceholder('style name'); + suggestInput.setValue('seaborn-darkgrid'); // set default (seaborn-darkgrid) // suggestInput.setNormalFilter(false); $(stylesheetTag).replaceWith(function() { return suggestInput.toTagString(); @@ -243,6 +416,20 @@ define([ return suggestInput.toTagString(); }); }); + + let that = this; + // setting popup - set default + $(this.wrapSelector('#setDefault')).on('change', function() { + let checked = $(this).prop('checked'); + + if (checked) { + // disable input + $(that.wrapSelector('.vp-chart-setting-body input')).prop('disabled', true); + } else { + // enable input + $(that.wrapSelector('.vp-chart-setting-body input')).prop('disabled', false); + } + }); } handleInnerOk() { @@ -292,53 +479,128 @@ define([ var code = new com_String(); // get parameters - var figWidth = $(this.wrapSelector('#figureWidth')).val(); - var figHeight = $(this.wrapSelector('#figureHeight')).val(); - var styleName = $(this.wrapSelector('#styleSheet')).val(); - var fontName = $(this.wrapSelector('#fontName')).val(); - var fontSize = $(this.wrapSelector('#fontSize')).val(); - - code.appendLine('import matplotlib.pyplot as plt'); - code.appendFormatLine("plt.rc('figure', figsize=({0}, {1}))", figWidth, figHeight); - if (styleName && styleName.length > 0) { - code.appendFormatLine("plt.style.use('{0}')", styleName); - } - code.appendLine(); - - code.appendLine('from matplotlib import rcParams'); - if (fontName && fontName.length > 0) { - code.appendFormatLine("rcParams['font.family'] = '{0}'", fontName); - } - if (fontSize && fontSize.length > 0) { - code.appendFormatLine("rcParams['font.size'] = {0}", fontSize); + let setDefault = $(this.wrapSelector('#setDefault')).prop('checked'); + if (setDefault == true) { + code.appendLine('from matplotlib import rcParams, rcParamsDefault'); + code.append('rcParams.update(rcParamsDefault)'); + } else { + var figWidth = $(this.wrapSelector('#figureWidth')).val(); + var figHeight = $(this.wrapSelector('#figureHeight')).val(); + var styleName = $(this.wrapSelector('#styleSheet')).val(); + var fontName = $(this.wrapSelector('#fontName')).val(); + var fontSize = $(this.wrapSelector('#fontSize')).val(); + + code.appendLine('import matplotlib.pyplot as plt'); + code.appendFormatLine("plt.rc('figure', figsize=({0}, {1}))", figWidth, figHeight); + if (styleName && styleName.length > 0) { + code.appendFormatLine("plt.style.use('{0}')", styleName); + } + code.appendLine(); + + code.appendLine('from matplotlib import rcParams'); + if (fontName && fontName.length > 0) { + code.appendFormatLine("rcParams['font.family'] = '{0}'", fontName); + } + if (fontSize && fontSize.length > 0) { + code.appendFormatLine("rcParams['font.size'] = {0}", fontSize); + } + code.append("rcParams['axes.unicode_minus'] = False"); } - code.append("rcParams['axes.unicode_minus'] = False"); return code.toString(); } generateCode(preview=false) { - let { chartType, data, x, y, userOption='', allocateTo='', useSampling } = this.state; + let { + chartType, data, userOption='', + title, x_label, y_label, useLegend, legendPos, + useGrid, useMarker, markerStyle, + x_limit_from, x_limit_to, y_limit_from, y_limit_to, + useSampling, sampleCount + } = this.state; + + let indent = ''; let code = new com_String(); let config = this.chartConfig[chartType]; + let state = JSON.parse(JSON.stringify(this.state)); + + let chartCode = new com_String(); + + let etcOptionCode = [] + if (useMarker == 'True') { + // TODO: marker to seaborn argument (ex. marker='+' / markers={'Lunch':'s', 'Dinner':'X'}) + etcOptionCode.push(com_util.formatString("marker='{0}'", markerStyle)); + } - let chartCode = com_generator.vp_codeGenerator(this, config, this.state, (userOption != ''? ', ' + userOption : '')); + // add user option + if (userOption != '') { + etcOptionCode.push(userOption); + } + + if (etcOptionCode.length > 0) { + etcOptionCode = [ + '', + ...etcOptionCode + ] + } + + let generatedCode = com_generator.vp_codeGenerator(this, config, state, etcOptionCode.join(', ')); + + // Info + if (title && title != '') { + chartCode.appendFormatLine("plt.title('{0}')", title); + } + if (x_label && x_label != '') { + chartCode.appendFormatLine("plt.xlabel('{0}')", x_label); + } + if (y_label && y_label != '') { + chartCode.appendFormatLine("plt.ylabel('{0}')", y_label); + } + if (x_limit_from != '' && x_limit_to != '') { + chartCode.appendFormatLine("plt.xlim(({0}, {1}))", x_limit_from, x_limit_to); + } + if (y_limit_from != '' && y_limit_to != '') { + chartCode.appendFormatLine("plt.ylim(({0}, {1}))", y_limit_from, y_limit_to); + } + if (useLegend == 'True' && legendPos != '') { + chartCode.appendFormatLine("plt.legend(loc='{0}')", legendPos); + } + if (useGrid == 'True') { + chartCode.appendLine("plt.grid(True)"); + // TODO: grid types + // plt.grid(True, axis='x', color='red', alpha=0.5, linestyle='--') + } + chartCode.append('plt.show()'); let convertedData = data; if (preview) { + // set indent + indent = ' '.repeat(4); + + // Ignore warning + code.appendLine('import warnings'); + code.appendLine('with warnings.catch_warnings():'); + code.appendFormatLine("{0}warnings.simplefilter('ignore')", indent); + // set figure size for preview chart - code.appendLine('plt.figure(figsize=(6, 4))'); + let defaultWidth = 5; + let defaultHeight = 4; + let previewSize = parseInt($(this.wrapSelector('#previewSize')).val()); + code.appendFormatLine('{0}plt.figure(figsize=({1}, {2}))', indent, defaultWidth + previewSize, defaultHeight + previewSize); if (useSampling) { // data sampling code for preview - convertedData = data + '.sample(n=30, random_state=0)'; + convertedData = data + '.sample(n=' + sampleCount + ', random_state=0)'; + // replace pre-defined options + generatedCode = generatedCode.replace(data, convertedData); } - } - - // replace pre-defined options - chartCode = chartCode.replace(data, convertedData); - code.appendLine(chartCode); - code.append('plt.show()'); + code.appendFormatLine("{0}{1}", indent, generatedCode); + code.appendFormatLine("{0}{1}", indent, chartCode.toString().replaceAll('\n', '\n' + indent)); + + } else { + code.appendLine(generatedCode); + code.appendLine(chartCode.toString()); + } return code.toString(); }