4
4
import pandas as pd
5
5
import numpy as np
6
6
import copy
7
+ import math
7
8
import warnings
8
9
9
10
from itertools import cycle
@@ -217,8 +218,13 @@ def plot( data, **kwargs ):
217
218
if config ['volume' ] and volumes is None :
218
219
raise ValueError ('Request for volume, but NO volume data.' )
219
220
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
+
222
228
need_lower_panel = False
223
229
addplot = config ['addplot' ]
224
230
if addplot is not None :
@@ -236,15 +242,14 @@ def plot( data, **kwargs ):
236
242
ax1 = fig .add_axes ( [0.15 , 0.38 , 0.70 , 0.50 ] )
237
243
ax2 = fig .add_axes ( [0.15 , 0.18 , 0.70 , 0.20 ], sharex = ax1 )
238
244
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 )
244
247
else :
245
248
ax1 = fig .add_axes ( [0.15 , 0.18 , 0.70 , 0.70 ] )
246
249
ax2 = None
247
- ax3 = None
250
+ ax4 = None
251
+ ax3 = ax1 .twinx ()
252
+ ax3 .grid (False )
248
253
249
254
avg_days_between_points = (dates [- 1 ] - dates [0 ]) / float (len (dates ))
250
255
@@ -329,24 +334,29 @@ def plot( data, **kwargs ):
329
334
ax2 .set_ylim ( miny , maxy )
330
335
ax2 .xaxis .set_major_formatter (formatter )
331
336
337
+ used_ax3 = False
338
+ used_ax4 = False
332
339
addplot = config ['addplot' ]
333
340
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
+
334
354
if isinstance (addplot ,dict ):
335
355
addplot = [addplot ,] # make list of dict to be consistent
336
356
337
357
elif not _list_of_dict (addplot ):
338
358
raise TypeError ('addplot must be `dict`, or `list of dict`, NOT ' + str (type (addplot )))
339
359
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
-
350
360
for apdict in addplot :
351
361
apdata = apdict ['data' ]
352
362
if isinstance (apdata ,list ) and not isinstance (apdata [0 ],(float ,int )):
@@ -357,21 +367,50 @@ def plot( data, **kwargs ):
357
367
havedf = False # must be a single series or array
358
368
apdata = [apdata ,] # make it iterable
359
369
360
- if apdict ['panel' ] == 'lower' :
361
- ax = ax3
362
- else :
363
- ax = ax1
364
-
365
370
for column in apdata :
366
371
if havedf :
367
372
ydata = apdata .loc [:,column ]
368
373
else :
369
374
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
+
370
409
if apdict ['scatter' ]:
371
410
size = apdict ['markersize' ]
372
411
mark = apdict ['marker' ]
373
412
color = apdict ['color' ]
374
- ax .scatter (xdates , ydata , s = size , marker = mark )
413
+ ax .scatter (xdates , ydata , s = size , marker = mark , color = color )
375
414
else :
376
415
ls = apdict ['linestyle' ]
377
416
color = apdict ['color' ]
@@ -381,21 +420,21 @@ def plot( data, **kwargs ):
381
420
if style ['y_on_right' ]:
382
421
ax1 .yaxis .set_label_position ('right' )
383
422
ax1 .yaxis .tick_right ()
384
- if ax2 and ax3 :
423
+ if ax2 and ax4 :
385
424
ax2 .yaxis .set_label_position ('right' )
386
425
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 ()
390
429
else :
391
430
ax1 .yaxis .set_label_position ('left' )
392
431
ax1 .yaxis .tick_left ()
393
- if ax2 and ax3 :
432
+ if ax2 and ax4 :
394
433
ax2 .yaxis .set_label_position ('left' )
395
434
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 ()
399
438
400
439
if need_lower_panel or config ['volume' ]:
401
440
ax1 .spines ['bottom' ].set_linewidth (0.25 )
@@ -441,6 +480,12 @@ def plot( data, **kwargs ):
441
480
if config ['title' ] is not None :
442
481
fig .suptitle (config ['title' ],size = 'x-large' ,weight = 'semibold' )
443
482
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
+
444
489
if config ['savefig' ] is not None :
445
490
save = config ['savefig' ]
446
491
if isinstance (save ,dict ):
0 commit comments