Repository: bokeh/bokeh

Issue:
Logarithmic axes for plots
Currently, Bokeh only supports linear scaled axes. Logarithmic axes will allow people to choose if they want their plots to be scaled linearly or logarithmically.



Discussion:

Resolving patch:
diff --git a/bokeh/objects.py b/bokeh/objects.py
--- a/bokeh/objects.py
+++ b/bokeh/objects.py
@@ -174,6 +174,9 @@ class YearsTicker(Ticker):
 class BasicTicker(Ticker):
     pass
 
+class LogTicker(Ticker):
+    pass
+
 class CategoricalTicker(Ticker):
     pass
 
@@ -190,6 +193,9 @@ class BasicTickFormatter(TickFormatter):
     power_limit_high = Int(5)
     power_limit_low = Int(-3)
 
+class LogTickFormatter(TickFormatter):
+    pass
+
 class CategoricalTickFormatter(TickFormatter):
     """ Represents a categorical tick formatter for an axis object """
     pass
@@ -263,6 +269,8 @@ class Plot(Widget):
 
     x_range = Instance(Range)
     y_range = Instance(Range)
+    x_mapper_type = String('auto')
+    y_mapper_type = String('auto')
     png = String('')
     title = String('')
     title_props = Include(TextProps, prefix="title")
@@ -400,8 +408,11 @@ class Axis(GuideRenderer):
     major_tick_in = Int
     major_tick_out = Int
 
-class LinearAxis(Axis):
-    type = String("linear_axis")
+class ContinuousAxis(Axis):
+    pass
+
+class LinearAxis(ContinuousAxis):
+    type = String("continuous_axis")
 
     def __init__(self, **kwargs):
         if 'ticker' not in kwargs:
@@ -410,6 +421,16 @@ def __init__(self, **kwargs):
             kwargs['formatter'] = BasicTickFormatter()
         super(LinearAxis, self).__init__(**kwargs)
 
+class LogAxis(ContinuousAxis):
+    type = String("continuous_axis")
+
+    def __init__(self, **kwargs):
+        if 'ticker' not in kwargs:
+            kwargs['ticker'] = LogTicker()
+        if 'formatter' not in kwargs:
+            kwargs['formatter'] = LogTickFormatter()
+        super(LogAxis, self).__init__(**kwargs)
+
 class CategoricalAxis(Axis):
     type = String("categorical_axis")
 
diff --git a/bokeh/plotting_helpers.py b/bokeh/plotting_helpers.py
--- a/bokeh/plotting_helpers.py
+++ b/bokeh/plotting_helpers.py
@@ -11,7 +11,7 @@
 from .objects import (
     BoxSelectionOverlay, BoxSelectTool, BoxZoomTool, CategoricalAxis,
     ColumnDataSource, ClickTool, CrosshairTool, DataRange1d, DatetimeAxis,
-    EmbedTool, FactorRange, Grid, HoverTool, Legend, LinearAxis,
+    EmbedTool, FactorRange, Grid, HoverTool, Legend, LinearAxis, LogAxis,
     ObjectExplorerTool, PanTool, Plot, PreviewSaveTool, Range, Range1d,
     ResetTool, ResizeTool, Tool, WheelZoomTool,
 )
@@ -240,6 +240,8 @@ def _get_axis_class(axis_type, range_input):
         return None
     elif axis_type is "linear":
         return LinearAxis
+    elif axis_type is "log":
+        return LogAxis
     elif axis_type == "datetime":
         return DatetimeAxis
     elif axis_type == "auto":
@@ -276,6 +278,8 @@ def _new_xy_plot(x_range=None, y_range=None, plot_width=None, plot_height=None,
 
     x_axiscls = _get_axis_class(x_axis_type, p.x_range)
     if x_axiscls:
+        if x_axiscls is LogAxis:
+            p.x_mapper_type = 'log'
         if x_minor_ticks is None:
             x_minor_ticks = 0
         xaxis = x_axiscls(plot=p, dimension=0, location="min", bounds="auto")
@@ -287,6 +291,8 @@ def _new_xy_plot(x_range=None, y_range=None, plot_width=None, plot_height=None,
 
     y_axiscls = _get_axis_class(y_axis_type, p.y_range)
     if y_axiscls:
+        if y_axiscls is LogAxis:
+            p.y_mapper_type = 'log'
         if y_minor_ticks is None:
             y_minor_ticks = 0
         yaxis = y_axiscls(plot=p, dimension=1, location="min", bounds="auto")
diff --git a/examples/plotting/file/logplot.py b/examples/plotting/file/logplot.py
new file mode 100644
--- /dev/null
+++ b/examples/plotting/file/logplot.py
@@ -0,0 +1,21 @@
+import numpy as np
+from bokeh.plotting import *
+
+N = 100
+
+x = np.linspace(0.1, 10, N)
+
+output_file("logplot.html", title="log axis example")
+
+hold()
+
+figure(tools="pan,wheel_zoom,box_zoom,reset,previewsave", y_axis_type="log")
+
+scatter(x, x, legend="y=x", name="legend_example")
+line(x, x, legend="y=x")
+
+line(x, np.exp(x), line_dash=[4, 4], line_color="orange", line_width=2, legend="y=exp(x)")
+
+square(x, x ** 2, fill_color=None, line_color="green", legend="y=x**2")
+
+show()  # open a browser
\ No newline at end of file


Test patch: