Skip to content

Commit

Permalink
Merge pull request #7250 from cfpb/wagtailcharts-test
Browse files Browse the repository at this point in the history
Testing implementation of wagtailcharts
  • Loading branch information
csebianlander authored Dec 18, 2024
2 parents c48826b + 4480dca commit a77aea7
Show file tree
Hide file tree
Showing 18 changed files with 408 additions and 11 deletions.
1 change: 1 addition & 0 deletions cfgov/cfgov/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"filing_instruction_guide",
"health_check",
"health_check.db",
"wagtailcharts",
# Satellites
"complaint_search",
"countylimits",
Expand Down
18 changes: 18 additions & 0 deletions cfgov/unprocessed/css/on-demand/wagtail-chart.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@use 'sass:math';
@use '@cfpb/cfpb-design-system/src/abstracts' as *;
@import '../main';

.o-wagtail-chart {
max-width: math.div(670px, $base-font-size-px) + em;
margin-bottom: math.div(60px, $base-font-size-px) + em;

&__subtitle {
margin: 0 0 (math.div(30px, $base-font-size-px) + em);
}

&__footnote {
max-width: math.div(670px, $size-vi) + rem;
padding-top: math.div(15px, $size-vi) + em;
font-size: 0.75em;
}
}
126 changes: 126 additions & 0 deletions cfgov/unprocessed/js/routes/on-demand/wagtail-charts-chart-block.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/* eslint-disable no-undef */
import pattern from 'patternomaly';

/**
* Set default text color to a dark gray
*
* https://www.chartjs.org/docs/latest/general/colors.html
*/
Chart.defaults.color = '#5a5d61';

/**
* Takes an array of Chart.js datasets and returns a new array
* with a different line pattern assigned to each dataset's
* borderDash property.
*
* The first line pattern is solid, the second is dashed,
* the third is dotted and all subsequent patterns are dashed
* with an increasingly thicker line.
*
* @param {array} datasets - Array of Chart.js datasets
* @returns {array} Array of Chart.js datasets with borderDash property set
*
* https://www.chartjs.org/docs/latest/samples/line/styling.html
* https://www.chartjs.org/docs/latest/configuration/#dataset-configuration
* https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash
*/
const patternizeChartLines = (datasets) => {
const DASH_THICKNESS = 5;
const DASH_PATTERNS = [
[0, 0], // solid
[DASH_THICKNESS, 2], // dashed
[2, 1], // dotted
];
return datasets.map((dataset, i) => {
dataset.borderDash = DASH_PATTERNS[i] || [DASH_THICKNESS * i, 2];
return dataset;
});
};

/**
* Takes an array of Chart.js datasets and returns a new array
* with a different pattern assigned to each dataset's
* backgroundColor property.
*
* Patterns are from the patternomaly library.
*
* @param {array} datasets - Array of Chart.js datasets
* @returns {array} Array of Chart.js datasets with backgroundColor property set
*
* https://www.chartjs.org/docs/latest/general/colors.html#patterns-and-gradients
* https://github.com/ashiguruma/patternomaly
*/
const patternizeChartBars = (datasets) => {
const patterns = [
'dot',
'diagonal',
'dash',
'cross-dash',
'zigzag-vertical',
'dot-dash',
'plus',
'cross',
'disc',
'ring',
'line',
'line-vertical',
'weave',
'zigzag',
'diagonal-right-left',
'square',
'box',
'triangle',
'triangle-inverted',
'diamond',
'diamond-box',
];
return datasets.map((dataset, i) => {
dataset.backgroundColor = dataset.data.map(() => {
// First pattern is just the solid color
if (i === 0)
return Array.isArray(dataset.backgroundColor)
? dataset.backgroundColor[0]
: dataset.backgroundColor;
return pattern.draw(
patterns[i - 1],
dataset.backgroundColor,
'rgba(255, 255, 255, 0.6)',
10,
);
});
return dataset;
});
};

