fixed saving png issue
This commit is contained in:
68
plots.py
68
plots.py
@@ -8,6 +8,7 @@ import pandas as pd
|
||||
import polars as pl
|
||||
from theme import ColorPalette
|
||||
|
||||
import hashlib
|
||||
|
||||
class JPMCPlotsMixin:
|
||||
"""Mixin class for plotting functions in JPMCSurvey."""
|
||||
@@ -56,8 +57,13 @@ class JPMCPlotsMixin:
|
||||
continue
|
||||
|
||||
if len(value) > 3:
|
||||
# If more than 3 options selected, use count to keep slug short
|
||||
val_str = f"{len(value)}_grps"
|
||||
# If more than 3 options selected, create a hash of the sorted values
|
||||
# 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:
|
||||
# Join values with '+'
|
||||
clean_values = []
|
||||
@@ -196,9 +202,25 @@ class JPMCPlotsMixin:
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
filename = f"{self._sanitize_filename(title)}.png"
|
||||
filepath = path / filename
|
||||
|
||||
# Save using vl-convert backend
|
||||
chart.save(str(path / filename), format='png', scale_factor=2.0)
|
||||
# Use vl_convert directly with theme config for consistent rendering
|
||||
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
|
||||
|
||||
@@ -282,7 +304,7 @@ class JPMCPlotsMixin:
|
||||
# Combine layers
|
||||
chart = (bars + text).properties(
|
||||
title=title,
|
||||
width=width if width else 'container',
|
||||
width=width or 800,
|
||||
height=height or getattr(self, 'plot_height', 400)
|
||||
)
|
||||
|
||||
@@ -339,7 +361,7 @@ class JPMCPlotsMixin:
|
||||
]
|
||||
).add_params(selection).properties(
|
||||
title=title,
|
||||
width=width if width else 'container',
|
||||
width=width or 800,
|
||||
height=height or getattr(self, 'plot_height', 400)
|
||||
)
|
||||
|
||||
@@ -399,7 +421,7 @@ class JPMCPlotsMixin:
|
||||
]
|
||||
).add_params(selection).properties(
|
||||
title=title,
|
||||
width=width if width else 'container',
|
||||
width=width or 800,
|
||||
height=height or getattr(self, 'plot_height', 400)
|
||||
)
|
||||
|
||||
@@ -452,7 +474,7 @@ class JPMCPlotsMixin:
|
||||
]
|
||||
).properties(
|
||||
title=title,
|
||||
width=width if width else 'container',
|
||||
width=width or 800,
|
||||
height=height or getattr(self, 'plot_height', 400)
|
||||
)
|
||||
|
||||
@@ -493,7 +515,7 @@ class JPMCPlotsMixin:
|
||||
|
||||
chart = (bars + text).properties(
|
||||
title=title,
|
||||
width=width if width else 'container',
|
||||
width=width or 800,
|
||||
height=height or getattr(self, 'plot_height', 400)
|
||||
)
|
||||
|
||||
@@ -550,7 +572,7 @@ class JPMCPlotsMixin:
|
||||
]
|
||||
).properties(
|
||||
title=title,
|
||||
width=width if width else 'container',
|
||||
width=width or 800,
|
||||
height=height or getattr(self, 'plot_height', 400)
|
||||
)
|
||||
|
||||
@@ -606,7 +628,7 @@ class JPMCPlotsMixin:
|
||||
]
|
||||
).properties(
|
||||
title=title,
|
||||
width=width if width else 'container',
|
||||
width=width or 800,
|
||||
height=height or getattr(self, 'plot_height', 400)
|
||||
)
|
||||
|
||||
@@ -663,9 +685,10 @@ class JPMCPlotsMixin:
|
||||
else:
|
||||
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(
|
||||
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'),
|
||||
tooltip=[
|
||||
alt.Tooltip('Voice:N'),
|
||||
@@ -674,24 +697,29 @@ class JPMCPlotsMixin:
|
||||
]
|
||||
)
|
||||
|
||||
# Count text inside bars
|
||||
text = bars.mark_text(
|
||||
align='center',
|
||||
# Count text at end of bars (right-aligned inside bar)
|
||||
text = alt.Chart(stats).mark_text(
|
||||
align='right',
|
||||
baseline='middle',
|
||||
color='white',
|
||||
fontSize=16
|
||||
fontSize=12,
|
||||
dx=-5 # Slight padding from bar end
|
||||
).encode(
|
||||
x='mean_score:Q',
|
||||
y=alt.Y('Voice:N', sort='-x'),
|
||||
text='count:Q'
|
||||
)
|
||||
|
||||
# Combine
|
||||
# Combine layers
|
||||
chart = (bars + text).properties(
|
||||
title={
|
||||
"text": title,
|
||||
"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)
|
||||
).configure_view(
|
||||
strokeWidth=0 # Remove frame which might obscure labels
|
||||
)
|
||||
|
||||
chart = self._save_plot(chart, title)
|
||||
@@ -749,7 +777,7 @@ class JPMCPlotsMixin:
|
||||
]
|
||||
).properties(
|
||||
title=title,
|
||||
width=width if width else 'container',
|
||||
width=width or 800,
|
||||
height=height or 350
|
||||
)
|
||||
|
||||
@@ -805,7 +833,7 @@ class JPMCPlotsMixin:
|
||||
]
|
||||
).properties(
|
||||
title=title,
|
||||
width=width if width else 'container',
|
||||
width=width or 800,
|
||||
height=height or 350
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user