9
$\begingroup$

Is there a name for this sort of chart below (sourced from New Zealand's Ministry of Business, Innovation and Employment, for whom I work but I was not involved in creating this plot)? It consists of rectangles where the area is proportional to a variable, and resembles a sort of cross between a pie chart, a mosaic plot and a mekko plot. It is perhaps closest to a mekko plot but has the complication that we are not working with columns but a more complex jigsaw.

The original looks a little better as there are white borders between the rectangles for each region.

Surprisingly, it actually strikes me as not too bad a statistical graphic, although it could be improved through better use of colour mapped to something meaningful. A powerful interactive version showing the US 2011 budget has been used by the New York Times.

An interesting challenge is to think of an automatic algorithm to draw one and make it look reasonable too. The rectangles need to be allowed to have different aspect ratios, within an acceptable range.

enter image description here

| cite | improve this question | | | | |
$\endgroup$
11
$\begingroup$

That's a treemap, I guess (http://en.wikipedia.org/wiki/Treemapping).

There are several packages, e.g. in R, that create treemaps. One of the packages is called treemap, and another one is portfolio. For example, Nathan Yau offers a tutorial on how to create a treemap using R (http://flowingdata.com/2010/02/11/an-easy-way-to-make-a-treemap/).

| cite | improve this answer | | | | |
$\endgroup$
12
$\begingroup$

The question is the name, but how well it works is also open to discussion.

Here's something much more prosaic as an alternative, a horizontal bar chart.

enter image description here

What we might want to do with such a graph varies from some grasp of the overall pattern to some scrutiny of individual cases (what about Hawke's Bay, and so forth). I'd assert that both are easier with a bar chart. Small details are that I use lower case in titles and names where easy and don't repeat the % sign. I've roughly imitated the colour coding without finding out what it means, so that is just as clear, or obscure, as what you copied.

I suggest that some of the appeal of treemaps lies in their relative novelty. They might work as well as or better than bar charts if there are dozens of names, which can be spread over a two-dimensional area rather than listed in a long column. But for 15 or so names, a bar chart remains a strong competitor in my view.

I am happy with anyone who prefers a (Cleveland) dot chart here. A vertical bar chart would face the difficulty of placing the region names comfortably. (Just imagine rotating this graph to see that.) I like the idea of giving the numbers too, although conservatives don't like mixing graph and table ideas.

The graph was drawn in Stata.

| cite | improve this answer | | | | |
$\endgroup$
  • 4
    $\begingroup$ I'll have to dig it up, but if my memory serves me correctly one of the original motivations of the tree map was for a hierarchical organization of information (i.e. to allow you to view the combined size of different levels of the hierarchy) and for many more numbers. The intent was never for small lists of numbers and had a more exploratory appeal, see Perceptual Guidelines for Creating Rectangular Treemaps (Kong et al. 2010) $\endgroup$ – Andy W Sep 5 '13 at 12:59
  • 1
    $\begingroup$ That's my impression too, hence the name treemap. Only one level of a hierarchy is obvious here. $\endgroup$ – Nick Cox Sep 5 '13 at 13:02
  • 4
    $\begingroup$ Bill Shneiderman has put together a nice history of treemaps with links to some relevant publications (cs.umd.edu/hcil/treemap-history). Treemaps were initially intended to display a multilevel hierarchy in a less cluttered way than dendrograms or trees could, and they were at first used for visualizing the contents of hard disks. Nowadays, treemaps are used for visualizing large phylogenetics trees (they show relationships between species), among other applications. For more examples, see the article by Shneiderman at perceptualedge.com/articles/b-eye/treemaps.pdf. $\endgroup$ – JTT Sep 5 '13 at 13:36
  • $\begingroup$ Thanks; for what it's worth I agree in this particular case. $\endgroup$ – Peter Ellis Sep 5 '13 at 20:28
3
$\begingroup$

Edit / addition

I have since discovered that the treemap package gives a much better result than the map.market() function mentioned (and adapted) below; but I'll leave my answer in for historical reasons.

Original Answer

Thanks for the answers. Building on the flowing data link provided by @JTT but disliking the need to tweak by hand in Illustrator or Inkscape just to get a reasonable graphic, I tweaked the map.market() function in Jeff Enos and David Kane's portfolio package to make it more user-controlled, the labels vary by rectangle size, and avoid red-green contrasts. Example usage:

library(portfolio)
library(extrafont)
data(dow.jan.2005)

with(dow.jan.2005, 
    treemap(id    = symbol,
        area  = price,
        group = sector,
        color = 100 * month.ret,
        labsc = .12,  # user-chosen scaling of labels 
        fontfamily="Comic Sans MS")
    )

enter image description here

For what it's worth, I also agree with @NickCox that in the example in my original question a dot plot is superior. Code of my adapted treemap() function follows.

treemap <- function (id, area, group, color, scale = NULL, lab = c(group = TRUE, 
    id = FALSE), low="red", middle="grey60", high="blue", main = "Map of the Market", labsc = c(.5, 1), print = TRUE, ...) 
{
    # Adapted by Peter Ellis from map.market() by Jeff Enos and David Kane in the portfolio package on CRAN
    # See map.market for the original helpfile.  The changes are:
    # 1. low, middle and high are user-set color ramp choices
    # 2. The font size now varies with the area of the rectangle being labelled; labsc is a scaling parameter to make it look ok.
    #    First element of labsc is scaling parameter for size of group labels.  Second element is scaling for id labels.
    # 3. ... extra arguments to be passed to gpar() when drawing labels; expected use is for fontfamily="whatever"
    require(portfolio)
    if (any(length(id) != length(area), length(id) != length(group), 
        length(id) != length(color))) {
        stop("id, area, group, and color must be the same length.")
    }
    if (length(lab) == 1) {
        lab[2] <- lab[1]
    }
    if (missing(id)) {
        id <- seq_along(area)
        lab["id"] <- FALSE
    }
    stopifnot(all(!is.na(id)))
    data <- data.frame(label = id, group, area, color)
    data <- data[order(data$area, decreasing = TRUE), ]
    na.idx <- which(is.na(data$area) | is.na(data$group) | is.na(data$color))
    if (length(na.idx)) {
        warning("Stocks with NAs for area, group, or color will not be shown")
        data <- data[-na.idx, ]
    }
    zero.area.idx <- which(data$area == 0)
    if (length(zero.area.idx)) {
        data <- data[-zero.area.idx, ]
    }
    if (nrow(data) == 0) {
        stop("No records to display")
    }
    data$color.orig <- data$color
    if (is.null(scale)) {
        data$color <- data$color * 1/max(abs(data$color))
    }
    else {
        data$color <- sapply(data$color, function(x) {
            if (x/scale > 1) 
                1
            else if (-1 > x/scale) 
                -1
            else x/scale
        })
    }
    data.by.group <- split(data, data$group, drop = TRUE)
    group.data <- lapply(data.by.group, function(x) {
        sum(x[, 3])
    })
    group.data <- data.frame(area = as.numeric(group.data), label = names(group.data))
    group.data <- group.data[order(group.data$area, decreasing = TRUE), 
        ]
    group.data$color <- rep(NULL, nrow(group.data))
    color.ramp.pos <- colorRamp(c(middle, high))
    color.ramp.neg <- colorRamp(c(middle, low))
    color.ramp.rgb <- function(x) {
        col.mat <- mapply(function(x) {
            if (x < 0) {
                color.ramp.neg(abs(x))
            }
            else {
                color.ramp.pos(abs(x))
            }
        }, x)
        mapply(rgb, col.mat[1, ], col.mat[2, ], col.mat[3, ], 
            max = 255)
    }
    add.viewport <- function(z, label, color, x.0, y.0, x.1, 
        y.1) {
        for (i in 1:length(label)) {
            if (is.null(color[i])) {
                filler <- gpar(col = "blue", fill = "transparent", 
                  cex = 1)
            }
            else {
                filler.col <- color.ramp.rgb(color[i])
                filler <- gpar(col = filler.col, fill = filler.col, 
                  cex = 0.6)
            }
            new.viewport <- viewport(x = x.0[i], y = y.0[i], 
                width = (x.1[i] - x.0[i]), height = (y.1[i] - 
                  y.0[i]), default.units = "npc", just = c("left", 
                  "bottom"), name = as.character(label[i]), clip = "on", 
                gp = filler)
            z <- append(z, list(new.viewport))
        }
        z
    }
    squarified.treemap <- function(z, x = 0, y = 0, w = 1, h = 1, 
        func = add.viewport, viewport.list) {
        cz <- cumsum(z$area)/sum(z$area)
        n <- which.min(abs(log(max(w/h, h/w) * sum(z$area) * 
            ((cz^2)/z$area))))
        more <- n < length(z$area)
        a <- c(0, cz[1:n])/cz[n]
        if (h > w) {
            viewport.list <- func(viewport.list, z$label[1:n], 
                z$color[1:n], x + w * a[1:(length(a) - 1)], rep(y, 
                  n), x + w * a[-1], rep(y + h * cz[n], n))
            if (more) {
                viewport.list <- Recall(z[-(1:n), ], x, y + h * 
                  cz[n], w, h * (1 - cz[n]), func, viewport.list)
            }
        }
        else {
            viewport.list <- func(viewport.list, z$label[1:n], 
                z$color[1:n], rep(x, n), y + h * a[1:(length(a) - 
                  1)], rep(x + w * cz[n], n), y + h * a[-1])
            if (more) {
                viewport.list <- Recall(z[-(1:n), ], x + w * 
                  cz[n], y, w * (1 - cz[n]), h, func, viewport.list)
            }
        }
        viewport.list
    }
    map.viewport <- viewport(x = 0.05, y = 0.05, width = 0.9, 
        height = 0.75, default.units = "npc", name = "MAP", just = c("left", 
            "bottom"))
    map.tree <- gTree(vp = map.viewport, name = "MAP", children = gList(rectGrob(gp = gpar(col = "dark grey"), 
        name = "background")))
    group.viewports <- squarified.treemap(z = group.data, viewport.list = list())
    for (i in 1:length(group.viewports)) {
        this.group <- data.by.group[[group.data$label[i]]]
        this.data <- data.frame(this.group$area, this.group$label, 
            this.group$color)
        names(this.data) <- c("area", "label", "color")
        stock.viewports <- squarified.treemap(z = this.data, 
            viewport.list = list())
        group.tree <- gTree(vp = group.viewports[[i]], name = group.data$label[i])
        for (s in 1:length(stock.viewports)) {
            stock.tree <- gTree(vp = stock.viewports[[s]], name = this.data$label[s], 
                children = gList(rectGrob(name = "color")))
            if (lab[2]) {
                stock.tree <- addGrob(stock.tree, textGrob(x = unit(1, 
                  "lines"), y = unit(1, "npc") - unit(1, "lines"), 
                  label = this.data$label[s], gp = gpar(col = "white", fontsize=this.data$area[s] * labsc[2], ...), 
                  name = "label", just = c("left", "top")))
            }
            group.tree <- addGrob(group.tree, stock.tree)
        }
        group.tree <- addGrob(group.tree, rectGrob(gp = gpar(col = "grey"), 
            name = "border"))
        if (lab[1]) {
            group.tree <- addGrob(group.tree, textGrob(label = group.data$label[i], 
                name = "label", gp = gpar(col = "white", fontsize=group.data$area[i] * labsc[1], ...)))
        }
        map.tree <- addGrob(map.tree, group.tree)
    }
    op <- options(digits = 1)
    top.viewport <- viewport(x = 0.05, y = 1, width = 0.9, height = 0.2, 
        default.units = "npc", name = "TOP", , just = c("left", 
            "top"))
    legend.ncols <- 51
    l.x <- (0:(legend.ncols - 1))/(legend.ncols)
    l.y <- unit(0.25, "npc")
    l.cols <- color.ramp.rgb(seq(-1, 1, by = 2/(legend.ncols - 
        1)))
    if (is.null(scale)) {
        l.end <- max(abs(data$color.orig))
    }
    else {
        l.end <- scale
    }
    top.list <- gList(textGrob(label = main, y = unit(0.7, "npc"), 
        just = c("center", "center"), gp = gpar(cex = 2, ...)), segmentsGrob(x0 = seq(0, 
        1, by = 0.25), y0 = unit(0.25, "npc"), x1 = seq(0, 1, 
        by = 0.25), y1 = unit(0.2, "npc")), rectGrob(x = l.x, 
        y = l.y, width = 1/legend.ncols, height = unit(1, "lines"), 
        just = c("left", "bottom"), gp = gpar(col = NA, fill = l.cols), 
        default.units = "npc"), textGrob(label = format(l.end * 
        seq(-1, 1, by = 0.5), trim = TRUE), x = seq(0, 1, by = 0.25), 
        y = 0.1, default.units = "npc", just = c("center", "center"), 
        gp = gpar(col = "black", cex = 0.8, fontface = "bold")))
    options(op)
    top.tree <- gTree(vp = top.viewport, name = "TOP", children = top.list)
    mapmarket <- gTree(name = "MAPMARKET", children = gList(rectGrob(gp = gpar(col = "dark grey", 
        fill = "dark grey"), name = "background"), top.tree, 
        map.tree))
    if (print) {
        grid.newpage()
        grid.draw(mapmarket)
    }
    invisible(mapmarket)
}
| cite | improve this answer | | | | |
$\endgroup$
  • $\begingroup$ That code will no doubt be useful. I don't want to drag the discussion into areas where it will not go, but is the example quite arbitrary or is there a rationale for letting the areas represent stock prices? What are we supposed to see or be looking for on this plot? (I am not hostile, just totally inexperienced with trying to use this design for real, although I have seen plenty of examples.) $\endgroup$ – Nick Cox Sep 6 '13 at 9:19
  • 1
    $\begingroup$ Actually I just took example that from the help file for map.market() by Enos and Kane. Reflecting on it I don't see why they chose to have area show price; a more sensible measure would surely be to show total capitalisation ie price x number of shares (either number of shares in the market, or just the number of shares I happen to own depending on the purpose). Then you would have a good intuitive use of the plot to show the importance of the different stocks. $\endgroup$ – Peter Ellis Sep 9 '13 at 0:09
  • $\begingroup$ I was puzzled too by the use of price. $\endgroup$ – Nick Cox Sep 10 '13 at 7:03
1
$\begingroup$

It is a treemap, you can do it easily with Tableau 8 and free Tableau Public, see sample here: http://www.tableausoftware.com/new-features/new-view-types . You can also see @this URL that Treemap can be combined with Bar Chart

| cite | improve this answer | | | | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.