/**
* Change the default Chart.js tooltip options
*/
const tooltipOptions = {
yAlign: 'bottom',
displayColors: false,
};

/**
* Define a Chart.js plugin for our CFPB customizations
*
* https://www.chartjs.org/docs/latest/developers/plugins.html
*/
const ChartjsPluginCFPB = {
id: 'cfpb-charts',
beforeInit: (chart) => {
chart.config.options.plugins.tooltip = tooltipOptions;

if (chart.config.type === 'line') {
patternizeChartLines(chart.config.data.datasets);
}

if (chart.config.type === 'bar') {
patternizeChartBars(chart.config.data.datasets);
}

chart.update();
},
};

Chart.register(ChartjsPluginStacked100.default);
Chart.register({ ChartjsPluginCFPB });
109 changes: 109 additions & 0 deletions cfgov/v1/atomic_elements/charts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from wagtail import blocks

from wagtailcharts.blocks import ChartBlock as WagtailChartBlock

from v1 import blocks as v1_blocks


CHART_TYPES = (
("line", "Line Chart"),
("bar", "Vertical Bar Chart"),
("bar_horizontal", "Horizontal Bar Chart"),
)


CHART_COLORS = (
("#20aa3f", "CFPB Green"),
("#254b87", "Navy"),
("#7eb7e8", "Pacific 60"),
("#ffb858", "Gold 80"),
("#c55998", "Purple 80"),
("#addc91", "Green 60"),
("#1fa040", "Mid Dark Green"),
("#257675", "Teal"),
("#89b6b5", "Teal 60"),
("#d14124", "Red"),
("#e79e8e", "Red 60"),
("#0072ce", "Pacific"),
("#254b87", "Navy"),
("#dc731c", "Dark Gold"),
("#745745", "Dark Neutral"),
("#baa496", "Neutral 60"),
("#dc9cbf", "Purple 50"),
("#a01b68", "Dark Purple"),
("#d2d3d5", "Gray 20"),
)


class ChartBlock(WagtailChartBlock):
eyebrow = blocks.CharBlock(
required=False,
help_text=(
"Optional: Adds an H5 eyebrow above H1 heading text. "
"Only use in conjunction with heading."
),
label="Pre-heading",
)
title = v1_blocks.HeadingBlock(required=False)
intro = blocks.RichTextBlock(required=False, icon="edit")
description = blocks.TextBlock(
required=False, help_text="Accessible description of the chart content"
)
data_source = blocks.TextBlock(
required=False,
help_text="Description of the data source",
)
date_published = blocks.CharBlock(
required=False, help_text="When the underlying data was published"
)
download_text = blocks.CharBlock(
required=False,
help_text="Custom text for the chart download field. Required to "
"display a download link.",
)
download_file = blocks.CharBlock(
required=False,
help_text="Location of a file to download",
)
notes = blocks.TextBlock(required=False, help_text="Note about the chart")

def __init__(self, **kwargs):
# Always override chart_types and colors with ours
super().__init__(
chart_types=CHART_TYPES, colors=CHART_COLORS, **kwargs
)

# Create a more user-friendly ordering of this block's child blocks.
#
# This puts our content-focused blocks in front of the
# chart-configuration blocks we inherit from wagtailcharts.
#
# child_blocks is an OrderedDict that comes from Wagtail's
# StructBlock. This just calls OrderedDict.move_to_end() in the
# order we want the blocks to appear.
self.child_blocks.move_to_end("chart_type")
self.child_blocks.move_to_end("datasets")
self.child_blocks.move_to_end("settings")

# We also want the eyebrow to appear above the title field.
self.child_blocks.move_to_end("eyebrow", last=False)

class Meta:
label = "Chart"
icon = "image"
template = "v1/includes/organisms/wagtail-chart.html"

