Строим диаграммы с помощью Scala Chart

3 апреля 2013

Сегодня мы научимся строить графики, гистограммы и круговые диаграммы в Scala. В этом нам поможет замечательная библиотека Scala Chart, которая представляет собой обертку над широко известной в мире Java библиотекой JFreeChart. Также будут объяснены некоторые интересные возможности и особенности Scala, которые ранее не рассматривались в этом блоге.

Нам понадобится примерно такой build.sbt:

name := "scala-chart-examples"

version := "0.1"

scalaVersion := "2.10.1"

libraryDependencies += "com.github.wookietreiber" %%
                       "scala-chart" %
                       "0.2.0"

scalacOptions ++= Seq("-unchecked", "-deprecation")

Если вы хотите использовать самую последнюю версию Scala Chart, то вместо конкретной версии "0.2.0" можете написать "latest.integration". Но если хотите знать мое мнение, это неразумно.

Рассмотрим построение простейшего графика:

package me.eax.scalachart

import scalax.chart._
import scalax.chart.Charting._
import java.io._

object Application extends App {
  val defaultChartSize = (512, 384)
  def createSimpleXYLineChart {
    val dataset = Seq((1,2),(2,4),(3,6),(4,8))
    val chart = XYLineChart(
                  dataset.toXYSeriesCollection("f(x)"),
                  title = "XYLineChart Simple Example",
                  domainAxisLabel = "x axis description",
                  rangeAxisLabel = "y axis description"
                )
    chart.saveAsPNG(new File("./simpleXYLineChart.png"),
                    defaultChartSize)
  }

С кортежами мы уже знакомы. Seq — это трейт (трейты в первом приближении можно считать аналогами интерфейсов в Java или абстрактных классов в C++) для последовательностей. С последовательностями мы тоже уже сталкивались, но не предавали этому особого значения. Последовательности похожи на итерируемые контейнеры, только элементы в последовательностях всегда упорядочены.

Вызов Seq(1,2,3) представляет собой всего лишь вызов метода apply класса-одиночки scala.collection.Seq, который возвращает обыкновенный односвязный список из переданных элементов:

scala> Seq(1,2,3)
res0: Seq[Int] = List(1, 2, 3)

Обратите внимание на создание экземпляра класса XYLineChart. Тут были использованы именованные параметры, с чем мы раньше не сталкивались. Эта возможность очень удобна в случае, если у метода много аргументов, и некоторые из них имеют значения по умолчанию. Также возникает вопрос, откуда у класса List взялся метод toXYSeriesCollection? В действительности, тут используется неявное приведение типов. Если в двух словах, вот как это работает:

scala> class MyIterable[T](it: Iterable[T]) { def secret { it foreach println } }
defined class MyIterable

scala> implicit def iterableToMyIterable[T](x: Iterable[T]) = new MyIterable(x)
iterableToMyIterable: [T](x: Iterable[T])MyIterable[T]

scala> List(1,2,3).secret
1
2
3

Класс List не имеет метода secret, однако, с помощью iterableToMyIterable, он может быть неявным образом преобразован в MyIterable, у которого этот метод есть. Компилятор Scala автоматически преобразует последнюю строку кода в iterableToMyIterable(List(1,2,3)).secret, в результате чего у класса List «появляется новый метод». Кстати, в Scala 2.10, благодаря появившийся поддержке implicit-классов, можно обойтись без вспомогательной функции.

Далее с помощью метода saveAsPNG мы сохраняем график в PNG-файл:

Простой график, построенный в Scala Chart

Существуют аналогичные методы для форматов JPEG и PDF. Кроме того, с помощью метода show можно вывести график в окне, созданном с помощью Swing.

Рассмотрим чуть более сложный пример:

  def createXYLineChart {
    val dataset = Seq(
                    ("x*x - 2*x", for(x <- -3.0 to 8.0 by 1.0)
                                    yield (x, x*x - 2 * x)),
                    ("3 * x", for(x <- 1.0 to 10.0 by 0.1)
                                yield (x, 3 * x))
                  )  
    val chart = XYLineChart(
                  dataset.toXYSeriesCollection,
                  title = "XYLineChart Example",
                  domainAxisLabel = "время, с",
                  rangeAxisLabel = "скорость, м/c"
                )  
    chart.saveAsPNG(new File("./xyLineChart.png"), defaultChartSize)
  }

Для нас здесь нет ничего нового. Полученные графики:

Графики, построенные в Scala Chart

Иногда требуется построить график не по координатам точек, а, например, по месяцам и количеству переходов на сайт в эти месяцы. Для решения этой задачи в Scala Chart предусмотрен класс LineChart:

  val categoryDatasetExample = Map(
        "январь" -> Map("заработано" -> 2, "потрачено" -> 4),
        "февраль" -> Map("заработано" -> 4, "потрачено" -> 5),
        "март" -> Map("заработано" -> 8, "потрачено" -> 6),
        "апрель" -> Map("заработано" -> 16, "потрачено" -> 7)
      ).toCategoryDataset

  def createLineChart {
    val chart = LineChart(
                  categoryDatasetExample,
                  title = "LineChart Example",
                  domainAxisLabel = "месяц",
                  rangeAxisLabel = "млн рублей"
                )
    chart.saveAsPNG(new File("./lineChart.png"), defaultChartSize)
  }

Здесь мы используем контейнер immutable.Map. Можно было использовать его и в предыдущем примере, в этом смысле библиотека Scala Chart является очень гибкой. Что интересно, -> — это не часть синтаксиса Scala, а обычная функция, возвращающая пару из переданных ей аргументов.

Полученные графики:

Использование класса LineChart

Построение круговой диаграммы:

  def createPieChart {
    val dataset = Map(
        "Erlang" -> 33.3,
        "Scala" -> 33.3,
        "Clojure" -> 13.4,
        "Haskell" -> 10.0,
        "OCaml" -> 10.0
      )
    val chart = PieChart.threeDimensional(
                  dataset.toPieDataset,
                  legend = false,
                  title = """|Результаты опроса
                             |"Ваш любимый язык программирования"
                             |"""
.stripMargin
                )
    chart.saveAsPNG(new File("./pieChart.png"), defaultChartSize)
  }

Диаграмма:

Круговая диаграмма, построенная в Scala Chart

Построение простой гистограммы:

  def createSimpleBarChart {
    val dataset = Map("alpha" -> 123.45, "beta" -> 678.9)
    val chart = BarChart(
                  dataset.toCategoryDataset,
                  title = "Simple Bar Chart Example"
                )
    chart.saveAsPNG(new File("./simpleBarChart.png"), defaultChartSize)
  }

Результат:

Простая гистограмма, построенная в Scala Chart

Чуть более сложная гистограмма:

  def createBarChart {
    val chart = BarChart(
                  categoryDatasetExample, // см выше
                  title = "Bar Chart Example"
                )
    chart.saveAsPNG(new File("./barChart.png"), defaultChartSize)
  }

Результат:

Более сложная гистограмма, созданная с помощью Scala Chart

Также Scala Chart умеет строить некоторые другие виды диаграмм. Подробности вы найдете в официальной ScalaDoc. Полная версия приведенного выше кода доступна в этом архиве.

Дополнение: Вас также могут заинтересовать статьи Работа с Excel-файлами в Scala и Построение диаграмм на Python с помощью Matplotlib.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.