Repository: napjon/krisk

Issue:
Add bar_line chart
Adding chart with bar and line with different series but same x-axis. For example,

``` Python
kk.bar_line(df, x, y_bar, y_line, c=None, .....)
```


Discussion:
codecov-io: ## [Current coverage](https://codecov.io/gh/napjon/krisk/pull/61?src=pr) is 87.72% (diff: 40.62%)
> Merging [#61](https://codecov.io/gh/napjon/krisk/pull/61?src=pr) into [master](https://codecov.io/gh/napjon/krisk/branch/master?src=pr) will decrease coverage by **4.16%**

```diff
@@             master        #61   diff @@
==========================================
  Files             8          8          
  Lines           370        391    +21   
  Methods           0          0          
  Messages          0          0          
  Branches         55         60     +5   
==========================================
+ Hits            340        343     +3   
- Misses           19         37    +18   
  Partials         11         11          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [75dfe7d...550c497](https://codecov.io/gh/napjon/krisk/compare/75dfe7d414d23c488802ec242c913845253527bf...550c49756af45f8d46d670216dda7fd6ff8d3fb1?src=pr)

Patch:
diff --git a/krisk/chart/core.py b/krisk/chart/core.py
index e62d98f..f4ae35a 100644
--- a/krisk/chart/core.py
+++ b/krisk/chart/core.py
@@ -419,6 +419,7 @@ class Chart(object):
     def _set_label_axes(self, xy, **kwargs):
         """Set label axes name and other customization"""
         assert xy in ['x','y']
+
         self.option[xy + 'Axis'].update(**kwargs)
         return self
 
@@ -468,6 +469,8 @@ class Chart(object):
         ------
         Chart object
         """
+        if self._kwargs_chart_.get('type', None) == 'bar_line':
+            raise NotImplementedError
         label_kwargs = dict(name=name,
                             nameLocation=axis_position,
                             nameGap=axis_gap,
diff --git a/krisk/plot/api.py b/krisk/plot/api.py
index dd4437d..d37dc89 100644
--- a/krisk/plot/api.py
+++ b/krisk/plot/api.py
@@ -21,32 +21,31 @@ def bar(df,
     x: string
         columns to be used as category axis
     y: string, default None
-        if None, use count of category value. otherwise aggregate based on y columns
-    category: string, default None
+        if None, use count of category. otherwise aggregate based on y columns
+    c: string, default None
         another grouping columns inside x-axis
     how: string, default None
-        to be passed to pd.group_by(x).aggregate(how). Can be mean,median, or any 
-        reduced operations.
+        to be passed to pd.group_by(x).aggregate(how). Can be mean,median,
+        or any reduced operations.
     stacked: Boolean, default False.
         Whether to stacked category on top of the other categories.
-    annotate: string, {'all',True} default None
-        if True, annotate value on top of the plot element. If stacked is also True, annotate the 
-        last category. if 'all' and stacked, annotate all category
+    annotate: string, {'all', True, None} default None
+        if True, annotate value on top of the plot element. If stacked is
+        also True, annotate the last category. if 'all' and stacked,
+        annotate all category
     full: boolean, default False.
         If true, set to full area stacked chart. Only work if stacked is True.
     trendline: boolean, default False.
-        If true, add line that connected the bars. Only work if not category, category but stacked,
-        or not full.
-    sort_on: {'index', 'values', int, 'count', 'mean', 'std', 'min', '25%', '50%', '75%', 'max'},
-         default 'index'.
-        Add sort mode. Only work when c is None.
+        If true, add line that connected the bars. Only work if not category,
+        category but stacked, or not full.
+    sort_on: {'index', 'values', int, function}, default 'index'.
         If index, sort index on lexicographical order. use as s.sort_index()
         if values, sort based on values. Use as s.sort_values()
-        If string, deviation from value provided by pd.Series.describe()
+        If function, use it as aggregate (e.g. grouped.agg('mean' or np.mean))
         if integer, treat as value and deviate from that value
     sort_c_on: string, default None.
-        specify a category as basis sort value if c is specified. Must be specified when use 
-        sort_on other than default value.
+        specify a category as basis sort value if c is specified. Must be
+        specified when use sort_on other than default value.
     ascending: boolean, default True
         sort ascending vs. descending
     
@@ -54,8 +53,9 @@ def bar(df,
     -------
     Chart Object
     """
-    return make_chart(df,type='bar',x=x,y=y,c=c,how=how,stacked=stacked,full=full,
-                      trendline=trendline, sort_on=sort_on, sort_c_on=sort_c_on, ascending=ascending,
+    return make_chart(df,type='bar',x=x,y=y,c=c,how=how,stacked=stacked,
+                      full=full, trendline=trendline,
+                      sort_on=sort_on, sort_c_on=sort_c_on, ascending=ascending,
                       annotate='top' if annotate == True else annotate)
 
 
@@ -80,31 +80,32 @@ def line(df,
     x: string
         columns to be used as category axis
     y: string, default None
-        if None, use count of category value. otherwise aggregate based on y columns
+        if None, use count of category. otherwise aggregate based on y columns
     c: string, default None
         category column inside x-axis
     how: string, default None
-        to be passed to pd.group_by(x).aggregate(how). Can be mean,median, or any 
-        reduced operations.
-    stacked: Boolean, default False.
+        to be passed to pd.group_by(x).aggregate(how). Can be mean,median,
+        or any reduced operations.
+    stacked: boolean, default False.
         Whether to stacked category on top of the other categories.
+    area: boolean, default False.
+        Whether to fill the area with line colors.
     annotate: string, {'all',True} default None
-        if True, annotate value on top of the plot element. If stacked is also True, annotate the last
-        category. if 'all' and stacked, annotate all category
+        if True, annotate value on top of the plot element. If stacked is
+        also True, annotate the last category. if 'all' and stacked,
+        annotate all category
     full: boolean, default False.
         If true, set to full area stacked chart. Only work if stacked is True.
     smooth: boolean, default False.
         If true, smooth the line.
-   sort_on: {'index', 'values', int, 'count', 'mean', 'std', 'min', '25%', '50%', '75%', 'max'},
-         default 'index'.
-        Add sort mode. Only work when c is None.
+    sort_on: {'index', 'values', int, function}, default 'index'.
         If index, sort index on lexicographical order. use as s.sort_index()
         if values, sort based on values. Use as s.sort_values()
-        If string, deviation from value provided by pd.Series.describe()
+        If function, use it as aggregate (e.g. grouped.agg('mean' or np.mean))
         if integer, treat as value and deviate from that value
     sort_c_on: string, default None.
-        specify a category as basis sort value if c is specified. Must be specified when use 
-        sort_on other than default value.
+        specify a category as basis sort value if c is specified. Must be
+        specified when use sort_on other than default value.
     ascending: boolean, default True
         sort ascending vs. descending
         
@@ -112,9 +113,57 @@ def line(df,
     -------
     Chart Object
     """
-    return make_chart(df,type='line',x=x,y=y,c=c,how=how,stacked=stacked,area=area,full=full,
-                      smooth=smooth, sort_on=sort_on, sort_c_on=sort_c_on, ascending=ascending,
-                      annotate='top' if annotate == True else annotate)
+    return make_chart(df,type='line',x=x,y=y,c=c,how=how,stacked=stacked,
+                      area=area,full=full, smooth=smooth,
+                      sort_on=sort_on, sort_c_on=sort_c_on, ascending=ascending,
+                      annotate='top' if annotate is True else annotate)
+
+
+def bar_line(df, x, ybar, yline, bar_aggfunc='mean', line_aggfunc='mean',
+             sort_on='index', ascending=True, is_distinct=False,
+             hide_split_line=True, style_tooltip=True):
+    """
+    Parameters
+    ----------
+    df: pd.DataFrame
+        data to be used for the chart
+    x: string
+        column to be used as category axis
+    ybar: string
+        column to be used as bar values
+    yline:
+        column to be used as line values
+    bar_aggfunc: string (mapping function) or function, default 'mean'
+        Function to use for aggregating groups on bar values
+    line_aggfunc: string (mapping function) or function, default 'mean'
+        Function to use for aggregating groups on line values
+    sort_on: {'index', 'ybar', 'yline'}, default 'index'
+        sorting x-axis. If index, sort on x. if either `ybar` or `yline`,
+        sort based on values
+    ascending: boolean, default True
+        sort ascending vs. descending
+    is_distinct: boolean, default False
+        Don't use aggregation on this data. Will use drop_duplicates instead.
+        Ignore `bar_aggfunc`, `line_aggfunc`, `sort_on`, `ascending` parameters.
+        sort_on deliberately disabled in is_distinct mode to allow already
+        sorted distinct data.
+    hide_split_line: boolean, default True
+        Whether to hide the split line of both y-axis.
+    style_tooltip: boolean, default True
+        Whether to offer help to style tooltip. If True, execute
+        `chart.set_tooltip_style(trigger='axis',axis_pointer='shadow')`
+
+    Returns
+    -------
+    Chart Object
+    """
+    return make_chart(df, x=x, ybar=ybar, yline=yline,
+                      bar_aggfunc=bar_aggfunc, line_aggfunc=line_aggfunc,
+                      is_distinct=is_distinct,
+                      sort_on=sort_on, ascending=ascending,
+                      hide_split_line=hide_split_line,
+                      style_tooltip=style_tooltip,
+                      c=None, type='bar_line')
 
 
 def hist(df,
@@ -140,9 +189,10 @@ def hist(df,
         Whether normalize the histogram
     stacked: Boolean, default False.
         Whether to stacked category on top of the other categories.
-    annotate: string, {'all',True} default None
-        if True, annotate value on top of the plot element. If stacked is also True, annotate the last
-        category. if 'all' and stacked, annotate all category
+    annotate: string, {'all',True, None} default None
+        if True, annotate value on top of the plot element. If stacked is also
+        True, annotate the last category. if 'all' and stacked, annotate all
+        category
     density: boolean, default False.
         Whether to add density to the plot
     
@@ -150,9 +200,9 @@ def hist(df,
     -------
     Chart Object
     """
-    return make_chart(df,type='hist',x=x,c=c,bins=bins,normed=normed,stacked=stacked,
-                      density=density,
-                      annotate='top' if annotate == True else annotate)
+    return make_chart(df,type='hist',x=x,c=c,bins=bins,normed=normed,
+                      stacked=stacked, density=density,
+                      annotate='top' if annotate is True else annotate)
    
 
 def scatter(df, x, y, s=None, c=None, saturate=None, size_px=(10, 70)):
@@ -167,7 +217,8 @@ def scatter(df, x, y, s=None, c=None, saturate=None, size_px=(10, 70)):
         Used as sizing value of the scatter points
     c: string, default None
         column used as grouping color category
-    saturation
+    saturate: string, default None
+        column to use for saturation
     size_px: tuple, default (10,70)
         boundary size, lower and upper limit in pixel for min-max scatter points
 
@@ -176,4 +227,5 @@ def scatter(df, x, y, s=None, c=None, saturate=None, size_px=(10, 70)):
     -------
     Chart Object
     """
-    return make_chart(df,type='scatter',x=x,y=y,s=s,c=c,saturate=saturate,size_px=size_px)
+    return make_chart(df,type='scatter',x=x,y=y,s=s,c=c,
+                      saturate=saturate,size_px=size_px)
diff --git a/krisk/plot/bar_line.py b/krisk/plot/make_bar_line.py
similarity index 66%
rename from krisk/plot/bar_line.py
rename to krisk/plot/make_bar_line.py
index 931a83e..05f7201 100644
--- a/krisk/plot/bar_line.py
+++ b/krisk/plot/make_bar_line.py
@@ -12,7 +12,7 @@ def set_bar_line_chart(chart, df, x, c, **kwargs):
     chart_type = kwargs['type']
 
     if chart_type in ['bar', 'line']:
-        data = get_bar_line_data(df, x, c, **kwargs)
+        data = get_bar_or_line_data(df, x, c, **kwargs)
         chart.option['xAxis']['data'] = data.index.values.tolist()
 
     elif chart_type == 'hist':
@@ -20,6 +20,11 @@ def set_bar_line_chart(chart, df, x, c, **kwargs):
         data, bins = get_hist_data(df, x, c, **kwargs)
         chart.option['xAxis']['data'] = bins
 
+    elif chart_type == 'bar_line':
+        data = set_barline(df, x, chart, **kwargs)
+        chart.option['xAxis']['data'] = data.index.values.tolist()
+        return
+
     if c:
         # append data for every category
         for cat in data.columns:
@@ -50,20 +55,21 @@ def set_bar_line_chart(chart, df, x, c, **kwargs):
 
     if kwargs['annotate'] == 'top':
         series[-1]['label'] = d_annotate
-     # TODO: make annotate receive all kinds supported in echarts.
+    # TODO: make annotate receive all kinds supported in echarts.
 
     # Special Bar Condition: Trendline
     if kwargs['type'] == 'bar' and kwargs['trendline']:
-        trendline = {'name':'trendline', 'type': 'line',
+        trendline = {'name': 'trendline', 'type': 'line',
                      'lineStyle': {'normal': {'color': '#000'}}}
 
         if c and kwargs['stacked']:
-            trendline['data']  =  [0] * len(series[-1]['data'])
+            trendline['data'] = [0] * len(series[-1]['data'])
             trendline['stack'] = c
         elif c is None:
             trendline['data'] = series[0]['data']
         else:
-            raise AssertionError('Trendline must either stacked category, or not category')
+            raise AssertionError('Trendline must either stacked category,'
+                                 ' or not category')
 
         series.append(trendline)
 
@@ -81,8 +87,8 @@ def set_bar_line_chart(chart, df, x, c, **kwargs):
                    'lineStyle': {'normal': {'color': '#000'}}}
         chart.option['xAxis']['boundaryGap'] = False
 
-        # The density have to be closed at zero. So all of xAxis and series must be updated
-        # To incorporate the changes
+        # The density have to be closed at zero. So all of xAxis and series
+        # must be updated to incorporate the changes
         chart.option['xAxis']['data'] = [0] + chart.option['xAxis']['data'] + [0]
 
         for s in series:
@@ -93,12 +99,13 @@ def set_bar_line_chart(chart, df, x, c, **kwargs):
         elif c is None:
             density['data'] =  [0] + round_list(data) + [0] 
         else:
-            raise AssertionError('Density must either stacked category, or not category')
+            raise AssertionError('Density must either stacked category, '
+                                 'or not category')
 
         series.append(density)   
 
 
-def get_bar_line_data(df, x, c, y, **kwargs):
+def get_bar_or_line_data(df, x, c, y, **kwargs):
     """Get Bar and Line manipulated data"""
     
     if c and y:
@@ -115,7 +122,7 @@ def get_bar_line_data(df, x, c, y, **kwargs):
     else:
         data = df[x].value_counts()
 
-    #Specify sort_on and order method
+    # Specify sort_on and order method
     sort_on = kwargs['sort_on']
     descr_keys = pd.Series([0]).describe().keys().tolist()
     
@@ -126,11 +133,13 @@ def get_bar_line_data(df, x, c, y, **kwargs):
         data.sort_index(inplace=True, ascending=kwargs['ascending'])
     else:
         if sort_on != 'values':
-            val_deviation = data.describe().loc[sort_on] if isinstance(sort_on, str) else sort_on
+            val_deviation = sort_on(data) if callable(sort_on) else sort_on
             data = data - val_deviation
         if c:
             assert kwargs['sort_c_on'] is not None
-            data.sort_values(kwargs['sort_c_on'], inplace=True, ascending=kwargs['ascending'])
+            (data.sort_values(kwargs['sort_c_on'],
+                              inplace=True,
+                              ascending=kwargs['ascending']))
         else:
             data.sort_values(inplace=True, ascending=kwargs['ascending'])
 
@@ -157,3 +166,42 @@ def get_hist_data(df, x, c, **kwargs):
         data = pd.Series(y_val)
 
     return data, bins
+
+
+def set_barline(df, x, chart, **kwargs):
+    """Set Bar-Line charts"""
+
+    ybar = kwargs['ybar']
+    yline = kwargs['yline']
+
+    if kwargs['is_distinct'] is True:
+        data = df[[x, ybar, yline]].drop_duplicates(subset=[x]).copy()
+        data.index = data.pop(x)
+    else:
+        data = (df
+                .groupby(x)
+                .agg({ybar: kwargs['bar_aggfunc'],
+                      yline: kwargs['line_aggfunc']}))
+
+        assert kwargs['sort_on'] in ['index', 'ybar', 'yline']
+        if kwargs['sort_on'] == 'index':
+            data.sort_index(ascending=kwargs['ascending'], inplace=True)
+        else:
+            data.sort_values(kwargs[kwargs['sort_on']],
+                             ascending=kwargs['ascending'], inplace=True)
+
+    def get_series(col, type): return dict(name=col, type=type,
+                                           data=round_list(data[col]))
+    chart.option['series'] = [
+        get_series(ybar, 'bar'),
+        dict(yAxisIndex=1, **get_series(yline, 'line'))
+    ]
+
+    if kwargs['hide_split_line'] is True:
+        def get_yaxis(col): return {'name': col, 'splitLine': {'show': False}}
+        chart.option['yAxis'] = [get_yaxis(ybar), get_yaxis(yline)]
+
+    if kwargs['style_tooltip'] is True:
+        chart.set_tooltip_style(axis_pointer='shadow', trigger='axis')
+
+    return data
diff --git a/krisk/plot/make_chart.py b/krisk/plot/make_chart.py
index 68e3c41..c11b91d 100644
--- a/krisk/plot/make_chart.py
+++ b/krisk/plot/make_chart.py
@@ -33,20 +33,20 @@ def insert_series_data(data, x, chart_type, chart, cat=None):
 
 def make_chart(df, **kwargs):
 
-    from krisk.plot.bar_line import set_bar_line_chart
-    from krisk.plot.scatter_geo import set_scatter_chart
+    from krisk.plot.make_bar_line import set_bar_line_chart
+    from krisk.plot.make_scatter_geo import set_scatter_chart
 
     chart = Chart(**kwargs)
     chart._kwargs_chart_['data_columns'] = df.columns
-
     chart.set_xlabel(kwargs['x'])
+
     if kwargs.get('y', None):
         chart.set_ylabel(kwargs['y'])
 
     if kwargs['type'] == 'line':
         chart.set_tooltip_style(trigger='axis',axis_pointer='shadow')
 
-    if kwargs['type'] in ['bar', 'line', 'hist']:
+    if kwargs['type'] in ['bar', 'line', 'hist', 'bar_line']:
         set_bar_line_chart(chart, df, **kwargs)
 
     elif kwargs['type'] == 'scatter':


Test patch:
diff --git a/krisk/plot/scatter_geo.py b/krisk/plot/make_scatter_geo.py
similarity index 100%
rename from krisk/plot/scatter_geo.py
rename to krisk/plot/make_scatter_geo.py
diff --git a/krisk/tests/test_plot.py b/krisk/tests/test_plot.py
index e3861b5..29ba507 100644
--- a/krisk/tests/test_plot.py
+++ b/krisk/tests/test_plot.py
@@ -1,6 +1,7 @@
 import json
 import pytest
 import krisk.plot as kk
+import numpy as np
 DATA_DIR = 'krisk/tests/data'
 read_option_tests = lambda f: json.load(open(DATA_DIR + '/' + f, 'r'))
 
@@ -11,6 +12,7 @@ def assert_barline_data(plot, true_option, test_legend=True):
     if test_legend:
         assert plot.option['legend']['data'] == true_option['legend']['data']
 
+
 def assert_scatter_data(plot, true_option):
     assert plot.option['series'][0]['data'] == true_option['series'][0]['data']
     assert plot.option['xAxis'] == true_option['xAxis']
@@ -49,12 +51,14 @@ def test_bar(gapminder):
 
     p4 = kk.bar(gapminder,'continent',y='gdpPercap',how='mean')
     opt4 = {'legend': {'data': []},
-                             'series': [{'data': [4426.026, 8955.554, 802.675, 3255.367, 19980.596],
+                             'series': [{'data': [4426.026, 8955.554, 802.675,
+                                                  3255.367, 19980.596],
                                'name': 'continent',
                                'type': 'bar'}],
                              'title': {'text': ''},
                              'tooltip': {'axisPointer': {'type': ''}},
-                             'xAxis': {'data': ['Africa', 'Americas', 'Asia', 'Europe', 'Oceania']},
+                             'xAxis': {'data': ['Africa', 'Americas', 'Asia',
+                                                'Europe', 'Oceania']},
                              'yAxis': {}}
     assert_barline_data(p4, opt4, test_legend=False) 
 
@@ -70,7 +74,8 @@ def test_trendline(gapminder):
     assert p1.option['series'][-1]['type'] == 'line'
     assert p1.option['series'][-1]['lineStyle'] == {'normal': {'color': '#000'}}
 
-    p2 = kk.bar(gapminder,'year',how='mean',y='pop',trendline=True,c='continent',stacked=True)
+    p2 = kk.bar(gapminder,'year',how='mean',y='pop',trendline=True,
+                c='continent',stacked=True)
     opt2 = read_option_tests('bar_year_pop_mean_continent_trendline.json')
     assert_barline_data(p2, opt2)
     assert p2.option['series'][-1]['data'] == opt2['series'][-1]['data']
@@ -97,12 +102,16 @@ def test_line(gapminder):
 
 def test_smooth_line(gapminder):
 
-    p = kk.line(gapminder[gapminder.year == 1952],'continent',y='pop',how='mean',smooth=True)
+    p = kk.line(gapminder[gapminder.year == 1952],'continent',y='pop',
+                how='mean',smooth=True)
     assert p.option['series'][0]['smooth'] == True
 
+
 def test_full_bar_line(gapminder):
-    bar = kk.bar(gapminder,'year',c='continent',y='pop',how='mean',stacked=True,full=True,annotate='all')
-    line = kk.line(gapminder,'year',c='continent',y='pop',how='mean',stacked=True,full=True,annotate='all')
+    bar = kk.bar(gapminder,'year',c='continent',y='pop',how='mean',
+                 stacked=True,full=True,annotate='all')
+    line = kk.line(gapminder,'year',c='continent',y='pop',how='mean',
+           stacked=True,full=True,annotate='all')
    
     for i in range(len(bar.option['series'])):
         bar.option['series'][i].pop('type')
@@ -116,11 +125,15 @@ def test_full_bar_line(gapminder):
     assert_barline_data(bar, true_option)
     assert_barline_data(line, true_option)
 
+
 def test_sort_bar_line(gapminder):
-    p = kk.line(gapminder,'year', y='pop', how='mean',c='continent', sort_on='mean', sort_c_on='Americas')
+    p = kk.line(gapminder,'year', y='pop', how='mean',c='continent',
+                sort_on= np.mean ,sort_c_on='Americas')
 
-    assert p.option['xAxis']['data'] == [1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992, 1997, 2002, 2007]
-    assert p.option['legend']['data'] == ['Africa', 'Americas', 'Asia', 'Europe', 'Oceania']
+    assert p.option['xAxis']['data'] == [1952, 1957, 1962, 1967, 1972, 1977,
+                                         1982, 1987, 1992, 1997, 2002, 2007]
+    assert p.option['legend']['data'] == ['Africa', 'Americas', 'Asia',
+                                          'Europe', 'Oceania']
     assert p.option['series'][0] == {'data': [-10595881.167,
                                               -9604550.167,
                                               -8874458.167,
@@ -157,15 +170,18 @@ def test_density(gapminder):
 
     p1 = kk.hist(gapminder,'lifeExp',density=True)
     assert p1.option['series'][0]['data'] == [0, 4, 2, 7, 2, 2, 3, 5, 13, 16, 6]
-    assert p1.option['series'][-1] == {'data': [0, 4, 2, 7, 2, 2, 3, 5, 13, 16, 6, 0],
+    assert p1.option['series'][-1] == {'data': [0, 4, 2, 7, 2, 2,
+                                                3, 5, 13, 16, 6, 0],
                                    'lineStyle': {'normal': {'color': '#000'}},
                                    'name': 'density',
                                    'smooth': True,
                                    'type': 'line'}
     assert p1.option['xAxis']['boundaryGap'] ==  False
-    assert p1.option['xAxis']['data'] ==  [0, 28, 34, 39, 44, 49, 55, 60, 65, 70, 75, 81, 0]
+    assert p1.option['xAxis']['data'] ==  [0, 28, 34, 39, 44, 49, 55,
+                                           60, 65, 70, 75, 81, 0]
 
-    p2 = kk.hist(gapminder,'lifeExp',bins=10,c='continent',stacked=True,density=True)
+    p2 = kk.hist(gapminder,'lifeExp',bins=10,c='continent',
+                 stacked=True,density=True)
     opt2 = read_option_tests('hist_lifeExp_b10_continent_density.json')
     assert_barline_data(p2, opt2)
 
@@ -175,7 +191,6 @@ def test_density(gapminder):
         pass
 
 
-
 def test_scatter(gapminder):
     # Simple Scatter
     p1 = kk.scatter(gapminder[gapminder.year == 1952],'pop','lifeExp')
@@ -204,6 +219,33 @@ def test_scatter(gapminder):
 
     # Scatter
     
-    p3 = kk.scatter(gapminder[gapminder.year == 1952], 'lifeExp', 'gdpPercap', s='pop')
+    p3 = kk.scatter(gapminder[gapminder.year == 1952],
+                    'lifeExp', 'gdpPercap', s='pop')
     opt3 = read_option_tests('scatter_single.json')
     assert_scatter_data(p3, opt3)
+
+
+def test_bar_line(gapminder):
+
+    p1 = kk.bar_line(gapminder, 'continent', 'lifeExp', 'gdpPercap')
+    assert p1.option['series'][0] == {'data': [59.03, 69.06, 37.479,
+                                               68.433, 74.663],
+                                      'name': 'lifeExp',
+                                      'type': 'bar'}
+    assert p1.option['series'][-1] == {'data': [4426.026, 8955.554, 802.675,
+                                                3255.367, 19980.596],
+                                       'name': 'gdpPercap',
+                                       'type': 'line',
+                                       'yAxisIndex': 1}
+    assert p1.option['xAxis']['data'] == ['Africa', 'Americas', 'Asia',
+                                          'Europe', 'Oceania']
+
+    p2 = kk.bar_line(gapminder, 'continent', 'lifeExp', 'gdpPercap',
+                     is_distinct=True)
+    assert p2.option['series'][0]['data'] == [43.077, 62.485, 28.801,
+                                              55.23, 69.12]
+    assert p2.option['series'][-1]['data'] == [2449.008, 5911.315, 779.445,
+                                               1601.056, 10039.596]
+    assert p2.option['xAxis']['data'] == ['Africa', 'Americas', 'Asia',
+                                          'Europe', 'Oceania']
+