boB Rudis
5 years ago
8 changed files with 151 additions and 42 deletions
@ -0,0 +1,71 @@ |
|||
#' Generate an ATT&CK heatmap |
|||
#' |
|||
#' @param xdf a data frame with `tactic`, `technique` and `value` columns. |
|||
#' If no `value` column exists, then the function will assume you |
|||
#' have passed in individual events and will perform a "count" |
|||
#' summarization before generating the heatmap. |
|||
#' @param input,output,matrix if both are not `NULL` then they should be |
|||
#' what [fct_tactic()] takes as parameters. Otherwise, the function |
|||
#' will assume that the `tactic` column is already an ordered factor. |
|||
#' @param tile_col,tile_size color/size for the tile borders; |
|||
#' defaults to "`white`" and `0.5`, respectively. |
|||
#' @param dark_lab,light_lab text colors for when they appear on top of a |
|||
#' dark or light tile |
|||
#' @param dark_value_threshold since you can supply your own fill scale |
|||
#' and may use a transformation (e.g. "`log10`") when doing so, you |
|||
#' can specify the cutoff value for when to use `dark_lab` vs `light_lab`. |
|||
#' If `NULL` then half of `max(value)` will be used. |
|||
#' @param ... passed on to the internal call to [ggplot2::geom_text()] |
|||
#' @return a ggplot2 plot object which you can add a fill scale to as well |
|||
#' as themeing. |
|||
#' @export |
|||
attck_map <- function(xdf, input = NULL, output = NULL, matrix = NULL, |
|||
tile_col = "white", tile_size = 0.5, |
|||
dark_lab = "white", light_lab = "black", |
|||
dark_value_threshold = NULL, ...) { |
|||
|
|||
cn <- colnames(xdf) |
|||
if (!all(c("tactic", "technique") %in% cn)) { |
|||
stop("'xdf' needs both 'tactic' and 'technique' columns.", call.=FALSE) |
|||
} |
|||
|
|||
if (!("value" %in% cn)) { |
|||
xdf <- dplyr::count(xdf, tactic, technique, name = "value") |
|||
} |
|||
|
|||
if (is.null(input) && is.null(output)) { |
|||
if (!is.factor(xdf$tactic)) { |
|||
stop( |
|||
"No 'input'/'output' transformation specified but 'tactic' is not a factor.", |
|||
call.=FALSE |
|||
) |
|||
} |
|||
} else { |
|||
if (sum(c(!is.null(input), !is.null(output), !is.null(matrix))) != 3) { |
|||
stop("Must specify 'input', 'output', and 'matrix' if any one of them is not NULL", call.=FALSE) |
|||
} |
|||
xdf$tactic <- fct_tactic(xdf$tactic, input = input, output = output, matrix = matrix) |
|||
} |
|||
|
|||
if (is.null(dark_value_threshold)) dark_value_threshold <- max(xdf$value)/2 |
|||
|
|||
xdf <- dplyr::arrange(xdf, value) |
|||
xdf$technique <- factor(gsub(" ", "\n", xdf$technique)) |
|||
xdf <- dplyr::group_by(xdf, tactic) |
|||
xdf <- dplyr::mutate(xdf, ids = (n():1)) |
|||
xdf <- dplyr::ungroup(xdf) |
|||
|
|||
gg <- ggplot(xdf, aes(tactic, ids)) |
|||
gg <- gg + geom_tile(aes(fill = value), color = tile_col, size = tile_size) |
|||
gg <- gg + geom_text( |
|||
aes( |
|||
label = technique, |
|||
color = I(ifelse(value <= dark_value_threshold, dark_lab, light_lab)) |
|||
), ... |
|||
) |
|||
gg <- gg + scale_x_discrete(expand = c(0, 0), position = "top") |
|||
gg <- gg + scale_y_reverse(expand = c(0, 0)) |
|||
|
|||
gg |
|||
|
|||
} |
@ -0,0 +1,40 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/attck-map.R |
|||
\name{attck_map} |
|||
\alias{attck_map} |
|||
\title{Generate an ATT&CK heatmap} |
|||
\usage{ |
|||
attck_map(xdf, input = NULL, output = NULL, matrix = NULL, |
|||
tile_col = "white", tile_size = 0.5, dark_lab = "white", |
|||
light_lab = "black", dark_value_threshold = NULL, ...) |
|||
} |
|||
\arguments{ |
|||
\item{xdf}{a data frame with \code{tactic}, \code{technique} and \code{value} columns. |
|||
If no \code{value} column exists, then the function will assume you |
|||
have passed in individual events and will perform a "count" |
|||
summarization before generating the heatmap.} |
|||
|
|||
\item{input, output, matrix}{if both are not \code{NULL} then they should be |
|||
what \code{\link[=fct_tactic]{fct_tactic()}} takes as parameters. Otherwise, the function |
|||
will assume that the \code{tactic} column is already an ordered factor.} |
|||
|
|||
\item{tile_col, tile_size}{color/size for the tile borders; |
|||
defaults to "\code{white}" and \code{0.5}, respectively.} |
|||
|
|||
\item{dark_lab, light_lab}{text colors for when they appear on top of a |
|||
dark or light tile} |
|||
|
|||
\item{dark_value_threshold}{since you can supply your own fill scale |
|||
and may use a transformation (e.g. "\code{log10}") when doing so, you |
|||
can specify the cutoff value for when to use \code{dark_lab} vs \code{light_lab}. |
|||
If \code{NULL} then half of \code{max(value)} will be used.} |
|||
|
|||
\item{...}{passed on to the internal call to \code{\link[ggplot2:geom_text]{ggplot2::geom_text()}}} |
|||
} |
|||
\value{ |
|||
a ggplot2 plot object which you can add a fill scale to as well |
|||
as themeing. |
|||
} |
|||
\description{ |
|||
Generate an ATT&CK heatmap |
|||
} |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 79 KiB |
Loading…
Reference in new issue