# Commutative diagrams using MetaPost or Asymptote

Xy-pic, TikZ and PSTricks seem to be the graphics packages commonly used to draw commutative diagrams. Having heard about the power of MetaPost and Asymptote, I would like to experiment with them.

How good are MetaPost and Asymptote for drawing commutative diagrams? There does not seem to be any official packages for this purpose at the moment.

-
Asymptote is designed for 3D drawings. Those would have to be some super-complicated commutative diagrams to make it worthwhile... –  Seamus Dec 16 '10 at 23:06
Actually, Asymptote is not really designed for 3D drawings. It can do 3D, it did from the beginning extend the metapost path syntax to 3D, but it did not have true 3D drawing capabilities (like hidden surface removal) till quite recently. The asymptote gallery at asymptote.sourceforge.net has a number of good 2D examples. I don't see any commutative diagrams, but they would certainly be possible. –  Jan Hlavacek Dec 16 '10 at 23:30

For Metapost, look at this page. I am sure you can do the same with Asymptote, but I am not aware of any examples.

-

Here's a more serious example using Asymptote. I would note that for commutative diagrams in a *TeX document, I still recommend using tikz-cd (or tikz directly for sufficiently complicated examples). But I believe this answer is still potentially useful because it solves a couple of Asymptote problems that might come up in other contexts:

• how to compute the bounding box of a label (see the boundingbox() function in the example code; note that this would not work if the picture were scaled with size() rather than unitsize())
• how to set up labels to have the same baseline without changing their bounding boxes (make sure they are drawn with a pen that has the basealign option AND that they have alignment N (north); these must be used together to have the desired effect).

Here's the example code:

settings.outformat="pdf";
real xunit=2cm, yunit=1.4cm;
unitsize(xunit,yunit);
defaultpen(basealign);
picture blank = currentpicture.copy();
usepackage("amssymb");

string[][] nodestext = {{"$\hat{A}$", "$d$", "$A$"},
{"$\sum_i a_i$", "$c$"},
{"$\hat{A}$", "$\displaystyle\prod_{n \in \mathbb{Z}} A_n$", "$\displaystyle\prod_{n \in \mathbb{Z}} A_n$", "$A$"},
{},
{"", "", minipage("node not in math mode",60pt)}};

Label[][] nodes;
for (int r = 0; r < nodestext.length; ++r) {
nodes.push(new Label[nodestext[r].length]);
for (int c = 0; c < nodestext[r].length; ++c) {
nodes[r][c] = Label(nodestext[r][c], position=(c,-r), align=N);
label(nodes[r][c]);
}
}

/*
* This function computes the bounding box of a Label by creating a new blank
* picture with the same sizing information as the old picture, adding the
* Label to that blank picture, and then computing the bounding box of that picture.
*/
path boundingbox(Label L) {
picture currentpic = blank.copy();
label(currentpic, L);
pair min = min(currentpic, user=true);  //Without the user=true option, the returned answer would be measured in postscript points.
pair max = max(currentpic, user=true);
return box(min, max);
}

path[][] boundingboxes;
pair[][] centers;
for (int r = 0; r < nodes.length; ++r) {
path[] boundingboxesr;
pair[] centersr;
for (int c = 0; c < nodes[r].length; ++c) {
Label currentnode = nodes[r][c];
pair currentpos = (c,-r);
boundingboxesr.push(boundingbox(currentnode));
centersr.push(currentpos + (0,7pt/yunit));
}
boundingboxes.push(boundingboxesr);
centers.push(centersr);
}

path truncate(path thepath, int sourcerow, int sourcecol, int up=0, int right=0) {
pair source = centers[sourcerow][sourcecol];
int destrow = sourcerow - up;
int destcol = sourcecol + right;
pair dest = centers[destrow][destcol];
path toreturn = thepath;
toreturn = firstcut(toreturn, knife=boundingboxes[sourcerow][sourcecol]).after;
toreturn = lastcut(toreturn, knife=boundingboxes[destrow][destcol]).before;
}

void cdarrow(int sourcerow, int sourcecol, int up=0, int right=0, Label L="", bool crossingover = false) {
pair source = centers[sourcerow][sourcecol];
int destrow = sourcerow - up;
int destcol = sourcecol + right;
pair dest = centers[destrow][destcol];
path touse = truncate(source -- dest, sourcerow, sourcecol, up, right);
if (crossingover) draw(touse, white+linewidth(3pt));
}

