Мы работаем с данными, представленными в различных форматах каждый день. С моей точки зрения, одним из наиболее удобных форматов представления числовых данных является график. Пользователи любят графики, особенно интерактивные, потому что они красивые и забавные — в отличие от скучных статических таблиц.
Существует множество решений для визуализации красивых интерактивных графиков в веб-приложениях, но сегодня мы обсудим нечто особенное: решение, созданное для 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
-
name
string
-
age
integer
-
country_id
integer
sporters
countries
countries
-
title
string
competitions
-
title
string
competition_results
Это будет промежуточная таблица для установления связи «многие ко многим» между sporters
competitions
-
sporter_id
integer
-
competition_id
integer
-
place
integer
Для этой демонстрации мы предположим, что этот столбец может принимать значения от 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_tag
layouts / 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
[...]
Теперь ваша диаграмма будет загружена асинхронно, что позволит пользователям просматривать другое содержимое на странице. @sporters
index
StatisticsController
[...]
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
Затем просто используйте where
count
Затем, как мы уже видели, назначьте название страны для :name
places
: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. Конечно, в этих драгоценных камнях есть кое-что еще, поэтому обязательно просмотрите их документы и поэкспериментируйте с кодом.
Как всегда, не стесняйтесь оставлять свои вопросы и отзывы. Счастливого графика и до скорой встречи!