Статьи

Создавайте простые графики и диаграммы на рельсах с Chartkick

диаграмма

Мы работаем с данными, представленными в различных форматах каждый день. С моей точки зрения, одним из наиболее удобных форматов представления числовых данных является график. Пользователи любят графики, особенно интерактивные, потому что они красивые и забавные — в отличие от скучных статических таблиц.

Существует множество решений для визуализации красивых интерактивных графиков в веб-приложениях, но сегодня мы обсудим нечто особенное: решение, созданное для Rails, которое делает рендеринг графиков быстрым. Это решение называется Chartkick и было разработано Эндрю Кейном. Chartkick может работать с Google Charts , Highcharts и Chart.js . Он имеет много опций для настройки, а также имеет множество вспомогательных библиотек, таких как groupdate , hightop и active_median .

В этой статье мы обсудим, как интегрировать Chartkick в приложение на Rails, визуализировать различные графики, настраивать их, асинхронно загружать их и как еще больше усилить ваш код с помощью гемов groupdate и hightop.

Исходный код можно найти на GitHub .

Рабочая демоверсия доступна на Heroku .

Подготовка заявки

Давайте создадим новое приложение Rails:

$ rails new HappyGrapher -T

В этой статье я буду использовать кандидат на релиз Rails 5, но приведенные примеры кода должны работать и с Rails 4 и 3.

Предположим, наше приложение отслеживает «спортсменов» (спортсменов) и соревнования, в которых они участвуют. Вот информация обо всех таблицах, которые нам потребуются:

sporters

  • namestring
  • ageinteger
  • country_idintegersporterscountries

countries

  • titlestring

competitions

  • titlestring

competition_results

Это будет промежуточная таблица для установления связи «многие ко многим» между sporterscompetitions

  • sporter_idinteger
  • competition_idinteger
  • placeinteger Для этой демонстрации мы предположим, что этот столбец может принимать значения от 1 до 6.

Создайте и примените все необходимые миграции:

 $ rails g model Country name:string
$ rails g model Sporter name:string age:integer country:references
$ rails g model Competition title:string
$ rails g model CompetitionResult sporter:references competition:references place:integer
$ rake db:migrate

Также создайте контроллер, представление и корневой маршрут:

statistics_controller.rb

 class StatisticsController < ApplicationController
  def index
  end
end

просмотров / статистика / index.html.erb

 <h1>Statistics</h1>

конфиг / routes.rb

 [...]
root 'statistics#index'
[...]

Пока все хорошо, но для рендеринга диаграмм нам, очевидно, потребуются некоторые примерные данные, поэтому давайте добавим это сейчас.

Загрузка данных образца

Чтобы ускорить процесс загрузки образцов данных, я буду использовать два гема: фейкер, который позволяет генерировать различные тексты из имен и электронных писем в абзацы и псевдохакерские фразы, и страны, которые значительно упрощают получение информации о существующих странах. Добавьте эти драгоценные камни в Gemfile :

Gemfile

 [...]
gem 'countries'
gem 'faker'
[...]

и установить их:

 $ bundle install

Теперь откройте файл seed.rb и вставьте этот код, чтобы добавить 50 стран:

дБ / seeds.rb

 ISO3166::Country.all.shuffle.first(50).each do |country|
  Country.create({name: country.name})
end

Это добавит совершенно случайные страны, так что не удивляйтесь, если у вас в итоге окажется Антарктида или Сан-Томе.

Теперь нам также нужна куча спортеров:

дБ / seeds.rb

 100.times { Sporter.create({
             name: Faker::Name.name,
             age: rand(18..50),
             country_id: rand(1..50)
         }) }

Здесь мы используем Faker для генерации имени образца.

Далее соревнования. Я не смог найти ни одного готового к использованию списка, поэтому мы будем печатать спортивные имена вручную:

дБ / seeds.rb

 %w(tennis parachuting badminton archery chess boxing racing golf running skiing walking cycling surfing swimming skeleton).each {|c| Competition.create({title: c}) }

И, наконец, результаты конкурса:

дБ / seeds.rb

 Competition.all.each do |competition|
  sporters = Sporter.all.shuffle.first(6)
  (1..6).each do |place|
    CompetitionResult.create({
                       sporter_id: sporters.pop.id,
                       competition_id: competition.id,
                       place: place,
                       created_at: rand(5.years.ago..Time.now)
                    })
  end
end

Заметим, что мы переопределяем столбец created_at

Отображение простой диаграммы

Ладно, все готово для реализации основных функций — графиков. Добавьте драгоценный камень в Gemfile :

Gemfile

 [...]
gem 'chartkick'
[...]

и установить его:

 $ bundle install

Chartkick поддерживает как Google Charts, так и Highcharts и Chart.js (начиная с версии 2.0 это адаптер по умолчанию.) В этой демонстрации я буду использовать Highcharts, но процесс установки Google Charts очень похож.

