Napisała do mnie koleżanka, że wymyśliła sobie, by na swojej stronie zrobić coś w stylu Wheel Of Life (koło życia). Można powiedzieć, że nawet odrobiła „pracę domową”, bo „zapukała do mnie” z gotową propozycją, tzn. skryptem, z którego chciałaby skorzystać. Chodziło o projekt Wheel-Of-Life (codescale), który nie tylko jest dostępny w serwisie GitHub, ale chyba można z niego korzystać bezpłatnie, również w celach komercyjnych. Już samo to brzmi wystarczająco dobrze… ;-)

Wheel Of Life z demo amCharts w WordPressie

Po wstępnej analizie potrzeb i przedstawionego projektu (rozwiązania) uznałem, że będzie można zaadaptować to do wymagań, choć z pewnych względów stronę tę wyjmiemy z WordPressa, na którym działa strona. Co nie tyle oznaczało, że strona będzie całkowicie niezależna, co po prostu wykorzystamy tutaj mechanizm szablonów stron, by przygotować odpowiednią stronę.

Prace ruszyły, aż tu nagle koleżanka pisze, że znalazła coś, co jej (jeszcze) bardziej odpowiada, czyli Wheel Of Life z demo amCharts… I trzeba uczciwie przyznać, że faktycznie ta wersja „z automatu” prezentuje się lepiej.

Co jeszcze ciekawsze, dostępna jest oficjalna wtyczka do WordPressa (amCharts: Charts and Maps), dzięki czemu taki wykres można osadzić właściwie w dowolnym miejscu, za pomocą tzw. krótkiego kodu (shortcode):

Kolejny krokiem było przeniesienie demo „Interactive Wheel of Life” do WordPressa, a konkretnie do ich wtyczki, tak by to wszystko nie tylko działało, ale by później koleżanka mogła edytować m.in. kolory i treści:

Mamy tutaj 5 pól do wypełnienia, zaczynając od tytułu, który właściwie może być dowolny, i ma najmniejsze znaczenie.

Dalej mamy „resources”, i tutaj wpisujemy (lub wybieramy z listy) zasoby/biblioteki, które będą załadowane na potrzeby danego wykresu. Bazując na dostępnym na stronie amCharts demo Wheel Of Life wybrałem z listy:

//www.amcharts.com/lib/4/core.js
//www.amcharts.com/lib/4/charts.js
//www.amcharts.com/lib/4/themes/animated.js

Kolejne istotne pole, to HTML, i tutaj również skorzystałem z demo. Na początku – by sprawdzić, czy to w ogóle tak zadziała – jedynie rozbijając kod na HTML i JavaScript, oraz zmieniając w dwóch miejscach identyfikator głównego DIVa z „chartdiv” na „$CHART$” (zresztą zgodnie z informacją przekazywaną przez wtyczkę). Od razu dodałem też akapit dla opisu przy każdym pytaniu, oraz „spolszczyłem” teksty, tak by osoba podmieniająca później tekst lepiej wiedziała co i gdzie zrobić:

<style>
body {}

#$CHART$ {
  width: 100%;
  height: 600px;
}

.areas {
  text-align: center;
  margin-bottom: 15px;
  min-height: 95px;
}

.areas .value {
  display: inline-block;
  font-size: 1.3em;
  padding: 5px 10px;
  border: 1px solid #ddd;
  cursor: pointer;
}

.areas .value:hover {
  background: #ecc;
}

.area p {
  margin: 1em 0 1em 0;
}

.area.finisz {
  margin: 1em 0 1em 0 !important;
}
</style>

