Skip to content

added fill_percent to params for insert, swap and remove #946

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

Merged
merged 17 commits into from
Mar 7, 2018
Merged
Show file tree
Hide file tree
Changes from 7 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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [2.4.2] - UNRELEASED
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this is more of added functionality than a bug fix (do you agree?) so I think it should be version 2.5.0 based on: https://semver.org/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree they are not bug fixes. The question I have now about my message below is if we consider these points as backwards-compatible. Functionally the API is the same and produces dashboards that look the same. I just had to modify a test or two.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no way to add this functionality as an option rather than the default so that it remains backwards compatible?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's worth it. There was a technical issue with, before, using pixels as the metric for slicing up the boxes (since it was always in half) but the new param uses percentage to insert the boxes next to a pre-existing box

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok got it 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kully did you get a chance to update this to 2.5.0 and update plotly/version.py as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no thank you for reminding me

### Added
- New parameter `fill_percent` to the `.insert` method for the dashboards API. You can now insert a box into the dashboard layout and specify what proportion of the original container box it will occupy. Run `help(plotly.dashboard_objs.Dashboard.insert)` for more information on `fill_percent`.

## [2.4.1] - 2018-02-21
### Fixed
- The required shapefiles to generate the choropleths via `plotly.figure_factory.create_choropleth` are now shipped in the package data.
Expand Down Expand Up @@ -673,4 +677,4 @@ it does.
```

### Fixed
- The height of the graph in `iplot` respects the figure's height in layout
- The height of the graph in `iplot` respects the figure's height in layout
174 changes: 87 additions & 87 deletions plotly/dashboard_objs/dashboard_objs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@

IPython = optional_imports.get_module('IPython')

# default HTML parameters
MASTER_WIDTH = 400
MASTER_HEIGHT = 400
FONT_SIZE = 10
# default parameters for HTML preview
MASTER_WIDTH = 500
MASTER_HEIGHT = 500
FONT_SIZE = 9


ID_NOT_VALID_MESSAGE = (
"Your box_id must be a number in your dashboard. To view a "
Expand All @@ -44,9 +45,9 @@ def _box(fileId='', shareKey=None, title=''):
}
return box


def _container(box_1=None, box_2=None, size=MASTER_HEIGHT,
sizeUnit='px', direction='vertical'):
def _container(box_1=None, box_2=None,
size=50, sizeUnit='%',
direction='vertical'):
if box_1 is None:
box_1 = _empty_box()
if box_2 is None:
Expand All @@ -60,6 +61,7 @@ def _container(box_1=None, box_2=None, size=MASTER_HEIGHT,
'first': box_1,
'second': box_2
}

return container

dashboard_html = ("""
Expand All @@ -74,7 +76,7 @@ def _container(box_1=None, box_2=None, size=MASTER_HEIGHT,
</style>
</head>
<body>
<canvas id="myCanvas" width="400" height="400"></canvas>
<canvas id="myCanvas" width="{width}" height="{height}"></canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
Expand All @@ -91,17 +93,15 @@ def _container(box_1=None, box_2=None, size=MASTER_HEIGHT,


def _draw_line_through_box(dashboard_html, top_left_x, top_left_y, box_w,
box_h, direction='vertical'):
is_horizontal = (direction == 'horizontal')

box_h, is_horizontal, direction, fill_percent=50):
if is_horizontal:
new_top_left_x = top_left_x + box_w / 2
new_top_left_x = top_left_x + box_w * (fill_percent / 100.)
new_top_left_y = top_left_y
new_box_w = 1
new_box_h = box_h
else:
new_top_left_x = top_left_x
new_top_left_y = top_left_y + box_h / 2
new_top_left_y = top_left_y + box_h * (fill_percent / 100.)
new_box_w = box_w
new_box_h = 1

Expand All @@ -120,13 +120,13 @@ def _draw_line_through_box(dashboard_html, top_left_x, top_left_y, box_w,
return dashboard_html


def _add_html_text(dashboard_html, text, top_left_x, top_left_y, box_w, box_h):
def _add_html_text(dashboard_html, text, top_left_x, top_left_y, box_w,
box_h):
html_text = """<!-- Insert box numbers -->
context.font = '{font_size}pt Times New Roman';
context.font = '{}pt Times New Roman';
context.textAlign = 'center';
context.fillText({text}, {top_left_x} + 0.5*{box_w}, {top_left_y} + 0.5*{box_h});
""".format(text=text, top_left_x=top_left_x, top_left_y=top_left_y,
box_w=box_w, box_h=box_h, font_size=FONT_SIZE)
context.fillText({}, {} + 0.5*{}, {} + 0.5*{});
""".format(FONT_SIZE, text, top_left_x, box_w, top_left_y, box_h)

index_to_add_text = dashboard_html.find('</script>') - 1
dashboard_html = (dashboard_html[:index_to_add_text] + html_text +
Expand Down Expand Up @@ -212,8 +212,6 @@ def __init__(self, content=None):
self['version'] = content['version']
self['settings'] = content['settings']

self._set_container_sizes()

def _compute_box_ids(self):
box_ids_to_path = {}
all_nodes = list(node_generator(self['layout']))
Expand Down Expand Up @@ -260,24 +258,6 @@ def _make_all_nodes_and_paths(self):
all_paths.remove(path_second)
return all_nodes, all_paths

def _set_container_sizes(self):
if self['layout'] is None:
return

all_nodes, all_paths = self._make_all_nodes_and_paths()

# set dashboard_height proportional to max_path_len
max_path_len = max(len(path) for path in all_paths)
dashboard_height = 500 + 250 * max_path_len
self['layout']['size'] = dashboard_height
self['layout']['sizeUnit'] = 'px'

for path in all_paths:
if len(path) != 0:
if self._path_to_box(path)['type'] == 'split':
self._path_to_box(path)['size'] = 50
self._path_to_box(path)['sizeUnit'] = '%'

def _path_to_box(self, path):
loc_in_dashboard = self['layout']
for first_second in path:
Expand Down Expand Up @@ -325,17 +305,17 @@ def get_preview(self):
elif self['layout'] is None:
return IPython.display.HTML(dashboard_html)

x = 0
y = 0
top_left_x = 0
top_left_y = 0
box_w = MASTER_WIDTH
box_h = MASTER_HEIGHT
html_figure = dashboard_html
box_ids_to_path = self._compute_box_ids()
# used to store info about box dimensions
path_to_box_specs = {}
first_box_specs = {
'top_left_x': x,
'top_left_y': y,
'top_left_x': top_left_x,
'top_left_y': top_left_y,
'box_w': box_w,
'box_h': box_h
}
Expand All @@ -351,57 +331,64 @@ def get_preview(self):
current_box_specs = path_to_box_specs[path]

if self._path_to_box(path)['type'] == 'split':
html_figure = _draw_line_through_box(
html_figure,
current_box_specs['top_left_x'],
current_box_specs['top_left_y'],
current_box_specs['box_w'],
current_box_specs['box_h'],
direction=self._path_to_box(path)['direction']
)
fill_percent = self._path_to_box(path)['size']
direction = self._path_to_box(path)['direction']
is_horizontal = (direction == 'horizontal')

# determine the specs for resulting two boxes from split
is_horizontal = (
self._path_to_box(path)['direction'] == 'horizontal'
)
x = current_box_specs['top_left_x']
y = current_box_specs['top_left_y']
top_left_x = current_box_specs['top_left_x']
top_left_y = current_box_specs['top_left_y']
box_w = current_box_specs['box_w']
box_h = current_box_specs['box_h']

html_figure = _draw_line_through_box(
html_figure, top_left_x, top_left_y, box_w, box_h,
is_horizontal=is_horizontal, direction=direction,
fill_percent=fill_percent
)

# determine the specs for resulting two box split
if is_horizontal:
new_box_w = box_w / 2
new_top_left_x = top_left_x
new_top_left_y = top_left_y
new_box_w = box_w * (fill_percent / 100.)
new_box_h = box_h
new_top_left_x = x + box_w / 2
new_top_left_y = y

new_top_left_x_2 = top_left_x + new_box_w
new_top_left_y_2 = top_left_y
new_box_w_2 = box_w * ((100 - fill_percent) / 100.)
new_box_h_2 = box_h
else:
new_top_left_x = top_left_x
new_top_left_y = top_left_y
new_box_w = box_w
new_box_h = box_h / 2
new_top_left_x = x
new_top_left_y = y + box_h / 2
new_box_h = box_h * (fill_percent / 100.)

box_1_specs = {
'top_left_x': x,
'top_left_y': y,
new_top_left_x_2 = top_left_x
new_top_left_y_2 = (top_left_y +
box_h * (fill_percent / 100.))
new_box_w_2 = box_w
new_box_h_2 = box_h * ((100 - fill_percent) / 100.)

first_box_specs = {
'top_left_x': top_left_x,
'top_left_y': top_left_y,
'box_w': new_box_w,
'box_h': new_box_h
}
box_2_specs = {
'top_left_x': new_top_left_x,
'top_left_y': new_top_left_y,
'box_w': new_box_w,
'box_h': new_box_h
second_box_specs = {
'top_left_x': new_top_left_x_2,
'top_left_y': new_top_left_y_2,
'box_w': new_box_w_2,
'box_h': new_box_h_2
}

path_to_box_specs[path + ('first',)] = box_1_specs
path_to_box_specs[path + ('second',)] = box_2_specs
path_to_box_specs[path + ('first',)] = first_box_specs
path_to_box_specs[path + ('second',)] = second_box_specs

elif self._path_to_box(path)['type'] == 'box':
for box_id in box_ids_to_path:
if box_ids_to_path[box_id] == path:
number = box_id

html_figure = _add_html_text(
html_figure, number,
path_to_box_specs[path]['top_left_x'],
Expand All @@ -413,7 +400,7 @@ def get_preview(self):
# display HTML representation
return IPython.display.HTML(html_figure)

def insert(self, box, side='above', box_id=None):
def insert(self, box, side='above', box_id=None, fill_percent=50):
"""
Insert a box into your dashboard layout.

Expand All @@ -423,7 +410,14 @@ def insert(self, box, side='above', box_id=None):
'left', and 'right'.
:param (int) box_id: the box id which is used as the reference box for
the insertion of the box.

:param (float) fill_percent: specifies the percentage of the container
box from the given 'side' that the new box occupies. For example
if you apply the method\n
.insert(box=new_box, box_id=2, side='left', fill_percent=20)\n
to a dashboard object, a new box is inserted 20% from the left
side of the box with id #2. Run .get_preview() to see the box ids
assigned to each box in the dashboard layout.
Default = 50
Example:
```
import plotly.dashboard_objs as dashboard
Expand All @@ -440,7 +434,7 @@ def insert(self, box, side='above', box_id=None):
my_dboard.insert(box_1, 'left', 1)
my_dboard.insert(box_1, 'below', 2)
my_dboard.insert(box_1, 'right', 3)
my_dboard.insert(box_1, 'above', 4)
my_dboard.insert(box_1, 'above', 4, fill_percent=30)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

