Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing implementation of wagtailcharts #7250

Draft
wants to merge 37 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
dc918d1
WIP implementation of WagtailCharts on Browse page
csebianlander Sep 21, 2022
f9da52b
Properly load Wagtail Charts JS
chosak Sep 21, 2022
7510895
Add wagtailcharts to requirements
chosak Sep 21, 2022
c20564e
Remove unnecessary template
chosak Sep 21, 2022
34d6a44
Get wagtailcharts working with version 0.5
chosak Jul 25, 2024
a81155c
Adding CFPB data viz color palette
csebianlander Jul 26, 2024
f1a5561
Update charts migration
willbarton Aug 26, 2024
a5fa89f
Update charts migration
willbarton Sep 19, 2024
0fd4ea8
Update wagtailcharts to fix localization
willbarton Sep 19, 2024
d35a7fe
Add custom Chart.js plugin to accommodate CFPB design needs
contolini Sep 24, 2024
89bc998
Add background patterns to bar chart bars
contolini Sep 25, 2024
b8e7898
Accommodate both horizontal and vertical bar chart patterns
contolini Sep 26, 2024
7c52273
Remove front-end cruft
contolini Sep 26, 2024
5c8dc88
Add our fields to chart block
willbarton Sep 26, 2024
b52b99d
Remove unnecessary imports
willbarton Sep 27, 2024
1404498
Reorder chart fields for the content editor
willbarton Sep 27, 2024
a31b787
Add a quick test of our chartblock overrides
willbarton Sep 27, 2024
a1e1663
Remove duplicate chart block
willbarton Sep 27, 2024
5470dc1
Recreate migrations
willbarton Sep 27, 2024
1f70393
Position pre-heading above the title field
willbarton Sep 30, 2024
0b7ca2a
Recreate migrations again
willbarton Sep 30, 2024
dc6feec
Fix download links
willbarton Sep 30, 2024
ebbc7a4
Customize Chart.js bar chart patterns
contolini Sep 30, 2024
3838ccb
Update bar chart patterns, update wagtailcharts dependencies
contolini Oct 2, 2024
cf564c9
Accommodate row-based entry of data in CMS
contolini Oct 2, 2024
1044cf1
Add wagtail charts stylesheet and clean up HTML template
contolini Oct 4, 2024
f0b2558
Remove wagtail charts CSS cruft
contolini Oct 4, 2024
522f943
Remove pie chart option from Wagtail Charts block
contolini Oct 29, 2024
8309830
Remake Wagtail charts migration
contolini Oct 29, 2024
4dc98c2
Updating color palette
csebianlander Nov 14, 2024
c3c28c1
Re-adding Dark Purple to palette
csebianlander Nov 14, 2024
ab3626e
Merge branch 'main' into wagtailcharts-test
contolini Nov 21, 2024
3e91c66
Update npm cached packages
contolini Nov 26, 2024
612c136
Recreate migrations
contolini Nov 26, 2024
7773370
Merge branch 'main' into wagtailcharts-test
contolini Nov 26, 2024
c141340
Add additional blocks to 'Not commonly used' section, recreate migrat…
contolini Nov 26, 2024
ce9f110
Remove canvas fallback text
contolini Nov 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cfgov/cfgov/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,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') %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a placeholder approach and there's definitely a better way to do this. After #7303 we assume either that a path is its own URL if it contains https:// or lives in js/routes/on-demand/ if not. The wagtailcharts block type needs to load its own JS file that is being served out of the static root, so neither of these work. Per @anselmbradford's comment on #7303 (comment), maybe this is sufficient motivation to adopt paths as objects?

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/0040_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")))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change allows us to preserve the order of JS script loads for a block, for example given a block like

class MyBlockWithJS(blocks.StructBlock):
    class Media:
        js = ["b.js", "a.js"]

Our template will currently sort these and generate tags like

<script src="/static/js/routes/on-demand/a.js"></script>
<script src="/static/js/routes/on-demand/b.js"></script>

This causes problems if something in a.js relies on something in b.js being loaded first, as is the case with wagtailcharts.

I suppose in theory this change might break some alphabetized dependency we didn't know we needed, but I think it's better to be explicit with ordering.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only place I thought this might cause an issue is with our video player loading youtube's iframe API but the loading order doesn't matter because all the youtube script does is load a second script that explicitly has an async attribute.

So this change looks a-okay. 👍


# 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 @@ -20,6 +20,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
Loading