Skip to content

Commit e03c0c0

Browse files
committed
Merge branch 'master' into etienne-factories
2 parents 1d42987 + de0a8fe commit e03c0c0

File tree

10 files changed

+94
-29
lines changed

10 files changed

+94
-29
lines changed

Diff for: plotly/matplotlylib/mpltools.py

+29
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import math
99
import warnings
10+
import datetime
11+
import matplotlib.dates
1012

1113
def check_bar_match(old_bar, new_bar):
1214
"""Check if two bars belong in the same collection (bar chart).
@@ -416,6 +418,17 @@ def prep_ticks(ax, index, ax_type, props):
416418
return dict()
417419
# get tick label formatting information
418420
formatter = axis.get_major_formatter().__class__.__name__
421+
if ax_type == 'x' and formatter == 'DateFormatter':
422+
axis_dict['type'] = 'date'
423+
try:
424+
axis_dict['tick0'] = mpl_dates_to_datestrings(axis_dict['tick0'])
425+
except KeyError:
426+
pass
427+
finally:
428+
axis_dict.pop('dtick', None)
429+
axis_dict.pop('autotick', None)
430+
axis_dict['range'] = mpl_dates_to_datestrings(props['xlim'])
431+
419432
if formatter == 'LogFormatterMathtext':
420433
axis_dict['exponentformat'] = 'e'
421434
return axis_dict
@@ -442,6 +455,22 @@ def prep_xy_axis(ax, props, x_bounds, y_bounds):
442455
yaxis.update(prep_ticks(ax, 1, 'y', props))
443456
return xaxis, yaxis
444457

458+
def mpl_dates_to_datestrings(mpl_dates, format_string="%Y-%m-%d %H:%M:%S"):
459+
try:
460+
# make sure we have a list
461+
mpl_date_list = list(mpl_dates)
462+
epoch_times = matplotlib.dates.num2epoch(mpl_date_list)
463+
date_times = [datetime.datetime.utcfromtimestamp(epoch_time)
464+
for epoch_time in epoch_times]
465+
time_strings = [date_time.strftime(format_string)
466+
for date_time in date_times]
467+
if len(time_strings) > 1:
468+
return time_strings
469+
else:
470+
return time_strings[0]
471+
except TypeError:
472+
return mpl_dates
473+
445474

446475
DASH_MAP = {
447476
'10,0': 'solid',

Diff for: plotly/matplotlylib/renderer.py

+13
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __init__(self):
5454
self.bar_containers = None
5555
self.current_bars = []
5656
self.axis_ct = 0
57+
self.x_is_mpl_date = False
5758
self.mpl_x_bounds = (0, 1)
5859
self.mpl_y_bounds = (0, 1)
5960
self.msg = "Initialized PlotlyRenderer\n"
@@ -176,6 +177,10 @@ def open_axes(self, ax, props):
176177
self.plotly_fig['layout']['xaxis{0}'.format(self.axis_ct)] = xaxis
177178
self.plotly_fig['layout']['yaxis{0}'.format(self.axis_ct)] = yaxis
178179

180+
# let all subsequent dates be handled properly if required
181+
if xaxis.get('type') == 'date':
182+
self.x_is_mpl_date = True
183+
179184
def close_axes(self, ax):
180185
"""Close the axes object and clean up.
181186
@@ -190,6 +195,7 @@ def close_axes(self, ax):
190195
"""
191196
self.draw_bars(self.current_bars)
192197
self.msg += " Closing axes\n"
198+
self.x_is_mpl_date = False
193199

194200
def draw_bars(self, bars):
195201

@@ -250,6 +256,9 @@ def draw_bar(self, coll):
250256
y = [bar['y1'] for bar in trace]
251257
bar_gap = mpltools.get_bar_gap([bar['x0'] for bar in trace],
252258
[bar['x1'] for bar in trace])
259+
if self.x_is_mpl_date:
260+
x = [bar['x0'] for bar in trace]
261+
x = mpltools.mpl_dates_to_datestrings(x)
253262
else:
254263
self.msg += " Attempting to draw a horizontal bar chart\n"
255264
old_rights = [bar_props['x1'] for bar_props in trace]
@@ -357,6 +366,10 @@ def draw_marked_line(self, **props):
357366
yaxis='y{0}'.format(self.axis_ct),
358367
line=line,
359368
marker=marker)
369+
if self.x_is_mpl_date:
370+
marked_line['x'] = mpltools.mpl_dates_to_datestrings(
371+
marked_line['x']
372+
)
360373
self.plotly_fig['data'] += marked_line,
361374
self.msg += " Heck yeah, I drew that line\n"
362375
else:

Diff for: plotly/tests/test_core/test_get_figure/test_get_figure.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,17 @@ def is_trivial(obj):
9191

9292

9393
def test_get_figure():
94-
un = 'plotlyimagetest'
94+
un = 'PlotlyImageTest'
9595
ak = '786r5mecv0'
9696
file_id = 2
9797
py.sign_in(un, ak)
9898
print("getting: https://plot.ly/~{0}/{1}/".format(un, file_id))
9999
print("###########################################\n\n")
100-
fig = py.get_figure('plotlyimagetest', str(file_id))
100+
fig = py.get_figure('PlotlyImageTest', str(file_id))
101101

102102

103103
def test_get_figure_with_url():
104-
un = 'plotlyimagetest'
104+
un = 'PlotlyImageTest'
105105
ak = '786r5mecv0'
106106
url = "https://plot.ly/~PlotlyImageTest/2/"
107107
py.sign_in(un, ak)
@@ -112,7 +112,7 @@ def test_get_figure_with_url():
112112

113113
@raises(exceptions.PlotlyError)
114114
def test_get_figure_invalid_1():
115-
un = 'plotlyimagetest'
115+
un = 'PlotlyImageTest'
116116
ak = '786r5mecv0'
117117
url = "https://plot.ly/~PlotlyImageTest/a/"
118118
py.sign_in(un, ak)
@@ -123,7 +123,7 @@ def test_get_figure_invalid_1():
123123

124124
@raises(exceptions.PlotlyError)
125125
def test_get_figure_invalid_2():
126-
un = 'plotlyimagetest'
126+
un = 'PlotlyImageTest'
127127
ak = '786r5mecv0'
128128
url = "https://plot.ly/~PlotlyImageTest/-1/"
129129
py.sign_in(un, ak)
@@ -134,7 +134,7 @@ def test_get_figure_invalid_2():
134134

135135
@raises(exceptions.PlotlyError)
136136
def test_get_figure_does_not_exist():
137-
un = 'plotlyimagetest'
137+
un = 'PlotlyImageTest'
138138
ak = '786r5mecv0'
139139
url = "https://plot.ly/~PlotlyImageTest/1000000000/"
140140
py.sign_in(un, ak)
@@ -144,17 +144,17 @@ def test_get_figure_does_not_exist():
144144

145145

146146
def test_get_figure_raw():
147-
un = 'plotlyimagetest'
147+
un = 'PlotlyImageTest'
148148
ak = '786r5mecv0'
149149
file_id = 2
150150
py.sign_in(un, ak)
151151
print("getting: https://plot.ly/~{0}/{1}/".format(un, file_id))
152152
print("###########################################\n\n")
153-
fig = py.get_figure('plotlyimagetest', str(file_id), raw=True)
153+
fig = py.get_figure('PlotlyImageTest', str(file_id), raw=True)
154154

155155

156156
def test_all():
157-
un = 'plotlyimagetest'
157+
un = 'PlotlyImageTest'
158158
ak = '786r5mecv0'
159159
run_test = False
160160
end_file = 2
@@ -173,8 +173,8 @@ def test_all():
173173
try:
174174
print("testing: https://plot.ly/~{0}/{1}".format(un, file_id))
175175
print("###########################################\n\n")
176-
fig = py.get_figure('plotlyimagetest', str(file_id))
177-
fig_raw = py.get_figure('plotlyimagetest',
176+
fig = py.get_figure('PlotlyImageTest', str(file_id))
177+
fig_raw = py.get_figure('PlotlyImageTest',
178178
str(file_id),
179179
raw=True)
180180
except exceptions.PlotlyError:

Diff for: plotly/tests/test_core/test_get_requests/test_get_requests.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_user_does_not_exist():
4646

4747

4848
def test_file_does_not_exist():
49-
username = 'plotlyimagetest'
49+
username = 'PlotlyImageTest'
5050
api_key = '786r5mecv0'
5151
file_owner = 'get_test_user'
5252
file_id = 1000
@@ -67,7 +67,7 @@ def test_file_does_not_exist():
6767

6868

6969
def test_wrong_api_key(): # TODO: does this test the right thing?
70-
username = 'plotlyimagetest'
70+
username = 'PlotlyImageTest'
7171
api_key = 'invalid-apikey'
7272
file_owner = 'get_test_user'
7373
file_id = 0
@@ -84,7 +84,7 @@ def test_wrong_api_key(): # TODO: does this test the right thing?
8484
# TODO
8585

8686
def test_private_permission_defined():
87-
username = 'plotlyimagetest'
87+
username = 'PlotlyImageTest'
8888
api_key = '786r5mecv0'
8989
file_owner = 'get_test_user'
9090
file_id = 1 # 1 is a private file
@@ -124,7 +124,7 @@ def test_missing_headers():
124124

125125

126126
def test_valid_request():
127-
username = 'plotlyimagetest'
127+
username = 'PlotlyImageTest'
128128
api_key = '786r5mecv0'
129129
file_owner = 'get_test_user'
130130
file_id = 0

Diff for: plotly/tests/test_core/test_plotly/test_plot.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
from plotly.exceptions import PlotlyError
1414

1515

16-
# username for tests: 'plotlyimagetest'
16+
# username for tests: 'PlotlyImageTest'
1717
# api_key for account: '786r5mecv0'
1818

1919

2020
def test_plot_valid():
21-
py.sign_in('plotlyimagetest', '786r5mecv0')
21+
py.sign_in('PlotlyImageTest', '786r5mecv0')
2222
fig = {
2323
'data':[
2424
{

Diff for: plotly/tests/test_core/test_stream/test_stream.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import plotly.plotly as py
1414
from plotly import exceptions
1515

16-
un = 'pythonapi'
16+
un = 'PythonAPI'
1717
ak = 'ubpiol2cve'
1818
tk = 'vaia8trjjb'
1919

Diff for: plotly/tests/test_optional/test_ipython/test_embed.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
class TestPlotlyDisplay(unittest.TestCase):
1616

1717
def setUp(self):
18-
plot_info = {"un": "plotlyimagetest", "fid": "2"}
18+
plot_info = {"un": "PlotlyImageTest", "fid": "2"}
1919
url = "https://plot.ly/~{un}/{fid}".format(**plot_info)
2020
self.display_obj = tls.embed(url)
2121
self.results = {}

Diff for: plotly/tools.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,8 @@ def check_file_permissions():
7272
def ensure_local_plotly_files():
7373
"""Ensure that filesystem is setup/filled out in a valid way"""
7474
if _file_permissions:
75-
if not os.path.isdir(PLOTLY_DIR):
76-
os.mkdir(PLOTLY_DIR)
7775
for fn in [CREDENTIALS_FILE, CONFIG_FILE]:
76+
utils.ensure_file_exists(fn)
7877
contents = utils.load_json_dict(fn)
7978
for key, val in list(_FILE_CONTENT[fn].items()):
8079
# TODO: removed type checking below, may want to revisit
@@ -134,8 +133,7 @@ def get_credentials_file(*args):
134133

135134
def reset_credentials_file():
136135
ensure_local_plotly_files() # make sure what's there is OK
137-
f = open(CREDENTIALS_FILE, 'w')
138-
f.close()
136+
utils.save_json_dict(CREDENTIALS_FILE, {})
139137
ensure_local_plotly_files() # put the defaults back
140138

141139

Diff for: plotly/utils.py

+30-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
import json
1010
import os.path
1111
import sys
12+
import threading
13+
14+
### incase people are using threadig, we lock file reads
15+
lock = threading.Lock()
1216

1317

1418
### general file setup tools ###
@@ -17,13 +21,15 @@ def load_json_dict(filename, *args):
1721
"""Checks if file exists. Returns {} if something fails."""
1822
data = {}
1923
if os.path.exists(filename):
24+
lock.acquire()
2025
with open(filename, "r") as f:
2126
try:
2227
data = json.load(f)
2328
if not isinstance(data, dict):
2429
data = {}
2530
except:
26-
pass # TODO: issue a warning and bubble it up
31+
data = {} # TODO: issue a warning and bubble it up
32+
lock.release()
2733
if args:
2834
d = dict()
2935
for key in args:
@@ -36,13 +42,32 @@ def load_json_dict(filename, *args):
3642

3743

3844
def save_json_dict(filename, json_dict):
39-
"""Will error if filename is not appropriate, but it's checked elsewhere.
40-
"""
45+
"""Save json to file. Error if path DNE, not a dict, or invalid json."""
4146
if isinstance(json_dict, dict):
47+
# this will raise a TypeError if something goes wrong
48+
json_string = json.dumps(json_dict, indent=4)
49+
lock.acquire()
4250
with open(filename, "w") as f:
43-
f.write(json.dumps(json_dict, indent=4))
51+
f.write(json_string)
52+
lock.release()
4453
else:
45-
raise TypeError("json_dict was not a dictionay. couldn't save.")
54+
raise TypeError("json_dict was not a dictionary. not saving.")
55+
56+
57+
def ensure_file_exists(filename):
58+
"""Given a valid filename, make sure it exists (will create if DNE)."""
59+
if not os.path.exists(filename):
60+
head, tail = os.path.split(filename)
61+
ensure_dir_exists(head)
62+
with open(filename, 'w') as f:
63+
pass # just create the file
64+
65+
66+
def ensure_dir_exists(directory):
67+
"""Given a valid directory path, make sure it exists."""
68+
if dir:
69+
if not os.path.isdir(directory):
70+
os.makedirs(directory)
4671

4772

4873
### Custom JSON encoders ###

Diff for: plotly/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.2.8'
1+
__version__ = '1.2.9'

0 commit comments

Comments
 (0)