Прежде всего, загрузите последнюю версию Highcharts и поместите ее в каталог javascripts (в качестве альтернативы вы можете загрузить его через CDN, добавив javascript_include_taglayouts / application.html.erb ). Затем добавьте эти два файла:

JavaScripts / application.js

 [...]
//= require highcharts
//= require chartkick
[...]

Это довольно много — теперь мы можем визуализировать графики. Для начала давайте покажем гистограмму, показывающую возраст спортера.

Загрузить все спортеры:

statistics_controller.rb

 [...]
def index
  @sporters = Sporter.all
end
[...]

и настроить вид:

просмотров / статистика / index.html.erb

 <%= bar_chart @sporters.group(:age).count %>

Мы просто группируем игроков по возрасту и рассчитываем количество товаров в каждой группе. Действительно просто.

Вы, вероятно, задаетесь вопросом, как определить настройки для вашего графика, чтобы дать ему имя, настроить ширину, высоту и другие вещи. Это также легко — некоторые параметры передаются в качестве аргументов непосредственно в bar_chart:library

Создайте новый StatisticsHelper

statistics_helper.rb

 module StatisticsHelper
  def sporters_by_age
    bar_chart @sporters.group(:age).count, height: '500px', library: {
      title: {text: 'Sporters by age', x: -20},
      yAxis: {
         allowDecimals: false,
         title: {
             text: 'Ages count'
         }
      },
      xAxis: {
         title: {
             text: 'Age'
         }
      }
    }
  end
end

:library Здесь мы предотвращаем появление десятичных чисел на оси Y (очевидно, у нас не может быть 2,5 спортера в возрасте 20 лет) и присваиваем ему имя. Ось X также имеет определенное имя. Кроме того, укажите имя для всего графика (по умолчанию оно будет отображаться внизу). У Highcharts есть множество других доступных опций, поэтому обязательно просмотрите его документацию .

Также обратите внимание, что настройки могут быть определены глобально .

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

Если вы хотите отображать только самые «популярные» возрасты среди спортеров, используйте самоцвет hightop — небольшую, но полезную библиотеку, предназначенную для решения таких задач. Просто включите его в Gemfile :

Gemfile

 [...]
gem 'hightop'
[...]

и беги

 $ bundle install

Теперь вы можете отобразить, например, десять самых популярных возрастов:

просмотров / статистика / index.html.erb

 <%= bar_chart @sporters.top(:age, 10) %>

Рендеринг графиков асинхронно

Если в вашей базе данных есть много данных для обработки для отображения графика, страница будет загружаться медленно. Поэтому лучше визуализировать ваши графики асинхронно. Chartkick также поддерживает эту функцию. Все, что вам нужно сделать, это создать отдельный маршрут и действие контроллера, а затем использовать этот маршрут внутри представления. Обратите внимание, что для этого требуется наличие jQuery или Zepto.js.

Создайте новый маршрут:

конфиг / routes.rb

 [...]
resources :charts, only: [] do
  collection do
    get 'sporters_by_age'
  end
end
[...]

и контроллер:

charts_controller.rb

 class ChartsController < ApplicationController
  def sporters_by_age
    result = Sporter.group(:age).count
    render json: [{name: 'Count', data: result}]
  end
end

Имея это на месте, просто измените ваш помощник, чтобы вызвать вновь созданный маршрут:

statistics_helper.rb

 [...]
def sporters_by_age
  bar_chart sporters_by_age_charts_path, height: '500px', library: {
    [...]
  }
end
[...]

Теперь ваша диаграмма будет загружена асинхронно, что позволит пользователям просматривать другое содержимое на странице. @sportersindexStatisticsController[...]
def sporters_by_country
column_chart sporters_by_country_charts_path, library: {
title: {text: 'Sporters by country', x: -20},
yAxis: {
title: {
text: 'Sporters count'
}
},
xAxis: {
title: {
text: 'Country'
}
}
}
end
[...]

Больше типов графиков

Столбчатая диаграмма

Чтобы продемонстрировать использование столбцовой диаграммы, давайте покажем, сколько у каждой страны есть спортеров. Прежде всего, создайте новый помощник, подобный тому, который мы определили ранее:

statistics_helper.rb

 [...]
<%= sporters_by_country %>
[...]

Используйте его внутри вида:

просмотров / статистика / index.html.erb

 [...]
resources :charts, only: [] do
  collection do
    get 'sporters_by_age'
    get 'sporters_by_country'
  end
end
[...]

Добавьте маршрут:

конфиг / routes.rb

 [...]
def sporters_by_country
  result = {}
  Country.all.map do |c|
    result[c.name] = c.sporters.count
  end
  render json: [{name: 'Count', data: result}]
end
[...]

Что касается действия контроллера, оно будет немного сложнее, так как мы должны построить результат вручную:

charts_controller.rb

 result

Обратите внимание на то, как создается хэш [...]
def results_by_country
column_chart results_by_country_charts_path, stacked: true, height: '500px', library: {
title: {text: 'Results by country', x: -20},
yAxis: {
title: {
text: 'Count'
}
},
xAxis: {
title: {
text: 'Countries and places'
}
}
}
end
[...]