# Load wagtailcharts scripts when block is included on a page instead of
# by rendering a {% render_charts %} template tag.
# https://github.com/overcastsoftware/wagtailcharts/blob/v0.5/wagtailcharts/templates/wagtailcharts/tags/render_charts.html
class Media:
js = [
"wagtailcharts/js/accounting.js?staticroot",
"wagtailcharts/js/chart-types.js?staticroot",
"wagtailcharts/js/chart.js?staticroot",
"wagtailcharts/js/stacked-100.js?staticroot",
"wagtailcharts/js/chartjs-plugin-datalabels.min.js?staticroot",
"wagtail-charts-chart-block.js",
"wagtailcharts/js/wagtailcharts.js?staticroot",
]
css = ["wagtail-chart.css"]
60 changes: 60 additions & 0 deletions cfgov/v1/jinja2/v1/includes/organisms/wagtail-chart.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{# ==========================================================================

wagtail-chart

==========================================================================

Description:

Create a chart block based on wagtailcharts

========================================================================== #}

{# TODO What should this o-class be? #}
<div class="o-wagtail-chart">

{% if value.eyebrow %}
<div class="eyebrow">{{ value.eyebrow }}</div>
{% endif %}

{% if value.title %}
{% include_block value.title %}
{% endif %}

{% if value.intro %}
{% include_block value.intro %}
{% endif %}

{% if value.subtitle %}
<p class="o-wagtail-chart__subtitle">{{ value.subtitle }}</p>
{% endif %}

{# Copied from wagtailcharts/templates/wagtailcharts/blocks/chart_block.html #}
<canvas
id="chart-{{block.id}}"
data-datasets="{{value.datasets}}"
data-config="{{value.settings.config}}"
data-chart-type="{{value.chart_type}}"
{% if self.callbacks %}data-callback="{{value.callbacks}}"{% endif %}
{% if value.description %}aria-label="{{value.description}}"{% endif %}>
</canvas>

<p class="o-wagtail-chart__footnote block--sub block--border-top block">
{% if value.data_source %}
<strong>Source:</strong> {{value.data_source}}<br>
{% endif %}
{% if value.date_published %}
<strong>Date published:</strong> {{value.date_published}}<br>
{% endif %}

{% if value.download_text and value.download_file %}
<strong>Download:</strong>
<a href="{{value.download_file}}">{{value.download_text}}</a><br>
{% endif %}

{% if value.notes %}
<strong>Notes:</strong> {{value.notes}}
{% endif %}
</p>
</div>

2 changes: 2 additions & 0 deletions cfgov/v1/jinja2/v1/layouts/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,8 @@
{% for js in page.media_js %}
{% if 'https://' in js %}
s.push('{{ js }}');
{% elif js.endswith('?staticroot') %}
s.push('{{ static(js[:-11]) }}');
{% else %}
s.push( '{{ static('js/routes/on-demand/' + js) }}' );
{% endif %}
Expand Down
35 changes: 35 additions & 0 deletions cfgov/v1/migrations/0041_wagtail_charts_chart_block.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cfgov/v1/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ def page_js(self):
# Returns the JS files required by this page and its StreamField blocks.
@property
def media_js(self):
return sorted(set(self.page_js + self.streamfield_media("js")))
return list(dict.fromkeys(self.page_js + self.streamfield_media("js")))

# Returns the CSS files required by this page and its StreamField blocks.
@property
Expand Down
3 changes: 2 additions & 1 deletion cfgov/v1/models/blog_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from wagtail.fields import StreamField

from v1 import blocks as v1_blocks
from v1.atomic_elements import organisms, schema
from v1.atomic_elements import charts, organisms, schema
from v1.feeds import get_appropriate_rss_feed_url_for_page
from v1.models.learn_page import AbstractFilterPage

Expand All @@ -21,6 +21,7 @@ class BlogContent(blocks.StreamBlock):
simple_chart = organisms.SimpleChart()
faq_schema = schema.FAQ(label="FAQ schema")
how_to_schema = schema.HowTo(label="HowTo schema", max_num=1)
wagtailchart_block = charts.ChartBlock()


class BlogPage(AbstractFilterPage):
Expand Down
Loading

0 comments on commit a77aea7

Please sign in to comment.