Строим диаграммы с помощью Scala Chart
3 апреля 2013
Сегодня мы научимся строить графики, гистограммы и круговые диаграммы в Scala. В этом нам поможет замечательная библиотека Scala Chart, которая представляет собой обертку над широко известной в мире Java библиотекой JFreeChart. Также будут объяснены некоторые интересные возможности и особенности Scala, которые ранее не рассматривались в этом блоге.
Нам понадобится примерно такой build.sbt:
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"
. Но если хотите знать мое мнение, это неразумно.
Рассмотрим построение простейшего графика:
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, который возвращает обыкновенный односвязный список из переданных элементов:
res0: Seq[Int] = List(1, 2, 3)
Обратите внимание на создание экземпляра класса XYLineChart. Тут были использованы именованные параметры, с чем мы раньше не сталкивались. Эта возможность очень удобна в случае, если у метода много аргументов, и некоторые из них имеют значения по умолчанию. Также возникает вопрос, откуда у класса List взялся метод toXYSeriesCollection? В действительности, тут используется неявное приведение типов. Если в двух словах, вот как это работает:
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-файл:
Существуют аналогичные методы для форматов JPEG и PDF. Кроме того, с помощью метода show можно вывести график в окне, созданном с помощью Swing.
Рассмотрим чуть более сложный пример:
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 предусмотрен класс LineChart:
"январь" -> 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, а обычная функция, возвращающая пару из переданных ей аргументов.
Полученные графики:
Построение круговой диаграммы:
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)
}
Диаграмма:
Построение простой гистограммы:
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)
}
Результат:
Чуть более сложная гистограмма:
val chart = BarChart(
categoryDatasetExample, // см выше
title = "Bar Chart Example"
)
chart.saveAsPNG(new File("./barChart.png"), defaultChartSize)
}
Результат:
Также Scala Chart умеет строить некоторые другие виды диаграмм. Подробности вы найдете в официальной ScalaDoc. Полная версия приведенного выше кода доступна в этом архиве.
Дополнение: Вас также могут заинтересовать статьи Работа с Excel-файлами в Scala и Построение диаграмм на Python с помощью Matplotlib.
Метки: Scala, Функциональное программирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.