Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bqplot/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"""

from traitlets import (
Unicode, Instance, List, Dict, Enum, Float, Int, TraitError, default,
Bool, Unicode, Instance, List, Dict, Enum, Float, Int, TraitError, default,
validate
)
from ipywidgets import DOMWidget, register, widget_serialization
Expand Down Expand Up @@ -87,6 +87,8 @@ class Figure(DOMWidget):
pixel_ratio:
Pixel ratio of the WebGL canvas (2 on retina screens). Set to 1 for better performance,
but less crisp edges. If set to None it will use the browser's window.devicePixelRatio.
display_toolbar: boolean (default: True)
Show or hide the integrated toolbar.

Layout Attributes

Expand Down Expand Up @@ -152,6 +154,7 @@ class Figure(DOMWidget):
.tag(sync=True, display_name='Legend position')
animation_duration = Int().tag(sync=True,
display_name='Animation duration')
display_toolbar = Bool(default_value=True).tag(sync=True)

@default('scale_x')
def _default_scale_x(self):
Expand Down
11 changes: 2 additions & 9 deletions bqplot/pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,13 @@
import sys
from collections import OrderedDict
from IPython.display import display
from ipywidgets import VBox
from ipywidgets import Image as ipyImage
from numpy import arange, issubdtype, array, column_stack, shape
from .figure import Figure
from .scales import Scale, LinearScale, Mercator
from .axes import Axis
from .marks import (Lines, Scatter, ScatterGL, Hist, Bars, OHLC, Pie, Map, Image,
Label, HeatMap, GridHeatMap, topo_load, Boxplot, Bins)
from .toolbar import Toolbar
from .interacts import (BrushIntervalSelector, FastIntervalSelector,
BrushSelector, IndexSelector, MultiSelector,
LassoSelector)
Expand Down Expand Up @@ -152,13 +150,8 @@ def show(key=None, display_toolbar=True):
figure = current_figure()
else:
figure = _context['figure_registry'][key]
if display_toolbar:
if not hasattr(figure, 'pyplot'):
figure.pyplot = Toolbar(figure=figure)
figure.pyplot_vbox = VBox([figure, figure.pyplot])
display(figure.pyplot_vbox)
else:
display(figure)
figure.display_toolbar = display_toolbar
display(figure)


def figure(key=None, fig=None, **kwargs):
Expand Down
6 changes: 6 additions & 0 deletions js/less/bqplot.less
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@
image-rendering: -moz-crisp-edges; /* this is guaranteed to work for firefox */
}
}

.bqplot .toolbar_div {
position: absolute;
transition: visibility 0.5s linear, opacity 0.5s linear;
}

.tooltip_div {
z-index: 1001;
}
Expand Down
92 changes: 91 additions & 1 deletion js/src/Figure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { AxisModel } from './AxisModel';
import { Mark } from './Mark';
import { MarkModel } from './MarkModel';
import { Interaction } from './Interaction';
import { FigureModel } from './FigureModel';

THREE.ShaderChunk['scales'] =
require('raw-loader!../shaders/scales.glsl').default;
Expand Down Expand Up @@ -102,6 +103,7 @@ export class Figure extends widgets.DOMWidgetView {

protected async renderImpl() {
const figureSize = this.getFigureSize();

this.width = figureSize.width;
this.height = figureSize.height;

Expand Down Expand Up @@ -327,6 +329,20 @@ export class Figure extends widgets.DOMWidgetView {
window.removeEventListener('resize', this.debouncedRelayout);
});

this.toolbar_div = this.create_toolbar();
if (this.model.get('display_toolbar')) {
this.toolbar_div.node().style.display = 'unset';
}

this.model.on('change:display_toolbar', (_, display_toolbar) => {
const toolbar = this.toolbar_div.node();
if (display_toolbar) {
toolbar.style.display = 'unset';
} else {
toolbar.style.display = 'none';
}
});

return Promise.all([mark_views_updated, axis_views_updated]);
}

Expand Down Expand Up @@ -1249,6 +1265,79 @@ export class Figure extends widgets.DOMWidgetView {
this.el.classList.add(this.model.get('theme'));
}

/**
* Generate an integrated toolbar which is shown on mouse over
* for this figure.
*
*/
create_toolbar(): d3.Selection<HTMLDivElement, any, any, any> {
const toolbar = d3
.select(document.createElement('div'))
.attr('class', 'toolbar_div');

const panzoom = document.createElement('button');
panzoom.classList.add('jupyter-widgets'); // @jupyter-widgets/controls css
panzoom.classList.add('jupyter-button'); // @jupyter-widgets/controls css
panzoom.setAttribute('data-toggle', 'tooltip');
panzoom.setAttribute('title', 'PanZoom');
const panzoomicon = document.createElement('i');
panzoomicon.style.marginRight = '0px';
panzoomicon.className = 'fa fa-arrows';
panzoom.appendChild(panzoomicon);
panzoom.onclick = (e) => {
e.preventDefault();
(this.model as FigureModel).panzoom();
};

const reset = document.createElement('button');
reset.classList.add('jupyter-widgets'); // @jupyter-widgets/controls css
reset.classList.add('jupyter-button'); // @jupyter-widgets/controls css
reset.setAttribute('data-toggle', 'tooltip');
reset.setAttribute('title', 'Reset');
const refreshicon = document.createElement('i');
refreshicon.style.marginRight = '0px';
refreshicon.className = 'fa fa-refresh';
reset.appendChild(refreshicon);
reset.onclick = (e) => {
e.preventDefault();
(this.model as FigureModel).reset();
};

const save = document.createElement('button');
save.classList.add('jupyter-widgets'); // @jupyter-widgets/controls css
save.classList.add('jupyter-button'); // @jupyter-widgets/controls css
save.setAttribute('data-toggle', 'tooltip');
save.setAttribute('title', 'Save');
const saveicon = document.createElement('i');
saveicon.style.marginRight = '0px';
saveicon.className = 'fa fa-save';
save.appendChild(saveicon);
save.onclick = (e) => {
e.preventDefault();
this.save_png(undefined, undefined);
};

toolbar.node().appendChild(panzoom);
toolbar.node().appendChild(reset);
toolbar.node().appendChild(save);

this.el.appendChild(toolbar.node());
toolbar.node().style.top = `${this.margin.top / 2.0}px`;
toolbar.node().style.right = `${this.margin.right}px`;
toolbar.node().style.visibility = 'hidden';
toolbar.node().style.opacity = '0';
this.el.addEventListener('mouseenter', () => {
toolbar.node().style.visibility = 'visible';
toolbar.node().style.opacity = '1';
});
this.el.addEventListener('mouseleave', () => {
toolbar.node().style.visibility = 'hidden';
toolbar.node().style.opacity = '0';
});
toolbar.node().style.display = 'none';
return toolbar;
}

axis_views: widgets.ViewList<widgets.DOMWidgetView>;
bg: d3.Selection<SVGRectElement, any, any, any>;
bg_events: d3.Selection<SVGRectElement, any, any, any>;
Expand Down Expand Up @@ -1278,6 +1367,7 @@ export class Figure extends widgets.DOMWidgetView {
svg_background: d3.Selection<SVGElement, any, any, any>;
title: d3.Selection<SVGTextElement, any, any, any>;
tooltip_div: d3.Selection<HTMLDivElement, any, any, any>;
toolbar_div: d3.Selection<HTMLDivElement, any, any, any>;
width: number;
x_pad_dict: { [id: string]: number };
xPaddingArr: { [id: string]: number };
Expand All @@ -1287,7 +1377,7 @@ export class Figure extends widgets.DOMWidgetView {
private dummyNodes: Dict<any> = {};

private _update_requested: boolean;
private relayoutRequested: boolean = false;
private relayoutRequested = false;

// this is public for the test framework, but considered a private API
public _initial_marks_created: Promise<any>;
Expand Down
93 changes: 92 additions & 1 deletion js/src/FigureModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

import * as widgets from '@jupyter-widgets/base';
import { semver_range } from './version';

import { Interaction } from './Interaction';
import { PanZoomModel } from './PanZoomModel';
import * as _ from 'underscore';
export class FigureModel extends widgets.DOMWidgetModel {
defaults() {
return {
Expand Down Expand Up @@ -54,6 +56,7 @@ export class FigureModel extends widgets.DOMWidgetModel {
padding_y: 0.025,
legend_location: 'top-right',
animation_duration: 0,
display_toolbar: true,
};
}

Expand All @@ -76,6 +79,88 @@ export class FigureModel extends widgets.DOMWidgetModel {
this.trigger('save_png');
}

/**
* Start or stop pan zoom mode
*
*/
panzoom(): void {
if (this.panzoomData.panning) {
this.set('interaction', this.panzoomData.cached_interaction);
this.panzoomData.panning = false;
this.save_changes();
} else {
this.panzoomData.cached_interaction = this.get('interaction');
const panzoom = this.panzoomData.panzoom;
if (panzoom) {
this.set('interaction', panzoom);
this.save_changes();
} else {
this.create_panzoom_model().then((model) => {
this.set('interaction', model);
this.panzoomData.panzoom = model;
this.save_changes();
});
}
this.panzoomData.panning = true;
}
}

/**
* Creates a panzoom interaction widget for the this model.
*
* It will discover the relevant scales of this model.
*/
private create_panzoom_model(): Promise<PanZoomModel> {
return this.widget_manager
.new_widget({
model_name: 'PanZoomModel',
model_module: 'bqplot',
model_module_version: this.get('_model_module_version'),
view_name: 'PanZoom',
view_module: 'bqplot',
view_module_version: this.get('_view_module_version'),
})
.then((model: PanZoomModel) => {
return Promise.all(this.get('marks')).then((marks: any[]) => {
const x_scales = [],
y_scales = [];
for (let i = 0; i < marks.length; ++i) {
const preserve_domain = marks[i].get('preserve_domain');
const scales = marks[i].get('scales');
_.each(scales, (v, k) => {
const dimension = marks[i].get('scales_metadata')[k]['dimension'];
if (dimension === 'x' && !preserve_domain[k]) {
x_scales.push(scales[k]);
}
if (dimension === 'y' && !preserve_domain[k]) {
y_scales.push(scales[k]);
}
});
}
model.set('scales', {
x: x_scales,
y: y_scales,
});
model.save_changes();
return model;
});
});
}

/**
* Reset the scales, delete the PanZoom widget, set the figure
* interaction back to its previous value.
*/
reset(): void {
this.set('interaction', this.panzoomData.cached_interaction);
const panzoom = this.panzoomData.panzoom;
panzoom.reset_scales();
panzoom.close();
this.panzoomData.panzoom = null;
this.panzoomData.panning = false;
this.save_changes();
}

static serializers = {
...widgets.DOMWidgetModel.serializers,
marks: { deserialize: widgets.unpack_models },
Expand All @@ -85,4 +170,10 @@ export class FigureModel extends widgets.DOMWidgetModel {
scale_y: { deserialize: widgets.unpack_models },
layout: { deserialize: widgets.unpack_models },
};

private panzoomData: {
panning: boolean;
cached_interaction: Interaction;
panzoom: PanZoomModel | undefined;
} = { panning: false, cached_interaction: null, panzoom: undefined };
}
14 changes: 10 additions & 4 deletions js/src/Graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,9 @@ export class Graph extends Mark {
}

private dragstarted(d: NodeData) {
if (this.model.static) return;
if (this.model.static) {
return;
}
if (!d3GetEvent().active) {
this.force_layout.alphaTarget(0.4).restart();
}
Expand All @@ -379,13 +381,17 @@ export class Graph extends Mark {
}

private dragged(d: NodeData) {
if (this.model.static) return;
if (this.model.static) {
return;
}
d.fx = d3GetEvent().x;
d.fy = d3GetEvent().y;
}

private dragended(d: NodeData) {
if (this.model.static) return;
if (this.model.static) {
return;
}
if (!d3GetEvent().active) {
this.force_layout.alphaTarget(0.4);
}
Expand Down Expand Up @@ -573,7 +579,7 @@ export class Graph extends Mark {

compute_view_padding() {
const xPadding = d3.max<number>(
this.model.mark_data.map(function (d) {
this.model.mark_data.map((d) => {
return (
(d.shape_attrs.r || d.shape_attrs.width / 2 || d.shape_attrs.rx) + 1.0
);
Expand Down
2 changes: 1 addition & 1 deletion js/src/GridHeatMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ export class GridHeatMap extends Mark {
}

const clearing_style = {};
for (let key in style_dict) {
for (const key in style_dict) {
clearing_style[key] = null;
}
applyStyles(elements, clearing_style);
Expand Down
8 changes: 6 additions & 2 deletions js/src/HeatMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,14 @@ export class HeatMap extends Mark {

set_positional_scales() {
this.listenTo(this.scales.x, 'domain_changed', () => {
if (!this.model.dirty) this.draw();
if (!this.model.dirty) {
this.draw();
}
});
this.listenTo(this.scales.y, 'domain_changed', () => {
if (!this.model.dirty) this.draw();
if (!this.model.dirty) {
this.draw();
}
});
}

Expand Down
Loading