Repository: altair-viz/altair

Issue:
Refine data generator support
Vega-Lite supports _data generators_ for geo spheres, geo graticules, and numeric sequences: https://vega.github.io/vega-lite/docs/data.html#data-generators

Altair includes auto-generated classes (e.g., `alt.GraticuleGenerator`, `alt.GraticuleParams`) corresponding to these features. However, passing a generator class as the data for a Chart can result in warning output:

```
UserWarning: data of type <class ‘altair.vegalite.v3.schema.core.SphereGenerator’> not recognized
warnings.warn(“data of type {} not recognized”.format(type(data)))
```

Moreover, code like `alt.GraticuleGenerator(graticule=alt.GraticuleParams(...))` feels unnecessarily verbose. Perhaps a more concise syntax would be helpful? For example, merging the generator and params into a single call... For comparison, the Vega-Lite JavaScript API includes top-level methods for generators (`vl.graticule()`); https://observablehq.com/@vega/vega-lite-cartographic-projections provides an example of their use.


Discussion:

Patch:
diff --git a/altair/examples/interactive_layered_crossfilter.py b/altair/examples/interactive_layered_crossfilter.py
index edde1957..bf5e3d3f 100644
--- a/altair/examples/interactive_layered_crossfilter.py
+++ b/altair/examples/interactive_layered_crossfilter.py
@@ -26,13 +26,13 @@ base = alt.Chart().mark_bar().encode(
     height=130
 )
 
-# gray background with selection
-background = base.encode(
-    color=alt.value('#ddd')
-).add_selection(brush)
+# blue background with selection
+background = base.add_selection(brush)
 
-# blue highlights on the transformed data
-highlight = base.transform_filter(brush)
+# yellow highlights on the transformed data
+highlight = base.encode(
+    color=alt.value('goldenrod')
+).transform_filter(brush)
 
 # layer the two charts & repeat
 alt.layer(
diff --git a/altair/vegalite/v3/api.py b/altair/vegalite/v3/api.py
index 44663262..d148d3d8 100644
--- a/altair/vegalite/v3/api.py
+++ b/altair/vegalite/v3/api.py
@@ -93,7 +93,8 @@ def _prepare_data(data, context):
 
     # if data is still not a recognized type, then return
     if not isinstance(data, (dict, core.Data, core.UrlData,
-                             core.InlineData, core.NamedData)):
+                             core.InlineData, core.NamedData,
+                             core.GraticuleGenerator, core.SequenceGenerator)):
         warnings.warn("data of type {} not recognized".format(type(data)))
 
     return data
@@ -1962,3 +1963,25 @@ def _combine_subchart_data(data, subcharts):
             subcharts = [remove_data(c) for c in subcharts]
 
     return data, subcharts
+
+
+@utils.use_signature(core.SequenceParams)
+def sequence(start, stop=None, step=Undefined, as_=Undefined, **kwds):
+  """Sequence generator."""
+  if stop is None:
+    start, stop = 0, start
+  params = core.SequenceParams(
+    start=start, stop=stop, step=step, **{'as': as_}
+  )
+  return core.SequenceGenerator(sequence=params, **kwds)
+
+
+@utils.use_signature(core.GraticuleParams)
+def graticule(**kwds):
+  """Graticule generator."""
+  if not kwds:
+      # graticule: True indicates default parameters
+      graticule = True
+  else:
+      graticule = core.GraticuleParams(**kwds)
+  return core.GraticuleGenerator(graticule=graticule)
\ No newline at end of file
diff --git a/doc/user_guide/data.rst b/doc/user_guide/data.rst
index 33dc1e48..d5e02604 100644
--- a/doc/user_guide/data.rst
+++ b/doc/user_guide/data.rst
@@ -229,6 +229,52 @@ With it, the above chart can be reproduced as follows:
 Notice that unlike the pandas ``melt`` function we must explicitly specify the columns
 to be folded. The ``as_`` argument is optional, with the default being ``["key", "value"]``.
 
+
+.. _data-generated:
+
+Generated Data
+~~~~~~~~~~~~~~
+At times it is convenient to not use an external data source, but rather generate data for
+display within the chart specification itself. The benefit is that the chart specification
+can be made much smaller for generated data than for embedded data.
+
+Sequence Generator
+^^^^^^^^^^^^^^^^^^
+Here is an example of using the :func:`sequence` function to generate a sequence of  *x*
+data, along with a :ref:`user-guide-calculate-transform` to compute *y* data.
+
+.. altair-plot::
+
+   import altair as alt
+
+   # Note that the following generator is functionally similar to
+   # data = pd.DataFrame({'x': np.arange(0, 10, 0.1)})
+   data = alt.sequence(0, 10, 0.1, as_='x')
+
+   alt.Chart(data).transform_calculate(
+       y='sin(datum.x)'
+   ).mark_line().encode(
+       x='x:Q',
+       y='y:Q',
+   )
+
+Graticule Generator
+^^^^^^^^^^^^^^^^^^^
+Another type of data that is convenient to generate in the chart itself is the latitude/longitude
+lines on a geographic visualization, known as a graticule. These can be created using Altair's
+:func:`graticule` generator function. Here is a simple example:
+
+.. altair-plot::
+
+   import altair as alt
+
+   data = alt.graticule(step=[15, 15])
+
+   alt.Chart(data).mark_geoshape().project(
+       'orthographic',
+       rotate=[0, -45, 0]
+   )
+
 .. _Pandas: http://pandas.pydata.org/
 .. _Pandas pivot documentation: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.pivot.html
 .. _Pandas melt documentation: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.melt.html#pandas.DataFrame.melt
diff --git a/doc/user_guide/faq.rst b/doc/user_guide/faq.rst
index f5d3598b..cd0a69f6 100644
--- a/doc/user_guide/faq.rst
+++ b/doc/user_guide/faq.rst
@@ -209,4 +209,4 @@ And then enable the data transformer::
     import altair as alt
     alt.data_transformers.enable('data_server')
 
-Note that this approach may not work on some cloud-based Jupyter notebook services.
+Note that this may not approach on some cloud-based Jupyter notebook services.


Test patch:
diff --git a/altair/vegalite/v3/tests/test_api.py b/altair/vegalite/v3/tests/test_api.py
index 60b8fb2e..f556376d 100644
--- a/altair/vegalite/v3/tests/test_api.py
+++ b/altair/vegalite/v3/tests/test_api.py
@@ -840,3 +840,22 @@ def test_facet(chart_type, facet_arg):
         assert dct['facet'] == expected
     else:
         assert dct['facet'][facet_arg] == expected
+
+
+def test_sequence():
+    data = alt.sequence(100)
+    assert data.to_dict() == {'sequence': {'start': 0, 'stop': 100}}
+
+    data = alt.sequence(5, 10)
+    assert data.to_dict() == {'sequence': {'start': 5, 'stop': 10}}
+
+    data = alt.sequence(0, 1, 0.1, as_='x')
+    assert data.to_dict() == {'sequence': {'start': 0, 'stop': 1, 'step': 0.1, 'as': 'x'}}
+
+
+def test_graticule():
+    data = alt.graticule()
+    assert data.to_dict() == {'graticule': True}
+
+    data = alt.graticule(step=[15, 15])
+    assert data.to_dict() == {'graticule': {'step': [15, 15]}}
\ No newline at end of file