cdarrow(0,0,up=-1,right=1);
cdarrow(1,0,up=1,right=1,crossingover=true, L=Label("$\scriptstyle h$",align=Relative(0.3W),position=Relative(0.65)));
cdarrow(1,0,right=1,L=Label("$\scriptstyle f$",align=Relative(E)));
cdarrow(0,1,right=-1);

cdarrow(2,0,right=1);
cdarrow(2,1,right=1);
cdarrow(2,2,right=1);

path curvedarrow = centers[2][0]{SSE} .. tension 0.75 .. {NE} centers[2][2];
curvedarrow=truncate(curvedarrow, 2, 0, right=2);
draw(curvedarrow, arrow=Arrow(TeXHead), L=Label("$\scriptstyle g$",align=Relative(E)), margin=Margins);

curvedarrow = centers[0][1] {ESE} .. {ENE} centers[0][2];
curvedarrow = truncate(curvedarrow, 0,1, right=1);

curvedarrow = centers[0][1] {ENE} .. {ESE} centers[0][2];
curvedarrow = truncate(curvedarrow, 0,1, right=1);


The result:

-

Zoonekynd's example #269 from his Metapost examples page (excuse the grainy Gif):

The code contains a whole MP library defining a begindiag...enddiag group defining node and rarrow primitives for the parts of the CD, with which the above diagram can be typeset using:

  begindiag;
node "A";
rarrowto(1,0, "above" => "a",
"shape" => "middle",
"curved" => 3mm,
"dashed" => withsmalldots);
rarrowto(0,1, "below" => "b",
"color" => blue,
"shape" => "mapsto",
"dashed" => evenly);
node "A";
rarrowto(1,0, "above" => "c", "width" => 1bp, "shape" => "inj");
rarrowto(0,1, "below" => "d", "shape" => "mono");
node "A";
nextline;
node "A";
rarrowto(1,0, "below" => "e", "shape" => "epi");
node "A";
rarrowto(1,-1, "below" => "f", "curved" => -3mm, "shape" => "half_dotted");
enddiag;


Jan links to Alan Kennington's Metapost examples (thanks: I didn't know of them), but from what I can see, while the examples there are impressive, they make use of no real, reusable CD library.

Like Jan, I know of no Asymptote code for CDs, but it should not be difficult to translate the Alan Kennington CD examples to it, because they don't make use of equation solving.

-

For simple CDs, you could use the rather appealing (IMHO) approach of Eplain (link is a PDF-file), from which the following example is taken:

\input eplain
$$\commdiag{ Y & \mapright^f & E \cr \mapdown & \arrow(3,2)\lft{f_t} & \mapdown \cr Y \times I & \mapright^{\bar f_t} & X }$$
\bye


Which produces:

-

Here's a non-serious example "using Asymptote" (which is to say, using TikZ inside Asymptote):

settings.outformat = "pdf";    // Tell Asymptote to output a pdf ("eps" is also an acceptable choice).
unitsize(1cm);                 // One unit of distance should be translated as 1cm rather than the default 1pt. Actually, for this particular setup, I think this is unnecessary.
usepackage("tikz-cd");         // Whenever you execute LaTeX code, add the line \usepackage{tikz-cd} to the preamble
string str = "\begin{tikzcd}[ampersand replacement=\&]
A \rar{\phi} \dar{\theta} \& B \dar{\pi}
\\ C \rar{\beta} \& D
\end{tikzcd}";                 // Create a string containing some LaTeX code.
label(str, (0,0));             // Place a label (think TikZ node) at position (0,0) containing the result of running the string str through LaTeX.


For an explanation of "ampersand replacement" in the TikZ code, see this answer.

Here's the output:

-
@texenthusiast: I'm hardly an expert on how to execute the code. In my own case, I found that with an up-to-date version of MacTeX, Asymptote is automatically installed, so it suffices to save the code above into a file named filename.asy and then execute on the command line asy filename.asy. –  Charles Staats Apr 28 '13 at 13:24
@texenthusiast: I've added some comments to explain the code. –  Charles Staats Apr 28 '13 at 13:37