Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trim notebook large output for better performance #9594

Merged
merged 12 commits into from
Jan 21, 2021
9 changes: 7 additions & 2 deletions packages/cells/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,8 +573,12 @@ export namespace Cell {
/**
* Whether this cell is a placeholder for future rendering.
*/

placeholder?: boolean;

/**
* The maximum number of output items to display in cell output.
*/
maxNumberOutputs?: number;
}

/**
Expand Down Expand Up @@ -712,7 +716,8 @@ export class CodeCell extends Cell {
const output = (this._output = new OutputArea({
model: model.outputs,
rendermime,
contentFactory: contentFactory
contentFactory: contentFactory,
maxNumberOutputs: options.maxNumberOutputs
}));
output.addClass(CELL_OUTPUT_AREA_CLASS);
// Set a CSS if there are no outputs, and connect a signal for future
Expand Down
6 changes: 6 additions & 0 deletions packages/notebook-extension/schema/tracker.json
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,12 @@
"description": "Defines the observed bottom margin for the virtual notebook, set a positive number of pixels to render cells below the visible view",
"type": "string",
"default": "1000px"
},
"maxNumberOutputs": {
"title": "The maximum number of output cells to to be rendered in the output area. Set to 0 to have the complete display.",
"description": "Defines the maximum number of output cells to to to be rendered in the output area for cells with many outputs. The output area will have a head and a tail, and the outputs between will be trimmed and not displayed unless the user clicks on the information message.",
"type": "number",
"default": 10
}
},
"additionalProperties": false,
Expand Down
3 changes: 2 additions & 1 deletion packages/notebook-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,8 @@ function activateNotebookHandler(
renderCellOnIdle: settings.get('renderCellOnIdle').composite as boolean,
observedTopMargin: settings.get('observedTopMargin').composite as string,
observedBottomMargin: settings.get('observedBottomMargin')
.composite as string
.composite as string,
maxNumberOutputs: settings.get('maxNumberOutputs').composite as number
};
factory.shutdownOnClose = settings.get('kernelShutdown')
.composite as boolean;
Expand Down
11 changes: 9 additions & 2 deletions packages/notebook/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,8 @@ export class StaticNotebook extends Widget {
rendermime,
contentFactory,
updateEditorOnShow: false,
placeholder: false
placeholder: false,
maxNumberOutputs: this.notebookConfig.maxNumberOutputs
};
const cell = this.contentFactory.createCodeCell(options, this);
cell.syncCollapse = true;
Expand Down Expand Up @@ -938,6 +939,11 @@ export namespace StaticNotebook {
* to render cells below the visible view.
*/
observedBottomMargin: string;

/**
* Defines the maximum number of outputs per cell.
*/
maxNumberOutputs: number;
}

/**
Expand All @@ -950,7 +956,8 @@ export namespace StaticNotebook {
numberCellsToRenderDirectly: 20,
renderCellOnIdle: true,
observedTopMargin: '1000px',
observedBottomMargin: '1000px'
observedBottomMargin: '1000px',
maxNumberOutputs: 10
};

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/outputarea/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ export interface IOutputAreaModel extends IDisposable {
* Serialize the model to JSON.
*/
toJSON(): nbformat.IOutput[];

/**
* The maximum number of output items to display on top and bottom of cell output.
*/
maxNumberOutputs?: number;
}

/**
Expand Down
93 changes: 92 additions & 1 deletion packages/outputarea/src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export class OutputArea extends Widget {
this.contentFactory =
options.contentFactory || OutputArea.defaultContentFactory;
this.layout = new PanelLayout();
this.trimmedOutputModels = new Array<IOutputModel>();
this.maxNumberOutputs = options.maxNumberOutputs || 0;
this.headTailNumberOutputs = Math.round(this.maxNumberOutputs / 2);
this.headEndIndex = this.headTailNumberOutputs;
for (let i = 0; i < model.length; i++) {
const output = model.get(i);
this._insertOutput(i, output);
Expand All @@ -128,6 +132,28 @@ export class OutputArea extends Widget {
*/
readonly rendermime: IRenderMimeRegistry;

