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
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user