fixed saving png issue

This commit is contained in:
2026-01-29 10:43:05 +01:00
parent 0485f991d2
commit 36d8bc4d88

View File

@@ -8,6 +8,7 @@ import pandas as pd
import polars as pl import polars as pl
from theme import ColorPalette from theme import ColorPalette
import hashlib
class JPMCPlotsMixin: class JPMCPlotsMixin:
"""Mixin class for plotting functions in JPMCSurvey.""" """Mixin class for plotting functions in JPMCSurvey."""
@@ -56,8 +57,13 @@ class JPMCPlotsMixin:
continue continue
if len(value) > 3: if len(value) > 3:
# If more than 3 options selected, use count to keep slug short # If more than 3 options selected, create a hash of the sorted values
val_str = f"{len(value)}_grps" # This ensures uniqueness properly while keeping the slug short
sorted_vals = sorted([str(v) for v in value])
vals_str = "".join(sorted_vals)
# Create short 6-char hash
val_hash = hashlib.md5(vals_str.encode()).hexdigest()[:6]
val_str = f"{len(value)}_grps_{val_hash}"
else: else:
# Join values with '+' # Join values with '+'
clean_values = [] clean_values = []
@@ -196,9 +202,25 @@ class JPMCPlotsMixin:
path.mkdir(parents=True, exist_ok=True) path.mkdir(parents=True, exist_ok=True)
filename = f"{self._sanitize_filename(title)}.png" filename = f"{self._sanitize_filename(title)}.png"
filepath = path / filename
# Save using vl-convert backend # Use vl_convert directly with theme config for consistent rendering
chart.save(str(path / filename), format='png', scale_factor=2.0) import vl_convert as vlc
from theme import jpmc_altair_theme
# Get chart spec and theme config
chart_spec = chart.to_dict()
theme_config = jpmc_altair_theme()['config']
png_data = vlc.vegalite_to_png(
vl_spec=chart_spec,
scale=2.0,
ppi=72,
config=theme_config
)
with open(filepath, 'wb') as f:
f.write(png_data)
return chart return chart
@@ -282,7 +304,7 @@ class JPMCPlotsMixin:
# Combine layers # Combine layers
chart = (bars + text).properties( chart = (bars + text).properties(
title=title, title=title,
width=width if width else 'container', width=width or 800,
height=height or getattr(self, 'plot_height', 400) height=height or getattr(self, 'plot_height', 400)
) )
@@ -339,7 +361,7 @@ class JPMCPlotsMixin:
] ]
).add_params(selection).properties( ).add_params(selection).properties(
title=title, title=title,
width=width if width else 'container', width=width or 800,
height=height or getattr(self, 'plot_height', 400) height=height or getattr(self, 'plot_height', 400)
) )
@@ -399,7 +421,7 @@ class JPMCPlotsMixin:
] ]
).add_params(selection).properties( ).add_params(selection).properties(
title=title, title=title,
width=width if width else 'container', width=width or 800,
height=height or getattr(self, 'plot_height', 400) height=height or getattr(self, 'plot_height', 400)
) )
@@ -452,7 +474,7 @@ class JPMCPlotsMixin:
] ]
).properties( ).properties(
title=title, title=title,
width=width if width else 'container', width=width or 800,
height=height or getattr(self, 'plot_height', 400) height=height or getattr(self, 'plot_height', 400)
) )
@@ -493,7 +515,7 @@ class JPMCPlotsMixin:
chart = (bars + text).properties( chart = (bars + text).properties(
title=title, title=title,
width=width if width else 'container', width=width or 800,
height=height or getattr(self, 'plot_height', 400) height=height or getattr(self, 'plot_height', 400)
) )
@@ -550,7 +572,7 @@ class JPMCPlotsMixin:
] ]
).properties( ).properties(
title=title, title=title,
width=width if width else 'container', width=width or 800,
height=height or getattr(self, 'plot_height', 400) height=height or getattr(self, 'plot_height', 400)
) )
@@ -606,7 +628,7 @@ class JPMCPlotsMixin:
] ]
).properties( ).properties(
title=title, title=title,
width=width if width else 'container', width=width or 800,
height=height or getattr(self, 'plot_height', 400) height=height or getattr(self, 'plot_height', 400)
) )
@@ -663,9 +685,10 @@ class JPMCPlotsMixin:
else: else:
trait_description = "" trait_description = ""
# Horizontal bar chart # Horizontal bar chart - use x2 to explicitly start bars at x=1
bars = alt.Chart(stats).mark_bar(color=ColorPalette.PRIMARY).encode( bars = alt.Chart(stats).mark_bar(color=ColorPalette.PRIMARY).encode(
x=alt.X('mean_score:Q', title='Average Score (1-5)', scale=alt.Scale(domain=[1, 5])), x=alt.X('mean_score:Q', title='Average Score (1-5)', scale=alt.Scale(domain=[1, 5])),
x2=alt.datum(1), # Bars start at x=1 (left edge of domain)
y=alt.Y('Voice:N', title='Voice', sort='-x'), y=alt.Y('Voice:N', title='Voice', sort='-x'),
tooltip=[ tooltip=[
alt.Tooltip('Voice:N'), alt.Tooltip('Voice:N'),
@@ -674,24 +697,29 @@ class JPMCPlotsMixin:
] ]
) )
# Count text inside bars # Count text at end of bars (right-aligned inside bar)
text = bars.mark_text( text = alt.Chart(stats).mark_text(
align='center', align='right',
baseline='middle', baseline='middle',
color='white', color='white',
fontSize=16 fontSize=12,
dx=-5 # Slight padding from bar end
).encode( ).encode(
x='mean_score:Q',
y=alt.Y('Voice:N', sort='-x'),
text='count:Q' text='count:Q'
) )
# Combine # Combine layers
chart = (bars + text).properties( chart = (bars + text).properties(
title={ title={
"text": title, "text": title,
"subtitle": [trait_description, "(Numbers on bars indicate respondent count)"] "subtitle": [trait_description, "(Numbers on bars indicate respondent count)"]
}, },
width=width if width else 'container', width=width or 800,
height=height or getattr(self, 'plot_height', 400) height=height or getattr(self, 'plot_height', 400)
).configure_view(
strokeWidth=0 # Remove frame which might obscure labels
) )
chart = self._save_plot(chart, title) chart = self._save_plot(chart, title)
@@ -749,7 +777,7 @@ class JPMCPlotsMixin:
] ]
).properties( ).properties(
title=title, title=title,
width=width if width else 'container', width=width or 800,
height=height or 350 height=height or 350
) )
@@ -805,7 +833,7 @@ class JPMCPlotsMixin:
] ]
).properties( ).properties(
title=title, title=title,
width=width if width else 'container', width=width or 800,
height=height or 350 height=height or 350
) )