=========
Free Form
=========
.. include:: ../../incomplete.rst
.. contents::
A free form slice is used to create custom output based on a user defined
template.
Free Form config
================
FreeForm slices support the :doc:`common_configuration`. Additional
options are:
.. note:: If your Free Form slice doesn't require any data at all, you can
omit the ``data_service`` config option. An empty data service will be
generated and the ``WithNoData`` mixin will be applied to your slice
doesn't show the no data message.
contentTemplate
---------------
Name of the template that will be rendered into the body-slice-template. Free
form slice does not have a plugin, this template is the main source of
visual in the slice.
:Optional: No, without the template the slice would be empty
:Values: CSS selector
:Example:
.. code-block:: python
config:
contentTemplate: #free-form-tee-template
Flavors of Free Form
====================
Default (freeform)
------------------
By their nature free form will only have a default flavor. It creates a
response that contains all the metrics and dimensions by their name; however,
if there are multiple rows in the response, it will append a row number to the
name. For example, if you use pop2000 a metric and run a query that returns 3
results, you will get a result that has pop2000, pop2000_2, and pop2000_3.
.. image:: images/freeform-default.png
The code for the default flavor looks as follows:
.. code-block:: python
class FreeFormV3Service1(CensusService):
def build_response(self):
self.metrics = ('pctfemale', )
self.dimensions = ('state',)
recipe = self.recipe().metrics(*self.metrics).dimensions(
*self.dimensions).limit(1).apply_global_filters(False)
self.response['responses'].append(recipe.render())
The slice in stack.yaml:
.. code-block:: yaml
- slice_type: "free-form"
slug: "jam-free-form1"
style:
- "title-large"
bare: true
mixins:
- options: {}
target: "view"
class: "WithNoData"
config:
baseTemplateName: "#base-slice-bare-template"
contentTemplate: "#jam-free-form1-template"
data_service: "censusv2service.FreeFormV3Service1"
And finally the template:
.. code-block:: none
Free Style (No Flavor)
======================
So if you want to go completely custom, you can also build a response
completely from scratch.
.. image:: images/freeform-freestyle.png
The code for the free style flavor could looks as follows:
.. code-block:: python
class FreeFormV3Service2(CensusService):
def build_response(self):
data = {'name': 'Jason', 'pastry': 'cookies'}
response = self.response_template()
response['data'][0]['values'].append(data)
self.response['responses'].append(response)
The slice in stack.yaml:
.. code-block:: yaml
- slice_type: "free-form"
slug: "jam-free-form2"
style:
- "title-large"
bare: true
mixins:
- options: {}
target: "view"
class: "WithNoData"
config:
baseTemplateName: "#base-slice-bare-template"
contentTemplate: "#jam-free-form2-template"
data_service: "censusv2service.FreeFormV3Service2"
And finally the template:
.. code-block:: none
Linking to other stacks with the switch_stacks flavor
=====================================================
The freeform slice allows you to jump to another stack. When you jump to
another stack you need to decide whether you want to carry the current
selections over to the new stack.
Let's look at the example ``switchdemo``
app. It has a ranked-list slice followed by a freeform slice. When you choose
one of the two buttons at the bottom you will go to the ``Detail`` stack.
Anything you've selected in global filters or in the ranked list will be
selected when you load the ``Detail`` stack.
.. image:: images/freeform-switchdemo.png
Here's what it looks like when you click the "The Menfolk" button.
.. image:: images/freeform-detail.png
**What does the code look like?**
.. code-block:: yaml
:caption: stack.yaml
- slice_type: "free-form"
slug: "stack_switcher_free_form"
title: "Check out more details"
style:
- "title-large"
extra_css: |-
.slice-body__visualization {
text-align: center;
}
config:
"contentTemplate": "#stack-switcher-template"
data_service: "exploreservice.StackSwitcherService"
The stack switcher template defines the look of the button.
.. image:: images/freeform-switchbutton.png
:width: 200
``COOKIES`` is the slug of the ranked list. We love cookies so much sometimes we
have to shout.
.. code-block:: none
:caption: templates.html
The data service looks like this
.. code-block:: python
:caption: exploreservice.StackSwitcherService
def build_response(self):
# Make a copy of the automatic filters
menfilters = deepcopy(self.automatic_filters)
# Set an additional filter to limit to sex='M'
menfilters['sex'] = ['M']
# Do the same for women
womenfilters = deepcopy(self.automatic_filters)
womenfilters['sex'] = ['F']
choices = [
{'label': 'The Menfolk',
'app': 'switchdemo',
'stack': 'detail',
'filters': menfilters},
{'label': 'The Womenfolk',
'app': 'switchdemo',
'stack': 'detail',
'filters': womenfilters}
]
# There is no recipe!
# We have to create a renderer directly then tell it to render
# with the choices we've defined.
renderer = FreeFormRenderer(self, None, 'No name')
response = renderer.render(flavor='switch_stacks', render_config={
'choices': choices
})
self.response['responses'].append(response)
If you're carrying over selections from global filters that use widgets, you'll need to
tell the renderer which selections are from the widget. This is so that the selections can be
formatted appropriately in the hash.
In it's simplest form, you pass the filters as a dictionary where the key is
the ``group_by_type`` of the filter and the value is a list of ids of the selected items:
.. code-block:: python
filters = deepcopy(self.automatic_filters)
filters['sex'] = ['F']
To let the renderer know that the selection is from a widget, you pass the filters like this:
.. code-block:: python
filters = deepcopy(self.automatic_filters)
filters['sex'] = {
'selection': ['F'],
'widget': True
}
You can also pass the above structure if the global filter is not using a widget.
.. code-block:: python
filters = deepcopy(self.automatic_filters)
filters['sex'] = {
'selection': ['F']
}
In addition to carrying over filters, its also possible to carry over selections for slices across stacks.
Create a dictionary and for each slice whose selections needs to be set, add it's ``slug`` to that dictionary
along with the selections. The data service looks like this:
.. code-block:: python
:caption: exploreservice.StackSwitcherService
def build_response(self):
# Make a copy of the automatic filters
menfilters = deepcopy(self.automatic_filters)
# Set an additional filter to limit to sex='M'
menfilters['sex'] = ['M']
# Do the same for women
womenfilters = deepcopy(self.automatic_filters)
womenfilters['sex'] = ['F']
# Select "Avg age" as the metric in the option chooser on the Details stack.
# `foo` is the slug of the Option Chooser slice on the Details stack.
# `metric` is the `group_by_type` of the selection.
slice_selections = {
'foo': {
'selection': {'metric': self.custom_filters['metric']}
}
}
choices = [
{
'label': 'The Menfolk',
'app': 'switchdemo',
'stack': 'detail',
'filters': menfilters,
'slices': slice_selections
},
{
'label': 'The Womenfolk',
'app': 'switchdemo',
'stack': 'detail',
'filters': womenfilters,
'slices': slice_selections
}
]
# There is no recipe!
# We have to create a renderer directly then tell it to render
# with the choices we've defined.
renderer = FreeFormRenderer(self, None, 'No name')
response = renderer.render(flavor='switch_stacks', render_config={
'choices': choices
})
self.response['responses'].append(response)
To collapse slices that can be collapsed (i.e. ``collapsable`` is ``True``) pass the ``collapsed`` flag.
And to select a response set of a slice, pass the ``dataSetName`` flag, like this:
.. code-block:: python
:caption: exploreservice.StackSwitcherService
def build_response(self):
# Make a copy of the automatic filters
menfilters = deepcopy(self.automatic_filters)
# Set an additional filter to limit to sex='M'
menfilters['sex'] = ['M']
# Do the same for women
womenfilters = deepcopy(self.automatic_filters)
womenfilters['sex'] = ['F']
# `foo` is the slug of the Option Chooser slice on the Details stack.
# `distribution1` is the slug of the Distribution slice on the Details stack.
slice_selections = {
'foo': {
'collapsed': True
},
'distribution1': {
'dataSetName': 'Ages'
}
}
choices = [
{
'label': 'The Menfolk',
'app': 'switchdemo',
'stack': 'detail',
'filters': menfilters,
'slices': slice_selections
},
{
'label': 'The Womenfolk',
'app': 'switchdemo',
'stack': 'detail',
'filters': womenfilters,
'slices': slice_selections
}
]
# There is no recipe!
# We have to create a renderer directly then tell it to render
# with the choices we've defined.
renderer = FreeFormRenderer(self, None, 'No name')
response = renderer.render(flavor='switch_stacks', render_config={
'choices': choices
})
self.response['responses'].append(response)