screen shot 2018-02-28 at 11 29 07 am

does this output for the example look correct to you @Kully ? Not sure right/left or above/below is working

Copy link
Contributor Author

@Kully Kully Feb 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running the exact same code in my branch, I am getting this preview:
screen shot 2018-02-28 at 3 22 48 pm

My output is correct but yours is not. What could be different about our branches? (you are cloning from the latest commit I assume)

Copy link
Member

@cldougl cldougl Feb 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kully running on your branch in python 2.7 I get the desired output but in python 3.6 I get:
image
We should make this consistent across python versions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, that's interesting -
I'll investigate that (and probably need to add better tests)


my_dboard.get_preview()
```
Expand All @@ -449,7 +443,9 @@ def insert(self, box, side='above', box_id=None):

# doesn't need box_id or side specified for first box
if self['layout'] is None:
self['layout'] = _container(box, _empty_box())
self['layout'] = _container(
box, _empty_box(), size=MASTER_HEIGHT, sizeUnit='px'
)
else:
if box_id is None:
raise exceptions.PlotlyError(
Expand All @@ -458,28 +454,38 @@ def insert(self, box, side='above', box_id=None):
)
if box_id not in box_ids_to_path:
raise exceptions.PlotlyError(ID_NOT_VALID_MESSAGE)

if fill_percent < 0 or fill_percent > 100:
raise exceptions.PlotlyError(
'fill_percent must be a number between 0 and 100 '
'inclusive'
)
if side == 'above':
old_box = self.get_box(box_id)
self._insert(
_container(box, old_box, direction='vertical'),
_container(box, old_box, direction='vertical',
size=fill_percent),
box_ids_to_path[box_id]
)
elif side == 'below':
old_box = self.get_box(box_id)
self._insert(
_container(old_box, box, direction='vertical'),
_container(old_box, box, direction='vertical',
size=100 - fill_percent),
box_ids_to_path[box_id]
)
elif side == 'left':
old_box = self.get_box(box_id)
self._insert(
_container(box, old_box, direction='horizontal'),
_container(box, old_box, direction='horizontal',
size=fill_percent),
box_ids_to_path[box_id]
)
elif side == 'right':
old_box = self.get_box(box_id)
self._insert(
_container(old_box, box, direction='horizontal'),
_container(old_box, box, direction='horizontal',
size =100 - fill_percent),
box_ids_to_path[box_id]
)
else:
Expand All @@ -489,8 +495,6 @@ def insert(self, box, side='above', box_id=None):
"'above', 'below', 'left', and 'right'."
)

self._set_container_sizes()

def remove(self, box_id):
"""
Remove a box from the dashboard by its box_id.
Expand Down Expand Up @@ -530,8 +534,6 @@ def remove(self, box_id):
else:
self['layout'] = None

self._set_container_sizes()

def swap(self, box_id_1, box_id_2):
"""
Swap two boxes with their specified ids.
Expand Down Expand Up @@ -580,5 +582,3 @@ def swap(self, box_id_1, box_id_2):
for first_second in pairs[0][:-1]:
loc_in_dashboard = loc_in_dashboard[first_second]
loc_in_dashboard[pairs[0][-1]] = pairs[1]

self._set_container_sizes()
7 changes: 4 additions & 3 deletions plotly/tests/test_core/test_dashboard/test_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,12 @@ def test_dashboard_dict(self):
'sizeUnit': '%',
'type': 'split'},
'second': {'boxType': 'empty', 'type': 'box'},
'size': 1000,
'sizeUnit': 'px',
'type': 'split'},
'size': 500,
'sizeUnit': 'px',
'type': 'split'},
'settings': {},
'version': 2
}


self.assertEqual(dash['layout'], expected_dashboard['layout'])