# View Range-based Data ## Example Overview This example pairs a `TomSelectChoiceField` range selector with an HTMX-driven live preview: selecting a word-count range instantly redraws a bar chart of how many articles fall in each sub-bin (the selected range is broken into 10-word bins), all without a page reload. Use this pattern for interactive filtering and visualization - analytics dashboards, price or date-range filters, or any UI where users explore data distributions by selecting a range. **Visual Examples** ![Screenshot: View Range-based Data Initial Display](https://raw.githubusercontent.com/OmenApps/django-tomselect/refs/heads/main/docs/images/range-preview1.png) ![Screenshot: View Range-based Data Specific Page Count Bin](https://raw.githubusercontent.com/OmenApps/django-tomselect/refs/heads/main/docs/images/range-preview2.png) ## Key Code Segments ### Forms The form uses `TomSelectChoiceField` to allow users to select a range dynamically. :::{admonition} Form Definition :class: dropdown ```python class RangePreviewForm(forms.Form): """Form demonstrating dynamic range selection with live preview.""" word_count = TomSelectChoiceField( config=TomSelectConfig( url="autocomplete-page-count-range", value_field="value", label_field="label", css_framework="bootstrap5", placeholder="Select a word count range...", preload="focus", highlight=True, minimum_query_length=0, ), help_text="Select a word count range to see distribution visualization", ) ``` ::: **Explanation**: - The `word_count` field is configured with a `TomSelectConfig` object that connects to an autocomplete endpoint and allows range selection. - The `highlight` parameter ensures the selected range is visually prominent. ### Templates The form is rendered in the `range_preview.html` template, where HTMX is used to update the preview dynamically based on the selected range. :::{admonition} Template Code :class: dropdown ```html {% extends 'example/base_with_bootstrap5.html' %} {% block extra_header %} {{ form.media }} {% endblock %} {% block content %}

Range Selection with Preview

This example demonstrates how to create an interactive range selector with a live preview visualization.
Select a range to see detailed distribution in 10-word bins.
{% csrf_token %}
{{ form.word_count }} {% if form.word_count.help_text %}
{{ form.word_count.help_text }}
{% endif %}
{% comment %} {% include "example/intermediate_demos/range_preview_bars.html" %} {% endcomment %}
{% endblock %} ``` ::: We load the preview container with the initial data on page load and update it when the selected range changes. The `range_preview_bars.html` template is included in the preview container to display the data visualization. :::{admonition} Data Visualization Template :class: dropdown ```html {% load chart_tags %}
{% if is_detail_view %}
Distribution for {{ selected_range }} words ({{ total_articles }} articles)
{% else %}
Overall Distribution ({{ total_articles }} articles)
{% endif %}
{% for item in data %}
{{ item.range }} words {{ item.count }} article{{ item.count|pluralize }}
{% endfor %} {% if not is_detail_view %}
Select a range to see detailed distribution
{% endif %} ``` ::: ### HTMX Endpoint for Preview Updates The `update-range-preview` endpoint processes the selected range and returns the updated data visualization. It uses the `get_detailed_range_statistics` function to calculate the distribution within the selected range. If no range is selected, it displays the overall distribution. The `get_range_statistics` function calculates the distribution for predefined range bins. The `range_preview_demo` view renders the initial form, and `update_range_preview` updates the visualization based on the selected range. :::{admonition} Endpoint Code :class: dropdown ```python def get_range_statistics(): """Get article counts for each word count range.""" stats = [] for start, end in word_count_range: # Create range filter range_filter = Q(word_count__gte=start) & Q(word_count__lt=end) count = Article.objects.filter(range_filter).count() stats.append({"range": f"{start:,}-{end:,}", "count": count, "range_tuple": (start, end)}) return stats def get_detailed_range_statistics(start, end, bin_size=10): """Get detailed article counts within a range, binned by specified size.""" stats = [] # Create bins for the range for bin_start in range(start, end, bin_size): bin_end = min(bin_start + bin_size, end) # Create range filter for this bin range_filter = Q(word_count__gte=bin_start) & Q(word_count__lt=bin_end) count = Article.objects.filter(range_filter).count() stats.append( { "range": f"{bin_start:,}-{bin_end:,}", "count": count, "bin_start": bin_start, "bin_end": bin_end, } ) return stats def range_preview_demo(request): """View demonstrating range selection with preview. Provides the form and loads update_range_preview via HTMX. """ template = "example/intermediate_demos/range_preview.html" context = {} context["form"] = RangePreviewForm() return TemplateResponse(request, template, context) def update_range_preview(request): """HTMX endpoint for updating the visualization.""" template = "example/intermediate_demos/range_preview_bars.html" context = {} selected_value = request.GET.get("word_count") if selected_value: try: # Parse the selected range from "(start, end)" format selected_value = selected_value.strip("()") start, end = map(int, selected_value.split(",")) # Get detailed statistics for the selected range stats = get_detailed_range_statistics(start, end) range_label = f"{start:,}-{end:,}" is_detail_view = True total_articles = sum(item["count"] for item in stats) except (ValueError, TypeError) as e: logger.warning("Error parsing range: %s", e) stats = get_range_statistics() range_label = None is_detail_view = False total_articles = sum(item["count"] for item in stats) else: # Show overall statistics if no range selected stats = get_range_statistics() range_label = None is_detail_view = False total_articles = sum(item["count"] for item in stats) max_count = max((item["count"] for item in stats), default=0) context["data"] = stats context["selected_range"] = range_label context["max_count"] = max_count context["is_detail_view"] = is_detail_view context["total_articles"] = total_articles return TemplateResponse(request, template, context) ``` ::: ### Autocomplete Views The `autocomplete-page-count-range` endpoint serves the dropdown options for word count ranges. The `WordCountRangeAutocompleteView` class extends `AutocompleteIterablesView` to provide labeled options with counts for each range. It fetches the statistics using the `get_range_statistics` function, overriding the `get_iterable` method to return the formatted range options. :::{admonition} Autocomplete View :class: dropdown ```python class WordCountRangeAutocompleteView(AutocompleteIterablesView): """Autocomplete view for the word count ranges with actual statistics.""" iterable = word_count_range page_size = 10 def get_iterable(self): """Convert the word count range tuples into labeled options with counts.""" stats = get_range_statistics() ranges = [] for stat in stats: start, end = stat["range_tuple"] label = f"{stat['range']} words ({stat['count']} articles)" value = f"({start}, {end})" # Tuple string representation ranges.append({"value": value, "label": label}) return ranges ``` ::: ## Implementation Notes - The autocomplete view's `get_iterable` override turns each range tuple into a labeled option carrying its live article count, so the dropdown labels stay in sync with the data. - `get_detailed_range_statistics` bins the selected range into fixed-size sub-ranges (default 10 words) to drive the detailed preview; with no range selected, `get_range_statistics` returns the overall per-range distribution instead.