Suppose I want a string, say "123", to fill a given rectangle, like so:
Show[Plot[x, {x, 0, 1}],
Graphics[{EdgeForm[Thick], Yellow, Rectangle[{.1, .5}, {.4, .9}]}],
Graphics[Text[Style["123", Red, Bold, 67], {.1, .5}, {-1, -1}]]]
But I hand-tuned the font size there (67) so that it would fill up the rectangle.
How would you make an arbitrary string fill up an arbitrary rectangle?
I believe that this is a known difficult problem. The best answer I could find is from John Fultz.
TextRect[text_, {{left_, bottom_}, {right_, top_}}] :=
Inset[
Pane[text, {Scaled[1], Scaled[1]},
ImageSizeAction -> "ResizeToFit", Alignment -> Center],
{left, bottom}, {Left, Bottom}, {right - left, top - bottom}]
Show[
Plot[x, {x, 0, 1}],
Graphics[{
{EdgeForm[Thick], Yellow, Rectangle[{.1, .5}, {.4, .9}]},
TextRect[Style["123", Red, Bold], {{.1, .5}, {.4, .9}}]
}]
]
Here's an alternate approach that converts the text to a texture that gets mapped to a polygon. This has the feature of stretching the text to fit the region (since it's not really text anymore.)
Show[Plot[x, {x, 0, 1}],
Graphics[{EdgeForm[Thick], Yellow, Rectangle[{.1, .5}, {.4, .9}]}],
Graphics[{Texture[ImageData[
Rasterize[Style["123", Red, Bold], "Image", RasterSize -> 300,
Background -> None]]],
Polygon[{{0.1, 0.5}, {0.4, 0.5}, {0.4, 0.9}, {0.1, 0.9}},
VertexTextureCoordinates -> {{0, 0}, {1, 0}, {1, 1}, {0, 1}}]}]]
As a function for easier comparison:
(* Render string/style s to fill a rectangle with left/bottom corner {l,b} and
right/top corner {r,t}. *)
textrect[s_, {{l_,b_},{r_,t_}}] := Graphics[{
Texture[ImageData[Rasterize[s, "Image", RasterSize->300, Background->None]]],
Polygon[{{l,b}, {r,b}, {r,t}, {l,t}},
VertexTextureCoordinates->{{0,0},{1,0},{1,1},{0,1}}]}]
The suggested solution didn't work when the Plot wasn't there, I used the PlotRange option to solve it. I wrapped it in a function; Opacity, text color, etc; should be made into options;
textBox[text_, color_, position_: {0, 0}, width_: 2, height_: 1] :=
Graphics[{
{
color, Opacity[.1],
Rectangle[position, position + {width, height},
RoundingRadius -> 0.1]
}
,
Inset[
Pane[text, {Scaled[1], Scaled[1]},
ImageSizeAction -> "ResizeToFit", Alignment -> Center],
position, {Left, Bottom}, {width, height}]
}, PlotRange ->
Transpose[{position, position + {width, height}}]];
There are times when exporting to a pdf image is simply troublesome. If the data you are plotting contains many points then your figure will be big in size and the pdf viewer of your choice will spend most of its time rendering this high quality image. We can thus export this image as a jpeg, png or tiff. The picture will be fine from a certain view but when you zoom in it will look all distorted. This is fine to some extent for the figure we are plotting but if your image contains text then this text will look pixelated.
In order to try to get the best of both worlds we can separate this figure into two parts: Axes with labels and the 3D picture. The axes can thus be exported as pdf or eps and the 3D figure as a raster. I wish I knew how later combine the two in Mathematica, so for the moment we can use a vector graphics editor such as Inkscape or Illustrator to combine the two.
I managed to achieve this for a plot I made in a publication but this prompt me to create routines in Mathematica in order to automatize this process. Here is what I have so far:
SetDirectory[NotebookDirectory[]];
SetOptions[$FrontEnd, PrintingStyleEnvironment -> "Working"];
I like to start my notebook by setting the working directory to the notebook directory. Since I want my images to be of the size I specify I set the printing style environment to working, check this for more info.
in = 72;
G3D = Graphics3D[
AlignmentPoint -> Center,
AspectRatio -> 0.925,
Axes -> {True, True, True},
AxesEdge -> {{-1, -1}, {1, -1}, {-1, -1}},
AxesStyle -> Directive[10, Black],
BaseStyle -> {FontFamily -> "Arial", FontSize -> 12},
Boxed -> False,
BoxRatios -> {3, 3, 1},
LabelStyle -> Directive[Black],
ImagePadding -> All,
ImageSize -> 5 in,
PlotRange -> All,
PlotRangePadding -> None,
TicksStyle -> Directive[10],
ViewPoint -> {2, -2, 2},
ViewVertical -> {0, 0, 1}
]
Here we set the view of the plot we want to make. Now lets create our plot.
g = Show[
Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, Pi},
Mesh -> None,
AxesLabel -> {"x", "y", "z"}
],
Options[G3D]
]
Now we need to find a way of separating. Lets start by drawing the axes.
axes = Graphics3D[{}, AbsoluteOptions[g]]
fig = Show[g,
AxesStyle -> Directive[Opacity[0]],
FaceGrids -> {{-1, 0, 0}, {0, 1, 0}}
]
I included the facegrids so that we can match the figure with the axis in the post editing process. Now we export both images.
Export["Axes.pdf", axes];
Export["Fig.pdf", Rasterize[fig, ImageResolution -> 300]];
You will obtain two pdf files which you can edit in and put together into a pdf or eps. I wish it was that simple but it isn't. If you actually did this you will obtain this:
The two figures are different sizes. I know axes.pdf is correct because when I open it in Inkspace the figure size is 5 inches as I had previously specified.
I mentioned before that I managed to get this with one of my plots. I will clean the file and change the plots to make it more accessible for anyone who wants to see that this is in fact true. In any case, does anyone know why I can't get the two pdf files to be the same size? Also, keep in mind that we want to obtain a pretty plot for the Rasterized figure. Thank you for your time.
PS.
As a bonus, can we avoid the post editing and simply combine the two figures in mathematica? The rasterized version and the vector graphics version that is.
EDIT:
Thanks to rcollyer for his comment. I'm posting the results of his comment.
One thing to mention is that when we export the axes we need to set Background to None so that we can have a transparent picture.
Export["Axes.pdf", axes, Background -> None];
Export["Fig.pdf", Rasterize[fig, ImageResolution -> 300]];
a = Import["Axes.pdf"];
b = Import["Fig.pdf"];
Show[b, a]
And then, exporting the figure gives the desired effect
Export["FinalFig.pdf", Show[b, a]]
The axes preserve the nice components of vector graphics while the figure is now a Rasterized version of the what we plotted. But the main question still remains.
How do you make the two figures match?
UPDATE:
My question has been answered by Alexey Popkov. I would like to thank him for taking the time to look into my problem. The following code is an example for those of you want to use the technique I previously mentioned. Please see Alexey Popkov's answer for useful comments in his code. He managed to make it work in Mathematica 7 and it works even better in Mathematica 8. Here is the result:
SetDirectory[NotebookDirectory[]];
SetOptions[$FrontEnd, PrintingStyleEnvironment -> "Working"];
$HistoryLength = 0;
in = 72;
G3D = Graphics3D[
AlignmentPoint -> Center, AspectRatio -> 0.925, Axes -> {True, True, True},
AxesEdge -> {{-1, -1}, {1, -1}, {-1, -1}}, AxesStyle -> Directive[10, Black],
BaseStyle -> {FontFamily -> "Arial", FontSize -> 12}, Boxed -> False,
BoxRatios -> {3, 3, 1}, LabelStyle -> Directive[Black], ImagePadding -> 40,
ImageSize -> 5 in, PlotRange -> All, PlotRangePadding -> 0,
TicksStyle -> Directive[10], ViewPoint -> {2, -2, 2}, ViewVertical -> {0, 0, 1}
];
axesLabels = Graphics3D[{
Text[Style["x axis (units)", Black, 12], Scaled[{.5, -.1, 0}], {0, 0}, {1, -.9}],
Text[Style["y axis (units)", Black, 12], Scaled[{1.1, .5, 0}], {0, 0}, {1, .9}],
Text[Style["z axis (units)", Black, 12], Scaled[{0, -.15, .7}], {0, 0}, {-.1, 1.5}]
}];
fig = Show[
Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, Pi}, Mesh -> None],
ImagePadding -> {{40, 0}, {15, 0}}, Options[G3D]
];
axes = Show[
Graphics3D[{}, FaceGrids -> {{-1, 0, 0}, {0, 1, 0}},
AbsoluteOptions[fig]], axesLabels,
Epilog -> Text[Style["Panel A", Bold, Black, 12], ImageScaled[{0.075, 0.975}]]
];
fig = Show[fig, AxesStyle -> Directive[Opacity[0]]];
Row[{fig, axes}]
At this point you should see this:
The magnification takes care of the resolution of your image. You should try different values to see how this changes your picture.
fig = Magnify[fig, 5];
fig = Rasterize[fig, Background -> None];
Combine the graphics
axes = First#ImportString[ExportString[axes, "PDF"], "PDF"];
result = Show[axes, Epilog -> Inset[fig, {0, 0}, {0, 0}, ImageDimensions[axes]]];
Export them
Export["Result.pdf", result];
Export["Result.eps", result];
The only difference I found between M7 and M8 using the above code is that M7 does not export the eps file correctly. Other than that everything is working fine now. :)
The first column shows the output obtained from M7. Top is the eps version with file size of 614 kb, bottom is the pdf version with file size of 455 kb. The second column shows the output obtained from M8. Top is the eps version with file size of 643 kb, bottom is the pdf version with file size of 463 kb.
I hope you find this useful. Please check Alexey's answer to see the comments in his code, they will help you avoid pitfalls with Mathematica.
The complete solution for Mathematica 7.0.1: fixing bugs
The code with comments:
(*controls the resolution of rasterized graphics*)
magnification = 5;
SetOptions[$FrontEnd, PrintingStyleEnvironment -> "Working"]
(*Turn off history for saving memory*)
$HistoryLength = 0;
(*Epilog will give us the bounding box of the graphics*)
g1 = Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, Pi},
AlignmentPoint -> Center, AspectRatio -> 0.925,
Axes -> {True, True, True},
AxesEdge -> {{-1, -1}, {1, -1}, {-1, -1}},
BaseStyle -> {FontFamily -> "Arial", FontSize -> 12},
Boxed -> False, BoxRatios -> {3, 3, 1},
LabelStyle -> Directive[Black], ImagePadding -> All,
ImageSize -> 5*72, PlotRange -> All, PlotRangePadding -> None,
TicksStyle -> Directive[10], ViewPoint -> {2, -2, 2},
ViewVertical -> {0, 0, 1}, AxesStyle -> Directive[Opacity[0]],
FaceGrids -> {{-1, 0, 0}, {0, 1, 0}}, Mesh -> None,
ImagePadding -> 40,
Epilog -> {Red, AbsoluteThickness[1],
Line[{ImageScaled[{0, 0}], ImageScaled[{0, 1}],
ImageScaled[{1, 1}], ImageScaled[{1, 0}],
ImageScaled[{0, 0}]}]}];
(*The options list should NOT contain ImagePadding->Full.Even it is \
before ImagePadding->40 it is not replaced by the latter-another bug!*)
axes = Graphics3D[{Opacity[0],
Point[PlotRange /. AbsoluteOptions[g1] // Transpose]},
AlignmentPoint -> Center, AspectRatio -> 0.925,
Axes -> {True, True, True},
AxesEdge -> {{-1, -1}, {1, -1}, {-1, -1}},
AxesStyle -> Directive[10, Black],
BaseStyle -> {FontFamily -> "Arial", FontSize -> 12},
Boxed -> False, BoxRatios -> {3, 3, 1},
LabelStyle -> Directive[Black], ImageSize -> 5*72,
PlotRange -> All, PlotRangePadding -> None,
TicksStyle -> Directive[10], ViewPoint -> {2, -2, 2},
ViewVertical -> {0, 0, 1}, ImagePadding -> 40,
Epilog -> {Red, AbsoluteThickness[1],
Line[{ImageScaled[{0, 0}], ImageScaled[{0, 1}],
ImageScaled[{1, 1}], ImageScaled[{1, 0}],
ImageScaled[{0, 0}]}]}];
(*fixing bug with ImagePadding loosed when specifyed as option in \
Plot3D*)
g1 = AppendTo[g1, ImagePadding -> 40];
(*Increasing ImageSize without damage.Explicit setting for \
ImagePadding is important (due to a bug in behavior of \
ImagePadding->Full)!*)
g1 = Magnify[g1, magnification];
g2 = Rasterize[g1, Background -> None];
(*Fixing bug with non-working option Background->None when graphics \
is Magnifyed*)
g2 = g2 /. {255, 255, 255, 255} -> {0, 0, 0, 0};
(*Fixing bug with icorrect exporting of Ticks in PDF when Graphics3D \
and 2D Raster are combined*)
axes = First#ImportString[ExportString[axes, "PDF"], "PDF"];
(*Getting explicid ImageSize of graphics imported form PDF*)
imageSize =
Last#Transpose[{First##, Last##} & /#
Sort /# Transpose#
First#Cases[axes,
Style[{Line[x_]}, ___, RGBColor[1.`, 0.`, 0.`, 1.`], ___] :>
x, Infinity]]
(*combining Graphics3D and Graphics*)
result = Show[axes, Epilog -> Inset[g2, {0, 0}, {0, 0}, imageSize]]
Export["C:\\result.pdf", result]
Here is what I see in the Notebook:
And here is what I get in the PDF:
Just checking (Mma8):
SetOptions[$FrontEnd, PrintingStyleEnvironment -> "Working"];
in = 72;
G3D = Graphics3D[AlignmentPoint -> Center, AspectRatio -> 0.925,
Axes -> {True, True, True},
AxesEdge -> {{-1, -1}, {1, -1}, {-1, -1}},
AxesStyle -> Directive[10, Black],
BaseStyle -> {FontFamily -> "Arial", FontSize -> 12},
Boxed -> False, BoxRatios -> {3, 3, 1},
LabelStyle -> Directive[Black], ImagePadding -> All,
ImageSize -> 5 in, PlotRange -> All, PlotRangePadding -> None,
TicksStyle -> Directive[10], ViewPoint -> {2, -2, 2},
ViewVertical -> {0, 0, 1}];
g = Show[Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, Pi}, Mesh -> None,
AxesLabel -> {"x", "y", "z"}], Options[G3D]];
axes = Graphics3D[{}, AbsoluteOptions[g]];
fig = Show[g, AxesStyle -> Directive[Opacity[0]],
FaceGrids -> {{-1, 0, 0}, {0, 1, 0}}];
Export["c:\\Axes.pdf", axes, Background -> None];
Export["c:\\Fig.pdf", Rasterize[fig, ImageResolution -> 300]];
a = Import["c:\\Axes.pdf"];
b = Import["c:\\Fig.pdf"];
Export["c:\\FinalFig.pdf", Show[b, a]]
In Mathematica 8 the problem may be solved even simpler using new Overlay function.
Here is the code from the UPDATE section of the question:
SetOptions[$FrontEnd, PrintingStyleEnvironment -> "Working"];
$HistoryLength = 0;
in = 72;
G3D = Graphics3D[AlignmentPoint -> Center, AspectRatio -> 0.925,
Axes -> {True, True, True},
AxesEdge -> {{-1, -1}, {1, -1}, {-1, -1}},
AxesStyle -> Directive[10, Black],
BaseStyle -> {FontFamily -> "Arial", FontSize -> 12},
Boxed -> False, BoxRatios -> {3, 3, 1},
LabelStyle -> Directive[Black], ImagePadding -> 40,
ImageSize -> 5 in, PlotRange -> All, PlotRangePadding -> 0,
TicksStyle -> Directive[10], ViewPoint -> {2, -2, 2},
ViewVertical -> {0, 0, 1}];
axesLabels =
Graphics3D[{Text[Style["x axis (units)", Black, 12],
Scaled[{.5, -.1, 0}], {0, 0}, {1, -.9}],
Text[Style["y axis (units)", Black, 12],
Scaled[{1.1, .5, 0}], {0, 0}, {1, .9}],
Text[Style["z axis (units)", Black, 12],
Scaled[{0, -.15, .7}], {0, 0}, {-.1, 1.5}]}];
fig = Show[Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, Pi}, Mesh -> None],
ImagePadding -> {{40, 0}, {15, 0}}, Options[G3D]];
axes = Show[
Graphics3D[{}, FaceGrids -> {{-1, 0, 0}, {0, 1, 0}},
AbsoluteOptions[fig]], axesLabels,
Epilog ->
Text[Style["Panel A", Bold, Black, 12],
ImageScaled[{0.075, 0.975}]]];
fig = Show[fig, AxesStyle -> Directive[Opacity[0]]];
And here is the solution:
gr = Overlay[{axes,
Rasterize[fig, Background -> None, ImageResolution -> 300]}]
Export["Result.pdf", gr]
In this case we need not to convert fonts to outlines.
UPDATE
As jmlopez pointed out in the comments to this answer, the option Background -> None does not work properly under Mac OS X in Mathematica 8.0.1. One workaround is to replace white non-transparent points by transparent:
gr = Overlay[{axes,
Rasterize[fig, Background -> None,
ImageResolution -> 300] /. {255, 255, 255, 255} -> {0, 0, 0, 0}}]
Export["Result.pdf", gr]
Here I present another version of the original solution which uses the second argument of Raster instead of Inset. I think that this way is a little more straightforward.
Here is the code from the UPDATE section of the question (modified a bit):
SetOptions[$FrontEnd, PrintingStyleEnvironment -> "Working"];
$HistoryLength = 0;
in = 72;
G3D = Graphics3D[AlignmentPoint -> Center, AspectRatio -> 0.925,
Axes -> {True, True, True},
AxesEdge -> {{-1, -1}, {1, -1}, {-1, -1}},
AxesStyle -> Directive[10, Black],
BaseStyle -> {FontFamily -> "Arial", FontSize -> 12},
Boxed -> False, BoxRatios -> {3, 3, 1},
LabelStyle -> Directive[Black], ImagePadding -> 40,
ImageSize -> 5 in, PlotRange -> All, PlotRangePadding -> 0,
TicksStyle -> Directive[10], ViewPoint -> {2, -2, 2},
ViewVertical -> {0, 0, 1}];
axesLabels =
Graphics3D[{Text[Style["x axis (units)", Black, 12],
Scaled[{.5, -.1, 0}], {0, 0}, {1, -.9}],
Text[Style["y axis (units)", Black, 12],
Scaled[{1.1, .5, 0}], {0, 0}, {1, .9}],
Text[Style["z axis (units)", Black, 12],
Scaled[{0, -.15, .7}], {0, 0}, {-.1, 1.5}]}];
fig = Show[Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, Pi}, Mesh -> None],
ImagePadding -> {{40, 0}, {15, 0}}, Options[G3D]];
axes = Show[
Graphics3D[{}, FaceGrids -> {{-1, 0, 0}, {0, 1, 0}},
AbsoluteOptions[fig]], axesLabels,
Prolog ->
Text[Style["Panel A", Bold, Black, 12],
ImageScaled[{0.075, 0.975}]]];
fig = Show[fig, AxesStyle -> Directive[Opacity[0]]];
fig = Magnify[fig, 5];
fig = Rasterize[fig, Background -> None];
axes2D = First#ImportString[ExportString[axes, "PDF"], "PDF"];
The rest of the answer is the new solution.
At first, we set the second argument of Raster so that it will fill the complete PlotRange of axes2D. The general way to do this is:
fig = fig /.
Raster[data_, rectangle_, opts___] :>
Raster[data, {Scaled[{0, 0}], Scaled[{1, 1}]}, opts];
Another way is to make direct assignment to the corresponding Part of the original expression:
fig[[1, 2]] = {Scaled[{0, 0}], Scaled[{1, 1}]}
Note that this last code is based on the knowledge of internal structure of the expression generated by Rasterize which is potentially version-dependent.
Now we combine two graphical objects in a very straightforward way:
result = Show[axes2D, fig]
And export the result:
Export["C:/Result.pdf", result];
Export["C:/Result.eps", result];
Both .eps and .pdf are exported perfectly with Mathematica 8.0.4 under Windows XP 32 bit and look identical to the files exported with the original code:
result = Show[axes2D,
Epilog -> Inset[fig, Center, Center, ImageScaled[{1, 1}]]]
Export["C:/Result.pdf", result];
Export["C:/Result.eps", result];
Note that we need not necessarily to convert axes to outlines at least when exporting to PDF. The code
result = Show[axes,
Epilog -> Inset[fig, Center, Center, ImageScaled[{1, 1}]]]
Export["C:/Result.pdf", result];
and the code
fig[[1, 2]] = {ImageScaled[{0, 0}], ImageScaled[{1, 1}]};
result = Show[axes, Epilog -> First#fig]
Export["C:/Result.pdf", result];
produce PDF files looking identical to both previous versions.
This looks like much ado about nothing. As I read it, the problem you want to solve is the following:
You want to export in a vector format, so that when printed the optimal resolution is used for fonts, lines and graphics
In your edit program you don't want be bothered by the slowness of rendering a complex vector drawing
These requirements can be met by exporting as .eps and using an embedded rasterized preview image.
Export["file.eps","PreviewFormat"->"TIFF"]
This will work in many applications. Unfortunately, MS Word's eps filter has been changing wildly over the last four versions or so, and whereas it once worked for me in one of the older functions it doesn't anymore in W2010. I've heard rumors that it might work in the mac version, but I can't check right now.
Mathematica 9.0.1.0 / 64-bit Linux:
In general, it seems to be very tricky to place the vectorized axes at the correct position. In most applications it will be sufficient to simply rasterize everything with a high resolution:
fig = Plot3D[Sin[x y], {x, 0, 3}, {y, 0, 3}, Mesh -> None];
Export["export.eps", fig, "AllowRasterization" -> True,
ImageResolution -> 600];
The code exports the graphic to an EPS-file using a high quality rasterization of both the 3D content and the axis. Finally, you can convert the EPS-file to a PDF using for example the Linux command epspdf:
epspdf export.eps
This is probably sufficient for most of the users and it saves you a lot of time. However, if you really want to export the text as vector graphic, you might want to try the following function:
ExportAsSemiRaster[filename_, dpi_, fig_, plotrange_,
plotrangepadding_] := (
range =
Show[fig, PlotRange -> plotrange,
PlotRangePadding -> plotrangepadding];
axes = Show[Graphics3D[{}, AbsoluteOptions[range]]];
noaxes = Show[range, AxesStyle -> Transparent];
raster =
Rasterize[noaxes, Background -> None, ImageResolution -> dpi];
result =
Show[raster,
Epilog -> Inset[axes, Center, Center, ImageDimensions[raster]]];
Export[filename, result];
);
You need to explicitly specify the PlotRange and the PlotRangePadding. Example:
fig = Graphics3D[{Opacity[0.9], Orange,
Polygon[{{0, 0, 0}, {4, 0, 4}, {4, 5, 7}, {0, 5, 5}}],
Opacity[0.05], Gray, CuboidBox[{0, 0, 0}, {4, 5, 7}]},
Axes -> True, AxesStyle -> Darker[Orange],
AxesLabel -> {"x1", "x2", "x3"}, Boxed -> False,
ViewPoint -> {-8.5, -8, 6}];
ExportAsSemiRaster["export.pdf", 600,
fig, {{0, 4}, {0, 5}, {0, 7}}, {.0, .0, .0}];
Print[Import["export.pdf"]];