def create()

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())
  }