Skip to content

Commit c69ecc1

Browse files
secondary_y working, but need some code cleanup
1 parent 57bc444 commit c69ecc1

File tree

5 files changed

+425
-1025
lines changed

5 files changed

+425
-1025
lines changed

examples/addplot.ipynb

Lines changed: 198 additions & 26 deletions
Large diffs are not rendered by default.

examples/customization_and_styles.ipynb

Lines changed: 21 additions & 21 deletions
Large diffs are not rendered by default.

examples/scratch_pad/addplot_range_testing.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1047,7 +1047,7 @@
10471047
"name": "python",
10481048
"nbconvert_exporter": "python",
10491049
"pygments_lexer": "ipython3",
1050-
"version": "3.7.3"
1050+
"version": "3.7.4"
10511051
}
10521052
},
10531053
"nbformat": 4,

examples/scratch_pad/styles_and_customization_testing.ipynb

Lines changed: 128 additions & 945 deletions
Large diffs are not rendered by default.

src/mplfinance/plotting.py

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pandas as pd
55
import numpy as np
66
import copy
7+
import math
78
import warnings
89

910
from itertools import cycle
@@ -217,8 +218,13 @@ def plot( data, **kwargs ):
217218
if config['volume'] and volumes is None:
218219
raise ValueError('Request for volume, but NO volume data.')
219220

220-
# check if we need a lower panel for an additional plot.
221-
# if volume=True we will share the lower panel.
221+
# -------------------------------------------------------------
222+
# For now (06-Feb-2020) to keep the code somewhat simpler for
223+
# implementing `secondary_y` we are going to ALWAYS create
224+
# secondary (twinx) axes, whether we need them or not, and
225+
# then they will be available to use later when we are plotting:
226+
# -------------------------------------------------------------
227+
222228
need_lower_panel = False
223229
addplot = config['addplot']
224230
if addplot is not None:
@@ -236,15 +242,14 @@ def plot( data, **kwargs ):
236242
ax1 = fig.add_axes( [0.15, 0.38, 0.70, 0.50] )
237243
ax2 = fig.add_axes( [0.15, 0.18, 0.70, 0.20], sharex=ax1 )
238244
ax2.set_axisbelow(True) # so grid does not show through volume bars.
239-
if need_lower_panel and config['volume']:
240-
ax3 = ax2.twinx()
241-
ax3.grid(False)
242-
else:
243-
ax3 = ax2
245+
ax4 = ax2.twinx()
246+
ax4.grid(False)
244247
else:
245248
ax1 = fig.add_axes( [0.15, 0.18, 0.70, 0.70] )
246249
ax2 = None
247-
ax3 = None
250+
ax4 = None
251+
ax3 = ax1.twinx()
252+
ax3.grid(False)
248253

249254
avg_days_between_points = (dates[-1] - dates[0]) / float(len(dates))
250255

@@ -329,24 +334,29 @@ def plot( data, **kwargs ):
329334
ax2.set_ylim( miny, maxy )
330335
ax2.xaxis.set_major_formatter(formatter)
331336

337+
used_ax3 = False
338+
used_ax4 = False
332339
addplot = config['addplot']
333340
if addplot is not None:
341+
# Calculate the Order of Magnitude Range
342+
# If addplot['secondary_y'] == 'auto', then: If the addplot['data']
343+
# is out of the Order of Magnitude Range, then use secondary_y.
344+
# Calculate omrange for Main panel, and for Lower (volume) panel:
345+
lo = math.log(math.fabs(min(lows )),10) - 0.5
346+
hi = math.log(math.fabs(max(highs)),10) + 0.5
347+
omrange = {'main' :{'lo':lo,'hi':hi},
348+
'lower':None }
349+
if config['volume']:
350+
lo = math.log(math.fabs(min(volumes)),10) - 0.5
351+
hi = math.log(math.fabs(max(volumes)),10) + 0.5
352+
omrange.update(lower={'lo':lo,'hi':hi})
353+
334354
if isinstance(addplot,dict):
335355
addplot = [addplot,] # make list of dict to be consistent
336356

337357
elif not _list_of_dict(addplot):
338358
raise TypeError('addplot must be `dict`, or `list of dict`, NOT '+str(type(addplot)))
339359

