Skip to content

Commit a80f73d

Browse files
cell renderings attribute with light and dark
1 parent 7071969 commit a80f73d

File tree

7 files changed

+267
-1
lines changed

7 files changed

+267
-1
lines changed

news/changelog-1.7.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ All changes included in 1.7:
4343

4444
## `html` format
4545

46+
- ([#12277](https://github.com/quarto-dev/quarto-cli/pull/12277)): Provide light and dark plot and table renderings with `renderings: [light,dark]`
4647
- ([#11860](https://github.com/quarto-dev/quarto-cli/issues/11860)): ES6 modules that import other local JS modules in documents with `embed-resources: true` are now correctly embedded.
4748

4849
## `pdf` format

src/resources/filters/main.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import("./quarto-init/knitr-fixup.lua")
6161
import("./quarto-post/render-asciidoc.lua")
6262
import("./quarto-post/book.lua")
6363
import("./quarto-post/cites.lua")
64+
import("./quarto-post/cell_renderings.lua")
6465
import("./quarto-post/delink.lua")
6566
import("./quarto-post/docx.lua")
6667
import("./quarto-post/fig-cleanup.lua")
@@ -393,6 +394,9 @@ local quarto_post_filters = {
393394
},
394395
traverser = 'jog',
395396
},
397+
{ name = "post-choose-cell_renderings",
398+
filter = choose_cell_renderings()
399+
},
396400
{ name = "post-landscape-div",
397401
filter = landscape_div(),
398402
flags = { "has_landscape" },
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
function choose_cell_renderings()
2+
function jsonDecodeArray(json)
3+
if json:sub(1, 1) == '[' then
4+
return quarto.json.decode(json)
5+
elseif json:sub(1, 1) == '{' then
6+
quarto.log.warning('expected array or scalar', json)
7+
else
8+
return {json}
9+
end
10+
end
11+
12+
return {
13+
Div = function(div)
14+
-- Only process cell div with renderings attr
15+
if not div.classes:includes("cell") or not div.attributes["renderings"] then
16+
return nil
17+
end
18+
local renderingsJson = div.attributes['renderings']
19+
local renderings = jsonDecodeArray(renderingsJson)
20+
if not type(renderings) == "table" or #renderings == 0 then
21+
quarto.log.warning("renderings expected array of rendering names, got", renderings)
22+
return nil
23+
end
24+
local cods = {}
25+
for i, cellOutput in ipairs(div.content) do
26+
if cellOutput.classes:includes("cell-output-display") then
27+
table.insert(cods, cellOutput)
28+
end
29+
end
30+
31+
if #cods ~= #renderings then
32+
quarto.log.warning("need", #renderings, "cell-output-display for renderings", table.concat(renderings, ",") .. ";", "got", #cods)
33+
return nil
34+
end
35+
36+
local outputs = {}
37+
for i, r in ipairs(renderings) do
38+
outputs[r] = cods[i]
39+
end
40+
local lightDiv = outputs['light']
41+
local darkDiv = outputs['dark']
42+
43+
if quarto.format.isHtmlOutput() then
44+
div.content = pandoc.Blocks({
45+
pandoc.Div(lightDiv.content, pandoc.Attr("", {'light-content'}, {})),
46+
pandoc.Div(darkDiv.content, pandoc.Attr("", {'dark-content'}, {}))
47+
})
48+
else
49+
div.content = pandoc.Blocks({lightDiv})
50+
end
51+
return div
52+
end
53+
}
54+
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
body.quarto-light .dark-content {
2+
display: none;
3+
}
4+
5+
body.quarto-dark .light-content {
6+
display: none;
7+
}

src/resources/formats/html/bootstrap/dist/scss/bootstrap.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
@import "mixins/banner";
22
@include bsBanner("");
33

4-
54
// scss-docs-start import-stack
65
// Configuration
76
@import "variables-dark";
@@ -14,6 +13,7 @@
1413
@import "type";
1514
@import "images";
1615
@import "containers";
16+
@import "light-dark";
1717
@import "grid";
1818
@import "tables";
1919
@import "forms";

src/resources/schema/cell-attributes.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
schema: string
1111
description: "Classes to apply to cell container"
1212

13+
- name: renderings
14+
schema:
15+
arrayOf: string
16+
description: "Array of rendering names"
17+
1318
- name: tags
1419
tags:
1520
engine: jupyter
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
---
2+
title: "jupyter dark mode - matplotlib"
3+
engine: jupyter
4+
format:
5+
html:
6+
theme:
7+
light: united
8+
dark: slate
9+
keep-md: true
10+
_quarto:
11+
tests:
12+
html:
13+
ensureHtmlElements:
14+
-
15+
- 'div.light-content'
16+
- 'div.dark-content'
17+
- []
18+
---
19+
20+
```{python}
21+
#| echo: false
22+
import yaml
23+
import tempfile
24+
import os
25+
26+
def apply_mpl_colors(bgcolor, fgcolor, primarycolor):
27+
fd, name = tempfile.mkstemp("mplstyle")
28+
os.close(fd)
29+
with open(name, "w") as out:
30+
out.write("axes.facecolor: \"%s\"\n" % bgcolor)
31+
out.write("axes.edgecolor: \"%s\"\n" % fgcolor)
32+
out.write("axes.labelcolor: \"%s\"\n" % fgcolor)
33+
out.write("axes.titlecolor: \"%s\"\n" % fgcolor)
34+
out.write("figure.facecolor: \"%s\"\n" % bgcolor)
35+
out.write("figure.edgecolor: \"%s\"\n" % fgcolor)
36+
out.write("text.color: \"%s\"\n" % fgcolor)
37+
out.write("xtick.color: \"%s\"\n" % fgcolor)
38+
out.write("ytick.color: \"%s\"\n" % fgcolor)
39+
# seems to require named color, is there a better way?
40+
out.write("axes.prop_cycle: cycler('color', ['%s'])" % primarycolor)
41+
plt.style.use(name)
42+
os.unlink(name)
43+
44+
def united_colors():
45+
apply_mpl_colors("#ffffff", "#333333", "red")
46+
47+
def slate_colors():
48+
apply_mpl_colors("#282B30", "#aaaaaa", "white")
49+
```
50+
51+
### No crossref or caption
52+
```{python}
53+
#| echo: false
54+
#| renderings: [light, dark]
55+
import numpy as np
56+
import matplotlib.pyplot as plt
57+
58+
# Parameters for the normal distribution
59+
mean = 0
60+
std_dev = 1
61+
62+
# Generate data
63+
x = np.linspace(mean - 4*std_dev, mean + 4*std_dev, 1000)
64+
y = (1/(std_dev * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mean) / std_dev)**2)
65+
66+
# Plotting
67+
united_colors()
68+
plt.figure(figsize=(8, 5))
69+
plt.plot(x, y, label='Normal Distribution')
70+
plt.title('Normal Distribution Curve')
71+
plt.xlabel('X-axis')
72+
plt.ylabel('Probability Density')
73+
plt.legend()
74+
plt.grid(True)
75+
plt.show()
76+
77+
slate_colors()
78+
plt.figure(figsize=(8, 5))
79+
plt.plot(x, y, label='Normal Distribution')
80+
plt.title('Normal Distribution Curve')
81+
plt.xlabel('X-axis')
82+
plt.ylabel('Probability Density')
83+
plt.legend()
84+
plt.grid(True)
85+
plt.show()
86+
```
87+
88+
### With crossref but no caption
89+
90+
::: {#fig-matplotlib-line}
91+
```{python}
92+
#| echo: false
93+
#| renderings: [light, dark]
94+
import matplotlib.pyplot as plt
95+
96+
united_colors()
97+
plt.title("Hello")
98+
plt.plot([1,2,3])
99+
plt.grid(True)
100+
plt.show(block=False)
101+
102+
slate_colors()
103+
plt.figure()
104+
plt.title("Hello")
105+
plt.plot([1,2,3])
106+
plt.grid(True)
107+
plt.show(block=False)
108+
```
109+
:::
110+
111+
### With caption but no crossref
112+
113+
::: {}
114+
```{python}
115+
#| echo: false
116+
#| renderings: [light, dark]
117+
118+
# author: "anthropic claude-3-5-sonnet-20240620"
119+
import numpy as np
120+
import matplotlib.pyplot as plt
121+
122+
# Generate data points
123+
x = np.linspace(0, 2 * np.pi, 100)
124+
y = np.sin(x)
125+
126+
united_colors()
127+
plt.figure(figsize=(10, 6))
128+
plt.plot(x, y)
129+
plt.title('Sine Wave')
130+
plt.xlabel('x')
131+
plt.ylabel('sin(x)')
132+
plt.grid(True)
133+
plt.axhline(y=0, color='k', linestyle='--')
134+
plt.axvline(x=0, color='k', linestyle='--')
135+
plt.show()
136+
137+
slate_colors()
138+
plt.figure(figsize=(10, 6))
139+
plt.plot(x, y)
140+
plt.title('Sine Wave')
141+
plt.xlabel('x')
142+
plt.ylabel('sin(x)')
143+
plt.grid(True)
144+
plt.axhline(y=0, color='k', linestyle='--')
145+
plt.axvline(x=0, color='k', linestyle='--')
146+
plt.show()
147+
```
148+
matplotlib sine wave
149+
150+
:::
151+
152+
### With crossref and caption
153+
154+
::: {#fig-matplotlib-cosine}
155+
```{python}
156+
#| echo: false
157+
#| renderings: [dark, light]
158+
import numpy as np
159+
import matplotlib.pyplot as plt
160+
161+
# Generate data points
162+
x = np.linspace(0, 2 * np.pi, 100)
163+
y = np.cos(x)
164+
165+
# Create the plot
166+
slate_colors()
167+
plt.figure(figsize=(10, 6))
168+
plt.plot(x, y)
169+
plt.title('Cosine Wave')
170+
plt.xlabel('x')
171+
plt.ylabel('cos(x)')
172+
plt.grid(True)
173+
plt.axhline(y=0, color='k', linestyle='--')
174+
plt.axvline(x=0, color='k', linestyle='--')
175+
plt.show()
176+
177+
united_colors()
178+
plt.figure(figsize=(10, 6))
179+
plt.plot(x, y)
180+
plt.title('Cosine Wave')
181+
plt.xlabel('x')
182+
plt.ylabel('cos(x)')
183+
plt.grid(True)
184+
plt.axhline(y=0, color='k', linestyle='--')
185+
plt.axvline(x=0, color='k', linestyle='--')
186+
plt.show()
187+
```
188+
189+
matplotlib cosine wave
190+
:::
191+
192+
Here's a [link](https://example.com).
193+
194+
195+
{{< lipsum 3 >}}

0 commit comments

Comments
 (0)