# Recipes: qualitative palettes

Qualitative palettes are for categorical data where no category is “higher” or “lower” than another. The colors need to be distinct, not ordered. This article shows common plot types where qualitative palettes shine.

## Scatter plot - valley_of_flowers

The most basic use. Three species, three vivid colors that don’t suggest any ordering.

    ggplot(iris, aes(Sepal.Length, Petal.Length,
                     color = Species, shape = Species)) +
      geom_point(size = 3.5, alpha = 0.85) +
      scale_color_prakriti("valley_of_flowers") +
      labs(
        title = "Iris petal vs sepal length",
        subtitle = "valley_of_flowers palette",
        x = "Sepal length (cm)", y = "Petal length (cm)"
      ) +
      theme_prak()

## Stacked area chart - kaas

Five wildflower species tracked across months. `kaas` has 7 colors, so 5 categories sit comfortably without recycling.

    set.seed(42)
    months <- rep(month.abb, each = 5)
    species <- rep(c("Strobilanthes", "Smithia", "Impatiens",
                     "Senecio", "Eriocaulon"), 12)
    area_df <- data.frame(
      month   = factor(months, levels = month.abb),
      species = species,
      cover   = pmax(0, rnorm(60, 40, 20) +
                        rep(c(0, 10, 25, 35, 50, 60, 55, 40, 20, 10, 5, 0),
                            each = 5))
    )

    ggplot(area_df, aes(month, cover, fill = species, group = species)) +
      geom_area(alpha = 0.88, color = "white", linewidth = 0.3) +
      scale_fill_prakriti("kaas") +
      labs(
        title = "Seasonal wildflower ground cover on Kaas Plateau",
        subtitle = "kaas palette",
        x = NULL, y = "Cover (%)", fill = "Species"
      ) +
      theme_prak()

## Grouped bar chart - western_ghats

Comparing species richness across regions. `western_ghats` gives each taxonomic group a distinct color that reads well side by side.

    set.seed(7)
    bio_df <- data.frame(
      region   = rep(c("Nilgiris", "Coorg", "Agumbe", "Wayanad"), each = 5),
      taxon    = rep(c("Birds", "Butterflies", "Reptiles",
                       "Amphibians", "Mammals"), 4),
      richness = sample(30:180, 20, replace = TRUE)
    )
    bio_df$region <- factor(bio_df$region,
                            levels = c("Nilgiris", "Coorg", "Agumbe", "Wayanad"))

    ggplot(bio_df, aes(region, richness, fill = taxon)) +
      geom_col(position = position_dodge(width = 0.8), width = 0.7) +
      scale_fill_prakriti("western_ghats") +
      labs(
        title = "Species richness across Western Ghats sub-regions",
        subtitle = "western_ghats palette",
        x = NULL, y = "Number of species", fill = NULL
      ) +
      theme_prak() +
      theme(legend.position = "top")

## Donut chart - andaman

Proportional habitat composition. `andaman` pairs turquoise ocean tones with a coral-red accent that makes the donut segments pop.

    pie_df <- data.frame(
      habitat = c("Coral reef", "Mangrove", "Seagrass",
                  "Sandy beach", "Rainforest", "Open ocean"),
      pct     = c(28, 18, 12, 10, 22, 10)
    )
    pie_df$ymax <- cumsum(pie_df$pct)
    pie_df$ymin <- c(0, head(pie_df$ymax, -1))
    pie_df$mid  <- (pie_df$ymin + pie_df$ymax) / 2
    pie_df$label <- paste0(pie_df$habitat, "\n", pie_df$pct, "%")

    ggplot(pie_df, aes(ymax = ymax, ymin = ymin, xmax = 4, xmin = 2.2,
                       fill = habitat)) +
      geom_rect(color = "white", linewidth = 0.8) +
      geom_text(aes(x = 3.1, y = mid, label = label),
                size = 3.2, color = "grey15") +
      scale_fill_prakriti("andaman") +
      coord_polar(theta = "y") +
      xlim(c(0.5, 4.5)) +
      labs(
        title = "Andaman Islands habitat composition",
        subtitle = "andaman palette"
      ) +
      theme_void(base_size = 12) +
      theme(
        plot.title    = element_text(face = "bold", hjust = 0.5),
        plot.subtitle = element_text(hjust = 0.5, color = "grey35"),
        legend.position = "none",
        plot.margin = margin(10, 10, 10, 10)
      )

## Violin ridgeline - chilika

Simulated flamingo flock sizes across months. Using `scale_fill_manual()` with colors pulled from `prakriti_palette()` gives you full control when you need the fill and color aesthetics to match.

    set.seed(123)
    ridge_df <- do.call(rbind, lapply(1:6, function(m) {
      n <- 400
      base_mu <- c(120, 200, 350, 280, 160, 90)[m]
      data.frame(
        month = factor(month.abb[m], levels = rev(month.abb[1:6])),
        flock = pmax(0, rnorm(n, base_mu, base_mu * 0.3))
      )
    }))

    cols_chilika <- prakriti_palette("chilika")

    ggplot(ridge_df, aes(flock, month, fill = month, color = month)) +
      geom_violin(scale = "width", width = 0.85, alpha = 0.8,
                  trim = TRUE, linewidth = 0.3) +
      scale_fill_manual(values = cols_chilika) +
      scale_color_manual(values = cols_chilika) +
      labs(
        title = "Flamingo flock size distribution - Chilika Lake",
        subtitle = "chilika palette",
        x = "Flock size", y = NULL
      ) +
      theme_prak() +
      theme(legend.position = "none",
            panel.grid.major.y = element_blank())

