Интерактивная карта средних зарплат по регионам России
Туториал: компонент интерактивной SVG картограммы для InstantCMS 2
Сложность: средняя.
Необходимое время: 30 мин.
В статье представлена инструкция по созданию своего компонента для движка InstantCMS2. В конце статьи приведена ссылка на архив с исходным кодом и содержимым всех файлов из этой инструкции. Пример внешнего вида компонента, который можно создать, используя данный туториал, представлен на иллюстрации (картинка кликабельна).
Для начала несколько слов про движок соц.сети / сообщества / блогосоциальной сети InstantCMS2. Эта бесплатная CMS может являться отличным компромиссом, возможно, лучшим из существующих.
В базовой версии уже заложен более богатый функционал по сравнению с LiveStreet CMS.
Скриншот сравнения функционала не привожу, потому что по ссылке дана информация не по самой последней версии движка InstantCMS.
Достоинства и недостатки InstantCMS2
Из минусов - количество модулей, дополнений, тем для данного движка достаточно ограничено. Качество технической поддержки немного хромает. Живого активного сообщества вокруг данного движка нет, а регистрация на форуме вообще только по приглашению. Но все эти минусы с лихвой перекрывает факт бесплатности движка InstantCMS 2.
Из плюсов - из коробки предоставляется сразу: форум, профили пользователей с возможность добавления в друзья, статьи, блоги, новости, группы, фотоальбомы, статичные страницы сайта. А также комментарии, ленты RSS, поля RSS, возможность настраивать главное меню, нормальная модерация и вполне удобная админка.
Скачать движок InstantCMS 2 с функцией авто-установки можно с официального сайта проекта.
Процесс установки хорошо документирован и интуитивно понятен.
Свой компонент для InstantCMS2
Перейдем непосредственно к вопросу написания отдельного компонента.
Для создания нового компонента создайте папку, в которой будет ваш компонент (назовем его newcomponent), в директории \SiteDirectory\system\controllers\, т.е полный адрес к созданной директории будет \SiteDirectory\system\controllers\newcomponent\ - все буквы в названии компонента должны быть строчными, это важно!
Далее в этой папке создаем файл frontend.php - это главный файл, без которого компонент не будет работать.
В этом файле создаем класс с таким же названием. Название класса совпадает с названием папки. И этот класс наследуется от системного класса cmsFrontend.
В этом классе мы имеем возможность добавлять методы, описывающие действия компонента.
Что такое действия компонента? Давайте взглянем на следующее изображение:
Каждый адрес страницы состоит из нескольких сегментов:
- /controller - Название компонента.
- /action - Название действия. Каждый компонент может иметь несколько действий внутри себя.
- /p1/p2/p3/... - Любое количество параметров, необходимых для этого действия.
Как определяется действие компонента? Определяется публичный метод в классе компонента, который называется actionНазваниеДействияСБольшойБуквы. Для главной страницы компонента siteaddress.ru/newcomponent/ необходимо определить метод actionIndex(). Для внутренней страницы компонента siteaddress.ru/newcomponent/act/ необходимо определить метод actionAct().
Файл frontend.php
<?php class newcomponent extends cmsFrontend { public function actionIndex() { $template = cmsTemplate::getInstance(); $template->render('index'); } public function actionAct() { $errors = false; $form = $this->getForm('newForm'); $is_submitted = $this->request->has('submit'); $newForm = $form->parse($this->request, $is_submitted); if ($is_submitted){ $errors = $form->validate($this, $newForm); if (!errors) { $choropleth = $this->model->getChoropleth($newForm); } if (!errors) { cmsUser::addSessionMessage(LANG_FORM_ERRORS, 'error'); } } $template = cmsTemplate::getInstance(); $template->render('act', array( 'form' => $form, 'errors' => $errors, 'newForm' => $newForm )); } } ?>
Внимательный читатель заметил использование метода $this->model->getChoropleth().
Для использования методов модели в директории \SiteDirectory\system\controllers\newcomponent\ создаем файл model.php
Однако читать данные мы будем из файлов, поэтому поставим здесь заглушку. Описание файла модели приведено с целью обучения.
Файл model.php
<?php class modelNewComponent extends cmsModel { public function getChoropleth($average_zarplata) { $choropleth = array(); return $choropleth; } } ?>
Строка $template->render('index'); определяет вывод настоящего шаблона, который должен быть создан в директории \SiteDirectory\templates\default\controllers\newcomponent\. Где \default - название используемой темы на сайте (можно найти и скачать новую тему с сайта сообщества instantcms и изменить используемую тему через админку), папку \newcomponent необходимо будет создать самостоятельно, это папка для шаблонов нового компонента.
В этой папке должен быть создан файл index.tpl.php для главной страницы компонента, и act.tpl.php - для внутренней.
Файл index.tpl.php
<?php $this->setPageTitle('Заголовок страницы в названии окна браузера'); $this->addBreadcrumb('Название страницы в цепи хлебных крошек'); $this->addToolButton(array( 'class' => 'item', 'title' => 'Название кнопки в меню действий для перехода на внутреннюю страницу компонента', 'href' => $this->href_to('act') )); ?> <h1>Главный заголовок страницы</h1> <p>Содержание страницы</p>
Внутренняя страница компонента будет содержать форму выбора параметров.
Для начала создадим папку \forms\ в папке нашего компонента \SiteDirectory\system\controllers\newcomponent\.
В директории \SiteDirectory\system\controllers\newcomponent\forms\ создаем файл form_newForm.php
Форма будет очень простой, она предлагает пользователю выбрать два параметра из выпадающих списков.
Файл form_newForm.php
<?php class formNewcomponentnewform extends cmsForm { public function init() { return array( array( 'type' => 'fieldset', 'childs' => array ( new fieldList('par1', array( 'title' => 'Параметр1', 'items' => array ( "ТекстовыйИдентификатор1" => "ТекстовыйПараметр1", "ТекстовыйИдентификатор2" => "ТекстовыйПараметр2" ) )), new fieldList('par2', array( 'title' => 'Параметр2', 'items' => array ( 1 => "1", 2 => "2" ) )) ) ) ); } } ?>
Интерактивная SVG картограмма
После этого перейдем к созданию шаблона для внутренней страницы компонента /act - создаем файл act.tpl.php и размещаем его в директории \SiteDirectory\templates\default\controllers\newcomponent\.
Для создания уникального сервиса воспользуемся разработкой пользователя @KoGor (пользуясь случаем, хочу передать огромную благодарность за проведенный @KoGor 'ом труд и хорошо оформленную и интуитивно понятную статью) - инфограммой карты Российской Федерации с распределением по регионам.
В результате, у нас должна получится примерно такая приятная карта России:
Файл act.tpl.php
<?php $this->setPageTitle('Заголовок страницы в названии окна браузера'); $this->addBreadcrumb('Название главной страницы компонента в цепи хлебных крошек', $this->href_to('')); $this->addBreadcrumb('Название страницы в цепи хлебных крошек'); $arr_par1_id = array('ТекстовыйИдентификатор1' => 1 , 'ТекстовыйИдентификатор2' => 2 ); $filename='/upload/zarplata-'.$arr_par1_id[$_GET['par1']].'-'.$_GET['par2'].'.csv'; if (!isset ($_GET['par1']) || !isset ($_GET['par2'])) $filename='/upload/zarplata-1-1.csv'; $this->renderForm($form, $newForm, array( 'action' => '', 'method' => 'get', 'toolbar' => false ), $errors); ?> <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script> <script type="text/javascript" src="http://d3js.org/queue.v1.min.js"></script> <script type="text/javascript" src="http://d3js.org/topojson.v0.min.js"></script> <!-- <script type="text/javascript" src="http://d3js.org/topojson.v1.min.js"></script> --> <style> path { stroke:white; stroke-width: 1px; } body { font-family: Arial, sans-serif; } .city { font: 10px sans-serif; font-weight: bold; } .legend { font-size: 12px; } div.tooltip { position: absolute; text-align: center; width: 150px; height: 25px; padding: 2px; font-size: 10px; background: #FFFFE0; border: 1px; border-radius: 8px; pointer-events: none; } </style> <script type="text/javascript"> var width = 720, height = 375; // Setting color domains(intervals of values) for our map var color_domain = [10000, 15000, 20000, 30000, 50000] var ext_color_domain = [0, 10000, 15000, 20000, 30000, 50000] var legend_labels = ["до 10000 руб.", "10000-15000 руб.", "15000-20000 руб.", "20000-30000 руб.", "30000-50000 руб.", "от 50000 руб."] var color = d3.scale.threshold() .domain(color_domain) .range(["#ff1300", "#ff4e40", "#ff7d73", "#ffba00", "#ffcb40", "#adfcad"]); var div = d3.select("form").append("div") .attr("class", "tooltip") .style("opacity", 0); var svg = d3.select("form").append("svg") .attr("width", width) .attr("height", height) .style("margin", "10px auto"); var projection = d3.geo.albers() .rotate([-105, 0]) .center([-10, 65]) .parallels([52, 64]) .scale(500) .translate([width / 2, height / 2]); var path = d3.geo.path().projection(projection); //Reading map file and data queue() .defer(d3.json, "/upload/russia.json") .defer(d3.csv, "<?php echo $filename; ?>") .await(ready); //Start of Choropleth drawing function ready(error, map, data) { var rateById = {}; var nameById = {}; data.forEach(function(d) { rateById[d.RegionCode] = +d.AverageZarplata; nameById[d.RegionCode] = d.RegionName; }); //Drawing Choropleth svg.append("g") .attr("class", "region") .selectAll("path") .data(topojson.object(map, map.objects.russia).geometries) //.data(topojson.feature(map, map.objects.russia).features) <-- in case topojson.v1.js .enter().append("path") .attr("d", path) .style("fill", function(d) { return color(rateById[d.properties.region]); }) .style("opacity", 0.8) //Adding mouseevents .on("mouseover", function(d) { d3.select(this).transition().duration(300).style("opacity", 1); div.transition().duration(300) .style("opacity", 1) div.text(nameById[d.properties.region] + " : " + rateById[d.properties.region]) .style("left", (d3.event.pageX) + "px") .style("top", (d3.event.pageY -30) + "px"); }) .on("mouseout", function() { d3.select(this) .transition().duration(300) .style("opacity", 0.8); div.transition().duration(300) .style("opacity", 0); }) // Adding cities on the map d3.tsv("/upload/cities.tsv", function(error, data) { var city = svg.selectAll("g.city") .data(data) .enter() .append("g") .attr("class", "city") .attr("transform", function(d) { return "translate(" + projection([d.lon, d.lat]) + ")"; }); city.append("circle") .attr("r", 3) .style("fill", "lime") .style("opacity", 0.75); city.append("text") .attr("x", 5) .text(function(d) { return d.City; }); }); }; // <-- End of Choropleth drawing //Adding legend for our Choropleth var legend = svg.selectAll("g.legend") .data(ext_color_domain) .enter().append("g") .attr("class", "legend"); var ls_w = 20, ls_h = 20; legend.append("rect") .attr("x", 20) .attr("y", function(d, i){ return height - (i*ls_h) - 2*ls_h;}) .attr("width", ls_w) .attr("height", ls_h) .style("fill", function(d, i) { return color(d); }) .style("opacity", 0.8); legend.append("text") .attr("x", 50) .attr("y", function(d, i){ return height - (i*ls_h) - ls_h - 4;}) .text(function(d, i){ return legend_labels[i]; }); </script>
Исходные данные для картограммы
Для того, чтобы карта заработала, остался последний шаг. Размещаем файлы cities.tsv, russia.json, zarplata-1-1.csv, zarplata-1-2.csv, zarplata-2-1.csv, zarplata-2-2.csv (приведены в архиве, ссылка на который есть в конце статьи) в директории \SiteDirectory\upload\.
Заходим по адресу siteaddress.ru/newcomponent/act/ - здесь все регионы на карте России подкрашены темно-серым цветом и при наведении появляется название региона - NaN. Для отображения каких-нибудь реальных данных замените значения в последнем столбце .csv файлов на численные данные.
Демо на ЗарплатаБюджетников.РФ
Подобный модуль разработан мною для сайта ЗарплатаБюджетников.РФ в разделе Карта зарплат. Демо модуля можно посмотреть по ссылке.
Бонус
Напоследок, небольшой хинт. В дефолтном шаблоне по умолчанию в InstantCMS 2 боковая колонка вместе с меню действий пропадает при уменьшении ширины окна браузера. Но на мобильных девайсах исчезновение боковой колонки и меню действий очень не удобно, т.к. у пользователей пропадает довольно таки много возможных действий. Для изменения этой ситуации можно проделать следующее. Найдите в директории \templates\default\css\ файл theme-layout.css, и замените в нем кусочек кода
/* Media Queries ============================================================ */ @media screen and (max-width: 980px) { #body section { width:100% !important; } #body aside { display:none; } } @media screen and (max-width: 800px) { #body section { width:100% !important; } #body aside { display:none; }
на
/* Media Queries ============================================================ */ @media screen and (max-width: 980px) { #body section { width:100% !important; } #body aside { width:100% !important; } } @media screen and (max-width: 800px) { #body section { width:100% !important; } #body aside { width:100% !important; }
Т.е. по факту необходимо исправить всего 2 строчки #body aside { display:none; } на #body aside { width:100% !important; } - после этого боковая колонка при уменьшении ширины браузера будет съезжать в основную колонку после находящегося в нем контента (перед футером).
Исходный код
Все описанные скрипты и файлы можно скачать в архиве, который нужно будет просто загрузить и распаковать в корневую директорию вашего сайта, работающего на InstantCMS2.
P.S. О замеченных опечатках, ошибках или неточностях прошу писать в личные сообщения.