========= 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)