Repository: bokeh/bokeh

Issue:
plot is empty when log scale is used
Bokeh incorrectly creates an empty plot with the following:

``` python
from bokeh.plotting import figure, output_file, show
ys = [4.471799184102565e-05, 0.0009856299875536934, 0.0011045119899790734]
xs = range(len(ys))
output_file("log.html")
p = figure(y_axis_type="log")
p.line(xs, ys)
show(p)
```

The problem only occurs if `y_axis_type="log"` is used.

It seems when the range of y values is too narrow bokeh fails to compute the y-axis range.  If I multiple the y values by 100, it plots properly.

Tested on bokeh version:  0.11.1 py34_0



Discussion:
@sklam This seems to be due to a problem bokeh has in computing the min and max value for a range that is used with log axes. As a workaround, you could just specify a range yourself, and it should work. 

``` python
p = figure(y_axis_type="log", y_range=[10e-6, 10e-3])
```

When you don't put in a range yourself, then the default is to take the min and max of the data and add some padding to it, which is a percentage of the total range from data min to data max. The problem is that the padding is calculated linearly, which can cause problems in log plots, specifically when the padding is larger than the smallest data value. That causes the min value for the range to become negative, which is a problem for log axes since the log of a negative number is undefined.

This [issue](https://github.com/bokeh/bokeh/issues/2789) about range_padding is also related.

You could also compute a min and max value for the range using the range padding parameter in log space, something like: 

``` python
log_ys = np.log(ys)
log_range_padding = 0.2 * (log_ys[-1] - log_ys[0])
log_range = [log_ys[0] - log_range_padding, log_ys[1] + log_range_padding]
y_range = np.exp(log_range).tolist() #y_range is [2.354749231925326e-05, 0.0018717659249710699]
```

And then you could go on and find the next n-th powers so that you end up with nice and even major ticks like `y_range=[10e-6, 10e-3]`. 

This is still present on `0.12dev`

This is still present in 0.12.3.

Can somebody please look at this?  You have multiple very simple repro cases.  It results in random failures in a really fundamental feature: log-scale plotting.

Here's a suggestion for a simple fix for you: set range-padding to 0 when axis is log-scale.


Resolving patch:
diff --git a/bokeh/models/ranges.py b/bokeh/models/ranges.py
--- a/bokeh/models/ranges.py
+++ b/bokeh/models/ranges.py
@@ -115,7 +115,7 @@ class DataRange1d(DataRange):
     """
 
     range_padding = Float(default=0.1, help="""
-    A percentage of the total range size to add as padding to
+    A fraction of the total range size to add as padding to
     the range start and end.
     """)
 
@@ -188,7 +188,8 @@ class DataRange1d(DataRange):
     """)
 
     default_span = Float(default=2.0, help="""
-    A default width for the interval, in case ``start`` is equal to ``end``.
+    A default width for the interval, in case ``start`` is equal to ``end``
+    (if used with a log axis, default_span is in powers of 10).
     """)
 
     def __init__(self, *args, **kwargs):


Test patch:
diff --git a/bokehjs/test/models/ranges/data_range1d.coffee b/bokehjs/test/models/ranges/data_range1d.coffee
--- a/bokehjs/test/models/ranges/data_range1d.coffee
+++ b/bokehjs/test/models/ranges/data_range1d.coffee
@@ -160,6 +160,13 @@ describe "datarange1d module", ->
       r.default_span = 4
       expect(r._compute_range(3, 3)).to.be.deep.equal [1, 5]
 
+    it "should use default_span as powers of 10 when _mapper_type='log'", ->
+      r = new DataRange1d()
+      r._mapper_type = "log"
+      expect(r._compute_range(100, 100)).to.be.deep.equal [9.988493699365053, 1001.1519555381683]
+      r.default_span = 4
+      expect(r._compute_range(100, 100)).to.be.deep.equal [0.9988493699365047, 10011.519555381703]
+
     it "should swap max, min when flipped", ->
       r = new DataRange1d()
       r.flipped = true
@@ -204,6 +211,12 @@ describe "datarange1d module", ->
       r.range_padding = 0.5
       expect(r._compute_range(1, 3)).to.be.deep.equal [0.5, 3.5]
 
+    it "should apply range_padding logly when _mapper_type='log'", ->
+      r = new DataRange1d()
+      r.range_padding = 0.5
+      r._mapper_type = "log"
+      expect(r._compute_range(0.01, 10)).to.be.deep.equal [0.0017782794100389264, 56.23413251903488]
+
   describe "_compute_min_max", ->
 
     it "should compute max/min for dimension of a single plot_bounds", ->