├── .gitignore ├── MANIFEST.in ├── lc_multi_outputs ├── _version.py ├── nbextension │ ├── icon.png │ ├── demo-diff-dialog.gif │ ├── demo-pin-button.gif │ ├── lc-multi-outputs.yml │ ├── README.md │ ├── main.css │ └── main.js └── __init__.py ├── Dockerfile ├── setup.py ├── LICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | /build 4 | /dist 5 | /lc_multi_outputs/nbextension/lib/* 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include lc_multi_outputs/nbextension/* 2 | include lc_multi_outputs/nbextension/lib/* 3 | -------------------------------------------------------------------------------- /lc_multi_outputs/_version.py: -------------------------------------------------------------------------------- 1 | version_info = (2, 1, 0) 2 | __version__ = '.'.join(map(str, version_info)) 3 | -------------------------------------------------------------------------------- /lc_multi_outputs/nbextension/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NII-cloud-operation/Jupyter-multi_outputs/master/lc_multi_outputs/nbextension/icon.png -------------------------------------------------------------------------------- /lc_multi_outputs/nbextension/demo-diff-dialog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NII-cloud-operation/Jupyter-multi_outputs/master/lc_multi_outputs/nbextension/demo-diff-dialog.gif -------------------------------------------------------------------------------- /lc_multi_outputs/nbextension/demo-pin-button.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NII-cloud-operation/Jupyter-multi_outputs/master/lc_multi_outputs/nbextension/demo-pin-button.gif -------------------------------------------------------------------------------- /lc_multi_outputs/__init__.py: -------------------------------------------------------------------------------- 1 | # nbextension 2 | def _jupyter_nbextension_paths(): 3 | return [dict( 4 | section="notebook", 5 | src="nbextension", 6 | dest="multi_outputs", 7 | require="multi_outputs/main")] 8 | 9 | -------------------------------------------------------------------------------- /lc_multi_outputs/nbextension/lc-multi-outputs.yml: -------------------------------------------------------------------------------- 1 | Type: Jupyter Notebook Extension 2 | Name: LC multi outputs 3 | Section: notebook 4 | Description: "Multi outputs extension for Literate Computing" 5 | Link: README.md 6 | Icon: icon.png 7 | Main: main.js 8 | # 1.x means nbclassic - leave 6.x in place just in case. 9 | Compatibility: 1.x 4.x 5.x 6.x 10 | Parameters: 11 | - name: MultiOutputs.max_num_of_pinned_outputs 12 | description: | 13 | Maximum number of pinned output tabs 14 | input_type: number 15 | default: 5 16 | step: 1 17 | min: 1 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jupyter/scipy-notebook:latest 2 | 3 | USER root 4 | 5 | ### extensions for jupyter 6 | COPY . /tmp/multi_outputs 7 | RUN pip --no-cache-dir install jupyter_nbextensions_configurator \ 8 | /tmp/multi_outputs 9 | 10 | RUN jupyter nbclassic-extension install --py jupyter_nbextensions_configurator --sys-prefix && \ 11 | jupyter nbclassic-extension enable --py jupyter_nbextensions_configurator --sys-prefix && \ 12 | jupyter nbclassic-serverextension enable --py jupyter_nbextensions_configurator --sys-prefix && \ 13 | jupyter nbclassic-extension install --py lc_multi_outputs --sys-prefix && \ 14 | jupyter nbclassic-extension enable --py lc_multi_outputs --sys-prefix && \ 15 | fix-permissions /home/$NB_USER 16 | 17 | # Make classic notebook the default 18 | ENV DOCKER_STACKS_JUPYTER_CMD=nbclassic 19 | 20 | USER $NB_USER 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | import os 5 | import sys 6 | try: 7 | from urllib.request import urlopen 8 | except ImportError: 9 | from urllib import urlopen 10 | 11 | HERE = os.path.abspath(os.path.dirname(__file__)) 12 | VERSION_NS = {} 13 | with open(os.path.join(HERE, 'lc_multi_outputs', '_version.py')) as f: 14 | exec(f.read(), {}, VERSION_NS) 15 | 16 | LIB = os.path.join(HERE, 'lc_multi_outputs', 'nbextension', 'lib') 17 | if not os.path.exists(LIB): 18 | os.mkdir(LIB) 19 | 20 | with open(os.path.join(LIB, 'diff_match_patch.js'), 'wb') as f: 21 | f.write(urlopen('https://github.com/google/diff-match-patch/raw/master/javascript/diff_match_patch.js').read()) 22 | 23 | setup_args = dict (name='lc-multi-outputs', 24 | version=VERSION_NS['__version__'], 25 | description='LC multi outputs extension for Jupyter Notebook', 26 | packages=['lc_multi_outputs'], 27 | include_package_data=True, 28 | platforms=['Jupyter Notebook 4.2.x', 'Jupyter Notebook 5.x'], 29 | zip_safe=False, 30 | install_requires=[ 31 | 'notebook>=4.2.0', 32 | ] 33 | ) 34 | 35 | if __name__ == '__main__': 36 | setup(**setup_args) 37 | 38 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2017, National Institute of Informatics. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of National Institute of Informatics nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /lc_multi_outputs/nbextension/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This extension enables to keep multiple results as tabs in output_area. 4 | You can also save/load tabbed outputs as far as this extension is enabled 5 | As default behavior the "output_area" under a code cell is cleared and over written in each execution. 6 | 7 | # Feature 8 | 9 | * Pin button for saving outputs 10 | * Visualization of each code cell's executing status with colors 11 | * Diff dialog for comparing pinned outputs with current outputs 12 | * Search feature in the diff dialog 13 | 14 | # Usage 15 | 16 | ## Save current outputs 17 | 18 | 1. Execute a code cell 19 | 2. Select the current output (leftmost) tab (if output tabs exist) 20 | 3. Click the pin button in the output prompt area 21 | 22 | ![pin button demo](./demo-pin-button.gif) 23 | 24 | ## Execution status color of code cell 25 | 26 | The prompt area of code cell displays execution status with colors. 27 | The colors mean as follows: 28 | 29 | - White: Not Executed 30 | - Light cyan: Awaiting Execution 31 | - Green: Successfully Executed 32 | - Pink: Failed 33 | 34 | ## Diff dialog 35 | 36 | You can compare pinned output with current output. 37 | 38 | 1. Select the pinned output tab you want to compare with current output 39 | 2. Click the diff button in the output prompt area 40 | 41 | You can search keyword from the outputs compared in the diff dialog. 42 | To search, input keyword to the search field and press the Enter key. 43 | 44 | ![diff dialog demo](./demo-diff-dialog.gif) 45 | 46 | # Internals 47 | 48 | The pinned outputs are saved as cell metadata. 49 | 50 | This is an example of a code cell 51 | ``` 52 | { 53 | "cell_type": "code", 54 | "execution_count": 2, 55 | "metadata": { 56 | "pinned_outputs": [ 57 | { 58 | "execution_count": 1, 59 | "outputs": [ 60 | { 61 | "data": { 62 | "text/plain": "'2017/10/12 13:36:42'" 63 | }, 64 | "execution_count": 1, 65 | "metadata": {}, 66 | "output_type": "execute_result" 67 | } 68 | ] 69 | } 70 | ] 71 | }, 72 | "outputs": [ 73 | { 74 | "data": { 75 | "text/plain": [ 76 | "'2017/10/12 13:36:49'" 77 | ] 78 | }, 79 | "execution_count": 2, 80 | "metadata": {}, 81 | "output_type": "execute_result" 82 | } 83 | ], 84 | "source": [ 85 | "from datetime import datetime\n", 86 | "datetime.now().strftime('%Y/%m/%d %H:%M:%S')" 87 | ] 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This extension enables to keep multiple results as tabs in output_area. 4 | You can also save/load tabbed outputs as far as this extension is enabled 5 | As default behavior the "output_area" under a code cell is cleared and over written in each execution. 6 | 7 | # Feature 8 | 9 | * Pin button for saving outputs 10 | * Visualization of each code cell's executing status with colors 11 | * Diff dialog for comparing pinned outputs with current outputs 12 | * Search feature in the diff dialog 13 | 14 | # How to install 15 | 16 | To install the multi_outputs extension, the following steps are required. 17 | 18 | ## Install the python package 19 | 20 | Install the `lc_multi_outputs` python package. 21 | 22 | ``` 23 | pip install git+https://github.com/NII-cloud-operation/Jupyter-multi_outputs 24 | ``` 25 | 26 | ## Install extension files 27 | 28 | Copy the nbextension files into the jupyter server's search directory. 29 | 30 | ``` 31 | jupyter nbextension install --py lc_multi_outputs --user 32 | ``` 33 | 34 | About details of `jupyter nbextension` subcommand, execute this command with `--help` option, and see the results. 35 | 36 | ``` 37 | jupyter nbextension --help 38 | ``` 39 | 40 | ## Enabling extension 41 | 42 | To use the multi_outputs extension, you will also need to enable it. 43 | 44 | ``` 45 | jupyter nbextension enable --py lc_multi_outputs --user 46 | ``` 47 | 48 | # Usage 49 | 50 | ## Save current outputs 51 | 52 | 1. Execute a code cell 53 | 2. Select the current output (leftmost) tab (if output tabs exist) 54 | 3. Click the pin button in the output prompt area 55 | 56 | ![pin button demo](./lc_multi_outputs/nbextension/demo-pin-button.gif) 57 | 58 | ## Execution status color of code cell 59 | 60 | The prompt area of code cell displays execution status with colors. 61 | The colors mean as follows: 62 | 63 | - White: Not Executed 64 | - Light cyan: Awaiting Execution 65 | - Green: Successfully Executed 66 | - Pink: Failed 67 | 68 | ## Diff dialog 69 | 70 | You can compare pinned output with current output. 71 | 72 | 1. Select the pinned output tab you want to compare with current output 73 | 2. Click the diff button in the output prompt area 74 | 75 | You can search keyword from the outputs compared in the diff dialog. 76 | To search, input keyword to the search field and press the Enter key. 77 | 78 | ![diff dialog demo](./lc_multi_outputs/nbextension/demo-diff-dialog.gif) 79 | 80 | # Internals 81 | 82 | The pinned outputs are saved as cell metadata. 83 | 84 | This is an example of a code cell 85 | ``` 86 | { 87 | "cell_type": "code", 88 | "execution_count": 2, 89 | "metadata": { 90 | "pinned_outputs": [ 91 | { 92 | "execution_count": 1, 93 | "outputs": [ 94 | { 95 | "data": { 96 | "text/plain": "'2017/10/12 13:36:42'" 97 | }, 98 | "execution_count": 1, 99 | "metadata": {}, 100 | "output_type": "execute_result" 101 | } 102 | ] 103 | } 104 | ] 105 | }, 106 | "outputs": [ 107 | { 108 | "data": { 109 | "text/plain": [ 110 | "'2017/10/12 13:36:49'" 111 | ] 112 | }, 113 | "execution_count": 2, 114 | "metadata": {}, 115 | "output_type": "execute_result" 116 | } 117 | ], 118 | "source": [ 119 | "from datetime import datetime\n", 120 | "datetime.now().strftime('%Y/%m/%d %H:%M:%S')" 121 | ] 122 | } 123 | ``` 124 | 125 | # License 126 | 127 | This project is licensed under the terms of the Modified BSD License (also known as New or Revised or 3-Clause BSD), see LICENSE.txt. 128 | -------------------------------------------------------------------------------- /lc_multi_outputs/nbextension/main.css: -------------------------------------------------------------------------------- 1 | .output_wrapper .out_prompt_bg { 2 | padding: 0px 0.4em; 3 | position: absolute; 4 | border-radius: 2px; 5 | z-index: -1; 6 | } 7 | 8 | .multi-output-tabs ul.nav-tabs { 9 | overflow-x: auto; 10 | overflow-y: hidden; 11 | padding: 0.4em 0.4em 0px 0.4em; 12 | -webkit-box-flex: 1; 13 | -moz-box-flex: 1; 14 | box-flex: 1; 15 | flex: 1; 16 | } 17 | 18 | .multi-outputs-ui,.multi-outputs-diff-ui { 19 | text-align: left; 20 | padding-top: 2px; 21 | display: block; 22 | } 23 | 24 | .multi-outputs-ui .buttons,.multi-outputs-diff-ui .buttons { 25 | display: inline-block; 26 | } 27 | 28 | .multi-outputs-ui .btn,.multi-outputs-diff-ui .btn { 29 | padding: 0px; 30 | font-size: 90%; 31 | } 32 | 33 | .multi-outputs-ui .btn-default,.multi-outputs-diff-ui .btn-default { 34 | background-color: transparent; 35 | border-color: transparent; 36 | } 37 | 38 | .multi-outputs-ui .btn-default:hover,.multi-outputs-diff-ui .btn-default:hover { 39 | background-color: #e6e6e6; 40 | border-color: #adadad; 41 | } 42 | 43 | .multi-output-container .output_subarea { 44 | font-size: 14px; 45 | } 46 | 47 | .multi-output-container .prompt { 48 | font-size: 14px; 49 | } 50 | 51 | .cell-status-success .input_prompt { 52 | background-color: #bcffbc; 53 | height: 100%; 54 | } 55 | 56 | .cell-status-success .multi-output-tabs .out_prompt_bg { 57 | background-color: #bcffbc; 58 | } 59 | 60 | .cell-status-success .output_wrapper .out_prompt_bg { 61 | background-color: #bcffbc; 62 | height: 100%; 63 | } 64 | 65 | .cell-status-error .input_prompt { 66 | background-color: #ffc0cb; 67 | height: 100%; 68 | } 69 | 70 | .cell-status-error .multi-output-tabs .out_prompt_bg { 71 | background-color: #ffc0cb; 72 | } 73 | 74 | .cell-status-error .output_wrapper .out_prompt_bg { 75 | background-color: #ffc0cb; 76 | height: 100%; 77 | } 78 | 79 | .cell-status-inqueue .input_prompt { 80 | background-color: #e0ffff; 81 | height: 100%; 82 | } 83 | 84 | .cell-status-inqueue .multi-output-tabs .out_prompt_bg { 85 | background-color: #e0ffff; 86 | } 87 | 88 | .cell-status-inqueue .output_wrapper .out_prompt_bg { 89 | background-color: #e0ffff; 90 | height: 100%; 91 | } 92 | 93 | .multi-output-container.ui-tabs { 94 | border: none; 95 | padding: 0px; 96 | } 97 | 98 | .multi-output-container .ui-tabs-panel { 99 | padding: 0px; 100 | } 101 | 102 | .multi-output-tabs .ui-tabs-anchor { 103 | font-size: 0.6em; 104 | } 105 | 106 | .multi-output-tabs .ui-tabs-nav { 107 | border-top: none; 108 | border-left: none; 109 | border-right: none; 110 | border-bottom-left-radius: 0px; 111 | border-bottom-right-radius: 0px; 112 | background: transparent; 113 | } 114 | 115 | .multi-output-tabs .ui-tabs-nav button.ui-button-icon-only { 116 | border: none; 117 | background: transparent; 118 | } 119 | 120 | .multi-output-tabs li.ui-state-default { 121 | background: #EEE !important; 122 | } 123 | 124 | .multi-output-tabs li.ui-state-default a { 125 | color: #AAA; 126 | } 127 | 128 | .multi-output-tabs li.ui-state-active { 129 | background: white !important; 130 | } 131 | 132 | .multi-output-tabs li.ui-state-active a { 133 | color: black; 134 | } 135 | 136 | .multi-outputs-diff.ui-dialog-content { 137 | overflow: hidden; 138 | } 139 | 140 | .multi-outputs-diff .multi-outputs-search-bar { 141 | margin: 1px; 142 | padding: 1px; 143 | width: 100%; 144 | } 145 | 146 | .multi-outputs-diff .multi-outputs-search-bar .label { 147 | color: black; 148 | font-weight: bold; 149 | font-size: 10px; 150 | } 151 | 152 | .multi-outputs-diff .multi-outputs-search-bar input { 153 | font-size: 10px; 154 | width: 250px; 155 | } 156 | 157 | .multi-outputs-diff .search-highlight { 158 | background-color: #fe3; 159 | } 160 | 161 | .ui-widget-header { 162 | background: #e7e7e7; 163 | } 164 | 165 | div.cell div.multi-output-container { 166 | background: unset; 167 | } 168 | 169 | .fa-pin-output:before { 170 | content: "\f08d"; 171 | } 172 | 173 | .fa-pin-output:after { 174 | content: "+"; 175 | } 176 | 177 | .fa-pin-remove-leaving-one:before { 178 | content: "\f08d"; 179 | } 180 | 181 | .fa-pin-remove-leaving-one:after { 182 | content: "1"; 183 | } 184 | -------------------------------------------------------------------------------- /lc_multi_outputs/nbextension/main.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | map: { 3 | '*': { 4 | "diff_match_patch": "nbextensions/multi_outputs/lib/diff_match_patch" 5 | }, 6 | }, 7 | }); 8 | 9 | define([ 10 | 'base/js/namespace', 11 | 'jquery', 12 | 'require', 13 | 'base/js/events', 14 | 'base/js/dialog', 15 | 'services/config', 16 | 'base/js/utils', 17 | 'notebook/js/codecell', 18 | 'notebook/js/outputarea', 19 | 'codemirror/lib/codemirror', 20 | 'codemirror/addon/merge/merge', 21 | 'codemirror/addon/search/searchcursor', 22 | 'codemirror/addon/scroll/annotatescrollbar', 23 | 'codemirror/addon/search/matchesonscrollbar' 24 | ], function(Jupyter, $, require, events, jsdialog, configmod, utils, codecell, outputarea, codemirror, 25 | merge, searchcursor, annotatescrollbar, matchesonscrollbar) { 26 | "use strict"; 27 | 28 | var mod_name = 'MultiOutputs'; 29 | var log_prefix = '[' + mod_name + ']'; 30 | 31 | // defaults, overridden by server's config 32 | var options = { 33 | max_num_of_pinned_outputs: 5 34 | }; 35 | 36 | function changeColor(first, cell, msg){ 37 | var outback = cell.output_area.wrapper.find('.out_prompt_bg'); 38 | var inback = $(cell.input[0].firstChild); 39 | 40 | cell.element.removeClass('cell-status-success'); 41 | cell.element.removeClass('cell-status-error'); 42 | cell.element.removeClass('cell-status-inqueue'); 43 | 44 | if(first == true) { 45 | cell.element.addClass('cell-status-inqueue'); 46 | } else { 47 | if (msg.content.status != "ok" && msg.content.status != "aborted") { 48 | cell.element.addClass('cell-status-error'); 49 | } else if (msg.content.status != "aborted") { 50 | cell.element.addClass('cell-status-success'); 51 | } 52 | } 53 | } 54 | 55 | function init_events() { 56 | events.on('create.Cell', function (e, data) { 57 | if (data.cell instanceof codecell.CodeCell) { 58 | setTimeout(function() { 59 | extend_cell(data.cell); 60 | }, 0); 61 | } 62 | }); 63 | } 64 | 65 | function extend_cell(cell) { 66 | var pinned_outputs = cell.metadata.pinned_outputs; 67 | if(pinned_outputs === undefined) { 68 | pinned_outputs = []; 69 | } 70 | if(cell.pinned_outputs === undefined) { 71 | cell.pinned_outputs = []; 72 | } 73 | 74 | if(pinned_outputs.length > 0) { 75 | create_multi_output_tabs(cell) 76 | for(var i=0; i') 89 | .addClass('multi-output-container'); 90 | var tabbar = $('
') 91 | .addClass('multi-output-tabs') 92 | .addClass('output_area') 93 | .append($('
') 94 | .addClass('out_prompt_bg').addClass('prompt')) 95 | .appendTo(container); 96 | var tabs = $('