<div class="areas">
	<div class="area">
		<h3>Pytanie 1</h3>
		<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit?</p>
		<div class="values">
			<div class="value" onclick="setValue(0, 1);">1</div>
			<div class="value" onclick="setValue(0, 2);">2</div>
			<div class="value" onclick="setValue(0, 3);">3</div>
			<div class="value" onclick="setValue(0, 4);">4</div>
			<div class="value" onclick="setValue(0, 5);">5</div>
			<div class="value" onclick="setValue(0, 6);">6</div>
			<div class="value" onclick="setValue(0, 7);">7</div>
			<div class="value" onclick="setValue(0, 8);">8</div>
			<div class="value" onclick="setValue(0, 9);">9</div>
			<div class="value" onclick="setValue(0, 10);">10</div>
		</div>
	</div>
	<div class="area" style="display: none;">
		<h3>Pytanie 2</h3>
		<p>Quisque hendrerit sagittis massa, fermentum vestibulum?</p>
		<div class="values">
			<div class="value" onclick="setValue(1, 1);">1</div>
			<div class="value" onclick="setValue(1, 2);">2</div>
			<div class="value" onclick="setValue(1, 3);">3</div>
			<div class="value" onclick="setValue(1, 4);">4</div>
			<div class="value" onclick="setValue(1, 5);">5</div>
			<div class="value" onclick="setValue(1, 6);">6</div>
			<div class="value" onclick="setValue(1, 7);">7</div>
			<div class="value" onclick="setValue(1, 8);">8</div>
			<div class="value" onclick="setValue(1, 9);">9</div>
			<div class="value" onclick="setValue(1, 10);">10</div>
		</div>
	</div>
	<div class="area" style="display: none;">
		<h3>Pytanie 3</h3>
		<p>Integer aliquet justo posuere magna efficitur commodo?</p>
		<div class="values">
			<div class="value" onclick="setValue(2, 1);">1</div>
			<div class="value" onclick="setValue(2, 2);">2</div>
			<div class="value" onclick="setValue(2, 3);">3</div>
			<div class="value" onclick="setValue(2, 4);">4</div>
			<div class="value" onclick="setValue(2, 5);">5</div>
			<div class="value" onclick="setValue(2, 6);">6</div>
			<div class="value" onclick="setValue(2, 7);">7</div>
			<div class="value" onclick="setValue(2, 8);">8</div>
			<div class="value" onclick="setValue(2, 9);">9</div>
			<div class="value" onclick="setValue(2, 10);">10</div>
		</div>
	</div>
	<div class="area" style="display: none;">
		<h3>Pytanie 4</h3>
		<p>Nam cursus, turpis non consequat mattis, lacus odio pretium?</p>
		<div class="values">
			<div class="value" onclick="setValue(3, 1);">1</div>
			<div class="value" onclick="setValue(3, 2);">2</div>
			<div class="value" onclick="setValue(3, 3);">3</div>
			<div class="value" onclick="setValue(3, 4);">4</div>
			<div class="value" onclick="setValue(3, 5);">5</div>
			<div class="value" onclick="setValue(3, 6);">6</div>
			<div class="value" onclick="setValue(3, 7);">7</div>
			<div class="value" onclick="setValue(3, 8);">8</div>
			<div class="value" onclick="setValue(3, 9);">9</div>
			<div class="value" onclick="setValue(3, 10);">10</div>
		</div>
	</div>
	<div class="area" style="display: none;">
		<h3>Pytanie 5</h3>
		<p>Orci varius natoque penatibus et magnis dis parturient montes?</p>
		<div class="values">
			<div class="value" onclick="setValue(4, 1);">1</div>
			<div class="value" onclick="setValue(4, 2);">2</div>
			<div class="value" onclick="setValue(4, 3);">3</div>
			<div class="value" onclick="setValue(4, 4);">4</div>
			<div class="value" onclick="setValue(4, 5);">5</div>
			<div class="value" onclick="setValue(4, 6);">6</div>
			<div class="value" onclick="setValue(4, 7);">7</div>
			<div class="value" onclick="setValue(4, 8);">8</div>
			<div class="value" onclick="setValue(4, 9);">9</div>
			<div class="value" onclick="setValue(4, 10);">10</div>
		</div>
	</div>
	<div class="area" style="display: none;">
		<h3>Pytanie 6</h3>
		<p>In semper, erat ac scelerisque sagittis, ipsum orci magna?</p>
		<div class="values">
			<div class="value" onclick="setValue(5, 1);">1</div>
			<div class="value" onclick="setValue(5, 2);">2</div>
			<div class="value" onclick="setValue(5, 3);">3</div>
			<div class="value" onclick="setValue(5, 4);">4</div>
			<div class="value" onclick="setValue(5, 5);">5</div>
			<div class="value" onclick="setValue(5, 6);">6</div>
			<div class="value" onclick="setValue(5, 7);">7</div>
			<div class="value" onclick="setValue(5, 8);">8</div>
			<div class="value" onclick="setValue(5, 9);">9</div>
			<div class="value" onclick="setValue(5, 10);">10</div>
		</div>
	</div>
	<div class="area" style="display: none;">
		<h3>Pytanie 7</h3>
		<p>Vestibulum volutpat turpis mauris, quis hendrerit augue?</p>
		<div class="values">
			<div class="value" onclick="setValue(6, 1);">1</div>
			<div class="value" onclick="setValue(6, 2);">2</div>
			<div class="value" onclick="setValue(6, 3);">3</div>
			<div class="value" onclick="setValue(6, 4);">4</div>
			<div class="value" onclick="setValue(6, 5);">5</div>
			<div class="value" onclick="setValue(6, 6);">6</div>
			<div class="value" onclick="setValue(6, 7);">7</div>
			<div class="value" onclick="setValue(6, 8);">8</div>
			<div class="value" onclick="setValue(6, 9);">9</div>
			<div class="value" onclick="setValue(6, 10);">10</div>
		</div>
	</div>
	<div class="area" style="display: none;">
		<h3>Pytanie 8</h3>
		<p>Nam cursus, turpis non consequat mattis, odio pretium nulla?</p>
		<div class="values">
			<div class="value" onclick="setValue(7, 1);">1</div>
			<div class="value" onclick="setValue(7, 2);">2</div>
			<div class="value" onclick="setValue(7, 3);">3</div>
			<div class="value" onclick="setValue(7, 4);">4</div>
			<div class="value" onclick="setValue(7, 5);">5</div>
			<div class="value" onclick="setValue(7, 6);">6</div>
			<div class="value" onclick="setValue(7, 7);">7</div>
			<div class="value" onclick="setValue(7, 8);">8</div>
			<div class="value" onclick="setValue(7, 9);">9</div>
			<div class="value" onclick="setValue(7, 10);">10</div>
		</div>
	</div>
	<div class="area finisz" style="display: none;">
		<h3>Gotowe!</h3>
		<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
	</div>