## Proportion bar - coorg

Horizontal stacked proportions for crop composition. Works well with `geom_rect()` when you want labeled segments.

    crops <- data.frame(
      crop = c("Coffee", "Pepper", "Cardamom", "Rice", "Oranges", "Timber"),
      area = c(38, 22, 12, 15, 8, 5)
    )
    crops$xmax <- cumsum(crops$area)
    crops$xmin <- c(0, head(crops$xmax, -1))
    crops$mid  <- (crops$xmin + crops$xmax) / 2

    ggplot(crops, aes(xmin = xmin, xmax = xmax, ymin = 0, ymax = 1,
                      fill = crop)) +
      geom_rect(color = "white", linewidth = 0.6) +
      geom_text(aes(x = mid, y = 0.5,
                    label = paste0(crop, "\n", area, "%")),
                color = "white", fontface = "bold", size = 3.5) +
      scale_fill_prakriti("coorg") +
      coord_cartesian(expand = FALSE) +
      labs(
        title = "Coorg district crop composition",
        subtitle = "coorg palette"
      ) +
      theme_void(base_size = 12) +
      theme(
        plot.title    = element_text(face = "bold"),
        plot.subtitle = element_text(color = "grey30"),
        legend.position = "none",
        plot.margin = margin(12, 12, 12, 12)
      )

## Tile grid - kutch_textile

A 7-color showcase. `kutch_textile` has enough distinct colors for complex categorical data.

    set.seed(77)
    kutch_df <- data.frame(
      x     = rep(1:14, 10),
      y     = rep(1:10, each = 14),
      motif = sample(c("Mirror", "Indigo block", "Turmeric dye",
                        "Madder red", "Teal thread", "Orange applique",
                        "Purple resist"), 140, replace = TRUE)
    )

    ggplot(kutch_df, aes(x, y, fill = motif)) +
      geom_tile(color = "grey90", linewidth = 0.3) +
      scale_fill_prakriti("kutch_textile") +
      coord_equal(expand = FALSE) +
      labs(
        title = "Kutch textile motif sampler",
        subtitle = "kutch_textile palette",
        fill = "Motif"
      ) +
      theme_void(base_size = 11) +
      theme(
        plot.title    = element_text(face = "bold"),
        plot.subtitle = element_text(color = "grey30"),
        plot.margin   = margin(12, 12, 12, 12)
      )

## Jittered scatter with many groups - ladakh_monastery

6 vehicle classes, each getting a prayer-flag primary. `ladakh_monastery` keeps groups separable even when points overlap.

    ggplot(mpg, aes(displ, hwy, color = class)) +
      geom_jitter(size = 2.8, alpha = 0.8, width = 0.15) +
      scale_color_prakriti("ladakh_monastery") +
      labs(
        title = "Engine displacement vs highway MPG by vehicle class",
        subtitle = "ladakh_monastery palette",
        x = "Displacement (L)", y = "Highway MPG", color = "Class"
      ) +
      theme_prak()
    #> Warning: Requested 7 colors but 'ladakh_monastery' only has 6; recycling. Use
    #> type = "continuous" for smooth interpolation.

## Dark-mode scatter - nocturn

`nocturn` starts from near-black, so it pairs naturally with a dark background. Good for presentations or dashboards with dark themes.

    set.seed(23)
    bio_pts <- data.frame(
      x = c(rnorm(300, 3, 1.2), rnorm(200, 7, 0.8)),
      y = c(rnorm(300, 4, 1.5), rnorm(200, 6, 1.0))
    )
    bio_pts$intensity <- with(bio_pts,
      dnorm(x, 3, 1.2) * dnorm(y, 4, 1.5) +
      dnorm(x, 7, 0.8) * dnorm(y, 6, 1.0)
    )

    ggplot(bio_pts, aes(x, y, color = intensity)) +
      geom_point(size = 1.8, alpha = 0.85) +
      scale_color_prakriti("nocturn", discrete = FALSE) +
      labs(
        title = "Bioluminescent point field",
        subtitle = "nocturn palette on dark background",
        x = NULL, y = NULL
      ) +
      theme_minimal(base_size = 12) +
      theme(
        plot.background  = element_rect(fill = "#020A18", color = NA),
        panel.background = element_rect(fill = "#020A18", color = NA),
        panel.grid       = element_blank(),
        text             = element_text(color = "grey80"),
        plot.title       = element_text(face = "bold", color = "white"),
        plot.subtitle    = element_text(color = "grey50"),
        axis.text        = element_text(color = "grey50"),
        legend.position  = "none"
      )

## Hex bin density - munnar

`munnar` is pure green - bright tea-flush to deep shade. Pairs well with `geom_hex()` for bivariate density.

    set.seed(42)
    ggplot(diamonds[sample(nrow(diamonds), 5000), ],
           aes(carat, price)) +
      geom_bin2d(bins = 40) +
      scale_fill_prakriti("munnar", discrete = FALSE) +
      labs(
        title = "Diamond carat vs price density",
        subtitle = "munnar palette",
        x = "Carat", y = "Price (USD)", fill = "Count"
      ) +
      theme_prak()