Skip to content

Commit c187902

Browse files
jupyter: auto scale images (#1404)
1 parent add6620 commit c187902

8 files changed

Lines changed: 101 additions & 21 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* Add: Jupyter notebook images are auto-scaled down to fit the column width
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* Fix: `_stats/` request unique aggregated views instead of incorrectly calculating them on client

znai-jupyter/src/main/java/org/testingisdocumenting/znai/jupyter/JupyterOutput.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,4 @@ public record JupyterOutput(String format, String content) {
2222
public static final String SVG_FORMAT = "svg";
2323
public static final String IMG_FORMAT = "img";
2424
public static final String TEXT_FORMAT = "text";
25-
2625
}

znai-reactjs/src/doc-elements/images/AnnotatedImage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export function AnnotatedImage(props: AnnotatedImageProps) {
136136
const imageClassName =
137137
"znai-annotated-image" +
138138
(border ? " border" : "") +
139-
(fit ? " znai-image-fit" : "") +
139+
(fit && isScaledDown ? " znai-image-fit" : "") +
140140
(isScaledDown ? " znai-image-scaled-down" : "");
141141

142142
return (
@@ -249,7 +249,7 @@ export function AnnotatedImage(props: AnnotatedImageProps) {
249249
}
250250

251251
const singleColumnWidth = isMobile ? window.innerWidth : cssVarPixelValue("znai-single-column-full-width");
252-
return singleColumnWidth / width;
252+
return Math.min(1.0, singleColumnWidth / width);
253253
}
254254

255255
function renderTitle() {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2025 znai maintainers
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from "react";
18+
import { AnnotatedImage } from "../images/AnnotatedImage";
19+
import { useImageDimensions } from "./useImageDimensions";
20+
21+
interface Props {
22+
imageSrc: string;
23+
alt: string;
24+
elementsLibrary: any;
25+
}
26+
27+
export function JupyterImageWithFit({ imageSrc, alt, elementsLibrary }: Props) {
28+
const { dimensions, handleImageLoad } = useImageDimensions();
29+
30+
if (!dimensions) {
31+
return (
32+
<img src={imageSrc} alt={alt} onLoad={handleImageLoad} style={{ visibility: "hidden", position: "absolute" }} />
33+
);
34+
}
35+
36+
return (
37+
<AnnotatedImage
38+
imageSrc={imageSrc}
39+
width={dimensions.width}
40+
height={dimensions.height}
41+
fit={true}
42+
shapes={[]}
43+
alt={alt}
44+
elementsLibrary={elementsLibrary}
45+
/>
46+
);
47+
}
Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/*
2+
* Copyright 2025 znai maintainers
23
* Copyright 2019 TWO SIGMA OPEN SOURCE, LLC
34
*
45
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,14 +15,12 @@
1415
* limitations under the License.
1516
*/
1617

17-
import React from 'react'
18+
import React from 'react';
19+
import { JupyterImageWithFit } from './JupyterImageWithFit';
1820

19-
const JupyterImgCell = ({img}) => {
20-
return (
21-
<div className="jupyter-cell jupyter-img content-block">
22-
<img src={"data:image/png;base64," + img} alt="jupyter cell"/>
23-
</div>
24-
)
25-
}
21+
const JupyterImgCell = ({ img, elementsLibrary }) => {
22+
const imageSrc = "data:image/png;base64," + img;
23+
return <JupyterImageWithFit imageSrc={imageSrc} alt="jupyter output" elementsLibrary={elementsLibrary} />;
24+
};
2625

27-
export default JupyterImgCell
26+
export default JupyterImgCell;
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/*
2+
* Copyright 2025 znai maintainers
23
* Copyright 2019 TWO SIGMA OPEN SOURCE, LLC
34
*
45
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,14 +15,13 @@
1415
* limitations under the License.
1516
*/
1617

17-
import React from 'react'
18+
import React from 'react';
19+
import { JupyterImageWithFit } from './JupyterImageWithFit';
1820

19-
const JupyterSvgCell = ({svg, elementsLibrary}) => {
20-
return (
21-
<div className="jupyter-cell jupyter-svg content-block">
22-
<elementsLibrary.Svg svg={svg}/>
23-
</div>
24-
)
25-
}
21+
const JupyterSvgCell = ({ svg, elementsLibrary }) => {
22+
const base64Svg = btoa(unescape(encodeURIComponent(svg)));
23+
const imageSrc = "data:image/svg+xml;base64," + base64Svg;
24+
return <JupyterImageWithFit imageSrc={imageSrc} alt="jupyter svg output" elementsLibrary={elementsLibrary} />;
25+
};
2626

27-
export default JupyterSvgCell
27+
export default JupyterSvgCell;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2025 znai maintainers
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { useState, useCallback } from 'react';
18+
19+
interface Dimensions {
20+
width: number;
21+
height: number;
22+
}
23+
24+
export function useImageDimensions() {
25+
const [dimensions, setDimensions] = useState<Dimensions | null>(null);
26+
27+
const handleImageLoad = useCallback((event: React.SyntheticEvent<HTMLImageElement>) => {
28+
const { naturalWidth, naturalHeight } = event.currentTarget;
29+
setDimensions({ width: naturalWidth, height: naturalHeight });
30+
}, []);
31+
32+
return { dimensions, handleImageLoad };
33+
}

0 commit comments

Comments
 (0)