Главное Свежее Вакансии Образование
1 523 4 В избр. Сохранено
Авторизуйтесь
Вход с паролем

Интерактивная карта средних зарплат по регионам России

Сложность: средняя. Необходимое время: 30 мин. В статье представлена инструкция по созданию своего компонента для движка InstantCMS2. В конце статьи приведена ссылка на архив с исходным кодом этой инструкции.

Туториал: компонент интерактивной SVG картограммы для InstantCMS 2

Сложность: средняя.

Необходимое время: 30 мин.

В статье представлена инструкция по созданию своего компонента для движка InstantCMS2. В конце статьи приведена ссылка на архив с исходным кодом и содержимым всех файлов из этой инструкции. Пример внешнего вида компонента, который можно создать, используя данный туториал, представлен на иллюстрации (картинка кликабельна).

95098f82c79d4e488865fc43c16cd4ee.jpg

Для начала несколько слов про движок соц.сети / сообщества / блогосоциальной сети InstantCMS2. Эта бесплатная CMS может являться отличным компромиссом, возможно, лучшим из существующих.

В базовой версии уже заложен более богатый функционал по сравнению с LiveStreet CMS.

Скриншот сравнения функционала не привожу, потому что по ссылке дана информация не по самой последней версии движка InstantCMS.

Достоинства и недостатки InstantCMS2

Из минусов - количество модулей, дополнений, тем для данного движка достаточно ограничено. Качество технической поддержки немного хромает. Живого активного сообщества вокруг данного движка нет, а регистрация на форуме вообще только по приглашению. Но все эти минусы с лихвой перекрывает факт бесплатности движка InstantCMS 2.

Из плюсов - из коробки предоставляется сразу: форум, профили пользователей с возможность добавления в друзья, статьи, блоги, новости, группы, фотоальбомы, статичные страницы сайта. А также комментарии, ленты RSS, поля RSS, возможность настраивать главное меню, нормальная модерация и вполне удобная админка.

Скачать движок InstantCMS 2 с функцией авто-установки можно с официального сайта проекта.

Процесс установки хорошо документирован и интуитивно понятен.

Свой компонент для InstantCMS2

Перейдем непосредственно к вопросу написания отдельного компонента.

Для создания нового компонента создайте папку, в которой будет ваш компонент (назовем его newcomponent), в директории \SiteDirectory\system\controllers\, т.е полный адрес к созданной директории будет \SiteDirectory\system\controllers\newcomponent\ - все буквы в названии компонента должны быть строчными, это важно!

Далее в этой папке создаем файл frontend.php - это главный файл, без которого компонент не будет работать.

В этом файле создаем класс с таким же названием. Название класса совпадает с названием папки. И этот класс наследуется от системного класса cmsFrontend.

В этом классе мы имеем возможность добавлять методы, описывающие действия компонента.

Что такое действия компонента? Давайте взглянем на следующее изображение:

5feb26b076394b32baad5ff8891a2557.png

Каждый адрес страницы состоит из нескольких сегментов:

  1. /controller - Название компонента.
  2. /action - Название действия. Каждый компонент может иметь несколько действий внутри себя.
  3. /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 'ом труд и хорошо оформленную и интуитивно понятную статью) - инфограммой карты Российской Федерации с распределением по регионам.

В результате, у нас должна получится примерно такая приятная карта России:

e4220a0b0fcc4f06b710b42265ccce78.png

Файл 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. О замеченных опечатках, ошибках или неточностях прошу писать в личные сообщения.

+1
В избр. Сохранено
Авторизуйтесь
Вход с паролем
Комментарии
Taras Nikitin
Хоть бы уведомление какое выводили если данных нет для отображения, а то я уж грешным делом подумал, что вы в верстке или коде где-то ошиблись, когда никакой карты не увидел с параметрами "Все" и "2015". Вместо карты была просто белая область экрана.
Ёщкин кот! Так у вас ещё и если даных нет, например при выборке по "Работники прокуратуры" и "2014" то на карте выводятся NaN (это хорошо я знаю что такое NaN, а как люди будут с этим жить - не понятно).
Сырая у вас система в части карты, а заметка то о карте. Эх.
Ответить
ЗарплатаБюджетников.РФ
Первая служебная сеть для бюджетников России и карта средних зарплат по областям
Шайхутдинов Артур
Спасибо за обратную связь! Постараюсь сделать сервис поудобнее.
Ответить
LitTime
Ваш проводник в мире литературы
Скребнёв Алексей
У вас не хватает на карте Крыма.
Живу в Смоленске, совсем недавно видел данные РБК, что средняя зарплата у нас в районе 16к. Стало лучше?
Ответить
ЗарплатаБюджетников.РФ
Первая служебная сеть для бюджетников России и карта средних зарплат по областям
Шайхутдинов Артур
Да, пока точной векторной карты нет по причине отсутствия данных Росреестра, но данные по Республике Крым усреднены совместно с Краснодарским краем (ближайший регион) - это число можно увидеть в данных Красндарского края.
Ответить
Выбрать файл
Блог проекта
Расскажите историю о создании или развитии проекта, поиске команды, проблемах и решениях
Написать
Личный блог
Продвигайте свои услуги или личный бренд через интересные кейсы и статьи
Написать

Spark использует cookie-файлы. С их помощью мы улучшаем работу нашего сайта и ваше взаимодействие с ним.