[BioC] fontsize in Rgraphviz

Florian Hahne fhahne at fhcrc.org
Sun Feb 3 22:11:33 CET 2008


Hi Mark,
this is great, the only issue that I see here is that we completely 
bypass Graphviz and this might cause some problems:
1.) The start end endpoints of the edges might look a bit weird. 
Graphviz tries to keep them at a reasonable distance from the nodes. 
These is a concept of bounding boxes, so you could ask Graphviz to 
compute node and edge locations for rectangular nodes (in which case the 
node fills the whole bounding box), but this might look strange.
2.) I'm not sure that you can pass your shapes down to Graphviz as 
nodeAttrs arguments. In the best case those will only be ignored, but 
I'm almost positive that the Rgraphviz interface will not return those 
shapes in the Ragraph object, hence we don't get them back into the laid 
out graph object without messing around a lot in layoutGraph. The 
alternative to set them through nodeRenderInfo doesn't appeal to me that 
much, because this sort of breaks the concept of keeping the layout and 
the rendering part separate.

Maybe Deeapayan or Robert have something to add here, but ideally you 
might want to figure out how Rgraphviz passes stuff to the Graphviz 
library and implement your additional shapes at that level.

bw,
Florian

Mark W Kimpel schrieb:
> Florian,
>
> Here is a potential way to get some more plotting symbols into 
> Rgraphviz, most of these based on those available in Graphviz. I have 
> a vested interest in getting more symbols as I am already using 
> fill-color to denote fold change direction and p value and size to 
> indicate fold change magnitude. I'd like to denote up to 5 or 6 
> different KEGG pathways in one graph. Anyways, here goes:
>
> The relevant section of your code is found in renderGraph.R:
>
>  ## deal with different shapes
>     possible.shapes <-
>         c("circle", "ellipse", "box", "rectangle", "plaintext")
>     shape <-
>         possible.shapes[pmatch(shape,
>                                possible.shapes,
>                                duplicates.ok = TRUE)]
>     ## shape == circle
>     i <- shape == "circle"
>     if (any(i, na.rm=TRUE))
>     {
>         symbols(nodeX[i], nodeY[i], circles = rad[i],
>                 fg = col[i], bg = fill[i],
>                 inches = FALSE, add = TRUE)
>     }
>
> ##############################################################
>
> I have come up with the following self-contained code-snippet which 
> follows to demonstrate a way to draw other shapes with user-specified 
> radii. If you and others think this is an okay way to go, then I will 
> work on a patch for renderGraph.R. I also believe that the workhorse 
> function polygon.vertex.calc.func could be modified to calculate 
> ellipses so that box, rectangle, circle, and ellipse could all use 
> this as a generic and users could specify height and width instead of 
> radii to customize the other shapes below.
>
> Let me know what you think. Mark
>
> #calculates vertices of a regular polygon centered on origin using 
> radius and number of vertices. First vertex is on y axis
> # unless 'shift' is specified in degrees, positive (up) or negative 
> (down)
> polygon.vertex.calc.func <- function(no.vert, radius, shift.deg){
>   x <- y <- rep(0, no.vert)
>   shift.rad <- (shift.deg * 2 * pi)/360
>   for (i in 1:no.vert){
>     x[i] <- radius * (cos((((2 * pi)/no.vert) * (i - 1)) + (shift.rad)))
>     y[i] <- radius * (sin((((2 * pi)/no.vert) * (i - 1)) + (shift.rad)))
>   }
>   vertex.list <- list(x = x,y = y)
> }
> ############################################################################ 
>
> triangle.func <- function(radius = 1){
>   vertex.list <- polygon.vertex.calc.func(no.vert = 3, radius, 
> shift.deg = 90)
>   vertex.list
> }
> ############################################################################ 
>
> inverted.triangle.func <- function(radius = 1){
>   vertex.list <- polygon.vertex.calc.func(no.vert = 3, radius, 
> shift.deg = 270)
>   vertex.list
> }
> ############################################################################ 
>
> pentagon.func <- function(radius = 1){
>   vertex.list <- polygon.vertex.calc.func(no.vert = 5, radius, 
> shift.deg = 18)
>   vertex.list
> }
> ############################################################################ 
>
> octagon.func <- function(radius = 1){
>   vertex.list <- polygon.vertex.calc.func(no.vert = 8, radius, 
> shift.deg = 0)
>   vertex.list
> }
> ############################################################################ 
>
> house.func <- function(radius = 1){
>   vertex.list <- pentagon.func(radius)
>   vertex.list$x[4] <- vertex.list$x[3]
>   vertex.list$x[5] <- vertex.list$x[1]
>   vertex.list
> }
> ############################################################################ 
>
> inverted.house.func <- function(radius = 1){
>   vertex.list <- pentagon.func(radius)
>   vertex.list$x[4] <- vertex.list$x[3]
>   vertex.list$x[5] <- vertex.list$x[1]
>   vertex.list$x <- -1 * vertex.list$x
>   vertex.list$y <- -1 * vertex.list$y
>   vertex.list
> }
> ########################################
>
> op <- par()
> par(mfrow=c(2,4))
> ###########################################
> plot(c(-2,2), c(-2,2), type="n")
> vertex.list <- triangle.func(radius = 1)
> polygon(x = vertex.list$x, y = vertex.list$y)
> ###########################################
> plot(c(-2,2), c(-2,2), type="n")
> vertex.list <- inverted.triangle.func(radius = 1)
> polygon(x = vertex.list$x, y = vertex.list$y)
> ###########################################
> plot(c(-2,2), c(-2,2), type="n")
> vertex.list <- inverted.triangle.func(radius = 1)
> polygon(x = vertex.list$x, y = vertex.list$y)
> vertex.list <- triangle.func(radius = 1)
> polygon(x = vertex.list$x, y = vertex.list$y)
> ###########################################
> plot(c(-2,2), c(-2,2), type="n")
> vertex.list <- pentagon.func(radius = 1)
> polygon(x = vertex.list$x, y = vertex.list$y)
> ###########################################
> plot(c(-2,2), c(-2,2), type="n")
> vertex.list <- octagon.func(radius = 1)
> polygon(x = vertex.list$x, y = vertex.list$y)
> ###########################################
> plot(c(-2,2), c(-2,2), type="n")
> vertex.list <- octagon.func(radius = 1)
> polygon(x = vertex.list$x, y = vertex.list$y)
> vertex.list <- octagon.func(radius = 0.75)
> polygon(x = vertex.list$x, y = vertex.list$y)
> ###########################################
> plot(c(-2,2), c(-2,2), type="n")
> vertex.list <- house.func(radius = 1)
> polygon(x = vertex.list$x, y = vertex.list$y)
> ###########################################
> plot(c(-2,2), c(-2,2), type="n")
> vertex.list <- inv.house.func(radius = 1)
> polygon(x = vertex.list$x, y = vertex.list$y)
> ###########################################
> ###########################################
> ###########################################
> ###########################################
> ###########################################
> par(op)
>
> Mark W. Kimpel MD  ** Neuroinformatics ** Dept. of Psychiatry
> Indiana University School of Medicine
>
> 15032 Hunter Court, Westfield, IN  46074
>
> (317) 490-5129 Work, & Mobile & VoiceMail
> (317) 204-4202 Home (no voice mail please)
>
> mwkimpel<at>gmail<dot>com
>
> ******************************************************************
>
>
> Florian Hahne wrote:
>> Hi Mark,
>> to get you all the way, take a look at this:
>>
>> require(Rgraphviz)
>> # Generate random graph
>> set.seed(123)
>> nodeNames <- letters[1:10]
>> M <- 1:4
>> g1 <- randomGraph(nodeNames, M, .2)
>>
>> nAttrs <- list()
>>
>> #node width
>> nAttrs$width <-  rep(3, length(nodeNames))
>> names(nAttrs$width)<-nodeNames
>>
>> #node height
>> nAttrs$height <- rep(3, length(nodeNames))
>> names(nAttrs$height)<-nodeNames
>>
>> #font size
>> fontsize <- rep(c(1, 2), (length(nodeNames)/2))
>> names(fontsize)<-nodeNames
>>
>> #layout and render
>> x <- layoutGraph(g1, nodeAttr=nAttrs)
>> nodeRenderInfo(x) <- list(cex=fontsize)
>> renderGraph(x, graph.pars=list(nodes=list(fill="black",
>>                               textCol="white")))
>>
>> I didn't set the node shape at all, because the default for dot is to 
>> use "ellipse", and since you fix your node widths and node heights to 
>> the same size, this boils down to a circle. I also set the fill and 
>> text color as attributes in the graph.pars argument to renderGraph, 
>> but Deepayan's suggestion works, too. The only difference is, that 
>> the latter permanently assigns the colors to the graph object x, 
>> while the former takes effect only for the current rendering operation.
>>
>> nodeRenderInfo(x) <- list(cex=fontsize, fill="black", textCol="white")
>> renderGraph(x)
>>
>> fillcolor is ignored in your example because in the new rendering API 
>> this parameter is called fill (we tried to stay closer to R's base 
>> graphic parameter names, and thinking of that now, maybe we should 
>> rename textCol to font.col or something similar). I just uploaded a 
>> much more detailed vignette and some bug fixes, and these changes 
>> should be available soon in Rgraphviz version 1.17.12. If you know 
>> how to access the svn repository you can get this update immediately. 
>> In the Vignette there are also tables of the available parameters for 
>> nodes, edges and the whole graph and I will update the documentation 
>> accordingly ASAP.
>>
>> Everything that affects the graph layout can still be passed on to 
>> Graphviz as nodeAttrs, edgeAttrs or attrs arguments. For instance, 
>> the following would change the node shapes:
>>
>> nAttrs$shape <- c("circle", "rect", rep(c("ellipse", "box"), 4))
>> names(nAttrs$shape )<- nodeNames
>> x <- layoutGraph(g1, nodeAttr=nAttrs)
>> renderGraph(x)
>> Again, since widths and heights of the nodes are constant, there's no 
>> difference between ellipse and circle (box and rect are synonyms, 
>> anyways). You can see the difference here:
>>
>> nAttrs$width <-  rep(6, length(nodeNames))
>> names(nAttrs$width)<-nodeNames
>> x <- layoutGraph(g1, nodeAttr=nAttrs)
>> renderGraph(x)
>>
>> Another Graphviz parameter that I find quite useful is fixedsize. If 
>> this is set to TRUE (the default) all nodes will be forced to have 
>> the same size unless manually changed. For FALSE, the node sizes will 
>> be computed from the labels, so longer labels will create larger 
>> nodes. As far as I remeber, this has to be given to the attrs 
>> argument, something like this:
>>
>> attrs <- list(node=list(fixedsize=FALSE))
>> x <- layoutGraph(g1, nodeAttr=nAttrs, attrs=attrs)
>>
>> I'm affaid I can't really point you to the C code for the Graphviz 
>> interaction (we tried to stay clear of that, it's so easy to break 
>> things there...), but agopen is the workhorse in Rgraphviz to talk to 
>> the Graphviz library. Starting there and working your way though the 
>> code should quickly get you to the interesting C calls. Once you've 
>> figured out a way to access the different shapes in Graphviz and how 
>> to get the coordinates back into R, it is straightforward to render 
>> them. Everything that is stored in the Ragraph object (the return 
>> value of agopen) can be accessed by our rendering API.
>>
>> Cheers,
>> Florian
>>
>>
>>
>>
>>
>> Mark W Kimpel schrieb:
>>> Deepayan,
>>>
>>> We are making progress. I just updated Rgraphviz-devel. Simply 
>>> adjusting the way adjusting my parameters to "layoutGraph" as you 
>>> suggested fixed the font size problem! I did not have to add it at 
>>> the nodeRenderInfo stage. Attached is a postscript file documenting 
>>> the results, which would look prettier if I had bothered to adjust 
>>> the size of the output.
>>>
>>> Unfortunately, now nAttrs$fillcolor is being ignored, even if it is 
>>> added as an argument to noRenderInfo.
>>>
>>> Also, I would very much like to use many of the other shapes 
>>> available via graphviz. If I was willing to do some of the coding (I 
>>> know some C), would you point me in the right direction so that I 
>>> might provide a patch with more shapes?
>>>
>>> Thanks,
>>> Mark
>>>
>>>
>>> Mark W. Kimpel MD  ** Neuroinformatics ** Dept. of Psychiatry
>>> Indiana University School of Medicine
>>>
>>> 15032 Hunter Court, Westfield, IN  46074
>>>
>>> (317) 490-5129 Work, & Mobile & VoiceMail
>>> (317) 204-4202 Home (no voice mail please)
>>>
>>> mwkimpel<at>gmail<dot>com
>>>
>>> ******************************************************************
>>>
>>>
>>> Deepayan Sarkar wrote:
>>>> On 1/30/08, Mark W Kimpel <mwkimpel at gmail.com> wrote:
>>>>> Sorry for the complex prior example, I shouldn't write emails late at
>>>>> night. Below is a self-contained, reproducible example.
>>>>>
>>>>> I've made some progress and can get the following script to flow 
>>>>> without
>>>>> error messages. The fontsize, however, is not varying as I believe it
>>>>> should and when I substitute "ellipse" for circle the width and 
>>>>> height
>>>>> parameters do not appropriately effect the shape.
>>>>
>>>> There are certain attributes which need to be supplied at the layout
>>>> step, especially shape, width, and height, as without these the edges
>>>> (which need to start from node boundaries) will not be placed
>>>> properly. So, you need to supply nAttrs to layoutGraph as
>>>>
>>>> x <- layoutGraph(g1, nodeAttrs = nAttrs)
>>>>
>>>> Of course, it's not clear from the documentation that you can do this!
>>>>
>>>>> I also do not understand where I can specify "neato" vs. "dot" for 
>>>>> the
>>>>> rendering layout.
>>>>
>>>> Same goes for this; you can do
>>>>
>>>> x <- layoutGraph(g1, nodeAttrs = nAttrs, layoutType = "neato")
>>>>
>>>> The trick is to notice that the default layout function in
>>>> 'layoutGraph' is 'layoutGraphviz', which is unexported, but you can
>>>> get it's argument list by
>>>>
>>>>> args(Rgraphviz:::layoutGraphviz)
>>>> function (x, layoutType = "dot", name = "graph", recipEdges = 
>>>> "combined",
>>>>     nodeAttrs = list(), edgeAttrs = list(), ...)
>>>>
>>>>
>>>> Unfortunately, the fontsize doesn't seem to be retained in the result
>>>> of layoutGraph, and I'm not sure yet if that's a bug in our wrapper or
>>>> a limitation of the graphviz interface. We will look into it.
>>>>
>>>>> Also, where to specify a title.
>>>>
>>>> Generally speaking, our new model is that once the layout is done, you
>>>> should be able to modify other attributes before rendering. These
>>>> attributes are divided into three types: those for nodes, those for
>>>> edges, and those for the whole graph, and these can be accessed and
>>>> modified using nodeRenderInfo(), edgeRenderInfo(), and
>>>> graphRenderInfo(), respectively.
>>>>
>>>> So, to come back to your problem, once you have the layout done, you
>>>> can modify the fontsize and title as follows:
>>>>
>>>> nodeRenderInfo(x) <- list(fontsize = nAttrs$fontsize * 5)
>>>> graphRenderInfo(x) <- list(main = "Main title")
>>>>
>>>> and then finally render it with
>>>>
>>>> renderGraph(x)
>>>>
>>>> Obviously there are many warts still to be worked out.
>>>>
>>>> -Deepayan
>>>>
>>>>> Thanks, mark
>>>>>
>>>>>
>>>>> ####################
>>>>> require(Rgraphviz)
>>>>> # Generate random graph
>>>>> set.seed(123)
>>>>> nodeNames <- letters[1:10]
>>>>> M <- 1:4
>>>>> g1 <- randomGraph(nodeNames, M, .2)
>>>>>
>>>>> nAttrs <- list()
>>>>>
>>>>> #node shapes
>>>>> nAttrs$shape <- rep("circle", length(nodeNames))
>>>>> names(nAttrs$shape )<- nodeNames
>>>>>
>>>>> #node width
>>>>> nAttrs$width <-  rep(3, length(nodeNames))
>>>>> names(nAttrs$width)<-nodeNames
>>>>>
>>>>> #node height
>>>>> nAttrs$height <- rep(3, length(nodeNames))
>>>>> names(nAttrs$height)<-nodeNames
>>>>>
>>>>> #node colors
>>>>> nAttrs$fillcolor <-rep("black", length(nodeNames))
>>>>> names(nAttrs$fillcolor) <- nodeNames
>>>>>
>>>>> #font color
>>>>> nAttrs$fontcolor <- rep("white", length(nodeNames))
>>>>> names(nAttrs$fontcolor)<-nodeNames
>>>>>
>>>>> #font size
>>>>> nAttrs$fontsize <- rep(c(1, 2), (length(nodeNames)/2))
>>>>> names(nAttrs$fontsize)<-nodeNames
>>>>>
>>>>> postscript(file="my.test.graph.ps", paper="special",width=4, 
>>>>> height=5)
>>>>> #set up graphics device
>>>>> x <- layoutGraph(g1)
>>>>> nodeRenderInfo(x) = nAttrs
>>>>> renderGraph(x)
>>>>> dev.off()## close the graphics device
>>>>>
>>>>>
>>>>>
>>>>> Mark W. Kimpel MD  ** Neuroinformatics ** Dept. of Psychiatry
>>>>> Indiana University School of Medicine
>>>>>
>>>>> 15032 Hunter Court, Westfield, IN  46074
>>>>>
>>>>> (317) 490-5129 Work, & Mobile & VoiceMail
>>>>> (317) 204-4202 Home (no voice mail please)
>>>>>
>>>>> mwkimpel<at>gmail<dot>com
>>>>
>>> ------------------------------------------------------------------------ 
>>>
>>>
>>> _______________________________________________
>>> Bioconductor mailing list
>>> Bioconductor at stat.math.ethz.ch
>>> https://stat.ethz.ch/mailman/listinfo/bioconductor
>>> Search the archives: 
>>> http://news.gmane.org/gmane.science.biology.informatics.conductor
>>
>>


-- 
Florian Hahne, PhD
Computational Biology Program
Division of Public Health Sciences
Fred Hutchinson Cancer Research Center
1100 Fairview Ave. N, M2-B876
PO Box 19024
Seattle, Washington 98109-1024
206-667-3148
fhahne at fhcrc.org



More information about the Bioconductor mailing list