Pull to refresh

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

Reading time 9 min
Views 5.4K
Сложность: средняя.
Необходимое время: 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.
В этом классе мы имеем возможность добавлять методы, описывающие действия компонента.
Что такое действия компонента? Давайте взглянем на следующее изображение:


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

В результате, у нас должна получится примерно такая приятная карта России:
Уровень зарплат работников здравоохранения по регионам России в 2014 году по данным Росстата

Файл 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. О замеченных опечатках, ошибках или неточностях прошу писать в личные сообщения.
Tags:
Hubs:
+4
Comments 0
Comments Leave a comment

Articles