</div>
<div id="$CHART$"></div>

Przedostatnie pole to JavaScript, i tutaj wklejamy resztę kodu z demo (pamiętając o podmianie „chartdiv” na „$CHART$”):

// Themes begin
am4core.useTheme(am4themes_animated);
// Themes end

var chart = am4core.create("$CHART$", am4charts.RadarChart);
chart.hiddenState.properties.opacity = 0; // this creates initial fade-in

chart.data = [ {
	"category": "Pytanie 1",
	"value": 0,
	"color": chart.colors.next()
}, {
	"category": "Pytanie 2",
	"value": 0,
	"color": chart.colors.next()
}, {
	"category": "Pytanie 3",
	"value": 0,
	"color": chart.colors.next()
}, {
	"category": "Pytanie 4",
	"value": 0,
	"color": chart.colors.next()
}, {
	"category": "Pytanie 5",
	"value": 0,
	"color": chart.colors.next()
}, {
	"category": "Pytanie 6",
	"value": 0,
	"color": chart.colors.next()
}, {
	"category": "Pytanie 7",
	"value": 0,
	"color": chart.colors.next()
}, {
	"category": "Pytanie 8",
	"value": 0,
	"color": chart.colors.next()
} ];

chart.padding(20, 20, 20, 20);

var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "category";
categoryAxis.renderer.labels.template.location = 0.5;
categoryAxis.renderer.tooltipLocation = 0.5;

var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.renderer.labels.template.disabled = true;
valueAxis.min = 0;
valueAxis.max = 10;
valueAxis.renderer.minGridDistance = 10;

var series = chart.series.push(new am4charts.RadarColumnSeries());
series.columns.template.tooltipText = "{categoryX}: {valueY.value}";
series.columns.template.width = am4core.percent(100);
series.columns.template.strokeWidth = 0;
series.columns.template.column.propertyFields.fill = "color";
series.dataFields.categoryX = "category";
series.dataFields.valueY = "value";

/**
 * Sets value for particular index
 */
function setValue(index, value) {

	// Set value
	chart.data[index].value = value;
	chart.invalidateRawData();

	// Reveal next question
	var areas = document.getElementsByClassName("area");
	for(var i = 0; i < areas.length; i++) {
		areas[i].style.display = (index + 1) === i ? "block" : "none";
	}
}

Na koniec jeszcze „slug” zmieniłem na „wol-1”, ta by łatwiej było zapamiętać, i po zapisaniu można było już skorzystać z krótkiego kodu na stronie, by wyświetlić nasze „koło życia”:

[amcharts id="wol-1"]

A efekt wyglądał mniej więcej tak:

Mniej więcej, bo dla przykładu, by koleżanka wiedziała gdzie i jak podmieniać kolory na wykresie, od razu dla pytania pierwszego ustawiłem konkretny kolor, zamiast „automatycznego”.

W tym celu zamieniłem:

"color": chart.colors.next()

na:

"color": "#88b15b"

I gdy myślałem, że na tym już koniec, koleżanka rzuciła, że dobrze by było, gdyby użytkownik mógł sobie pobrać wygenerowany wykres, tak, by mógł sobie później porównać „przed” z „po” (prawdopodobnie w kontekście jakiegoś kursu internetowego, ale w to już nie wnikałem ;-)).

Uznałem, że amCarts wydaje się na tyle zaawansowanym rozwiązaniem, że na pewno jakieś opcje eksportu wykresu muszą być. I faktycznie, wystarczyło w sekcji JavaScript, zaraz za zamknięciem ostatniego nawiasu klamrowego dodać linijkę:

chart.exporting.menu = new am4core.ExportMenu();

Minusem tego rozwiązania było to, że użytkownik dostawał pełno opcji i formatów, i potencjalnie łatwo mógł się w tym zgubić. Dlatego postanowiłem ograniczyć dostępne opcje do 2 pozycji, czyli zapisz PNG i drukuj, dodając poniżej jeszcze:

chart.exporting.menu.items = [{
	"label": "...",
	"menu": [
    	{ "type": "png", "label": "Pobierz", "options": { "quality": 1 } },
    	{ "label": "Drukuj", "type": "print" }
  	]
}];

Kolejną modyfikacją było wymuszenie nazwy pliku (prefiks), tak by po pobraniu nie zginął on wśród innych. W tym celu dodałem jeszcze:

chart.exporting.filePrefix = "webinsider-wol";

Dzięki czemu plik zapisuje się jako „webinsider-wol.png”, zamiast „image.png”. I jeśli chodzi o moje zadanie to najpewniej na tym koniec, dalej już koleżanka musi sobie ten wykres zasilić treścią. Chyba że jeszcze coś jej do głowy wpadnie… ;-)

(!) Zgłoś błąd na stronie