Skip to content

Commit 32a7470

Browse files
MarkusNeusingerclaude[bot]github-actions[bot]
authored
feat: add histogram-basic implementation (9 libraries) (#512)
## Summary Adds `histogram-basic` plot implementation. ### Libraries - **Merged:** 9 (all libraries) - **Not Feasible:** 0 ### Links - **Spec:** `specs/histogram-basic.md` - **Parent Issue:** #204 --- :robot: *Auto-generated by pyplots CI* --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
1 parent e1a2fe9 commit 32a7470

File tree

9 files changed

+388
-101
lines changed

9 files changed

+388
-101
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""
2+
histogram-basic: Basic Histogram
3+
Library: altair
4+
"""
5+
6+
import altair as alt
7+
import numpy as np
8+
import pandas as pd
9+
10+
11+
# Data
12+
np.random.seed(42)
13+
data = pd.DataFrame({"value": np.random.normal(100, 15, 500)})
14+
15+
# Create histogram chart
16+
chart = (
17+
alt.Chart(data)
18+
.mark_bar(color="#306998", opacity=0.8)
19+
.encode(alt.X("value:Q", bin=alt.Bin(maxbins=30), title="Value"), alt.Y("count()", title="Frequency"))
20+
.properties(width=1600, height=900, title="Basic Histogram")
21+
.configure_axis(labelFontSize=16, titleFontSize=20)
22+
.configure_title(fontSize=20)
23+
)
24+
25+
# Save as PNG (1600 × 900 at scale 3 = 4800 × 2700)
26+
chart.save("plot.png", scale_factor=3.0)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
histogram-basic: Basic Histogram
3+
Library: bokeh
4+
"""
5+
6+
import numpy as np
7+
from bokeh.io import export_png
8+
from bokeh.plotting import figure
9+
10+
11+
# Data - 500 normally distributed values (mean=100, std=15)
12+
np.random.seed(42)
13+
values = np.random.normal(100, 15, 500)
14+
15+
# Compute histogram bins
16+
hist, edges = np.histogram(values, bins=30)
17+
18+
# Create figure (4800 x 2700 px for high resolution)
19+
p = figure(width=4800, height=2700, title="Basic Histogram", x_axis_label="Value", y_axis_label="Frequency")
20+
21+
# Draw histogram using quad glyph
22+
p.quad(
23+
top=hist,
24+
bottom=0,
25+
left=edges[:-1],
26+
right=edges[1:],
27+
fill_color="#306998",
28+
fill_alpha=0.7,
29+
line_color="white",
30+
line_width=1,
31+
)
32+
33+
# Style title
34+
p.title.text_font_size = "20pt"
35+
p.title.align = "center"
36+
37+
# Style axis labels
38+
p.xaxis.axis_label_text_font_size = "20pt"
39+
p.yaxis.axis_label_text_font_size = "20pt"
40+
p.xaxis.major_label_text_font_size = "16pt"
41+
p.yaxis.major_label_text_font_size = "16pt"
42+
43+
# Style grid - subtle
44+
p.xgrid.grid_line_alpha = 0.3
45+
p.ygrid.grid_line_alpha = 0.3
46+
47+
# Ensure y-axis starts at zero
48+
p.y_range.start = 0
49+
50+
# Save output
51+
export_png(p, filename="plot.png")
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""
2+
histogram-basic: Basic Histogram
3+
Library: highcharts
4+
"""
5+
6+
import tempfile
7+
import time
8+
import urllib.request
9+
from pathlib import Path
10+
11+
import numpy as np
12+
from highcharts_core.chart import Chart
13+
from highcharts_core.options import HighchartsOptions
14+
from highcharts_core.options.series.bar import ColumnSeries
15+
from selenium import webdriver
16+
from selenium.webdriver.chrome.options import Options
17+
18+
19+
# Data
20+
np.random.seed(42)
21+
values = np.random.normal(100, 15, 500) # 500 values, mean=100, std=15
22+
23+
# Calculate histogram bins
24+
bins = 30
25+
counts, bin_edges = np.histogram(values, bins=bins)
26+
27+
# Create bin labels (center of each bin)
28+
bin_centers = [(bin_edges[i] + bin_edges[i + 1]) / 2 for i in range(len(counts))]
29+
bin_labels = [f"{bin_edges[i]:.1f}-{bin_edges[i + 1]:.1f}" for i in range(len(counts))]
30+
31+
# Create chart with container ID
32+
chart = Chart(container="container")
33+
chart.options = HighchartsOptions()
34+
35+
# Chart configuration
36+
chart.options.chart = {
37+
"type": "column",
38+
"width": 4800,
39+
"height": 2700,
40+
"backgroundColor": "#ffffff",
41+
"spacingBottom": 120, # Add space for x-axis title
42+
}
43+
44+
# Title
45+
chart.options.title = {"text": "Basic Histogram", "style": {"fontSize": "48px", "fontWeight": "bold"}}
46+
47+
# X-axis configuration
48+
chart.options.x_axis = {
49+
"categories": bin_labels,
50+
"title": {"text": "Value", "style": {"fontSize": "40px"}},
51+
"labels": {
52+
"rotation": 315, # 315 degrees = -45 degrees
53+
"style": {"fontSize": "28px"},
54+
"step": 3, # Show every 3rd label to avoid overlap
55+
},
56+
}
57+
58+
# Y-axis configuration
59+
chart.options.y_axis = {
60+
"title": {"text": "Frequency", "style": {"fontSize": "40px"}},
61+
"min": 0,
62+
"gridLineWidth": 1,
63+
"gridLineDashStyle": "Dot",
64+
"gridLineColor": "rgba(0, 0, 0, 0.15)",
65+
"labels": {"style": {"fontSize": "32px"}},
66+
}
67+
68+
# Create series with histogram data
69+
series = ColumnSeries()
70+
series.data = counts.tolist()
71+
series.name = "Frequency"
72+
series.color = "#306998" # Python Blue
73+
series.border_color = "white"
74+
series.border_width = 1
75+
76+
# Plot options for histogram appearance
77+
chart.options.plot_options = {"column": {"pointPadding": 0, "groupPadding": 0, "borderWidth": 1, "opacity": 0.8}}
78+
79+
chart.add_series(series)
80+
81+
# Legend (single series, hide)
82+
chart.options.legend = {"enabled": False}
83+
84+
# Credits
85+
chart.options.credits = {"enabled": False}
86+
87+
# Download Highcharts JS (required for headless Chrome)
88+
highcharts_url = "https://code.highcharts.com/highcharts.js"
89+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
90+
highcharts_js = response.read().decode("utf-8")
91+
92+
# Generate HTML with inline scripts
93+
html_str = chart.to_js_literal()
94+
html_content = f"""<!DOCTYPE html>
95+
<html>
96+
<head>
97+
<meta charset="utf-8">
98+
<script>{highcharts_js}</script>
99+
</head>
100+
<body style="margin:0;">
101+
<div id="container" style="width: 4800px; height: 2700px;"></div>
102+
<script>{html_str}</script>
103+
</body>
104+
</html>"""
105+
106+
# Write temp HTML and take screenshot
107+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
108+
f.write(html_content)
109+
temp_path = f.name
110+
111+
chrome_options = Options()
112+
chrome_options.add_argument("--headless")
113+
chrome_options.add_argument("--no-sandbox")
114+
chrome_options.add_argument("--disable-dev-shm-usage")
115+
chrome_options.add_argument("--disable-gpu")
116+
chrome_options.add_argument("--window-size=4800,2800") # Slightly larger to capture full chart
117+
118+
driver = webdriver.Chrome(options=chrome_options)
119+
driver.get(f"file://{temp_path}")
120+
time.sleep(5) # Wait for chart to render
121+
driver.save_screenshot("plot.png")
122+
driver.quit()
123+
124+
Path(temp_path).unlink() # Clean up temp file
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""
2+
histogram-basic: Basic Histogram
3+
Library: letsplot
4+
"""
5+
6+
import numpy as np
7+
import pandas as pd
8+
from lets_plot import LetsPlot, aes, element_text, geom_histogram, ggplot, ggsave, ggsize, labs, theme, theme_minimal
9+
10+
11+
LetsPlot.setup_html()
12+
13+
# Data
14+
np.random.seed(42)
15+
data = pd.DataFrame({"value": np.random.normal(100, 15, 500)})
16+
17+
# Plot
18+
plot = (
19+
ggplot(data, aes(x="value"))
20+
+ geom_histogram(bins=30, fill="#306998", color="white", alpha=0.8)
21+
+ labs(x="Value", y="Frequency", title="Basic Histogram")
22+
+ theme_minimal()
23+
+ theme(plot_title=element_text(size=20), axis_title=element_text(size=20), axis_text=element_text(size=16))
24+
+ ggsize(1600, 900)
25+
)
26+
27+
# Save - scale 3x to get 4800 x 2700 px
28+
ggsave(plot, "plot.png", path=".", scale=3)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""
2+
histogram-basic: Basic Histogram
3+
Library: matplotlib
4+
"""
5+
6+
import matplotlib.pyplot as plt
7+
import numpy as np
8+
import pandas as pd
9+
10+
11+
# Data
12+
np.random.seed(42)
13+
data = pd.DataFrame({"value": np.random.normal(100, 15, 500)})
14+
15+
# Create plot
16+
fig, ax = plt.subplots(figsize=(16, 9))
17+
ax.hist(data["value"], bins=30, alpha=0.8, color="#306998", edgecolor="white", linewidth=0.5)
18+
19+
# Labels and styling
20+
ax.set_xlabel("Value", fontsize=20)
21+
ax.set_ylabel("Frequency", fontsize=20)
22+
ax.set_title("Basic Histogram", fontsize=20)
23+
ax.tick_params(axis="both", labelsize=16)
24+
ax.grid(True, alpha=0.3, axis="y")
25+
26+
plt.tight_layout()
27+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""
2+
histogram-basic: Basic Histogram
3+
Library: plotly
4+
"""
5+
6+
import numpy as np
7+
import plotly.graph_objects as go
8+
9+
10+
# Data
11+
np.random.seed(42)
12+
values = np.random.normal(100, 15, 500) # 500 values, mean=100, std=15
13+
14+
# Create figure
15+
fig = go.Figure()
16+
fig.add_trace(go.Histogram(x=values, marker={"color": "#306998", "line": {"color": "white", "width": 1}}, opacity=0.85))
17+
18+
# Layout
19+
fig.update_layout(
20+
title={"text": "Basic Histogram", "font": {"size": 40}, "x": 0.5, "xanchor": "center"},
21+
xaxis_title="Value",
22+
yaxis_title="Frequency",
23+
template="plotly_white",
24+
font={"size": 32},
25+
xaxis={"title_font": {"size": 40}, "tickfont": {"size": 32}, "showgrid": True, "gridcolor": "rgba(0,0,0,0.1)"},
26+
yaxis={"title_font": {"size": 40}, "tickfont": {"size": 32}, "showgrid": True, "gridcolor": "rgba(0,0,0,0.1)"},
27+
bargap=0.05,
28+
)
29+
30+
# Save
31+
fig.write_image("plot.png", width=1600, height=900, scale=3)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""
2+
histogram-basic: Basic Histogram
3+
Library: plotnine
4+
"""
5+
6+
import numpy as np
7+
import pandas as pd
8+
from plotnine import aes, element_text, geom_histogram, ggplot, labs, theme, theme_minimal
9+
10+
11+
# Data
12+
np.random.seed(42)
13+
data = pd.DataFrame({"value": np.random.normal(100, 15, 500)})
14+
15+
# Plot
16+
plot = (
17+
ggplot(data, aes(x="value"))
18+
+ geom_histogram(bins=30, fill="#306998", color="white", alpha=0.8)
19+
+ labs(x="Value", y="Frequency", title="Basic Histogram")
20+
+ theme_minimal()
21+
+ theme(
22+
figure_size=(16, 9),
23+
plot_title=element_text(size=20),
24+
axis_title=element_text(size=20),
25+
axis_text=element_text(size=16),
26+
)
27+
)
28+
29+
# Save
30+
plot.save("plot.png", dpi=300)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
histogram-basic: Basic Histogram
3+
Library: pygal
4+
"""
5+
6+
import numpy as np
7+
import pygal
8+
from pygal.style import Style
9+
10+
11+
# Data
12+
np.random.seed(42)
13+
values = np.random.normal(100, 15, 500) # 500 values, mean=100, std=15
14+
15+
# Calculate histogram bins
16+
counts, bin_edges = np.histogram(values, bins=20)
17+
18+
# Convert to pygal histogram format: (height, start, end)
19+
histogram_data = [(int(count), float(bin_edges[i]), float(bin_edges[i + 1])) for i, count in enumerate(counts)]
20+
21+
# Custom style matching default style guide colors
22+
custom_style = Style(
23+
background="white",
24+
plot_background="white",
25+
foreground="#333333",
26+
foreground_strong="#333333",
27+
foreground_subtle="#666666",
28+
opacity="0.8",
29+
opacity_hover="0.9",
30+
colors=("#306998",), # Python Blue
31+
guide_stroke_color="#cccccc",
32+
major_guide_stroke_color="#cccccc",
33+
title_font_size=48,
34+
label_font_size=36,
35+
major_label_font_size=36,
36+
)
37+
38+
# Create histogram chart
39+
chart = pygal.Histogram(
40+
width=4800,
41+
height=2700,
42+
title="Basic Histogram",
43+
x_title="Value",
44+
y_title="Frequency",
45+
style=custom_style,
46+
show_legend=False,
47+
show_y_guides=True,
48+
show_x_guides=False,
49+
)
50+
51+
# Add histogram data
52+
chart.add("Distribution", histogram_data)
53+
54+
# Save to PNG
55+
chart.render_to_png("plot.png")

0 commit comments

Comments
 (0)