340-
# This may create issues for some plots, but to keep things simple, at least for now,
341-
# we allow only 3 axes: 1 for the main panel, and two for the lower panel (one for
342-
# volume, the other for any and all addplots on the lower panel).
343-
#
344-
# I was playing around with logarithms to determine the order of magniture of the
345-
# addplots (see `addplot_range_testing.ipynb` in examples/scratch_pad/). If necessary,
346-
# we can parcel out the additional plots to appropriate axes (depending on magnitude),
347-
# but I am still inclined (for maintainability) to limit to two (or at most three) axes
348-
# per panel. Anything more, user can call mpf.axplot() and build their own figure.
349-
350360
for apdict in addplot:
351361
apdata = apdict['data']
352362
if isinstance(apdata,list) and not isinstance(apdata[0],(float,int)):
@@ -357,21 +367,50 @@ def plot( data, **kwargs ):
357367
havedf = False # must be a single series or array
358368
apdata = [apdata,] # make it iterable
359369

360-
if apdict['panel'] == 'lower':
361-
ax = ax3
362-
else:
363-
ax = ax1
364-
365370
for column in apdata:
366371
if havedf:
367372
ydata = apdata.loc[:,column]
368373
else:
369374
ydata = column
375+
yd = [y for y in ydata if not math.isnan(y)]
376+
ymhi = math.log(math.fabs(max(yd)),10)
377+
ymlo = math.log(math.fabs(min(yd)),10)
378+
secondary_y = False
379+
if apdict['secondary_y'] == 'auto':
380+
if apdict['panel'] == 'lower':
381+
# If omrange['lower'] is not yet set by volume,
382+
# then set it here as this is the first ydata
383+
# to be plotted on the lower panel, so consider
384+
# it to be the 'primary' lower panel axis.
385+
if omrange['lower'] is None:
386+
omrange.update(lower={'lo':ymlo,'hi':ymhi})
387+
elif ymlo < omrange['lower']['lo'] or ymhi > omrange['lower']['hi']:
388+
secondary_y = True
389+
elif ymlo < omrange['main']['lo'] or ymhi > omrange['main']['hi']:
390+
secondary_y = True
391+
if secondary_y:
392+
print('auto says USE secondary_y')
393+
else:
394+
print('auto says do NOT use secondary_y')
395+
else:
396+
secondary_y = apdict['secondary_y']
397+
print("apdict['secondary_y'] says secondary_y is",secondary_y)
398+
399+
if apdict['panel'] == 'lower':
400+
ax = ax4 if secondary_y else ax2
401+
else:
402+
ax = ax3 if secondary_y else ax1
403+
404+
if ax == ax3:
405+
used_ax3 = True
406+
if ax == ax4:
407+
used_ax4 = True
408+
370409
if apdict['scatter']:
371410
size = apdict['markersize']
372411
mark = apdict['marker']
373412
color = apdict['color']
374-
ax.scatter(xdates, ydata, s=size, marker=mark)
413+
ax.scatter(xdates, ydata, s=size, marker=mark, color=color)
375414
else:
376415
ls = apdict['linestyle']
377416
color = apdict['color']
@@ -381,21 +420,21 @@ def plot( data, **kwargs ):
381420
if style['y_on_right']:
382421
ax1.yaxis.set_label_position('right')
383422
ax1.yaxis.tick_right()
384-
if ax2 and ax3:
423+
if ax2 and ax4:
385424
ax2.yaxis.set_label_position('right')
386425
ax2.yaxis.tick_right()
387-
if ax3 != ax2:
388-
ax3.yaxis.set_label_position('left')
389-
ax3.yaxis.tick_left()
426+
if ax4 != ax2:
427+
ax4.yaxis.set_label_position('left')
428+
ax4.yaxis.tick_left()
390429
else:
391430
ax1.yaxis.set_label_position('left')
392431
ax1.yaxis.tick_left()
393-
if ax2 and ax3:
432+
if ax2 and ax4:
394433
ax2.yaxis.set_label_position('left')
395434
ax2.yaxis.tick_left()
396-
if ax3 != ax2:
397-
ax3.yaxis.set_label_position('right')
398-
ax3.yaxis.tick_right()
435+
if ax4 != ax2:
436+
ax4.yaxis.set_label_position('right')
437+
ax4.yaxis.tick_right()
399438

400439
if need_lower_panel or config['volume']:
401440
ax1.spines['bottom'].set_linewidth(0.25)
@@ -441,6 +480,12 @@ def plot( data, **kwargs ):
441480
if config['title'] is not None:
442481
fig.suptitle(config['title'],size='x-large',weight='semibold')
443482

483+
if not used_ax3 and ax3 is not None:
484+
ax3.get_yaxis().set_visible(False)
485+
486+
if not used_ax4 and ax4 is not None:
487+
ax4.get_yaxis().set_visible(False)
488+
444489
if config['savefig'] is not None:
445490
save = config['savefig']
446491
if isinstance(save,dict):

0 commit comments

Comments
 (0)