/**
* The hidden output models.
*/
private trimmedOutputModels: IOutputModel[];

/*
* The maximum outputs to show in the trimmed
* output area.
*/
private maxNumberOutputs: number;

/*
* The maximum outputs to show in the trimmed
* output head and tail areas.
*/
private headTailNumberOutputs: number;

/*
* The index for the end of the head in case of trim mode.
*/
private headEndIndex: number;

/**
* A read-only sequence of the chidren widgets in the output area.
*/
Expand Down Expand Up @@ -285,6 +311,7 @@ export class OutputArea extends Widget {
* Follow changes on the output model state.
*/
protected onStateChanged(sender: IOutputAreaModel): void {
this.trimmedOutputModels = new Array<IOutputModel>();
for (let i = 0; i < this.model.length; i++) {
this._setOutput(i, this.model.get(i));
}
Expand Down Expand Up @@ -411,16 +438,75 @@ export class OutputArea extends Widget {

/**
* Render and insert a single output into the layout.
*
* @param index - The index of the output to be inserted.
* @param model - The model of the output to be inserted.
*/
echarles marked this conversation as resolved.
Show resolved Hide resolved
private _insertOutput(index: number, model: IOutputModel): void {
if (index === 0) {
this.trimmedOutputModels = new Array<IOutputModel>();
}
if (index === this.maxNumberOutputs && this.maxNumberOutputs !== 0) {
// TODO Improve style of the display message.
const separatorModel = this.model.contentFactory.createOutputModel({
value: {
output_type: 'display_data',
data: {
echarles marked this conversation as resolved.
Show resolved Hide resolved
'text/html': `
<a style="margin: 10px; text-decoration: none;">
<pre>Output of this cell has been trimmed on the initial display.</pre>
<pre>Displaying the first ${this.maxNumberOutputs} top and last bottom outputs.</pre>
<pre>Click on this message to get the complete output.</pre>
</a>
`
}
}
});
const onClick = () =>
this._showTrimmedOutputs(this.headTailNumberOutputs);
const separator = this.createOutputItem(separatorModel);
separator!.node.addEventListener('click', onClick);
const layout = this.layout as PanelLayout;
layout.insertWidget(this.headEndIndex, separator!);
}
const output = this._createOutput(model);
const layout = this.layout as PanelLayout;
if (index < this.maxNumberOutputs || this.maxNumberOutputs === 0) {
layout.insertWidget(index, output);
} else if (index >= this.maxNumberOutputs) {
layout.removeWidgetAt(this.headTailNumberOutputs + 1);
layout.insertWidget(index, output);
}
if (index >= this.headTailNumberOutputs && this.maxNumberOutputs !== 0) {
this.trimmedOutputModels.push(model);
}
}

private _createOutput(model: IOutputModel): Widget {
let output = this.createOutputItem(model);
if (output) {
output.toggleClass(EXECUTE_CLASS, model.executionCount !== null);
} else {
output = new Widget();
}
return output;
}

/**
* Remove the information message related to the trimmed output
* and show all previously trimmed outputs.
*/
private _showTrimmedOutputs(headTailNumberOutputs: number) {
const layout = this.layout as PanelLayout;
layout.insertWidget(index, output);
layout.removeWidgetAt(headTailNumberOutputs);
for (
let i = 0;
i < this.trimmedOutputModels.length - this.headTailNumberOutputs;
i++
) {
const output = this._createOutput(this.trimmedOutputModels[i]);
layout.insertWidget(headTailNumberOutputs + i, output);
}
}

/**
Expand Down Expand Up @@ -608,6 +694,11 @@ export namespace OutputArea {
* The rendermime instance used by the widget.
*/
rendermime: IRenderMimeRegistry;

/**
* The maximum number of output items to display on top and bottom of cell output.
*/
maxNumberOutputs?: number;
}

/**
Expand Down