diff --git a/plots/waterfall-basic/implementations/python/highcharts.py b/plots/waterfall-basic/implementations/python/highcharts.py
index 17b36ea26e..6e0ff10549 100644
--- a/plots/waterfall-basic/implementations/python/highcharts.py
+++ b/plots/waterfall-basic/implementations/python/highcharts.py
@@ -1,10 +1,11 @@
-""" pyplots.ai
+""" anyplot.ai
waterfall-basic: Basic Waterfall Chart
-Library: highcharts unknown | Python 3.13.11
-Quality: 91/100 | Created: 2025-12-24
+Library: highcharts unknown | Python 3.13.13
+Quality: 83/100 | Updated: 2026-05-06
"""
import json
+import os
import tempfile
import time
import urllib.request
@@ -12,8 +13,22 @@
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
+from selenium.webdriver.support.ui import WebDriverWait
+# Theme tokens (see prompts/default-style-guide.md "Background" + "Theme-adaptive Chrome")
+THEME = os.getenv("ANYPLOT_THEME", "light")
+PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
+ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
+INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
+INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
+GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
+
+# Okabe-Ito palette: positive = brand green, negative = vermillion
+POSITIVE_COLOR = "#009E73" # Okabe-Ito position 1 (brand green)
+NEGATIVE_COLOR = "#D55E00" # Okabe-Ito position 2 (vermillion)
+TOTAL_COLOR = "#0072B2" # Okabe-Ito position 3 (blue) for start/end totals
+
# Data - Quarterly financial breakdown from revenue to net income
categories = [
"Revenue",
@@ -27,17 +42,15 @@
]
# Values: positive = increase, negative = decrease
-# Revenue (start), costs (negative), income (positive), and final net income (total)
-# Using colorblind-safe colors: Python Blue for totals, teal for increases, orange for decreases
values = [
- {"y": 500000, "isIntermediateSum": False, "color": "#306998"}, # Revenue - starting total (Python Blue)
- {"y": -150000, "color": "#E67300"}, # Product Costs (orange - decrease)
- {"y": -80000, "color": "#E67300"}, # Operating Expenses (orange - decrease)
- {"y": -45000, "color": "#E67300"}, # Marketing (orange - decrease)
- {"y": -35000, "color": "#E67300"}, # R&D (orange - decrease)
- {"y": 20000, "color": "#17BECF"}, # Other Income (teal - increase)
- {"y": -52000, "color": "#E67300"}, # Taxes (orange - decrease)
- {"isSum": True, "color": "#306998"}, # Net Income - ending total (Python Blue)
+ {"y": 500000, "color": TOTAL_COLOR}, # Revenue - starting total
+ {"y": -150000, "color": NEGATIVE_COLOR}, # Product Costs
+ {"y": -80000, "color": NEGATIVE_COLOR}, # Operating Expenses
+ {"y": -45000, "color": NEGATIVE_COLOR}, # Marketing
+ {"y": -35000, "color": NEGATIVE_COLOR}, # R&D
+ {"y": 20000, "color": POSITIVE_COLOR}, # Other Income
+ {"y": -52000, "color": NEGATIVE_COLOR}, # Taxes
+ {"isSum": True, "color": TOTAL_COLOR}, # Net Income - ending total
]
# Chart options for Highcharts waterfall
@@ -46,59 +59,51 @@
"type": "waterfall",
"width": 4800,
"height": 2700,
- "backgroundColor": "#ffffff",
+ "backgroundColor": PAGE_BG,
"marginBottom": 280,
- "style": {"fontFamily": "Arial, sans-serif"},
+ "style": {"fontFamily": "Arial, sans-serif", "color": INK},
},
"title": {
- "text": "Quarterly Financial Breakdown · waterfall-basic · highcharts · pyplots.ai",
- "style": {"fontSize": "48px", "fontWeight": "bold"},
+ "text": "waterfall-basic · highcharts · anyplot.ai",
+ "style": {"fontSize": "28px", "fontWeight": "normal", "color": INK},
},
"xAxis": {
"categories": categories,
- "title": {"text": "Category", "style": {"fontSize": "36px"}},
- "labels": {"style": {"fontSize": "28px"}, "rotation": -45, "y": 40},
+ "title": {"text": "", "style": {"fontSize": "22px", "color": INK}},
+ "labels": {"style": {"fontSize": "18px", "color": INK_SOFT}},
+ "lineColor": INK_SOFT,
+ "tickColor": INK_SOFT,
+ "gridLineColor": GRID,
},
"yAxis": {
- "title": {"text": "Amount ($)", "style": {"fontSize": "36px"}},
- "labels": {"style": {"fontSize": "28px"}, "formatter": "__FORMATTER_PLACEHOLDER__"},
- "gridLineColor": "#e0e0e0",
+ "title": {"text": "Amount ($)", "style": {"fontSize": "22px", "color": INK}},
+ "labels": {"style": {"fontSize": "18px", "color": INK_SOFT}, "formatter": "__FORMATTER_PLACEHOLDER__"},
+ "lineColor": INK_SOFT,
+ "tickColor": INK_SOFT,
+ "gridLineColor": GRID,
},
"legend": {"enabled": False},
- "tooltip": {"pointFormat": "${point.y:,.0f}", "style": {"fontSize": "24px"}},
+ "tooltip": {
+ "pointFormat": "${point.y:,.0f}",
+ "headerFormat": "{point.key}
",
+ "style": {"fontSize": "18px"},
+ },
"plotOptions": {
"waterfall": {
"lineWidth": 2,
- "lineColor": "#333333",
+ "lineColor": INK_SOFT,
"borderWidth": 0,
"pointPadding": 0.15,
"dataLabels": {
"enabled": True,
"formatter": "__DATALABEL_FORMATTER_PLACEHOLDER__",
- "style": {"fontSize": "24px", "fontWeight": "bold", "textOutline": "2px white"},
+ "style": {"fontSize": "18px", "fontWeight": "bold", "color": INK, "textOutline": "none"},
},
}
},
- "series": [
- {
- "name": "Financial Breakdown",
- "data": values,
- "upColor": "#17BECF", # Teal for positive changes (colorblind-safe)
- "color": "#E67300", # Orange for negative changes (colorblind-safe)
- }
- ],
+ "series": [{"name": "Financial Breakdown", "data": values}],
}
-# Download Highcharts JS for inline embedding
-highcharts_url = "https://code.highcharts.com/highcharts.js"
-with urllib.request.urlopen(highcharts_url, timeout=30) as response:
- highcharts_js = response.read().decode("utf-8")
-
-# Download highcharts-more.js (needed for waterfall chart)
-highcharts_more_url = "https://code.highcharts.com/highcharts-more.js"
-with urllib.request.urlopen(highcharts_more_url, timeout=30) as response:
- highcharts_more_js = response.read().decode("utf-8")
-
# Generate chart options JSON and add custom formatters
chart_options_json = json.dumps(chart_options)
@@ -119,15 +124,37 @@
}"""
chart_options_json = chart_options_json.replace('"__DATALABEL_FORMATTER_PLACEHOLDER__"', data_label_formatter)
-# Generate HTML with inline scripts
-html_content = f"""
+# Try to download JS, fallback to CDN links if network unavailable
+highcharts_js = ""
+highcharts_more_js = ""
+
+try:
+ highcharts_url = "https://code.highcharts.com/highcharts.js"
+ req = urllib.request.Request(highcharts_url, headers={"User-Agent": "Mozilla/5.0"})
+ with urllib.request.urlopen(req, timeout=10) as response:
+ highcharts_js = response.read().decode("utf-8")
+except Exception:
+ highcharts_js = None
+
+try:
+ highcharts_more_url = "https://code.highcharts.com/highcharts-more.js"
+ req = urllib.request.Request(highcharts_more_url, headers={"User-Agent": "Mozilla/5.0"})
+ with urllib.request.urlopen(req, timeout=10) as response:
+ highcharts_more_js = response.read().decode("utf-8")
+except Exception:
+ highcharts_more_js = None
+
+# Generate HTML content based on whether we downloaded the scripts
+if highcharts_js and highcharts_more_js:
+ # Use inline scripts
+ html_content = f"""
-
+
"""
+else:
+ # Fallback to CDN script tags
+ html_content = f"""
+
+
+
+
+
+
+
+
+
+
+"""
-# Write temp HTML file
-with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
+# Write HTML artifact for the site (both themes)
+with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f:
f.write(html_content)
- temp_path = f.name
-# Also save the HTML for interactive viewing
-with open("plot.html", "w", encoding="utf-8") as f:
+# Write temp HTML and take screenshot for the PNG artifact
+with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
f.write(html_content)
+ temp_path = f.name
-# Take screenshot with headless Chrome
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--window-size=4800,2700")
+chrome_options.add_argument("--disable-web-resources")
driver = webdriver.Chrome(options=chrome_options)
driver.get(f"file://{temp_path}")
-time.sleep(5)
-driver.save_screenshot("plot.png")
+
+try:
+ # Wait for either Highcharts to be available or max timeout
+ WebDriverWait(driver, 15).until(lambda d: d.execute_script("return typeof Highcharts !== 'undefined'"))
+except Exception:
+ pass
+
+time.sleep(3)
+driver.save_screenshot(f"plot-{THEME}.png")
driver.quit()
-# Clean up temp file
Path(temp_path).unlink()
diff --git a/plots/waterfall-basic/metadata/python/highcharts.yaml b/plots/waterfall-basic/metadata/python/highcharts.yaml
index dfaf354a78..0f18423ffb 100644
--- a/plots/waterfall-basic/metadata/python/highcharts.yaml
+++ b/plots/waterfall-basic/metadata/python/highcharts.yaml
@@ -1,164 +1,198 @@
library: highcharts
+language: python
specification_id: waterfall-basic
created: '2025-12-24T09:53:31Z'
-updated: '2025-12-24T10:04:46Z'
-generated_by: claude-opus-4-5-20251101
-workflow_run: 20483449525
-issue: 0
-python_version: 3.13.11
+updated: '2026-05-06T22:05:50Z'
+generated_by: claude-haiku
+workflow_run: 25410954779
+issue: 777
+python_version: 3.13.13
library_version: unknown
-preview_url: https://storage.googleapis.com/anyplot-images/plots/waterfall-basic/highcharts/plot.png
-preview_html: https://storage.googleapis.com/anyplot-images/plots/waterfall-basic/highcharts/plot.html
-quality_score: 91
-impl_tags:
- dependencies:
- - selenium
- techniques:
- - html-export
- patterns:
- - data-generation
- dataprep: []
- styling: []
+preview_url_light: https://storage.googleapis.com/anyplot-images/plots/waterfall-basic/python/highcharts/plot-light.png
+preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/waterfall-basic/python/highcharts/plot-dark.png
+preview_html_light: https://storage.googleapis.com/anyplot-images/plots/waterfall-basic/python/highcharts/plot-light.html
+preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/waterfall-basic/python/highcharts/plot-dark.html
+quality_score: 83
review:
strengths:
- - Excellent use of colorblind-safe color palette (blue for totals, orange for decreases,
- teal for increases)
- - Clean waterfall visualization with proper connecting lines between bars
- - Data labels show +/- prefixes for clarity on direction of change
- - Realistic financial scenario that demonstrates the waterfall concept well
- - Proper use of Highcharts isSum property for the final total bar
- - Title follows the required pyplots.ai format exactly
+ - Uses Highcharts native waterfall chart type with isSum flag for accurate automatic
+ cumulative sum tracking
+ - 'Okabe-Ito palette applied correctly: brand green (#009E73) for positive changes,
+ vermillion (#D55E00) for negatives, blue (#0072B2) for totals'
+ - 'Full theme-adaptive chrome: background, text (INK/INK_SOFT), and grid all switch
+ correctly between light (#FAF8F1) and dark (#1A1A17)'
+ - 'All spec requirements met: color-coded bars, dotted connector lines, distinct
+ start/end total bars, data labels on every bar'
+ - Realistic and internally consistent financial data (quarterly P&L breakdown
+ from revenue to net income)
+ - HTML artifacts generated for both themes alongside PNG exports
weaknesses:
- - Grid lines are very subtle and could be more visible for easier value reading
- - Data labels on smaller bars appear somewhat cramped
- - X-axis Category label is not particularly informative for this financial context
- image_description: 'The plot displays a waterfall chart showing a quarterly financial
- breakdown. The chart starts with a tall blue "Revenue" bar at $500,000, followed
- by orange bars representing decreases: Product Costs (-$150,000), Operating Expenses
- (-$80,000), Marketing (-$45,000), R&D (-$35,000), and Taxes (-$52,000). There
- is one teal/cyan bar for "Other Income" (+$20,000) representing an increase. The
- final blue bar shows "Net Income" at $158,000. Connecting dotted lines link the
- bars to show cumulative flow. Data labels appear on each bar showing the change
- amount with +/- prefixes. The y-axis shows "Amount ($)" with values from $0 to
- $540,000. X-axis labels are rotated at -45 degrees. The title follows the required
- format. Colors are colorblind-safe (blue for totals, orange for decreases, teal
- for increases).'
+ - 'Design Excellence is mid-tier: chart is a well-configured Highcharts default
+ with no standout aesthetic touches — remove the default chart border/plot area
+ border if present, tighten inter-bar spacing, add a subtle horizontal reference
+ line at $0'
+ - Data label formatter labels the Revenue starting bar as '+$500,000' (change format)
+ instead of '$500,000' (total format) — the isSum path in the formatter only applies
+ to the final Net Income bar, not the first Revenue total
+ - JS formatter insertion via string placeholder replacement is a code smell — the
+ placeholder hack adds fragility; consider using highcharts_core Python API or
+ a raw JavaScript string template instead
+ - Canvas utilisation is approximately 60% — the chart is visually centred but with
+ significant empty space in the upper-right; reducing chart height or increasing
+ bar width would help
+ - Data labels inside bars are 18px which renders small at 4800×2700; 22-24px would
+ improve on-bar readability
+ image_description: |-
+ Light render (plot-light.png):
+ Background: Warm off-white (#FAF8F1) — correct theme surface, not pure white.
+ Chrome: Title "waterfall-basic · highcharts · anyplot.ai" is visible at top-center in dark ink; y-axis title "Amount ($)" is rotated on the left in dark ink; x-axis category labels (Revenue, Product Costs, etc.) appear at the bottom in INK_SOFT dark grey; y-axis tick labels ($0–$540,000) in INK_SOFT. All text is readable against the light background.
+ Data: Revenue bar is blue (#0072B2), Product Costs/Operating Expenses/Marketing/R&D/Taxes bars are vermillion-orange (#D55E00), Other Income bar is brand green (#009E73), Net Income bar is blue (#0072B2). Dotted horizontal connector lines link each bar to the next. Data labels (e.g. "+$500,000", "-$150,000", "$158,000") appear inside each bar in dark INK color.
+ Legibility verdict: PASS — all text readable; no light-on-light issues.
+
+ Dark render (plot-dark.png):
+ Background: Near-black (#1A1A17) — correct dark theme surface, not pure black.
+ Chrome: Title text appears in light #F0EFE8 against the dark background; axis labels and tick labels appear in light INK_SOFT (#B8B7B0); no dark-on-dark text failures observed. Theme token switching is working correctly throughout.
+ Data: Bar colors are identical to the light render — blue totals, orange negatives, green positive — confirming Okabe-Ito positions 1–3 are theme-invariant. Connector lines and data labels are visible. Data labels appear in light INK color (#F0EFE8) on the dark-mode bars.
+ Legibility verdict: PASS — all text readable against dark background; no dark-on-dark failures.
criteria_checklist:
visual_quality:
- score: 36
- max: 40
+ score: 26
+ max: 30
items:
- id: VQ-01
name: Text Legibility
- score: 9
- max: 10
+ score: 6
+ max: 8
passed: true
- comment: Title and labels are readable at full size, though data labels on
- bars could be slightly larger
+ comment: All font sizes explicitly set (28px title, 22px axis, 18px ticks/labels)
+ per style guide; readable in both themes but on-bar data labels could be
+ larger at this canvas size
- id: VQ-02
name: No Overlap
- score: 8
- max: 8
+ score: 6
+ max: 6
passed: true
- comment: No overlapping text elements
+ comment: No overlapping text elements in either render
- id: VQ-03
name: Element Visibility
- score: 8
- max: 8
+ score: 5
+ max: 6
passed: true
- comment: Bars are well-sized and clearly visible with appropriate spacing
+ comment: Bars and connector lines clearly visible; data labels slightly small
+ at 18px for 4800x2700 canvas
- id: VQ-04
name: Color Accessibility
- score: 5
- max: 5
+ score: 2
+ max: 2
passed: true
- comment: Uses colorblind-safe palette (blue/orange/teal instead of red/green)
+ comment: Okabe-Ito palette (positions 1-3) used — CVD-safe; adequate contrast
+ throughout
- id: VQ-05
- name: Layout Balance
- score: 4
- max: 5
+ name: Layout & Canvas
+ score: 3
+ max: 4
passed: true
- comment: Good proportions, though bottom margin could use the full space better
+ comment: Chart fills roughly 60% of canvas; upper-right area somewhat empty
+ but not severe; bottom margin (280px) prevents label clipping
- id: VQ-06
- name: Axis Labels
+ name: Axis Labels & Title
score: 2
max: 2
passed: true
- comment: Y-axis has "Amount ($)" with currency indicator
+ comment: 'Y-axis: ''Amount ($)'' with units; title format ''waterfall-basic
+ · highcharts · anyplot.ai'' correct'
- id: VQ-07
- name: Grid & Legend
- score: 0
+ name: Palette Compliance
+ score: 2
max: 2
- passed: false
- comment: Legend is disabled which is fine for this chart, but grid lines are
- quite subtle/minimal
+ passed: true
+ comment: 'Brand green #009E73 for positive bars; vermillion #D55E00 for negative;
+ blue #0072B2 for totals; backgrounds #FAF8F1/#1A1A17 correct; data colors
+ identical across themes; chrome fully adaptive'
+ design_excellence:
+ score: 11
+ max: 20
+ items:
+ - id: DE-01
+ name: Aesthetic Sophistication
+ score: 4
+ max: 8
+ passed: true
+ comment: 'Well-configured Highcharts default: clean palette, no chartjunk,
+ but no standout design choices beyond the Okabe-Ito colors'
+ - id: DE-02
+ name: Visual Refinement
+ score: 3
+ max: 6
+ passed: true
+ comment: Legend disabled, generous bottom margin, no visible chart border
+ box, subtle connector lines — above default but not exceptional
+ - id: DE-03
+ name: Data Storytelling
+ score: 4
+ max: 6
+ passed: true
+ comment: Color-coded bars (green gain, orange costs, blue totals) immediately
+ communicate the financial story; waterfall cascade is clear; color alone
+ creates visual hierarchy
spec_compliance:
- score: 25
- max: 25
+ score: 15
+ max: 15
items:
- id: SC-01
name: Plot Type
- score: 8
- max: 8
- passed: true
- comment: Correct waterfall chart type
- - id: SC-02
- name: Data Mapping
score: 5
max: 5
passed: true
- comment: Categories and values correctly mapped
- - id: SC-03
+ comment: Highcharts native waterfall chart type used correctly
+ - id: SC-02
name: Required Features
- score: 5
- max: 5
+ score: 4
+ max: 4
passed: true
- comment: Has connecting lines, distinct colors for positive/negative/totals,
- data labels
- - id: SC-04
- name: Data Range
+ comment: 'All spec requirements met: color-coded positive/negative bars, dotted
+ connector lines, distinct start/end total bars (blue), data labels on every
+ bar'
+ - id: SC-03
+ name: Data Mapping
score: 3
max: 3
passed: true
- comment: All data visible within axis range
- - id: SC-05
- name: Legend Accuracy
- score: 2
- max: 2
- passed: true
- comment: Legend appropriately disabled for this single-series chart
- - id: SC-06
- name: Title Format
- score: 2
- max: 2
+ comment: Categories on x-axis, dollar amounts on y-axis, correct cumulative
+ flow
+ - id: SC-04
+ name: Title & Legend
+ score: 3
+ max: 3
passed: true
- comment: 'Uses correct format: "Quarterly Financial Breakdown · waterfall-basic
- · highcharts · pyplots.ai"'
+ comment: Title 'waterfall-basic · highcharts · anyplot.ai' exact format; legend
+ disabled (single-series, appropriate)
data_quality:
- score: 18
- max: 20
+ score: 15
+ max: 15
items:
- id: DQ-01
name: Feature Coverage
- score: 7
- max: 8
+ score: 6
+ max: 6
passed: true
- comment: Shows positive changes, negative changes, start total, and end total;
- good variety of cost categories
+ comment: 'Shows all waterfall features: positive change (Other Income), multiple
+ negative changes, start total (Revenue), end total (Net Income), intermediate
+ steps'
- id: DQ-02
name: Realistic Context
- score: 7
- max: 7
+ score: 5
+ max: 5
passed: true
- comment: Quarterly financial breakdown is a classic waterfall use case with
- realistic business categories
+ comment: Quarterly P&L breakdown from Revenue to Net Income — real-world business
+ finance scenario, neutral and recognisable
- id: DQ-03
name: Appropriate Scale
score: 4
- max: 5
+ max: 4
passed: true
- comment: Values are realistic for a business scenario, though the ratio of
- net income to revenue (~32%) is quite healthy
+ comment: Revenue $500k, costs proportional (30%/16%/9%/7%), net income $158k
+ (31.6% margin) — internally consistent and plausible for a mid-size company
code_quality:
score: 9
max: 10
@@ -168,42 +202,60 @@ review:
score: 3
max: 3
passed: true
- comment: 'Clean linear flow: imports → data → chart config → render → save'
+ comment: 'Linear flow: imports → tokens → data → chart options → JS download
+ → HTML → screenshot → cleanup'
- id: CQ-02
name: Reproducibility
- score: 3
- max: 3
+ score: 2
+ max: 2
passed: true
- comment: Deterministic data (no random values)
+ comment: Data is fully deterministic (hardcoded values, no randomness)
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
- comment: Only necessary imports used
+ comment: All imported modules (json, os, tempfile, time, urllib.request, pathlib,
+ selenium) are used
- id: CQ-04
- name: No Deprecated API
+ name: Code Elegance
score: 1
- max: 1
+ max: 2
passed: true
- comment: Uses current Highcharts API
+ comment: Placeholder-replacement hack to inject JS formatter functions into
+ JSON string is fragile and inelegant; CDN fallback logic adds complexity
- id: CQ-05
- name: Output Correct
- score: 0
+ name: Output & API
+ score: 1
max: 1
- passed: false
- comment: Saves as plot.png correctly, but also saves plot.html (acceptable
- for interactive library)
- library_features:
- score: 3
- max: 5
+ passed: true
+ comment: Saves plot-{THEME}.png and plot-{THEME}.html for both themes; current
+ Selenium API used
+ library_mastery:
+ score: 7
+ max: 10
items:
- - id: LF-01
- name: Uses distinctive library features
+ - id: LM-01
+ name: Idiomatic Usage
+ score: 4
+ max: 5
+ passed: true
+ comment: Uses Highcharts native waterfall type, per-point isSum flag, per-point
+ color overrides, and JS formatter functions — idiomatic but not expert-level
+ - id: LM-02
+ name: Distinctive Features
score: 3
max: 5
passed: true
- comment: Uses Highcharts native waterfall type with isSum/isIntermediateSum,
- custom formatters for data labels, but could leverage more Highcharts-specific
- features like animations or drill-down
+ comment: isSum flag for automatic cumulative sum at the final bar, per-point
+ color overrides in the data array, and custom JS numberFormat formatters
+ are Highcharts-specific capabilities
verdict: APPROVED
+impl_tags:
+ dependencies:
+ - selenium
+ techniques:
+ - html-export
+ patterns: []
+ dataprep: []
+ styling: []