Перезагрузите страницу и наблюдайте за результатом!

Столбчатая диаграмма с накоплением

Давайте также покажем, сколько раз каждая страна занимала определенное место (от 1 до 6). Еще раз, определите новый помощник:

statistics_helper.rb

 stacked: true

Обратите внимание на опцию [...]
<%= results_by_country %>
[...]

сложены

Используйте помощника внутри представления:

просмотров / статистика / index.html.erb

 [...]
resources :charts, only: [] do
  collection do
    get 'sporters_by_age'
    get 'sporters_by_country'
    get 'results_by_country'
  end
end
[...]

Добавьте маршрут:

конфиг / routes.rb

 [...]
def results_by_country
  result = Country.all.map do |c|
    places = {}
    (1..6).each do |place|
      places[place] = c.sporters.joins(:competition_results).
          where("competition_results.place = #{place}").count
    end
    {
        name: c.name,
        data: places
    }
  end
  render json: result
end
[...]

Наконец, создайте действие контроллера:

charts_controller.rb

 map

Мы берем все страны и используем joins Внутри найдите всех спортеров из этой страны, которые заняли определенное место. competition_results Затем просто используйте wherecount Затем, как мы уже видели, назначьте название страны для :nameplaces:data В результате будет создан массив хэшей.

Линейный график и дата группы

Последний тип графика, который мы сегодня рассмотрим, это линейный график. Чтобы продемонстрировать это, давайте покажем, сколько соревнований проводилось каждый год.

Еще раз, создайте помощника

statistics_helper.rb

 [...]
def competitions_by_year
  line_chart competitions_by_year_charts_path, library: {
      title: {text: 'Competitions by year', x: -20},
      yAxis: {
          crosshair: true,
          title: {
              text: 'Competitions count'
          }
      },
      xAxis: {
          crosshair: true,
          title: {
              text: 'Year'
          }
      }
  }
end
[...]

:crosshair

перекрестие

Используйте этот помощник по вашему мнению:

просмотров / статистика / index.html.erb

 [...]
<%= competitions_by_year %>
[...]

и добавьте новый маршрут:

конфиг / routes.rb

 [...]
resources :charts, only: [] do
  collection do
    get 'sporters_by_age'
    get 'sporters_by_country'
    get 'results_by_country'
    get 'competitions_by_year'
  end
end
[...]

Теперь нам нужно создать новое действие контроллера, но как мы собираемся группировать соревнования по годам и считать их? Конечно, мы можем построить собственный запрос, но автор Chartkick уже позаботился об этом и создал удобный гем Groupdate . Как следует из названия, он позволяет группировать записи по годам, месяцам, дням и т. Д. Он поддерживает часовые пояса, диапазоны дат, форматирование, упорядочивание и другие интересные вещи, поэтому это хорошее решение для использования с Chartkick.

Добавьте Groupdate в свой Gemfile :

Gemfile

 [...]
gem 'groupdate'
[...]

Единственная проблема с Groupdate заключается в том, что он не поддерживает SQLite3, поэтому вам придется использовать некоторые другие DMBS. Для этой демонстрации я буду использовать PostgreSQL. Обратите внимание, что если вы решите использовать MySQL, должна быть также установлена поддержка часового пояса .

Еще раз настройте свой Gemfile , заменив sqlite3 на pg gem:

Gemfile

 [...]
gem 'pg'
[...]

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

 $ bundle install

и измените файл конфигурации database.yml :

конфиг / database.yml

 [...]
development:
  adapter: postgresql
  encoding: unicode
  database: your_database
  pool: 5
  username: your_user
  password: your_password
  host: localhost
  port: 5432
[...]

Теперь снова запустите миграцию и заполните таблицы примерами данных.

 $ rake db:migrate
$ rake db:seed

Не забудьте создать базу данных ( rake db:create

Теперь мы можем кодировать действие контроллера:

charts_controller.rb

 [...]
def competitions_by_year
  result = CompetitionResult.group_by_year(:created_at, format: "%Y").count
  render json: [{name: 'Count', data: result}]
end
[...]

Выглядит отлично. Параметр :format Пока мы хотим отображать только годы, я использовал %Y Полный список доступных директив можно найти в официальной документации Ruby .

Перезагрузите страницу еще раз и наблюдайте за конечным результатом. Если вы не удовлетворены тем, как выглядят графики, поиграйте с настройками дисплея, приведенными в документации Highcharts .

Вывод

В этой статье мы обсудили Chartkick — великолепный камень для упрощения рендеринга графиков. Мы пытались использовать различные типы диаграмм, заставляли их загружаться асинхронно, а также использовали дополнительные гемы, такие как Groupdate и Hightop. Конечно, в этих драгоценных камнях есть кое-что еще, поэтому обязательно просмотрите их документы и поэкспериментируйте с кодом.

Как всегда, не стесняйтесь оставлять свои вопросы и отзывы. Счастливого графика и до скорой встречи!