in atlas-eval/src/main/scala/com/netflix/atlas/eval/graph/Grapher.scala [299:418]
def create(config: GraphConfig, eval: StyleExpr => ResultSet): GraphDef = {
val warnings = List.newBuilder[String]
val plotExprs = config.exprs.groupBy(_.axis.getOrElse(0))
val multiY = plotExprs.size > 1 && !GraphDef.ambiguousMultiY(config.flags.hints)
val palette = newPalette(config.flags.palette)
val shiftPalette = newPalette(settings.offsetPalette(config.flags.theme))
val start = config.startMillis
val end = config.endMillis
val plots = plotExprs.toList.sortWith(_._1 < _._1).map {
case (yaxis, exprs) =>
val axisCfg = config.flags.getAxis(yaxis)
val dfltStyle = if (axisCfg.stack) LineStyle.STACK else LineStyle.LINE
val statFormatter = axisCfg.tickLabelMode match {
case TickLabelMode.BINARY =>
(v: Double) => UnitPrefix.binary(v).format(v)
case _ =>
(v: Double) => UnitPrefix.decimal(v).format(v)
}
val axisPalette = axisCfg.palette.fold(palette) { v =>
newPalette(v)
}
var messages = List.empty[String]
var heatmapColor: Color = null
val lines = exprs.flatMap { s =>
val result = eval(s)
// Pick the last non empty message to appear. Right now they are only used
// as a test for providing more information about the state of filtering. These
// can quickly get complicated when used with other features. For example,
// sorting can mix and match lines across multiple expressions. Also binary
// math operations that combine the results of multiple filter expressions or
// multi-level group by with filtered input. For now this is just an
// experiment for the common simple case to see how it impacts usability
// when dealing with filter expressions that remove some of the lines.
if (result.messages.nonEmpty) messages = result.messages.take(1)
val ts = result.data
val labelledTS = ts.map { t =>
val stats = SummaryStats(t.data, start, end)
val offset = Strings.toString(Duration.ofMillis(s.offset))
val outputTags = t.tags + (TagKey.offset -> offset)
// Additional stats can be used for substitutions, but should not be included
// as part of the output tag map
val legendTags = outputTags ++ stats.tags(statFormatter)
val newT = t.withTags(outputTags)
newT.withLabel(s.legend(newT.label, legendTags)) -> stats
}
val palette = s.palette.map(newPalette).getOrElse {
s.color
.map { c =>
val color = settings.resolveColor(config.flags.theme, c)
val p = Palette.singleColor(color).iterator
(_: String) => p.next()
}
.getOrElse {
if (s.offset > 0L) shiftPalette else axisPalette
}
}
val lineDefs = labelledTS.sortWith(_._1.label < _._1.label).map {
case (t, stats) =>
val lineStyle = s.lineStyle.fold(dfltStyle)(LineStyle.parse)
val color = s.color.fold {
val c = lineStyle match {
case LineStyle.HEATMAP =>
if (axisCfg.heatmapPalette.nonEmpty) {
// Don't consume a color if the the global heatmap palette is configured.
// Just set it to something.
if (heatmapColor == null) heatmapColor = Color.BLACK
} else {
if (heatmapColor == null) heatmapColor = palette(s"heatmap$yaxis")
}
heatmapColor
case _ => palette(t.label)
}
// Alpha setting if present will set the alpha value for the color automatically
// assigned by the palette. If using an explicit color it will have no effect as the
// alpha can be set directly using an ARGB hex format for the color.
s.alpha.fold(c)(a => Colors.withAlpha(c, a))
} { c =>
settings.resolveColor(config.flags.theme, c)
}
LineDef(
data = t,
query = Some(s.toString),
groupByKeys = s.expr.finalGrouping,
color = color,
lineStyle = s.lineStyle.fold(dfltStyle)(LineStyle.parse),
lineWidth = s.lineWidth,
legendStats = stats
)
}
// Lines must be sorted for presentation after the colors have been applied
// using the palette. The colors selected should be stable regardless of the
// sort order that is applied. Otherwise colors would change each time a user
// changed the sort.
val sorted = sort(warnings, s.sortBy, s.useDescending, lineDefs)
s.limit.fold(sorted)(n => sorted.take(n))
}
// Apply sort based on URL parameters. This will take precedence over
// local sort on an expression.
val sortedLines = sort(warnings, axisCfg.sort, axisCfg.order.contains("desc"), lines)
axisCfg.newPlotDef(sortedLines ::: messages.map(s => MessageDef(s"... $s ...")), multiY)
}
config.newGraphDef(plots, warnings.result())
}