Skip to content

Commit dc905e8

Browse files
Support w, h and unit parameters in ggsave(). Implement issue #281
1 parent 480ca7e commit dc905e8

File tree

2 files changed

+86
-14
lines changed

2 files changed

+86
-14
lines changed

future_changes.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ GeoTools [v 33.2](https://github.com/geotools/geotools/releases/tag/33.2)
55

66
### Added
77

8+
- ggsave():
9+
- support font synthesis for *italic* and **bold** styles.
10+
- `w`, `h` and `unit` parameters support [[#281]https://github.com/JetBrains/lets-plot-kotlin/issues/281],
11+
[[#1368](https://github.com/JetBrains/lets-plot/issues/1368)].
12+
813
### Changed
914

1015
- `facetWrap()` now drops factor levels that do not appear in the data (i.e., empty panels) by default [[#1322](https://github.com/JetBrains/lets-plot/issues/1322)]. <br>

plot-api/src/jvmMain/kotlin/org/jetbrains/letsPlot/export/ggsave.kt

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ package org.jetbrains.letsPlot.export
77

88
import org.jetbrains.letsPlot.Figure
99
import org.jetbrains.letsPlot.awt.plot.PlotSvgExport
10+
import org.jetbrains.letsPlot.commons.geometry.DoubleVector
11+
import org.jetbrains.letsPlot.core.util.PlotExportCommon
1012
import org.jetbrains.letsPlot.core.util.PlotHtmlExport
1113
import org.jetbrains.letsPlot.core.util.PlotHtmlHelper.scriptUrl
1214
import org.jetbrains.letsPlot.intern.toSpec
13-
import java.lang.Double.NaN
1415
import java.nio.file.Path
1516
import java.util.*
1617
import kotlin.io.path.*
@@ -22,29 +23,72 @@ private const val DEF_EXPORT_DIR = "lets-plot-images"
2223
* Supported formats: SVG, HTML, PNG, JPEG and TIFF.
2324
* Note: in some configurations raster formats might not be supported.
2425
*
25-
* The exported file is created in directory ${user.dir}/lets-plot-images
26-
* if not specified otherwise (see the `path` parameter).
26+
* If [path] is not specified, the output file will be saved in
27+
* `${user.dir}/lets-plot-images`.
2728
*
2829
* ## Examples
2930
*
3031
* - [export_to_file.ipynb](https://nbviewer.org/github/JetBrains/lets-plot-docs/blob/master/source/kotlin_examples/cookbook/export_to_file.ipynb)
3132
*
3233
* @param plot Plot to export.
33-
* @param filename The name of file. It mast end with file extention corresponding
34-
* to one of the supported formats: svg, html (or htm), png, jpeg (or jpg) or tiff (or tif)
34+
* @param filename Name of the file. Must include an extension matching
35+
* one of the supported formats: `.svg`, `.html`/`.htm`, `.png`, `.jpeg`/`.jpg`, or `.tiff`/`.tif`
3536
* @param scale Scaling factor (only for raster formats). Default: 2.0
36-
* @param dpi Dot-per-Inch value to store in the exported file metadata (only for raster formats).
37+
* @param w Width of the output image in units.
38+
* @param h Height of the output image in units.
39+
* @param unit Unit of the output image. One of: `"in"`, `"cm"`, `"mm"`, `"px"`.
40+
* Only applicable when exporting to SVG, PNG, JPG, or TIFF.
41+
* @param dpi Resolution in dots per inch to store in the exported file metadata.
42+
* Only applicable when exporting to the raster formats: PNG, JPG, or TIFF.
3743
* Default: no metadata is stored.
3844
* @param path Path to a directory to save image files in.
3945
* Default: `${user.dir}/lets-plot-images`
4046
*
41-
* @return Absolute pathname of created file.
47+
* @return Absolute pathname of the created file.
48+
*
49+
* ### Notes
50+
*
51+
* The output format is inferred from the file extension.
52+
*
53+
* **For PNG and PDF:**
54+
*
55+
* - If [w], [h], [unit], and [dpi] are all specified:
56+
*
57+
* - The plot's pixel size (default or set via [ggsize()](https://lets-plot.org/kotlin/api-reference/-lets--plot--kotlin/org.jetbrains.letsPlot/ggsize.html) is ignored.
58+
* - The output size is computed from the given dimensions and DPI.
59+
* - The plot is resized to fit the specified [w] x [h] area, which may affect the layout.
60+
*
61+
* - If only [dpi] is specified:
62+
* - The plot's pixel size (default or set via [ggsize()](https://lets-plot.org/kotlin/api-reference/-lets--plot--kotlin/org.jetbrains.letsPlot/ggsize.html)) is converted to inches assuming the standard display PPI of 96 PPI.
63+
* - The output size is computed from this size and [dpi].
64+
* - The plot maintains its aspect ratio, preserving layout, tick labels, and other visual elements.
65+
* - Useful for printing - the plot will appear nearly the same size as on screen.
66+
*
67+
* - If [w] and [h] are not specified:
68+
* - The [scale] parameter is used to determine the output size.
69+
*
70+
* - The plot maintains its aspect ratio, preserving layout, tick labels, and other visual elements.
71+
* - Useful for generating high-resolution images suitable for publication.
72+
*
73+
* **For SVG:**
74+
* - If [w], [h], and [unit] are specified:
75+
* - The plot's pixel size (default or set via [ggsize()](https://lets-plot.org/kotlin/api-reference/-lets--plot--kotlin/org.jetbrains.letsPlot/ggsize.html)) is ignored.
76+
* - The output size is set from the given values.
77+
*
78+
* **For HTML:**
79+
* - If [w] and [h] are specified:
80+
* - The plot's pixel size (default or set via [ggsize()](https://lets-plot.org/kotlin/api-reference/-lets--plot--kotlin/org.jetbrains.letsPlot/ggsize.html)) is ignored.
81+
* - The output size is determined directly from the specified [w] and [h], which are treated as pixel values.
82+
*
4283
*/
4384
@Suppress("SpellCheckingInspection")
4485
fun ggsave(
4586
plot: Figure,
4687
filename: String,
47-
scale: Number = 2,
88+
scale: Number? = null,
89+
w: Number? = null,
90+
h: Number? = null,
91+
unit: String? = null,
4892
dpi: Number? = null,
4993
path: String? = null
5094
): String {
@@ -65,10 +109,16 @@ fun ggsave(
65109
val file = dir.resolve(filename)
66110

67111
val spec: MutableMap<String, Any> = plot.toSpec()
112+
val sizeUnit = PlotExportCommon.SizeUnit.fromName(unit ?: "")
113+
val plotSize = toDoubleVector(w, h)
68114

69115
when (ext) {
70116
"svg" -> {
71-
val svg = PlotSvgExport.buildSvgImageFromRawSpecs(spec)
117+
val svg = PlotSvgExport.buildSvgImageFromRawSpecs(
118+
spec,
119+
plotSize = plotSize,
120+
sizeUnit = sizeUnit?: PlotExportCommon.SizeUnit.PX,
121+
)
72122
if (file.notExists()) {
73123
file.createFile()
74124
}
@@ -78,8 +128,9 @@ fun ggsave(
78128
"html", "htm" -> {
79129
val html = PlotHtmlExport.buildHtmlFromRawSpecs(
80130
spec,
131+
scriptUrl = scriptUrl(VersionChecker.letsPlotJsVersion),
81132
iFrame = true,
82-
scriptUrl = scriptUrl(VersionChecker.letsPlotJsVersion)
133+
plotSize = plotSize
83134
)
84135
if (file.notExists()) {
85136
file.createFile()
@@ -91,8 +142,10 @@ fun ggsave(
91142
exportRasterImage(
92143
spec,
93144
file,
94-
scalingFactor = scale.toDouble(),
95-
targetDPI = dpi?.toDouble() ?: NaN
145+
scalingFactor = scale,
146+
plotSize = plotSize,
147+
unit = sizeUnit,
148+
targetDPI = dpi
96149
)
97150
}
98151

@@ -107,11 +160,23 @@ fun ggsave(
107160
return file.toRealPath().toString()
108161
}
109162

163+
164+
private fun toDoubleVector(x: Number?, y: Number?): DoubleVector? {
165+
return if (x != null && y != null) {
166+
DoubleVector(x.toDouble(), y.toDouble())
167+
} else {
168+
null
169+
}
170+
}
171+
172+
110173
private fun exportRasterImage(
111174
spec: MutableMap<String, Any>,
112175
file: Path,
113-
scalingFactor: Double,
114-
targetDPI: Double
176+
scalingFactor: Number? = null,
177+
plotSize: DoubleVector? = null,
178+
unit: PlotExportCommon.SizeUnit? = null,
179+
targetDPI: Number? = null
115180
) {
116181
// lets-plot-image-export.jar might not be present in the classpath.
117182
val imageBytes: ByteArray = try {
@@ -127,6 +192,8 @@ private fun exportRasterImage(
127192
plotSpec = spec,
128193
format = format,
129194
scalingFactor = scalingFactor,
195+
plotSize = plotSize,
196+
unit = unit,
130197
targetDPI = targetDPI
131198
)
132199
image.bytes

0 commit comments

Comments
 (0)