<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Advitum.de</title>
	<atom:link href="https://advitum.de/feed/" rel="self" type="application/rss+xml" />
	<link>https://advitum.de</link>
	<description>News und Tutorials rund um Webdesign und Webentwicklung</description>
	<lastBuildDate>Sun, 18 Jun 2017 08:50:27 +0000</lastBuildDate>
	<language>de-DE</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.8.3</generator>
	<item>
		<title>Events im Diagramm – Interaktive SVG-Diagramme im Web</title>
		<link>https://advitum.de/2016/10/events-im-diagramm-interaktive-svg-diagramme-im-web/</link>
		<comments>https://advitum.de/2016/10/events-im-diagramm-interaktive-svg-diagramme-im-web/#respond</comments>
		<pubDate>Mon, 24 Oct 2016 07:00:16 +0000</pubDate>
		<dc:creator><![CDATA[Lars Ebert]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Programmieren]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Diagramm]]></category>
		<category><![CDATA[Events]]></category>
		<category><![CDATA[Hover]]></category>
		<category><![CDATA[Interaktiv]]></category>
		<category><![CDATA[Klick]]></category>
		<category><![CDATA[Quellcode]]></category>
		<category><![CDATA[SVG]]></category>
		<category><![CDATA[Tutorial]]></category>

		<guid isPermaLink="false">https://advitum.de/?p=2671</guid>
		<description><![CDATA[Nachdem wir im letzten Artikel der Artikelserie ein Linien-Diagramm erstellt haben, kümmern wir uns in diesem Artikel darum, das Diagramm für den Nutzer interaktiv zu gestalten. Hier punktet ein SVG-Diagramm gegenüber einem Canvas oder gar einer Bilddatei: Wir können dem Nutzer recht einfach die Möglichkeit geben, mit dem Diagramm zu interagieren, um zum Beispiel genaue [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Nachdem wir im <a href="https://advitum.de/2015/09/das-linien-diagramm-interaktive-svg-diagramme-im-web/">letzten Artikel der Artikelserie</a> ein Linien-Diagramm erstellt haben, kümmern wir uns in diesem Artikel darum, das Diagramm für den Nutzer interaktiv zu gestalten. Hier punktet ein SVG-Diagramm gegenüber einem Canvas oder gar einer Bilddatei: Wir können dem Nutzer recht einfach die Möglichkeit geben, mit dem Diagramm zu interagieren, um zum Beispiel genaue Werte abzulesen oder in eine tiefere Detail-Ansicht zu gelangen.</p>
<p>Im Folgenden gehe ich die Änderungen Schritt für Schritt durch.<span id="more-2671"></span></p>
<h2>Das Resultat</h2>
<div><iframe width="100%" height="600" src="/wp-content/examples/svg-charts/svg-charts-2/index.html" frameborder="0"></iframe></div>
<p>So wird das Diagramm am Ende des Artikels aussehen. Optisch ändert sich nicht viel, allerdings kann der Nutzer nun mit der Maus über die Punkte fahren und sie anklicken.</p>
<h2>Ausgangssituation</h2>
<p>Im <a href="https://advitum.de/2015/09/das-linien-diagramm-interaktive-svg-diagramme-im-web/">vorherigen Artikel</a> haben wir schon ein Linien-Diagramm erstellt, dort findest Du auch den aktuellen Stand des Quellcodes. So sieht unser Diagramm momentan aus:</p>
<div><iframe width="100%" height="600" src="/wp-content/examples/svg-charts/svg-charts-1/index.html" frameborder="0"></iframe></div>
<h2>Tooltips zeigen detaillierte Daten</h2>
<p>Die erste neue Funktion für unser interaktives Diagramm werden Tooltips sein. Beim überfahren mit der Maus wollen wir dem Nutzer die genauen Zahlen zeigen, die sich hinter dem Messwert verbergen. Bewegt der Nutzer die Maus über eine Spalte (also einen Tag), werden für diesen Tag die genauen Zahlen aller Messreihen angezeigt.</p>
<p>Dafür müssen wir als erstes die Position der Maus verfolgen.</p>
<pre><code title="line-chart.js">function LineChart(container, data) {
	Chart.call(this);
	
	var chart = this;
	
	chart.container = container;
	chart.labels = data.labels;
	chart.lines = data.lines;
	chart.tooltip = null;
	
	chart.svgElement = chart.createSvgElement('svg', {
		style: 'font-family: Arial, sans-serif;'
	});
	chart.container.appendChild(chart.svgElement);
	
	window.addEventListener('resize', function() {
		chart.render();
	});
	
	chart.svgElement.addEventListener('mousemove', function(event) {
		
	});
	chart.svgElement.addEventListener('mouseleave', function(event) {
		// Tooltip löschen
	});
	
	chart.render();
}</code></pre>
<p>Da die einzelnen Elemente im SVG reguläre DOM-Elemente sind, können wir ihnen auch einfach <code>EventListener</code> verpassen. Über das <code>mousemove</code> Event prüfen wir beim Bewegen der Maus, ob ein Tooltip angezeigt werden muss, beim <code>mouseleave</code> Event wird der Tooltip dann wieder entfernt.</p>
<pre><code>var chartBounds = chart.svgElement.getBoundingClientRect();
chartBounds.top += document.body.scrollTop;
chartBounds.left += document.body.scrollLeft;
var mouse = {
	x: event.pageX - chartBounds.left,
	y: event.pageY - chartBounds.top
};</code></pre>
<p>Im <code>mousemove</code> Event können wir die aktuelle Position der Maus leicht bestimmen. <code>getBoundingClientRect()</code> gibt die Position des Diagramms im Browserfenster zurück, diese muss dann allerdings noch mit der Scroll-Position verrechnet werden, um absolute Zahlen zu bekommen. In <code>mouse</code> speichern wir schließlich die Mausposition relativ zur linken oberen Ecke des Diagramms.</p>
<pre><code>if(mouse.x &gt; chart.padding.left &amp;&amp; mouse.y &gt; chart.padding.top &amp;&amp; mouse.y &lt; chart.height - chart.padding.bottom) {
	
} else {
	// Tooltip löschen
}</code></pre>
<p>Der Tooltip soll nur angezeigt werden, wenn die Maus sich über der tatsächlichen Diagramm-Fläche befindet, aber nicht über den Beschriftungen oder der Legende. Daher prüfen wir nun, ob die Mausposition innerhalb der Diagramm-Fläche liegt.</p>
<pre><code>var datumIndex = Math.max(0, Math.min(chart.labels.length - 1, Math.round((mouse.x - chart.padding.left - 20) / chart.axis.x.stepSize)));</code></pre>
<p>Anschließend rechnen wir aus, über welcher Spalte sich die Maus befindet. Da die Punkte auf der X-Achse immer den gleichen Abstand haben, können wir das mit einer einfachen Formel erledigen.</p>
<pre><code>if(chart.tooltip === null || chart.tooltip.datumIndex !== datumIndex) {
	
}</code></pre>
<p>Nun noch ein Sonderfall: Wenn der richtige Tooltip schon angezeigt wird, brauchen wir ihn nicht neu anzeigen. Daher werden wir später das aktive Tooltip in <code>chart.tooltip</code> speichern und gleichen hier den Spalten-Index ab. So müssen wir den Tooltip nur neu darstellen, wenn es wirklich nötig ist.</p>
<pre><code>chart.tooltip = {
	datumIndex: datumIndex,
	element: chart.createSvgElement('g', {
		'pointer-events': 'none'
	}),
	line: chart.createSvgElement('line', {
		x1: chart.padding.left + 20 + datumIndex * chart.axis.x.stepSize,
		y1: chart.padding.top,
		x2: chart.padding.left + 20 + datumIndex * chart.axis.x.stepSize,
		y2: chart.height - chart.padding.bottom,
		stroke: '#c0c0c0'
	})
};
chart.svgElement.insertBefore(chart.tooltip.line, chart.svgElement.firstChild);
chart.svgElement.appendChild(chart.tooltip.element);</code></pre>
<p>Jetzt können wir das Tooltip-Element schon hinzufügen. Genau genommen fügen wir zwei Elemente hinzu: einmal den Tooltip selbst, und eine Linie, die dem Nutzer zeigt, in welcher Spalte er sich gerade befindet.</p>
<p>Wir speichern die beiden Elemente und den Spalten-Index in <code>chart.tooltip</code>, damit wir später noch darauf zugreifen können.</p>
<p>Das Tooltip-Element ist bisher einfach nur eine leere Gruppe. Das Linien-Element ist eine <code>line</code>. Die Linie geht vom oberen bis zum unteren Rand der Diagrammfläche.</p>
<p>Damit die Linie im Hintergrund liegt, fügen wir sie an den Anfang des SVG-Elements hinzu. Der Tooltip soll im Vordergrund liegen, daher wird er ans Ende eingefügt.</p>
<pre><code>removeTooltip: {
	value: function() {
		var chart = this;
		
		if(chart.tooltip !== null) {
			chart.svgElement.removeChild(chart.tooltip.element);
			chart.svgElement.removeChild(chart.tooltip.line);
			chart.tooltip = null;
		}
	}
}</code></pre>
<p>Wir schreiben jetzt noch schnell eine Funktion, die die Tooltip-Elemente wieder entfernt. Diese können wir dann an den entsprechenden Stellen einfügen.</p>
<p>Die Linie verhält sich schon wie erwartet, also stimmt bis hierhin schonmal alles. Ein kleiner Zwischenstand:</p>
<pre><code title="line-chart.js" collapse="true">function LineChart(container, data) {
	Chart.call(this);
	
	var chart = this;
	
	chart.container = container;
	chart.labels = data.labels;
	chart.lines = data.lines;
	chart.tooltip = null;
	
	chart.svgElement = chart.createSvgElement('svg', {
		style: 'font-family: Arial, sans-serif;'
	});
	chart.container.appendChild(chart.svgElement);
	
	window.addEventListener('resize', function() {
		chart.render();
	});
	
	chart.svgElement.addEventListener('mousemove', function(event) {
		var chartBounds = chart.svgElement.getBoundingClientRect();
		chartBounds.top += document.body.scrollTop;
		chartBounds.left += document.body.scrollLeft;
		
		var mouse = {
			x: event.pageX - chartBounds.left,
			y: event.pageY - chartBounds.top
		};
		
		if(mouse.x &gt; chart.padding.left &amp;&amp; mouse.y &gt; chart.padding.top &amp;&amp; mouse.y &lt; chart.height - chart.padding.bottom) {
			var datumIndex = Math.max(0, Math.min(chart.labels.length - 1, Math.round((mouse.x - chart.padding.left - 20) / chart.axis.x.stepSize)));
			
			if(chart.tooltip === null || chart.tooltip.datumIndex !== datumIndex) {
				chart.removeTooltip();
				
				chart.tooltip = {
					datumIndex: datumIndex,
					element: chart.createSvgElement('g', {
						'pointer-events': 'none'
					}),
					line: chart.createSvgElement('line', {
						x1: chart.padding.left + 20 + datumIndex * chart.axis.x.stepSize,
						y1: chart.padding.top,
						x2: chart.padding.left + 20 + datumIndex * chart.axis.x.stepSize,
						y2: chart.height - chart.padding.bottom,
						stroke: '#c0c0c0'
					})
				};
				
				chart.svgElement.insertBefore(chart.tooltip.line, chart.svgElement.firstChild);
				chart.svgElement.appendChild(chart.tooltip.element);
			}
		} else {
			chart.removeTooltip();
		}
	});
	chart.svgElement.addEventListener('mouseleave', function(event) {
		chart.removeTooltip();
	});
	
	chart.render();
}

LineChart.prototype = Object.create(Chart.prototype, {
	constructor: {
		value: LineChart
	},
	render: {
		value: function() {
			var chart = this;
			
			chart.width = chart.container.clientWidth;
			chart.height = 500;
			
			chart.svgElement.setAttribute('width', chart.width);
			chart.svgElement.setAttribute('height', chart.height);
			chart.svgElement.setAttribute('viewBox', '0 0 ' + chart.width + ' ' + chart.height);
			
			while(chart.svgElement.childNodes.length) {
				chart.svgElement.removeChild(chart.svgElement.firstChild);
			}
			
			chart.renderLegend();
			chart.renderAxis();
			chart.renderLines();
		}
	},
	renderLegend: {
		value: function() {
			var chart = this;
			
			chart.legend = chart.createSvgElement('g');
			chart.svgElement.appendChild(chart.legend);
			
			var offset = 0;
			for(var index = 0; index &lt; chart.lines.length; index++) {
				var legendItemElement = chart.createSvgElement('g', {
					transform: 'translate(' + offset + ', 0)'
				});
				chart.legend.appendChild(legendItemElement);
				
				var circleElement = chart.createSvgElement('circle', {
					cx: 10,
					cy: 8,
					r: 3.5,
					fill: chart.colors[index % chart.colors.length],
					stroke: 'none'
				});
				legendItemElement.appendChild(circleElement);
				
				var textElement = chart.createSvgElement('text', {
					x: 20,
					y: 13,
					'font-size': 14
				});
				legendItemElement.appendChild(textElement);
				
				textElement.textContent = chart.lines[index].title;
				
				offset += legendItemElement.getBBox().width + 20;
			}
		}
	},
	renderAxis: {
		value: function() {
			var chart = this;
			
			chart.axis = {
				x: {
					nthLabel: 1
				},
				y: {
					maximum: 1,
					step: 1
				}
			};
			
			for(var lineIndex = 0; lineIndex &lt; chart.lines.length; lineIndex++) {
				for(var datumIndex = 0; datumIndex &lt; chart.lines[lineIndex].data.length; datumIndex++) {
					chart.axis.y.maximum = Math.max(chart.axis.y.maximum, chart.lines[lineIndex].data[datumIndex]);
				}
			}
			
			while(chart.axis.y.step * 7 &lt; chart.axis.y.maximum) {
				chart.axis.y.step *= 10;
			}
			if(chart.axis.y.step % 2 === 0 &amp;&amp; chart.axis.y.step * 3.5 &gt; chart.axis.y.maximum) {
				chart.axis.y.step /= 2;
			}
			
			chart.axis.y.maximum = Math.ceil(chart.axis.y.maximum / chart.axis.y.step) * chart.axis.y.step;
			
			chart.axis.x.element = chart.createSvgElement('g');
			chart.svgElement.appendChild(chart.axis.x.element);
			
			chart.axis.y.element = chart.createSvgElement('g');
			chart.svgElement.appendChild(chart.axis.y.element);
			
			for(var index = 0; index &lt; chart.labels.length; index++) {
				chart.axis.x.element.appendChild(chart.renderXAxisLabel(chart.labels[index]));
			}
			
			for(var y = 0; y &lt;= chart.axis.y.maximum; y += chart.axis.y.step) {
				chart.axis.y.element.appendChild(chart.renderYAxisLabel(chart.formatNumber(y)));
			}
			
			chart.padding = {
				top: chart.legend.getBBox().height + 20,
				left: -1 * Math.min(chart.axis.x.element.getBBox().x + 20, chart.axis.y.element.getBBox().x - 8),
				bottom: chart.axis.x.element.getBBox().height
			};
			
			chart.axis.x.element.setAttribute('transform', 'translate(0, ' + (chart.height - chart.padding.bottom) + ')');
			chart.axis.x.stepSize = chart.labels.length &gt; 1 ? (chart.width - chart.padding.left - 40) / (chart.labels.length - 1) : 20;
			
			while(chart.axis.x.stepSize * chart.axis.x.nthLabel &lt; 20) {
				chart.axis.x.nthLabel++;
			}
			
			if(chart.axis.x.nthLabel &gt; 1) {
				for(var index = chart.axis.x.element.childNodes.length - 1; index &gt; 0; index--) {
					if(index % chart.axis.x.nthLabel) {
						chart.axis.x.element.removeChild(chart.axis.x.element.childNodes[index]);
					}
				}
			}
			
			for(var index = 0; index &lt; chart.axis.x.element.childNodes.length; index++) {
				chart.axis.x.element.childNodes[index].setAttribute('transform', 'translate(' + (chart.padding.left + 20 + chart.axis.x.nthLabel * index * chart.axis.x.stepSize) + ', 0)');
			}
			
			chart.axis.y.element.setAttribute('transform', 'translate(' + chart.padding.left + ', 0)');
			chart.axis.y.stepSize = (500 - chart.padding.bottom - chart.padding.top) / (chart.axis.y.maximum / chart.axis.y.step);
			for(var index = 0; index &lt; chart.axis.y.element.childNodes.length; index++) {
				chart.axis.y.element.childNodes[index].setAttribute('transform', 'translate(0, ' + (500 - chart.padding.bottom - index * chart.axis.y.stepSize) + ')');
			}
		}
	},
	renderXAxisLabel: {
		value: function(label) {
			var chart = this;
			
			var labelElement = chart.createSvgElement('g');
			
			var lineElement = chart.createSvgElement('line', {
				x1: 0,
				y1: 0,
				x2: 0,
				y2: 5,
				stroke: '#c0c0c0'
			});
			labelElement.appendChild(lineElement);
			
			var textElement = chart.createSvgElement('text', {
				x: -15,
				y: 7,
				'font-size': 11,
				'text-anchor': 'end',
				transform: 'rotate(-45 0 0)'
			});
			textElement.textContent = label;
			labelElement.appendChild(textElement);
			
			return labelElement;
		}
	},
	renderYAxisLabel: {
		value: function(label) {
			var chart = this;
			
			var labelElement = chart.createSvgElement('g');
			
			var lineElement = chart.createSvgElement('line', {
				x1: 0,
				y1: 0,
				x2: chart.width,
				y2: 0,
				stroke: '#c0c0c0'
			});
			labelElement.appendChild(lineElement);
			
			var textElement = chart.createSvgElement('text', {
				x: -8,
				y: 4,
				'font-size': 11,
				'text-anchor': 'end'
			});
			textElement.textContent = label;
			labelElement.appendChild(textElement);
			
			return labelElement;
		}
	},
	renderLines: {
		value: function() {
			var chart = this;
			
			for(var lineIndex = 0; lineIndex &lt; chart.lines.length; lineIndex++) {
				chart.lines[lineIndex].element = chart.createSvgElement('g', {
					transform: 'translate(' + (chart.padding.left + 20) + ', ' + (500 - chart.padding.bottom) + ')'
				});
				chart.svgElement.appendChild(chart.lines[lineIndex].element);
				
				var points = [];
				chart.lines[lineIndex].circles = [];
				
				for(var datumIndex = 0; datumIndex &lt; chart.lines[lineIndex].data.length; datumIndex++) {
					var point = {
						x: datumIndex * chart.axis.x.stepSize,
						y: -1 * chart.lines[lineIndex].data[datumIndex] * (chart.axis.y.stepSize / chart.axis.y.step),
					};
					points.push(point.x + ',' + point.y);
					
					if(chart.axis.x.stepSize &gt;= 10) {
						var circleElement = chart.createSvgElement('circle', {
							cx: point.x,
							cy: point.y,
							r: 3.5,
							fill: chart.colors[lineIndex % chart.colors.length],
							stroke: 'none'
						});
						chart.lines[lineIndex].element.appendChild(circleElement);
						chart.lines[lineIndex].circles.push(circleElement);
					}
				}
				
				var lineElement = chart.createSvgElement('polyline', {
					points: points.join(' '),
					stroke: chart.colors[lineIndex % chart.colors.length],
					'stroke-width': 2,
					fill: 'none'
				});
				chart.lines[lineIndex].element.appendChild(lineElement);
				chart.lines[lineIndex].line = lineElement;
			}
		}
	},
	removeTooltip: {
		value: function() {
			var chart = this;
			
			if(chart.tooltip !== null) {
				chart.svgElement.removeChild(chart.tooltip.element);
				chart.svgElement.removeChild(chart.tooltip.line);
				chart.tooltip = null;
			}
		}
	}
});</code></pre>
<div><iframe width="100%" height="600" src="/wp-content/examples/svg-charts/svg-charts-2-1/index.html" frameborder="0"></iframe></div>
<p>Als nächstes brauchen wir das Tooltip-Element.</p>
<pre><code>var textElement = chart.createSvgElement('text', {
	x: 10,
	y: 21,
	'font-size': 12
});
textElement.textContent = chart.labels[datumIndex];
chart.tooltip.element.appendChild(textElement);</code></pre>
<p>Die erste Zeile im Tooltip soll das Label der Spalte sein, also das formatierte Datum.</p>
<pre><code>for(var lineIndex = 0; lineIndex &lt; chart.lines.length; lineIndex++) {
	var textElement = chart.createSvgElement('text', {
		x: 10,
		y: 21 + (lineIndex + 1) * 16,
		'font-size': 12
	});
	chart.tooltip.element.appendChild(textElement);
	
	var titleElement = chart.createSvgElement('tspan', {
		fill: chart.colors[lineIndex % chart.colors.length]
	});
	textElement.appendChild(titleElement);
	titleElement.textContent = chart.lines[lineIndex].title + ': ';
	
	var valueElement = chart.createSvgElement('tspan', {
		'font-weight': 'bold'
	});
	textElement.appendChild(valueElement);
	valueElement.textContent = chart.formatNumber(chart.lines[lineIndex].data[datumIndex]);
}</code></pre>
<p>Außerdem wollen wir die Daten jeder Datenreihe darstellen. Dafür zeigen wir den Namen der Datenreihe und den Wert der Datenreihe in der aktiven Spalte. Um den Namen der Datenreihe einzufärben, nutzen wir <code>tspan</code> Elemente.</p>
<pre><code>var box = chart.tooltip.element.getBBox();
var boxElement = chart.createSvgElement('rect', {
	x: 0,
	y: 0,
	width: box.width + 20,
	height: box.height + 20,
	rx: 3,
	ry: 3,
	fill: 'rgba(255, 255, 255, .9)',
	stroke: '#c0c0c0'
});
chart.tooltip.element.insertBefore(boxElement, chart.tooltip.element.firstChild);</code></pre>
<p>Außerdem brauchen wir noch eine Box im Hintergrund, um dem Tooltip einen Hintergrund und Rahmen zu geben.</p>
<pre><code>var left = chart.padding.left + 20 + datumIndex * chart.axis.x.stepSize - box.width - 30;
var top = mouse.y - (box.height + 20) / 2;
if(left &lt; 0) {
	left += box.width + 40;
}
top = Math.max(chart.padding.top, Math.min(500 - chart.padding.bottom - box.height - 20, top));
chart.tooltip.element.setAttribute('transform', 'translate(' + left + ', ' + top + ')');</code></pre>
<p>Nun zur Position des Tooltips: Die vertikale Position machen wir vom Mauszeiger abhängig. So sollte der Tooltip immer auf einer Höhe mit dem Mauszeiger sein. Die horizontale Position berechnen wir anhand der Spalte, damit der Tooltip immer links neben der vertikalen Linie steht.</p>
<p>Sonderfall: Wenn der Tooltip links aus dem Diagramm herausragen würde, positionieren wir es stattdessen rechts von der Linie.</p>
<div><iframe width="100%" height="600" src="/wp-content/examples/svg-charts/svg-charts-2-2/index.html" frameborder="0"></iframe></div>
<h2>Hover und Klick</h2>
<p>Nehmen wir an, hinter den einzelnen Messpunkten gibt es weitere Informationen. Beispielsweise könnte sich beim Klick auf den Datenpunkt eine genaue Auswertung oder eine Übersicht über den Tag öffnen. Daher machen wir die Datenpunkte jetzt einfach klickbar. Und damit man sieht, dass beim Klicken was passiert, heben wir die Punkte beim Hover etwas hervor.</p>
<pre><code>renderLines: {
	value: function() {
		var chart = this;
		
		for(var lineIndex = 0; lineIndex &lt; chart.lines.length; lineIndex++) {
			chart.lines[lineIndex].element = chart.createSvgElement('g', {
				transform: 'translate(' + (chart.padding.left + 20) + ', ' + (500 - chart.padding.bottom) + ')'
			});
			chart.svgElement.appendChild(chart.lines[lineIndex].element);
			
			var points = [];
			chart.lines[lineIndex].circles = [];
			
			for(var datumIndex = 0; datumIndex &lt; chart.lines[lineIndex].data.length; datumIndex++) {
				var point = {
					x: datumIndex * chart.axis.x.stepSize,
					y: -1 * chart.lines[lineIndex].data[datumIndex] * (chart.axis.y.stepSize / chart.axis.y.step),
				};
				points.push(point.x + ',' + point.y);
				
				if(chart.axis.x.stepSize &gt;= 10) {
					var circleElement = chart.createSvgElement('circle', {
						cx: point.x,
						cy: point.y,
						r: 3.5,
						fill: chart.colors[lineIndex % chart.colors.length],
						stroke: 'none',
						cursor: 'pointer'
					});
					chart.lines[lineIndex].element.appendChild(circleElement);
					chart.lines[lineIndex].circles.push(circleElement);
					
					circleElement.addEventListener('mouseenter', function(event) {
						event.target.setAttribute('r', 4.5);
					});
					circleElement.addEventListener('mouseleave', function(event) {
						event.target.setAttribute('r', 3.5);
					});
					circleElement.addEventListener('click', function(event) {
						// Klick-Event
					});
				}
			}
			
			var lineElement = chart.createSvgElement('polyline', {
				points: points.join(' '),
				stroke: chart.colors[lineIndex % chart.colors.length],
				'stroke-width': 2,
				fill: 'none'
			});
			chart.lines[lineIndex].element.insertBefore(lineElement, chart.lines[lineIndex].element.firstChild);
			chart.lines[lineIndex].line = lineElement;
		}
	}
},</code></pre>
<p>In der Funktion <code>renderLines()</code> hängen wir an die Punkte drei EventListener an: <code>mouseenter</code>, <code>mouseleave</code> und <code>click</code>. Beim <code>mouseenter</code> vergrößern wir den Radius des Punktes, beim <code>mouseleave</code> verkleinern wir ihn wieder auf den Originalwert. So wird der Punkt beim Überfahren mit der Maus hervorgehoben. Nebenbei geben wir den Punkten noch die Eigenschaft <code>cursor: 'pointer'</code>.</p>
<p>Außerdem passen wir den Code noch etwas an, damit die Linie vor den Punkten eingefügt wird, statt hinter den Punkten.</p>
<p>Im Klick-Event könnten wir nun noch den Klick auf den Punkt abfangen, um beispielsweise auf eine Detailseite weiterzuleiten. Da wir aber keine Detailseite haben, spare ich mir das an dieser Stelle.</p>
<h2>Fazit: SVG ist super für interaktivität</h2>
<div><iframe width="100%" height="600" src="/wp-content/examples/svg-charts/svg-charts-2/index.html" frameborder="0"></iframe></div>
<p>So sieht nun unser Endresultat aus. Der Nutzer kann nun mit dem Diagramm interagieren.</p>
<p>SVG-Grafiken sind super für Interaktivität. Auf die Elemente kann man mit JavaScript zugreifen und ihnen EventListeners anhängen. So kann man in diesem Fall Diagrammen sehr einfach einen Mehrwert geben.</p>
<p>Im nächsten Artikel schauen wir uns einen neuen Diagramm-Typen an.</p>
]]></content:encoded>
			<wfw:commentRss>https://advitum.de/2016/10/events-im-diagramm-interaktive-svg-diagramme-im-web/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>7 Schritte zu besserer Typografie im Web</title>
		<link>https://advitum.de/2016/05/7-schritte-zu-besserer-typografie-im-web/</link>
		<comments>https://advitum.de/2016/05/7-schritte-zu-besserer-typografie-im-web/#respond</comments>
		<pubDate>Mon, 02 May 2016 07:00:59 +0000</pubDate>
		<dc:creator><![CDATA[Timo Kleemann]]></dc:creator>
				<category><![CDATA[Allgemein]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Tipps]]></category>
		<category><![CDATA[Fonts]]></category>
		<category><![CDATA[Schriftart]]></category>
		<category><![CDATA[Typografie]]></category>
		<category><![CDATA[Web]]></category>

		<guid isPermaLink="false">https://advitum.de/?p=2639</guid>
		<description><![CDATA[Qualitativ hochwertige Texte sind nach wie vor die beste Möglichkeit sich im Web eine dauerhafte Leserschaft aufzubauen. Doch was helfen gute Inhalte, wenn die Optik nicht stimmt? Mit diesen 7 einfachen Tipps kannst Du die Typografie Deiner Website verbessern. 1. Visuelle Hierarchie Wer viel Text im Internet veröffentlicht, der sollte seinen Lesern die Möglichkeit geben, [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Qualitativ hochwertige Texte sind nach wie vor die beste Möglichkeit sich im Web eine dauerhafte Leserschaft aufzubauen. Doch was helfen gute Inhalte, wenn die Optik nicht stimmt? Mit diesen 7 einfachen Tipps kannst Du die Typografie Deiner Website verbessern.<span id="more-2639"></span></p>
<h2>1. Visuelle Hierarchie</h2>
<p>Wer viel Text im Internet veröffentlicht, der sollte seinen Lesern die Möglichkeit geben, zwischen verschiedenen Textelementen zu unterscheiden, um die Bedeutung eines Textes besser und schneller erfassen zu können. Die Bedeutung eines Textelements kann zum Beispiel durch seine Größe oder Gewichtung verdeutlicht werden. Ein Webdesigner, der seinen Job besonders ernst nimmt, legt für diese Zwecke eine »Schriftgrößen-Skala« an. Diese könnte etwa so aussehen, dass normaler Fließtext die Schriftgröße 12 erhält, Zwischenüberschriften 18 und Hauptüberschriften die Schriftgröße 24. Wenn man sich nicht gerade für »Comic Sans« oder »Impact« als Schriftart entscheidet, ist auf diese Weise ein harmonisches Schriftbild schon fast garantiert. </p>
<p>Ein Fehler, der leider auf sehr vielen Websites gemacht wird, ist der, alle Textelemente in derselben Schriftgröße zu präsentieren. Dies mag aus Sicht des Entwicklers einfach und wartungsarm sein, für viele Leser stellt es jedoch eine Barriere dar und hält sie davon ab, den Sinn eines Textes überhaupt zu erfassen.</p>
<h2>2. Zwischenüberschriften</h2>
<p>Insbesondere längere Texte sollten unbedingt durch Zwischenüberschriften aufgelockert werden, und zwar unter Zuhilfenahme einer Schriftgrößen-Skala (siehe Punkt 1). Zwischenüberschriften sorgen dafür, dass Leser die Kernaussagen eines Textes viel schneller erfassen können, ohne gleich den gesamten Text durchlesen zu müssen. Sie können dem Leser auch helfen, einen Einstieg irgendwo mitten im Text zu finden. Es ist ein Mythos zu glauben, dass jeder Leser einen Text immer vom ersten bis zum letzten Wort durchliest. Viele möchten an einer anderen Stelle einsteigen. Geschickt gesetzte Zwischenüberschriften können dies erheblich erleichtern.</p>
<h2>3. Optimale Zeilenlänge</h2>
<p>Insbesondere bei der Desktop-Ansicht der eigenen Website läuft man schnell Gefahr, den Leser mit zu langen Zeilen zu überfordern. Eine Faustregel (die ursprünglich aus dem Buchdruck stammt) besagt, dass eine Zeile nie mehr als 12 Wörter enthalten sollte, um eine gute Lesbarkeit zu gewährleisten. Mit jedem Wort, das darüber hinausgeht, wird es für das menschliche Auge schwieriger, den Anfang der nächsten Zeile zu finden. </p>
<p>Besonders wichtig ist diese Regel bei langen Fließtexten, zum Beispiel Fach- oder Blog-Artikeln. Um dem Leser hier optimalen Lesekomfort zu bieten, sollte man die Schriftgröße so weit erhöhen bis die Regel erfüllt ist. (Natürlich sollte dabei auch die Zeilenhöhe entsprechend angepasst werden.)</p>
<p>Ein Fließtext in Schriftgröße 24 mit maximal 12 Wörtern pro Zeile ist wesentlich lesbarer als einer in Schriftgröße 12 mit 24 Wörtern pro Zeile. Für einen Webdesigner gibt es keinen Grund sich für große, gut lesbare Schrift zu schämen!</p>
<h2>4. Anzahl und Auswahl von Schriftarten</h2>
<p>Man sollte weder zu viele Schriftarten verwenden (was schnell unübersichtlich wirkt) noch sich störrisch an nur eine einzige klammern (was schnell einfallslos wirkt). In der Praxis hat sich ein Mix aus zwei, maximal drei, Schriftarten bewährt. Auch bei sehr großen Websites mit vielen redaktionellen Inhalten reicht dies in der Regel völlig aus.</p>
<p>Was immer gut funktioniert, ist die Paarung einer Serifenschrift mit einer Nichtserifenschrift. Schöne Kombinationen gibt es bei <a href="http://www.fontpair.co">Font Pair</a> zu bewundern.</p>
<p>Zum Glück sind die Zeiten vorbei, in denen man auf »browser-sichere« Schriftarten zurückgreifen musste. Heutzutage findet man bei <a href="http://www.google.com/fonts">Google Web Fonts</a>, <a href="http://www.typekit.com">Typekit</a> oder <a href="http://www.fontsquirrel.com">FontSquirrel</a> ein überwältigendes Angebot an Schriften.</p>
<p>Bei den beiden erstgenannten werden die Schriften via Javascript in die Website eingebettet, weswegen es sich sowieso empfiehlt, die Anzahl der Schriftarten auf zwei zu begrenzen. Alles andere wäre gerade auf mobilen Endgeräten nicht mehr zu vertreten, weil die Wartezeiten bis zum Laden des Javascripts zu lang wären.</p>
<p>Weitere Quellen für kostenlose Web-Schriften finden sich im Artikel <a href="https://advitum.de/2010/11/font-face-kompatible-schriften-gratis-im-netz/">@font-face kompatible Schriften gratis im Netz</a>. Außerdem zeigt Lars Ebert <a href="http://t3n.de/news/webdesign-tipps-font-face-mehr-denkt-297320/">in diesem Artikel, welche Möglichkeiten @font-face darüber hinaus noch bietet</a>.</p>
<h2>5. Design folgt Funktion</h2>
<p>Im Gegensatz zu Druckerzeugnissen nimmt Typografie im Web deutlich mehr Funktionen wahr. Die Benutzer interagieren mit Links, Buttons, Hauptmenüs, Untermenüs etc. Diese unterschiedlichen Funktionen kann und sollte ein Webdesigner mit Hilfe von Typografie hervorheben und verdeutlichen. </p>
<p>Ein Link zum Beispiel sollte farblich vom umgebenden Fließtext abgesetzt sein. Ist er das nicht, dann sollte er zumindest unterstrichen sein, um dem Benutzer seine Funktion zu verdeutlichen. <a href="https://advitum.de/2012/07/externe-links-markieren-%E2%80%93-css-quick-tip/">Mittels CSS lassen sich auch explizit externe Links hervorheben</a>, um dem Nutzer einen weiteren Mehrwert zu bieten.</p>
<p>Ähnlich verhält es sich mit anderen Elementen auf der Website. Das Hauptmenü etwa sollte zwar eine der beiden unter Punkt 4 definierten Schriftarten aufweisen. Es spricht aber nichts dagegen, die einzigartige Funktion, die das Hauptmenü erfüllt, mit anderen optischen Mitteln zu verdeutlichen. So könnte man es zum Beispiel auf einer farblich hinterlegten Leiste platzieren, die Buchstaben mittels CSS als Großbuchstaben ausgeben oder den horizontalen Abstand zwischen den Buchstaben leicht erhöhen.</p>
<h2>6. Ausreichend Weißraum</h2>
<p>Weißraum ist eigentlich etwas ziemlich banales. Es ist die Fläche einer Website, die leer ist und keinen Text oder andere Inhalte enthält. Der Weißraum kann auch vergrößert werden, indem man einfach nur die Zeilenhöhe eines Fließtextes oder die Abstände zwischen zwei Absätzen erhöht. </p>
<p>Websites mit viel geschickt eingesetztem Weißraum wirken großzügiger und edler. Der Effekt ist so ähnlich wie beim Betreten eines Wohnhauses. Ein großer Salon wirkt wesentlich edler, prächtiger und einladender als ein mit Kartons zugestellter Flur in einer 1-Zimmer-Wohnung. </p>
<p>Gerade im Web gibt es eigentlich keinen Grund, sich diesen Effekt nicht zunutze zu machen. Zumal es ja im Gegensatz zum genannten Beispiel aus der Immobilienbranche keinerlei Mehrkosten verursacht. </p>
<p>Es ist auffallend und schade, dass bei vielen Websites Platz scheinbar als etwas kostbares gesehen wird. Dort wird dann oft versucht, möglichst viel winzigen Text mit geringer Zeilenhöhe auf eine Seite zu stopfen. </p>
<p>Hier gilt es anzusetzen und den eigenen Besuchern eine Website zu präsentieren, die mit viel luftigem Weißraum glänzt und zum Lesen einlädt.</p>
<h2>7. Kurze Wörter bevorzugen</h2>
<p>Im Gegensatz zu vielen andere Sprachen bietet die deutsche Sprache die Möglichkeit unendlich viele Hauptwörter miteinander zu verbinden und somit jeweils ein neues Wort zu erschaffen. Manchmal entstehen dabei regelrechte Wortungetüme.</p>
<p>Die Browser sind auch im Jahre 2016 leider noch nicht in der Lage, die Silben eines solchen Wortes automatisch und an den richtigen Stellen zu trennen. Damit es keine hässlichen Überlappungen gibt (gerade auch bei der mobilen Ansicht einer Website), sollte man solche Wörter tendenziell vermeiden und wenn das nicht möglich ist, einen Notbindestrich setzen, wo eigentlich keiner hingehört.</p>
<p>Auf diese Weise wird das Wort auch in der mobilen Ansicht einer Website einigermaßen korrekt umgebrochen und läuft nicht über den Bildschirmrand.</p>
<p>Alternativ kann auch mit der HTML-Entity &amp;shy; ein so genannter weicher Bindestrich angezeigt werden. Damit wird dem Browser mitgeteilt, dass an dieser Stelle mit einem Bindestrich bei Bedarf umgebrochen werden darf.</p>
<h2>Fazit: Es sind die kleinen Dinge…</h2>
<p>Bei Typografie gibt es viele Kleinigkeiten zu beachten, aber auch die Grundlagen müssen stimmen. Mit einer guten Kombination aus Schriftkombinationen, Schriftgrößen, Farben und Textfluss kann dem Leser das Lesen so einfach wie möglich gemacht werden. Je weiter man ins Detail geht, desto besser ist meistens auch das Ergebnis. Der Rest hängt dann nur noch von der Textqualität ab.</p>
]]></content:encoded>
			<wfw:commentRss>https://advitum.de/2016/05/7-schritte-zu-besserer-typografie-im-web/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Das Linien-Diagramm – Interaktive SVG-Diagramme im Web</title>
		<link>https://advitum.de/2015/09/das-linien-diagramm-interaktive-svg-diagramme-im-web/</link>
		<comments>https://advitum.de/2015/09/das-linien-diagramm-interaktive-svg-diagramme-im-web/#comments</comments>
		<pubDate>Mon, 21 Sep 2015 07:00:24 +0000</pubDate>
		<dc:creator><![CDATA[Lars Ebert]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Programmieren]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Diagramm]]></category>
		<category><![CDATA[Quellcode]]></category>
		<category><![CDATA[SVG]]></category>
		<category><![CDATA[Tutorial]]></category>

		<guid isPermaLink="false">https://advitum.de/?p=2568</guid>
		<description><![CDATA[Diagramme sind ein wichtiges Werkzeug, um Daten zu visualisieren. Sie können komplexe Zusammenhänge sichtbar machen. Auch im Web bietet sich der Einsatz von Diagrammen an – zum Beispiel zur Visualisierung von Nutzer- oder Verkaufszahlen, in wissenschaftlichen Artikeln etc. Es gibt bereits einige Tools und Bibliotheken, die uns bei der Nutzung von Diagrammen unterstützen können. Wer neugierig [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Diagramme sind ein wichtiges Werkzeug, um Daten zu visualisieren. Sie können komplexe Zusammenhänge sichtbar machen. Auch im Web bietet sich der Einsatz von Diagrammen an – zum Beispiel zur Visualisierung von Nutzer- oder Verkaufszahlen, in wissenschaftlichen Artikeln etc.</p>
<p>Es gibt bereits einige Tools und Bibliotheken, die uns bei der Nutzung von Diagrammen unterstützen können. Wer neugierig ist, gerne selbst experimentiert oder einfach nicht die passende Bibliothek findet, kann trotzdem mit JavaScript und SVG sehr einfach eigene Diagramme erstellen. In diesem Artikel erfährst Du, wie Du Linien-Diagramme aus Deinen Daten generieren kannst.<span id="more-2568"></span></p>
<h2>Vorab das Resultat</h2>
<div><iframe width="100%" height="600" src="/wp-content/examples/svg-charts/svg-charts-1/index.html" frameborder="0"></iframe></div>
<p>So wird das Diagramm am Ende dieses Tutorials aussehen. Das Prinzip ist gar nicht so kompliziert, wie es auf den ersten Blick aussieht. Im folgenden kannst Du die Entstehung des Diagramms Schritt für Schritt nachvollziehen.</p>
<h2>Schritt 1: HTML</h2>
<p>Bevor wir die erste Zeile JavaScript schreiben, sollten wir erst das HTML-Markup schreiben. Mein Markup sieht so aus:</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;de&quot;&gt;
&lt;head&gt;
	&lt;meta charset=&quot;UTF-8&quot;&gt;
	&lt;title&gt;Lininen-Diagramm&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
	&lt;div id=&quot;chart&quot;&gt;
	
	&lt;/div&gt;
	
	&lt;script type=&quot;text/javascript&quot; src=&quot;js/chart.js&quot;&gt;&lt;/script&gt;
	&lt;script type=&quot;text/javascript&quot; src=&quot;js/line-chart.js&quot;&gt;&lt;/script&gt;
	&lt;script type=&quot;text/javascript&quot; src=&quot;js/main.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>In dem <code>div#chart</code> wird später das Diagramm eingefügt. Ich spalte mein JavaScript gerne in übersichtliche Teile auf, deshalb habe ich drei JavaScript-Dateien eingebunden: <code>chart.js</code>, <code>line-charts</code> und <code>main.js</code>. Da ich plane, außer Linien-Diagrammen noch weitere Diagramm-Typen zu entwickeln, teile ich den Diagramm-Code auf: In <code>chart.js</code> kommt für alle Typen gültiger Code, in <code>line-charts.js</code> der spezialisierte Code für Linien-Diagramme. Diese beiden Dateien sind vom restlichen Code für die Webseite getrennt, sodass ich sie später wieder nutzen kann. In <code>main.js</code> geschieht genau diese Nutzung.</p>
<h2>Schritt 2: Prototyping</h2>
<p>In JavaScript Objekt-orientiert zu arbeiten, ist gar nicht so schwer, wie es sich anhört. Im Grunde tut es jeder, denn JavaScript besteht quasi nur aus Objekten.</p>
<p>Objekt-Prototypen kann man in JavaScript sehr einfach ähnlich wie Klassen in anderen Sprachen nutzen. Für dieses Tutorial werden wir zwei Objekt-Prototypen benötigen: <code>Chart</code> und <code>LineChart</code>, wobei der Prototyp für die Linien-Diagramme einige Funktionen vom allgemeineren Diagramm-Prototypen erben wird.</p>
<pre><code title="chart.js">function Chart() {}

Chart.prototype = {};</code></pre>
<p>Dies ist unser Chart-Prototype. Die Funktion <code>Chart()</code> wird als Konstruktor genutzt, im Objekt <code>Chart.prototype</code> können später Funktionen und Variablen definiert werden, die wir in allen Diagrammen benötigen könnten.</p>
<p>Bisher ist der Prototyp noch leer, im Laufe des Artikels werden hier Funktionen hinzukommen.</p>
<pre><code title="line-chart.js">function LineChart(container, data) {
	Chart.call(this);
}

LineChart.prototype = Object.create(Chart.prototype, {
	constructor: {
		value: LineChart
	}
});</code></pre>
<p>Auch der Prototype <code>LineChart</code> bleibt vorerst leer. Hier führen wir lediglich die nötigen Schritte durch, um von <code>Chart</code> zu erben. In Zeile 2 rufen wir innerhalb des Konstruktors den Konstruktor von <code>Chart</code> auf. Mit <code>.call(this)</code> sorgen wir dafür, dass das Schlüsselwort <code>this</code> im Eltern-Konstruktor auf das gleiche Objekt referenziert wie hier. Mit <code>Object.create()</code> erstellen wir den Objekt-Prototypen für <code>LineChart</code>, wobei dieser Prototype wiederum den Prototypen von <code>Chart</code> als Prototypen verwendet. So entsteht eine Prototype-Kette, in der Objekte von <code>Chart</code> zu <code>LineChart</code> »durchgereicht« werden.</p>
<p>Da der Chart-Prototype die Funktion <code>Chart</code> als Konstruktor verwendet und der LineChart-Prototype auf Basis dieses Prototyps erstellt wurde, benutzt der LineChart-Prototype bisher auch diesen Konstruktor. Deshalb definieren wir abschließend noch in Zeile 6-8 die Funktion <code>LineChart</code> als korrekten Konstruktor für diesen Prototypen.</p>
<h2>Schritt 3: Daten</h2>
<p>Wer ein Diagramm benötigt, hat meist schon irgendwelche Daten. Für diese Demo habe ich aber keine. Deshalb habe ich schnell ein paar Zeilen JavaScript geschrieben, die mir zufällige Daten generieren.</p>
<pre><code title="main.js">var weekdays = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
var months = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
var from = new Date(2015, 8, 21-28, 0, 0, 0);
var to = new Date(2015, 8, 21, 0, 0, 0);

var data = {
	labels: [],
	lines: [{
		title: 'Messwerte 1',
		data: []
	}, {
		title: 'Messwerte 2',
		data: []
	}]
};

while(from &lt;= to) {
	data.labels.push(weekdays[from.getDay()] + ', ' + from.getDate() + '. ' + months[from.getMonth()] + ' ' + from.getFullYear());
	
	for(var lineIndex = 0; lineIndex &lt; data.lines.length; lineIndex++) {
		data.lines[lineIndex].data.push(Math.round(lineIndex * 1000 + 500 + (Math.random() + Math.random()) * 500));
	}
	
	from.setDate(from.getDate() + 1);
}</code></pre>
<p>Wichtig ist hier für dieses Tutorial nur das Format der Daten. Alle Daten liegen in dem Objekt <code>data</code>. Dieses Objekte besteht aus <code>labels</code> und <code>lines</code>. In <code>labels</code> wird die Beschriftung für die X-Achse liegen, in <code>lines</code> die darzustellenden Linien. Jede Linie besteht wiederum aus einem Titel und einem Array mit den Datenwerten.</p>
<p>Der Aufbau der Daten hängt natürlich hauptsächlich von der Datenquelle ab. Kommen die Daten in einem anderen Format aus der Quelle, können diese entweder vorher konvertiert werden, oder die Nutzung innerhalb von <code>LineChart</code> wird angepasst.</p>
<h2>Schritt 4: Instanziierung des Objektes</h2>
<p>Nichts, was wir in unsere Prototypen schreiben, wird ausgeführt. Zumindest nicht, bevor wir den Prototyp nicht tatsächlich nutzen. Deshalb fügen wir dem Code jetzt eine weitere Zeile hinzu, um den Prototypen zu instanziieren.</p>
<pre><code title="main.js">new LineChart(document.getElementById('chart'), data);</code></pre>
<p>Mit dem Schlüsselwort <code>new</code> lassen wir den Konstruktor ein neues Objekt aus dem Prototypen instanziieren. Wir übergeben hier zum einen den Container, in welchen das Diagramm eingefügt werden soll, zum anderen das Objekt mit den Daten.</p>
<h2>Schritt 5: Erstellen des SVG-Elements</h2>
<p>Wir werden zum Darstellen des Diagramms ein SVG-Element benutzen. SVG ist vergleichbar mit dem HTML5 Canvas, es werden geometrische Formen, Texte etc. auf einer Arbeitsfläche abgebildet. Ein wichtiger Unterschied zum Canvas: Mit den Elementen in einem SVG kann der Nutzer interagieren. Auf alle Formen können <code>EventListener</code> gelegt werden, so kann beispielsweise beim Klick auf einen Datenpunkt eine Aktion ausgeführt werden und die Elemente können auf das Überfahren mit der Maus reagieren.</p>
<p>Da SVG nicht Bestandteil des HTML-Standards ist, müssen die SVG-Elemente mit einem speziellen Namespace erstellt werden. Dazu gibt es die Funktion <code>document.createElementNS()</code>. Um das erstellen von SVG-Elementen einfacher zu gestalten, schreiben wir dazu eine Helfer-Funktion. Da alle Diagramm-Typen diese Funktion nutzen werden, platzieren wir sie im Chart-Prototyp:</p>
<pre><code>function Chart() {}

Chart.prototype = {
	createSvgElement: function(tag, attributes) {
		var element = document.createElementNS('http://www.w3.org/2000/svg', tag);
		for(var key in attributes) {
			element.setAttribute(key, attributes[key]);
		}
		return element;
	}
};</code></pre>
<p>Im Konstruktor von <code>LineChart</code> können wir diese Funktion nun nutzen, um das SVG-Element zu generieren:</p>
<pre><code>function LineChart(container, data) {
	Chart.call(this);
	
	var chart = this;
	
	chart.container = container;
	chart.labels = data.labels;
	chart.lines = data.lines;
	
	chart.svgElement = chart.createSvgElement('svg', {
		style: 'font-family: Arial, sans-serif;'
	});
	chart.container.appendChild(chart.svgElement);
}</code></pre>
<p>Zu Anfang jeder Funktion innerhalb eines Prototyps kopiere ich meistens den Verweis <code>this</code> in eine separate Variable. Innerhalb der Funktion kann ich dann, auch in anonymen Funktionen, wo <code>this</code> gegebenenfalls auf ein anderes Objekt verweist, trotzdem noch auf das Objekt zugreifen. In diesen paar Zeilen Code ist das noch nicht nötig, aber spätestens wenn wir <code>EventListener</code> definieren, wird das nützlich werden.</p>
<p>Außerdem speichere ich die übergebenen Parameter als Variablen des Objektes, damit ich in anderen Funktionen des Objektes wieder darauf zugreifen kann.</p>
<p>Schließlich nutze ich die Funktion <code>createSvgElement()</code>, um das SVG-Element zu generieren und füge dies dann dem übergebenen Container mit <code>appendChild()</code> an.</p>
<p>Noch hat das Element keine definierte Größe. Diese definieren wir separat. Das macht Sinn, denn wir werden das jedes mal wieder tun, wenn sich die Fenstergröße ändert, um den jeweils verfügbaren Platz auszunutzen. Wird das Fenster schmaler, zeigen wir zum Beispiel weniger Beschriftung der X-Achse.</p>
<p>Im Prototyp legen wir deshalb eine Funktion an, die für das Rendern des Diagramms zuständig ist.</p>
<pre><code>LineChart.prototype = Object.create(Chart.prototype, {
	constructor: {
		value: LineChart
	},
	render: {
		value: function() {
			
		}
	}
});</code></pre>
<p>Im Konstruktor binden wir dann diese Funktion an das Resize-Event des Browser-Fensters. Damit wir das Diagramm nicht erst sehen, wenn der Nutzer sein Fenster tatsächlich vergrößert oder verkleinert, rufen wir diese Funktion auch noch direkt auf.</p>
<pre><code>function LineChart(container, data) {
	Chart.call(this);
	
	var chart = this;
	
	chart.container = container;
	chart.labels = data.labels;
	chart.lines = data.lines;
	
	chart.svgElement = chart.createSvgElement('svg', {
		style: 'font-family: Arial, sans-serif;'
	});
	chart.container.appendChild(chart.svgElement);
	
	window.addEventListener('resize', function() {
		chart.render();
	});
	
	chart.render();
}</code></pre>
<p>In der <code>render()</code>-Funktion werden zunächst nur die Dimensionen der SVG-Grafik definiert:</p>
<pre><code>LineChart.prototype = Object.create(Chart.prototype, {
	constructor: {
		value: LineChart
	},
	render: {
		value: function() {
			var chart = this;
			
			chart.width = chart.container.clientWidth;
			chart.height = 500;
			
			chart.svgElement.setAttribute('width', chart.width);
			chart.svgElement.setAttribute('height', chart.height);
			chart.svgElement.setAttribute('viewBox', '0 0 ' + chart.width + ' ' + chart.height);
			
			while(chart.svgElement.childNodes.length) {
				chart.svgElement.removeChild(chart.svgElement.firstChild);
			}
		}
	}
});</code></pre>
<p>Außer der Breite und Höhe setzen wir hier noch das Attribut <code>viewBox</code>. Mit <code>viewBox</code> wird definiert, wie das Koordinatensystem innerhalb des SVG-Elements auf die angezeigte SVG-Grafik übertragen werden. Da wir hier die Dimensionen der ViewBox genau gleich den Dimensionen der Grafik selbst gesetzt haben, werden die Koordinaten 1:1 übertragen, das heißt, ein Pixel am Bildschirm entspricht einem Pixel im SVG-Koordinatensystem.</p>
<p>Bevor wir neue Elemente im SVG-Element darstellen, entfernen wir alle alten Elemente. Dazu entfernen wir mit <code>removeChild</code> einfach so lange das erste Element im SVG, bis keine Elemente mehr vorhanden sind. Nun können wir uns an das Rendering der Elemente machen.</p>
<h2>Schritt 6: Anzeigen der Legende</h2>
<p>Als erstes werden wir die Legende auf den Bildschirm bringen. Dazu müssen wir als erstes die unterschiedlichen Farben für die Datensätze definieren.</p>
<pre><code>Chart.prototype = {
	colors: ['#14568a', '#8a1616', '#dddb2f', '#1d7e07', '#2c4a4a'],
	createSvgElement: function(tag, attributes) {
		var element = document.createElementNS('http://www.w3.org/2000/svg', tag);
		for(var key in attributes) {
			element.setAttribute(key, attributes[key]);
		}
		return element;
	}
};</code></pre>
<p>Im Array <code>colors</code> habe ich fünf verschiedene Farbtöne definiert, mit denen ich meine Linien färben kann. Die Farben speichere ich im Chart-Prototype, weil ich diese Farben auch für andere Diagramm-Typen nutzen werde.</p>
<p>Im LineChart-Prototype fügen wir eine neue Funktion <code>renderLegend()</code> hinzu und rufen diese am Ende der <code>render()</code>-Funktion auf:</p>
<pre><code>render: {
	value: function() {
		var chart = this;
		
		chart.width = chart.container.clientWidth;
		chart.height = 500;
		
		chart.svgElement.setAttribute('width', chart.width);
		chart.svgElement.setAttribute('height', chart.height);
		chart.svgElement.setAttribute('viewBox', '0 0 ' + chart.width + ' ' + chart.height);
		
		while(chart.svgElement.childNodes.length) {
			chart.svgElement.removeChild(chart.svgElement.firstChild);
		}
		
		chart.renderLegend();
	}
},
renderLegend: {
	value: function() {
		var chart = this;
		
		chart.legend = chart.createSvgElement('g');
		chart.svgElement.appendChild(chart.legend);
	}
}</code></pre>
<p>Wir erstellen ein neues G-Element – G–Elemente sind quasi Gruppen, welche sich gemeinsam beeinflussen lassen – für die Legende hinzu. Wir brauchen keine ID oder ähnliches, um das Element später wieder zu referenzieren, denn wir speichern uns einen Verweis direkt im Diagramm-Objekt unter <code>chart.legend</code>. Das Element fügen wir dann zum SVG-Element hinzu.</p>
<p>Anschließend fügen wir für jede Linie im Diagramm ein neues G-Element zur Legende hinzu.</p>
<pre><code>var offset = 0;
for(var index = 0; index &lt; chart.lines.length; index++) {
	var legendItemElement = chart.createSvgElement('g', {
		transform: 'translate(' + offset + ', 0)'
	});
	chart.legend.appendChild(legendItemElement);
}</code></pre>
<p>Über das Attribut <code>transform</code> wenden wir eine Transformation auf die Gruppe an – in diesem Fall eine Verschiebung um <code>offset</code> auf der X-Achse und um 0 auf der Y-Achse. Mehr zu <code>offset</code> folgt gleich.</p>
<pre><code>var offset = 0;
for(var index = 0; index &lt; chart.lines.length; index++) {
	var legendItemElement = chart.createSvgElement('g', {
		transform: 'translate(' + offset + ', 0)'
	});
	chart.legend.appendChild(legendItemElement);
	
	var circleElement = chart.createSvgElement('circle', {
		cx: 10,
		cy: 8,
		r: 3.5,
		fill: chart.colors[index % chart.colors.length],
		stroke: 'none'
	});
	legendItemElement.appendChild(circleElement);
	
	var textElement = chart.createSvgElement('text', {
		x: 20,
		y: 13,
		'font-size': 14
	});
	legendItemElement.appendChild(textElement);
	
	textElement.textContent = chart.lines[index].title;
}</code></pre>
<p>Jeder Eintrag in der Legende wird aus einem Kreis in der Farbe des Datensatzes und aus dem Titel des Datensatzes bestehen. Also generieren wir ein Circle-Element für den Kreis und ein Text-Element für den Titel.</p>
<p>Den Mittelpunkt des Kreises legen wir über <code>cx</code> und <code>cy</code> fest. Den Radius bestimmt <code>r</code>. Über <code>fill</code> legen wir fest, in welcher Farbe der Kreis gefüllt werden soll.</p>
<p>Die Position des Text-Elements legen wir über die Attribute <code>x</code> und <code>y</code> fest. Die Y-Position meint hier übrigens die Grundlinie des Textes. Das Attribut <code>font-size</code> legt die Schriftgröße fest. Der Text für das Text-Element stammt ist <code>chart.lines[index].title</code>.</p>
<p>Aktuell werden alle G-Elemente in der Legende noch an der gleichen Stelle angezeigt. Damit die Elemente korrekt nebeneinander stehen, müssen wir in jedem Schleifen-Durchlauf den Wert von <code>offset</code> um die Breite des aktuellen Elements erhöhen, damit das nächste Element entsprechend nach rechts verschoben wird.</p>
<pre><code>offset += legendItemElement.getBBox().width + 20;</code></pre>
<p>Mit der Funktion <code>getBBox()</code> können wir die Bounding-Box eines Elements, bestehend aus X- und Y-Position, Breite und Höhe, bestimmen. Die Breite dieser Bounding-Box addieren wir zum Offset, zusammen mit zusätzlichen 20 Pixeln als Abstand zwischen den Elementen.</p>
<p>Unser gesamter LineChart-Prototype sieht nun so aus:</p>
<pre><code title="line-chart.js" collapse="true">function LineChart(container, data) {
	Chart.call(this);
	
	var chart = this;
	
	chart.container = container;
	chart.labels = data.labels;
	chart.lines = data.lines;
	
	chart.svgElement = chart.createSvgElement('svg', {
		style: 'font-family: Arial, sans-serif;'
	});
	chart.container.appendChild(chart.svgElement);
	
	window.addEventListener('resize', function() {
		chart.render();
	});
	
	chart.render();
}

LineChart.prototype = Object.create(Chart.prototype, {
	constructor: {
		value: LineChart
	},
	render: {
		value: function() {
			var chart = this;
			
			chart.width = chart.container.clientWidth;
			chart.height = 500;
			
			chart.svgElement.setAttribute('width', chart.width);
			chart.svgElement.setAttribute('height', chart.height);
			chart.svgElement.setAttribute('viewBox', '0 0 ' + chart.width + ' ' + chart.height);
			
			while(chart.svgElement.childNodes.length) {
				chart.svgElement.removeChild(chart.svgElement.firstChild);
			}
			
			chart.renderLegend();
		}
	},
	renderLegend: {
		value: function() {
			var chart = this;
			
			chart.legend = chart.createSvgElement('g');
			chart.svgElement.appendChild(chart.legend);
			
			var offset = 0;
			for(var index = 0; index &lt; chart.lines.length; index++) {
				var legendItemElement = chart.createSvgElement('g', {
					transform: 'translate(' + offset + ', 0)'
				});
				chart.legend.appendChild(legendItemElement);
				
				var circleElement = chart.createSvgElement('circle', {
					cx: 10,
					cy: 8,
					r: 3.5,
					fill: chart.colors[index % chart.colors.length],
					stroke: 'none'
				});
				legendItemElement.appendChild(circleElement);
				
				var textElement = chart.createSvgElement('text', {
					x: 20,
					y: 13,
					'font-size': 14
				});
				legendItemElement.appendChild(textElement);
				
				textElement.textContent = chart.lines[index].title;
				
				offset += legendItemElement.getBBox().width + 20;
			}
		}
	}
});</code></pre>
<p>Und so sieht unser Diagramm bis jetzt aus:</p>
<div><iframe width="100%" height="600" src="/wp-content/examples/svg-charts/svg-charts-1-1/index.html" frameborder="0"></iframe></div>
<h2>Schritt 7: Achsen, Abstände und Skalierung</h2>
<p>Im nächsten Schritt generieren wir die Achsen. Hier wird es ein wenig komplizierter, da wir hier auch die Skalierung der Achsen und die für die Beschriftungen nötigen Abstände nach links und nach unten berechnen müssen.</p>
<p>Zunächst erstellen wir im LineChart-Prototype eine neue Funktion:</p>
<pre><code>renderAxis: {
	value: function() {
		var chart = this;
	}
}</code></pre>
<p>Diese Funktion rufen wir auch in der Funktion <code>render()</code> auf.</p>
<pre><code>render: {
	value: function() {
		var chart = this;
		
		chart.width = chart.container.clientWidth;
		chart.height = 500;
		
		chart.svgElement.setAttribute('width', chart.width);
		chart.svgElement.setAttribute('height', chart.height);
		chart.svgElement.setAttribute('viewBox', '0 0 ' + chart.width + ' ' + chart.height);
		
		while(chart.svgElement.childNodes.length) {
			chart.svgElement.removeChild(chart.svgElement.firstChild);
		}
		
		chart.renderLegend();
		chart.renderAxis();
	}
},</code></pre>
<p>In <code>chart.axis</code> werde ich alle berechneten Werte der Achsen speichern.</p>
<pre><code>renderAxis: {
	value: function() {
		var chart = this;
		
		chart.axis = {
			x: {
				nthLabel: 1
			},
			y: {
				maximum: 1,
				step: 1
			}
		};
	}
}</code></pre>
<p>Auf der X-Achse sollen alle verfügbaren Label aus <code>chart.labels</code> angezeigt werden. Abhängig von der Zahl der Labels und der Größe des Diagramms kann es aber vorkommen, dass nicht genug Platz für alle Beschriftungen verfügbar ist. In <code>chart.axis.x.nthLabel</code> lege ich daher fest, welche Label – angefangen beim ersten – angezeigt werden sollen. Nach Möglichkeit sollten natürlich alle Label angezeigt werden. Deshalb lege ich <code>nthLabel</code> zunächst auf 1 fest – es wird also jedes erste Label angezeigt. Wenn nicht genug Platz verfügbar ist, wird der Wert erhöht. Steht <code>nthLabel</code> zum Beispiel auf 3, wird nur jedes dritte Label angezeigt.</p>
<p>Für die Y-Achse legen wir zu Beginn zwei Variablen an. Die Variable <code>maximum</code> soll den Maximal-Wert beinhalten, der auf der Y-Achse abgebildet werden soll. In der Variablen <code>step</code> ist gespeichert, wie groß der Abstand der Beschriftungen auf der Y-Achse sein soll.</p>
<p>Momentan tragen diese drei Variablen noch alle den Wert 1. Hier werden wir im Folgenden die tatsächlichen Werte berechnen.</p>
<pre><code>for(var lineIndex = 0; lineIndex &lt; chart.lines.length; lineIndex++) {
	for(var datumIndex = 0; datumIndex &lt; chart.lines[lineIndex].data.length; datumIndex++) {
		chart.axis.y.maximum = Math.max(chart.axis.y.maximum, chart.lines[lineIndex].data[datumIndex
]);
	}
}</code></pre>
<p>Wir gehen als erstes alle Datenpunkte aller Datensätze durch und speichern das Maximum in der Variable <code>chart.axis.y.maximum</code>.</p>
<p>Als nächstes ermitteln wir anhand des Maximums eine sinnvolle Einteilung der Y-Achse. Mein erster Ansatz war, einfach das Maximum durch fünf zu teilen. Das führt aber leider zu unschön krummen Beschriftungen wie 478, wo beispielsweise 500 viel sinnvoller wäre.</p>
<pre><code>while(chart.axis.y.step * 7 &lt; chart.axis.y.maximum) {
	chart.axis.y.step *= 10;
}
if(chart.axis.y.step % 2 === 0 &amp;&amp; chart.axis.y.step * 3.5 &gt; chart.axis.y.maximum) {
	chart.axis.y.step /= 2;
}</code></pre>
<p>Schließlich habe ich das Problem so gelöst: <code>step</code> beginnt mit einem Wert von 1. Solange mit dem aktuellen Wert von <code>step</code> das Maximum nicht mit sieben Beschriftungen auf der Y-Achse erreicht werden kann, wird <code>step</code> mit 10 multipliziert. So landen wir schonmal bei runden Beschriftungen, die immer ein Vielfaches von 1, 10, 100, 1.000 usw. sind. Anschließend prüfen wir noch, ob das Maximum auch mit der halben Schritt-Größe erreicht werden kann – denn Beschriftungen wie 50, 150 oder 2.500 wären ja auch noch rund. Gegebenenfalls können wir deshalb <code>step</code> halbieren.</p>
<pre><code>chart.axis.y.maximum = Math.ceil(chart.axis.y.maximum / chart.axis.y.step) * chart.axis.y.step;</code></pre>
<p>Hier legen wir noch das tatsächliche Maximum des Diagramms anhand der eben ermittelten Schritt-Größe fest.</p>
<p>Als nächstes müssen wir ermitteln, wie viel Abstand die Achsen nach links und nach unten benötigen, damit die Beschriftungen genügend Platz haben. Dazu benötigen wir als erstes eine Hilfsfunktion, die Zahlen mit dem korrekten Tausender-Trennzeichen ausstattet.</p>
<pre><code>function Chart() {}

Chart.prototype = {
	colors: ['#14568a', '#8a1616', '#dddb2f', '#1d7e07', '#2c4a4a'],
	createSvgElement: function(tag, attributes) {
		var element = document.createElementNS('http://www.w3.org/2000/svg', tag);
		for(var key in attributes) {
			element.setAttribute(key, attributes[key]);
		}
		return element;
	},
	formatNumber: function(number) {
		var string = '';
		
		number = number + '';
		while(number.length &gt; 3) {
			string = '.' + number.substr(number.length - 3) + string;
			number = number.substr(0, number.length - 3);
		}
		string = number + string;
		
		return string;
	}
};</code></pre>
<p>Da auch diese Funktion in allen Diagramm-Typen Anwendung findet, erstellen wir die Funktion <code>formatNumber()</code> im Chart-Prototyp.</p>
<p>Die Erstellung der Labels für die Achsen lagern wir auch in eigene Funktionen innerhalb des LineChart-Prototyps aus:</p>
<pre><code>renderXAxisLabel: {
	value: function(label) {
		var chart = this;
		
		var labelElement = chart.createSvgElement('g');
		
		var lineElement = chart.createSvgElement('line', {
			x1: 0,
			y1: 0,
			x2: 0,
			y2: 5,
			stroke: '#c0c0c0'
		});
		labelElement.appendChild(lineElement);
		
		var textElement = chart.createSvgElement('text', {
			x: -15,
			y: 7,
			'font-size': 11,
			'text-anchor': 'end',
			transform: 'rotate(-45 0 0)'
		});
		textElement.textContent = label;
		labelElement.appendChild(textElement);
		
		return labelElement;
	}
},
renderYAxisLabel: {
	value: function(label) {
		var chart = this;
		
		var labelElement = chart.createSvgElement('g');
		
		var lineElement = chart.createSvgElement('line', {
			x1: 0,
			y1: 0,
			x2: chart.width,
			y2: 0,
			stroke: '#c0c0c0'
		});
		labelElement.appendChild(lineElement);
		
		var textElement = chart.createSvgElement('text', {
			x: -8,
			y: 4,
			'font-size': 11,
			'text-anchor': 'end'
		});
		textElement.textContent = label;
		labelElement.appendChild(textElement);
		
		return labelElement;
	}
}</code></pre>
<p>Beide Funktionen erstellen ein neues G-Element, legen in dem Element jeweils eine Linie und einen Text an und geben das G-Element dann zurück. Die Funktionen ordnen die Linien und Texte unterschiedlich an, für die Y-Achse werden horizontale Linien über das gesamte Diagramm gezogen, bei der X-Achse sind die Linien nur kurz und vertikal und markieren auf der X-Achse die Position des Labels.</p>
<p>Beide Texte werden mit dem Attribut <code>text-anchor="end"</code> ausgestattet, was zur Folge hat, dass die Position des Textes sich am Ende des Textes orientiert. Die Texte sind also rechtsbündig und enden jeweils an dem durch <code>x</code> und <code>y</code> definierten Punkt.</p>
<p>Der Text an der X-Achse wird zudem noch um 45° rotiert, sodass die Label diagonal nach oben laufen.</p>
<p>Die G-Elemente, welche diese Funktionen zurückgeben, liegen dann alle im Ursprung. Nachdem wir den benötigten Platz ermittelt haben, können wir jedes G-Element an die korrekte Position transformieren.</p>
<p>Aber zunächst generieren wir die Achsen und Label. Dazu fügen wir in der Funktion <code>renderAxis()</code> – direkt nach der Ermittlung von <code>chart.axis.y.step</code> und des neuen Maximums – folgenden Code hinzu:</p>
<pre><code>chart.axis.x.element = chart.createSvgElement('g');
chart.svgElement.appendChild(chart.axis.x.element);

chart.axis.y.element = chart.createSvgElement('g');
chart.svgElement.appendChild(chart.axis.y.element);

for(var index = 0; index &lt; chart.labels.length; index++) {
	chart.axis.x.element.appendChild(chart.renderXAxisLabel(chart.labels[index]));
}

for(var y = 0; y &lt;= chart.axis.y.maximum; y += chart.axis.y.step) {
	chart.axis.y.element.appendChild(chart.renderYAxisLabel(chart.formatNumber(y)));
}</code></pre>
<p>Dank der eben definierten Hilfs-Funktionen ist dieser Code relativ simpel. Wir legen zwei neue G-Elemente – eins für jede Achse – an und speichern diese in dem jeweiligen Achsen-Objekt in <code>chart.axis.x.element</code> beziehungsweise <code>chart.axis.y.element</code>. Diese Elemente fügen wir dann dem SVG-Element hinzu.</p>
<p>Anschließen fügen wir an die X-Achse alle Labels aus <code>chart.labels</code> an. Die Label für die Y-Achse bestimmen wir aus <code>chart.axis.y.step</code> und <code>chart.axis.y.maximum</code>.</p>
<p>Wie gesagt sind die Achsen und Beschriftungen jetzt noch nicht am richtigen Platz, aber mit <code>getBBox()</code> können wir jetzt die Größe der einzelnen Elemente ermitteln.</p>
<pre><code>chart.padding = {
	top: chart.legend.getBBox().height + 20,
	left: -1 * Math.min(chart.axis.x.element.getBBox().x + 20, chart.axis.y.element.getBBox().x - 8),
	bottom: chart.axis.x.element.getBBox().height
};</code></pre>
<p>In <code>chart.padding</code> können wir nun die benötigten Abstände speichern. Der Abstand nach oben hängt von der Größe der Legende ab. Hier geben wir zusätzlich 20 Pixel Abstand zur Legende.</p>
<p>Der Abstand nach links errechnet sich aus der aktuellen Position der gerade erstellten Achsen und Beschrifungen. Da sich die Texte rechts anordnen, ragen sie momentan, wo die Achsen noch alle in der oberen linken Ecke liegen, nach links aus dem SVG-Element heraus. <code>getBBox().x</code> liefert uns hier also einen negativen Wert als X-Position. Mit <code>Math.min</code> ermitteln wir, welches Element am weitesten aus dem Bild herausragt und nutzen diesen Wert – mit positivem Vorzeichen – als linken Abstand.</p>
<p>Der Abstand nach unten ist einfach die Höhe der X-Achse samt Beschriftung.</p>
<p>Jetzt wissen wir genau, wie groß das Diagramm sein wird. Als nächstes können wir nun die Achsen und Beschriftungen korrekt positionieren.</p>
<pre><code>chart.axis.x.element.setAttribute('transform', 'translate(0, ' + (chart.height - chart.padding.bottom) + ')');
chart.axis.x.stepSize = chart.labels.length &gt; 1 ? (chart.width - chart.padding.left - 40) / (chart.labels.length - 1) : 20;</code></pre>
<p>Zuerst transformieren wir die X-Achse an die korrekte Position. Da wir jetzt die Breite des Diagramms bestimmen können, können wir auch <code>stepSize</code> – den Abstand der Beschriftungen auf der X-Achse – errechnen. Hier gibt es einen Sonderfall: Wir können den Abstand natürlich nur bestimmen, wenn es mindestens zwei Beschriftungen gibt. Sind weniger als zwei Beschriftungen vorhanden, definieren wir den Abstand einfach als 20 Pixel.</p>
<p>Als nächstes stellen wir sicher, dass der Abstand zwischen den Beschriftungen nie kleiner als 20 Pixel ist.</p>
<pre><code>while(chart.axis.x.stepSize * chart.axis.x.nthLabel &lt; 20) {
	chart.axis.x.nthLabel++;
}</code></pre>
<p>Dazu erhöhen wir <code>nthLabel</code> so lange, bis der Abstand ausreicht. Gegebenfalls zeigen wir also nur jede zweite, dritte, vierte Beschriftung usw.</p>
<p>Wenn wir nicht jede Beschriftung anzeigen können, müssen wir die nicht angezeigten Beschriftungen nun löschen:</p>
<pre><code>if(chart.axis.x.nthLabel &gt; 1) {
	for(var index = chart.axis.x.element.childNodes.length - 1; index &gt; 0; index--) {
		if(index % chart.axis.x.nthLabel) {
			chart.axis.x.element.removeChild(chart.axis.x.element.childNodes[index]);
		}
	}
}</code></pre>
<p>Schließlich können wir die verbleibenden Beschriftungen an die korrekte Position transformieren:</p>
<pre><code>for(var index = 0; index &lt; chart.axis.x.element.childNodes.length; index++) {
	chart.axis.x.element.childNodes[index].setAttribute('transform', 'translate(' + (chart.padding.left + 20 + chart.axis.x.nthLabel * index * chart.axis.x.stepSize) + ', 0)');
}</code></pre>
<p>Die Beschriftungen der X-Achse sind nun an Ort und Stelle:</p>
<div><iframe width="100%" height="600" src="/wp-content/examples/svg-charts/svg-charts-1-2/index.html" frameborder="0"></iframe></div>
<p>Auch die Y-Achse können wir nun korrekt transformieren:</p>
<pre><code>chart.axis.y.element.setAttribute('transform', 'translate(' + chart.padding.left + ', 0)');
chart.axis.y.stepSize = (500 - chart.padding.bottom - chart.padding.top) / (chart.axis.y.maximum / chart.axis.y.step);
for(var index = 0; index &lt; chart.axis.y.element.childNodes.length; index++) {
	chart.axis.y.element.childNodes[index].setAttribute('transform', 'translate(0, ' + (500 - chart.padding.bottom - index * chart.axis.y.stepSize) + ')');
}</code></pre>
<p>Wieder wird zunächst die gesamte Achse transformiert, anschließend berechnen wir auch hier den Abstand zwischen den Beschriftungen und korrigieren dann die Position jeder Beschriftung.</p>
<p>Damit sind jetzt beide Achsen korrekt positioniert:</p>
<div><iframe width="100%" height="600" src="/wp-content/examples/svg-charts/svg-charts-1-3/index.html" frameborder="0"></iframe></div>
<h2>Schritt 8: Punkte und Linien</h2>
<p>Abschließend müssen wir nur noch die tatsächlichen Datenpunkte im Diagramm einfügen. Auch dafür erstellen wir eine neue Funktion im LineChart-Prototypen:</p>
<pre><code>renderLines: {
	value: function() {
		var chart = this;
	}
}</code></pre>
<p>Auch diese Funktion rufen wir in der Funktion <code>render()</code> auf:</p>
<pre><code>render: {
	value: function() {
		var chart = this;
		
		chart.width = chart.container.clientWidth;
		chart.height = 500;
		
		chart.svgElement.setAttribute('width', chart.width);
		chart.svgElement.setAttribute('height', chart.height);
		chart.svgElement.setAttribute('viewBox', '0 0 ' + chart.width + ' ' + chart.height);
		
		while(chart.svgElement.childNodes.length) {
			chart.svgElement.removeChild(chart.svgElement.firstChild);
		}
		
		chart.renderLegend();
		chart.renderAxis();
		chart.renderLines();
	}
},</code></pre>
<p>In der Funktion <code>renderLines()</code> legen wir nun für jeden Datensatz eine eigene Gruppe an:</p>
<pre><code>renderLines: {
	value: function() {
		var chart = this;
		
		for(var lineIndex = 0; lineIndex &lt; chart.lines.length; lineIndex++) {
			chart.lines[lineIndex].element = chart.createSvgElement('g', {
				transform: 'translate(' + (chart.padding.left + 20) + ', ' + (500 - chart.padding.bottom) + ')'
			});
			chart.svgElement.appendChild(chart.lines[lineIndex].element);
		}
	}
}</code></pre>
<p>Nun zur Darstellung der Daten: Erstens soll jeder Datenpunkt mit einem Punkt dargestellt werden, zweitens sollen alle Punkte mit einer Linie verbunden werden.</p>
<pre><code>renderLines: {
	value: function() {
		var chart = this;
		
		for(var lineIndex = 0; lineIndex &lt; chart.lines.length; lineIndex++) {
			chart.lines[lineIndex].element = chart.createSvgElement('g', {
				transform: 'translate(' + (chart.padding.left + 20) + ', ' + (500 - chart.padding.bottom) + ')'
			});
			chart.svgElement.appendChild(chart.lines[lineIndex].element);
			
			var points = [];
			chart.lines[lineIndex].circles = [];
			
			for(var datumIndex = 0; datumIndex &lt; chart.lines[lineIndex].data.length; datumIndex++) {
				var point = {
					x: datumIndex * chart.axis.x.stepSize,
					y: -1 * chart.lines[lineIndex].data[datumIndex] * (chart.axis.y.stepSize / chart.axis.y.step),
				};
				points.push(point.x + ',' + point.y);
				
				
			}
		}
	}
}</code></pre>
<p>Wir legen deshalb gleich zwei Arrays an, <code>points</code> für die Punkte der Linie, <code>circles</code> für die Kreis-Elemente, mit denen wir die Punkte darstellen.</p>
<p>Aus <code>stepSize</code> beider Achsen lässt sich unkompliziert die tatsächliche Position eines Punktes feststellen. In <code>points</code> speichern wir beide Koordinaten als String, diese Koordinaten werden wir später einem Polyline-Element übergeben.</p>
<pre><code>for(var datumIndex = 0; datumIndex &lt; chart.lines[lineIndex].data.length; datumIndex++) {
	var point = {
		x: datumIndex * chart.axis.x.stepSize,
		y: -1 * chart.lines[lineIndex].data[datumIndex] * (chart.axis.y.stepSize / chart.axis.y.step),
	};
	points.push(point.x + ',' + point.y);
	
	if(chart.axis.x.stepSize &gt;= 10) {
		var circleElement = chart.createSvgElement('circle', {
			cx: point.x,
			cy: point.y,
			r: 3.5,
			fill: chart.colors[lineIndex % chart.colors.length],
			stroke: 'none'
		});
		chart.lines[lineIndex].element.appendChild(circleElement);
		chart.lines[lineIndex].circles.push(circleElement);
	}
}</code></pre>
<p>Den Punkt selbst zeigen wir nur, wenn dafür genug Platz ist – sprich, wenn <code>stepSize</code> größer oder gleich 10 ist.</p>
<p>In diesem Fall legen wir dann ein neues Circle-Element in der Farbe des Datensatzes an und fügen es in die Gruppe und in das Array <code>circles</code> ein.</p>
<p>Nachdem wir alle Punkte erstellt und die Koordinaten in <code>points</code> gesammelt haben, können wir nun abschließend noch die Linie hinzufügen:</p>
<pre><code>var lineElement = chart.createSvgElement('polyline', {
	points: points.join(' '),
	stroke: chart.colors[lineIndex % chart.colors.length],
	'stroke-width': 2,
	fill: 'none'
});
chart.lines[lineIndex].element.appendChild(lineElement);
chart.lines[lineIndex].line = lineElement;</code></pre>
<p>Da wir in <code>points</code> schon die X- und Y-Koordinaten mit Kommata getrennt gesammelt haben, müssen wir das Array hier nur noch mit <code>join(' ')</code> zusammenführen. Auch die Linie wird in das SVG eingefügt.</p>
<h2>Fertiges Diagramm</h2>
<p>Damit kommen wir beim Endresultat an:</p>
<div><iframe width="100%" height="600" src="/wp-content/examples/svg-charts/svg-charts-1/index.html" frameborder="0"></iframe></div>
<p>Und hier zum Abschluss noch einmal der gesamte Code:</p>
<pre><code title="index.html" collapse="true">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;de&quot;&gt;
&lt;head&gt;
	&lt;meta charset=&quot;UTF-8&quot;&gt;
	&lt;title&gt;Lininen-Diagramm&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
	&lt;div id=&quot;chart&quot;&gt;
	
	&lt;/div&gt;
	
	&lt;script type=&quot;text/javascript&quot; src=&quot;js/chart.js&quot;&gt;&lt;/script&gt;
	&lt;script type=&quot;text/javascript&quot; src=&quot;js/line-chart.js&quot;&gt;&lt;/script&gt;
	&lt;script type=&quot;text/javascript&quot; src=&quot;js/main.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<pre><code title="chart.js" collapse="true">function Chart() {}

Chart.prototype = {
	colors: ['#14568a', '#8a1616', '#dddb2f', '#1d7e07', '#2c4a4a'],
	createSvgElement: function(tag, attributes) {
		var element = document.createElementNS('http://www.w3.org/2000/svg', tag);
		for(var key in attributes) {
			element.setAttribute(key, attributes[key]);
		}
		return element;
	},
	formatNumber: function(number) {
		var string = '';
		
		number = number + '';
		while(number.length &gt; 3) {
			string = '.' + number.substr(number.length - 3) + string;
			number = number.substr(0, number.length - 3);
		}
		string = number + string;
		
		return string;
	}
};</code></pre>
<pre><code title="line-chart.js" collapse="true">function LineChart(container, data) {
	Chart.call(this);
	
	var chart = this;
	
	chart.container = container;
	chart.labels = data.labels;
	chart.lines = data.lines;
	
	chart.svgElement = chart.createSvgElement('svg', {
		style: 'font-family: Arial, sans-serif;'
	});
	chart.container.appendChild(chart.svgElement);
	
	window.addEventListener('resize', function() {
		chart.render();
	});
	
	chart.render();
}

LineChart.prototype = Object.create(Chart.prototype, {
	constructor: {
		value: LineChart
	},
	render: {
		value: function() {
			var chart = this;
			
			chart.width = chart.container.clientWidth;
			chart.height = 500;
			
			chart.svgElement.setAttribute('width', chart.width);
			chart.svgElement.setAttribute('height', chart.height);
			chart.svgElement.setAttribute('viewBox', '0 0 ' + chart.width + ' ' + chart.height);
			
			while(chart.svgElement.childNodes.length) {
				chart.svgElement.removeChild(chart.svgElement.firstChild);
			}
			
			chart.renderLegend();
			chart.renderAxis();
			chart.renderLines();
		}
	},
	renderLegend: {
		value: function() {
			var chart = this;
			
			chart.legend = chart.createSvgElement('g');
			chart.svgElement.appendChild(chart.legend);
			
			var offset = 0;
			for(var index = 0; index &lt; chart.lines.length; index++) {
				var legendItemElement = chart.createSvgElement('g', {
					transform: 'translate(' + offset + ', 0)'
				});
				chart.legend.appendChild(legendItemElement);
				
				var circleElement = chart.createSvgElement('circle', {
					cx: 10,
					cy: 8,
					r: 3.5,
					fill: chart.colors[index % chart.colors.length],
					stroke: 'none'
				});
				legendItemElement.appendChild(circleElement);
				
				var textElement = chart.createSvgElement('text', {
					x: 20,
					y: 13,
					'font-size': 14
				});
				legendItemElement.appendChild(textElement);
				
				textElement.textContent = chart.lines[index].title;
				
				offset += legendItemElement.getBBox().width + 20;
			}
		}
	},
	renderAxis: {
		value: function() {
			var chart = this;
			
			chart.axis = {
				x: {
					nthLabel: 1
				},
				y: {
					maximum: 1,
					step: 1
				}
			};
			
			for(var lineIndex = 0; lineIndex &lt; chart.lines.length; lineIndex++) {
				for(var datumIndex = 0; datumIndex &lt; chart.lines[lineIndex].data.length; datumIndex++) {
					chart.axis.y.maximum = Math.max(chart.axis.y.maximum, chart.lines[lineIndex].data[datumIndex]);
				}
			}
			
			while(chart.axis.y.step * 7 &lt; chart.axis.y.maximum) {
				chart.axis.y.step *= 10;
			}
			if(chart.axis.y.step % 2 === 0 &amp;&amp; chart.axis.y.step * 3.5 &gt; chart.axis.y.maximum) {
				chart.axis.y.step /= 2;
			}
			
			chart.axis.y.maximum = Math.ceil(chart.axis.y.maximum / chart.axis.y.step) * chart.axis.y.step;
			
			chart.axis.x.element = chart.createSvgElement('g');
			chart.svgElement.appendChild(chart.axis.x.element);
			
			chart.axis.y.element = chart.createSvgElement('g');
			chart.svgElement.appendChild(chart.axis.y.element);
			
			for(var index = 0; index &lt; chart.labels.length; index++) {
				chart.axis.x.element.appendChild(chart.renderXAxisLabel(chart.labels[index]));
			}
			
			for(var y = 0; y &lt;= chart.axis.y.maximum; y += chart.axis.y.step) {
				chart.axis.y.element.appendChild(chart.renderYAxisLabel(chart.formatNumber(y)));
			}
			
			chart.padding = {
				top: chart.legend.getBBox().height + 20,
				left: -1 * Math.min(chart.axis.x.element.getBBox().x + 20, chart.axis.y.element.getBBox().x - 8),
				bottom: chart.axis.x.element.getBBox().height
			};
			
			chart.axis.x.element.setAttribute('transform', 'translate(0, ' + (chart.height - chart.padding.bottom) + ')');
			chart.axis.x.stepSize = chart.labels.length &gt; 1 ? (chart.width - chart.padding.left - 40) / (chart.labels.length - 1) : 20;
			
			while(chart.axis.x.stepSize * chart.axis.x.nthLabel &lt; 20) {
				chart.axis.x.nthLabel++;
			}
			
			if(chart.axis.x.nthLabel &gt; 1) {
				for(var index = chart.axis.x.element.childNodes.length - 1; index &gt; 0; index--) {
					if(index % chart.axis.x.nthLabel) {
						chart.axis.x.element.removeChild(chart.axis.x.element.childNodes[index]);
					}
				}
			}
			
			for(var index = 0; index &lt; chart.axis.x.element.childNodes.length; index++) {
				chart.axis.x.element.childNodes[index].setAttribute('transform', 'translate(' + (chart.padding.left + 20 + chart.axis.x.nthLabel * index * chart.axis.x.stepSize) + ', 0)');
			}
			
			chart.axis.y.element.setAttribute('transform', 'translate(' + chart.padding.left + ', 0)');
			chart.axis.y.stepSize = (500 - chart.padding.bottom - chart.padding.top) / (chart.axis.y.maximum / chart.axis.y.step);
			for(var index = 0; index &lt; chart.axis.y.element.childNodes.length; index++) {
				chart.axis.y.element.childNodes[index].setAttribute('transform', 'translate(0, ' + (500 - chart.padding.bottom - index * chart.axis.y.stepSize) + ')');
			}
		}
	},
	renderXAxisLabel: {
		value: function(label) {
			var chart = this;
			
			var labelElement = chart.createSvgElement('g');
			
			var lineElement = chart.createSvgElement('line', {
				x1: 0,
				y1: 0,
				x2: 0,
				y2: 5,
				stroke: '#c0c0c0'
			});
			labelElement.appendChild(lineElement);
			
			var textElement = chart.createSvgElement('text', {
				x: -15,
				y: 7,
				'font-size': 11,
				'text-anchor': 'end',
				transform: 'rotate(-45 0 0)'
			});
			textElement.textContent = label;
			labelElement.appendChild(textElement);
			
			return labelElement;
		}
	},
	renderYAxisLabel: {
		value: function(label) {
			var chart = this;
			
			var labelElement = chart.createSvgElement('g');
			
			var lineElement = chart.createSvgElement('line', {
				x1: 0,
				y1: 0,
				x2: chart.width,
				y2: 0,
				stroke: '#c0c0c0'
			});
			labelElement.appendChild(lineElement);
			
			var textElement = chart.createSvgElement('text', {
				x: -8,
				y: 4,
				'font-size': 11,
				'text-anchor': 'end'
			});
			textElement.textContent = label;
			labelElement.appendChild(textElement);
			
			return labelElement;
		}
	},
	renderLines: {
		value: function() {
			var chart = this;
			
			for(var lineIndex = 0; lineIndex &lt; chart.lines.length; lineIndex++) {
				chart.lines[lineIndex].element = chart.createSvgElement('g', {
					transform: 'translate(' + (chart.padding.left + 20) + ', ' + (500 - chart.padding.bottom) + ')'
				});
				chart.svgElement.appendChild(chart.lines[lineIndex].element);
				
				var points = [];
				chart.lines[lineIndex].circles = [];
				
				for(var datumIndex = 0; datumIndex &lt; chart.lines[lineIndex].data.length; datumIndex++) {
					var point = {
						x: datumIndex * chart.axis.x.stepSize,
						y: -1 * chart.lines[lineIndex].data[datumIndex] * (chart.axis.y.stepSize / chart.axis.y.step),
					};
					points.push(point.x + ',' + point.y);
					
					if(chart.axis.x.stepSize &gt;= 10) {
						var circleElement = chart.createSvgElement('circle', {
							cx: point.x,
							cy: point.y,
							r: 3.5,
							fill: chart.colors[lineIndex % chart.colors.length],
							stroke: 'none'
						});
						chart.lines[lineIndex].element.appendChild(circleElement);
						chart.lines[lineIndex].circles.push(circleElement);
					}
				}
				
				var lineElement = chart.createSvgElement('polyline', {
					points: points.join(' '),
					stroke: chart.colors[lineIndex % chart.colors.length],
					'stroke-width': 2,
					fill: 'none'
				});
				chart.lines[lineIndex].element.appendChild(lineElement);
				chart.lines[lineIndex].line = lineElement;
			}
		}
	}
});</code></pre>
<pre><code title="main.js" collapse="true">var weekdays = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
var months = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
var from = new Date(2015, 8, 21-28, 0, 0, 0);
var to = new Date(2015, 8, 21, 0, 0, 0);

var data = {
	labels: [],
	lines: [{
		title: 'Messwerte 1',
		data: []
	}, {
		title: 'Messwerte 2',
		data: []
	}]
};

while(from &lt;= to) {
	data.labels.push(weekdays[from.getDay()] + ', ' + from.getDate() + '. ' + months[from.getMonth()] + ' ' + from.getFullYear());
	
	for(var lineIndex = 0; lineIndex &lt; data.lines.length; lineIndex++) {
		data.lines[lineIndex].data.push(Math.round(lineIndex * 1000 + 500 + (Math.random() + Math.random()) * 500));
	}
	
	from.setDate(from.getDate() + 1);
}

new LineChart(document.getElementById('chart'), data);</code></pre>
<p>Noch ist das Diagramm statisch und reagiert nicht auf den Nutzer. Im nächsten Teil der Artikelserie wirst Du erfahren, wie Du dem Diagramm EventListener hinzufügen kannst und so auf Nutzer-Interaktionen reagieren kannst.</p>
]]></content:encoded>
			<wfw:commentRss>https://advitum.de/2015/09/das-linien-diagramm-interaktive-svg-diagramme-im-web/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>WordPress ist King – zumindest für Blogs</title>
		<link>https://advitum.de/2015/08/wordpress-ist-king-zumindest-fuer-blogs/</link>
		<comments>https://advitum.de/2015/08/wordpress-ist-king-zumindest-fuer-blogs/#respond</comments>
		<pubDate>Mon, 31 Aug 2015 07:00:18 +0000</pubDate>
		<dc:creator><![CDATA[Lars Ebert]]></dc:creator>
				<category><![CDATA[Content Management Systeme]]></category>
		<category><![CDATA[Wordpress]]></category>
		<category><![CDATA[CMS]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[Usability]]></category>
		<category><![CDATA[User Interface]]></category>

		<guid isPermaLink="false">https://advitum.de/?p=2525</guid>
		<description><![CDATA[Laut WordPress.org nutzen über 60 Millionen Menschen WordPress für Ihre Webseite – kein Wunder! Denn in einem Bereich gehört es zu den Besten seiner Klasse: Blogging. Aber WordPress wird immer häufiger als Wunder-Allzweck-Waffe eingesetzt, wird zu einem Schweizer Taschenmesser der Webseite-Pflege aufgebohrt. Am Ende entsteht oft ein Backend, durch das kein Redakteur wirklich durchsteigt, 20 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Laut WordPress.org nutzen über 60 Millionen Menschen WordPress für Ihre Webseite – kein Wunder! Denn in einem Bereich gehört es zu den Besten seiner Klasse: Blogging. Aber WordPress wird immer häufiger als Wunder-Allzweck-Waffe eingesetzt, wird zu einem Schweizer Taschenmesser der Webseite-Pflege aufgebohrt.</p>
<p>Am Ende entsteht oft ein Backend, durch das kein Redakteur wirklich durchsteigt, 20 Plugins verlangsamen das ganze System und im Menü finden sich dutzende ungenutzter Menüpunkte. Das riesige Angebot von so genannten Multi-Purpose-Themes begünstigt diesen Trend noch.</p>
<p>In diesem Artikel zeige ich auf, für welche Webseiten WordPress eine gute Wahl ist – und welche Webseiten besser nicht auf WordPress bauen sollten.<span id="more-2525"></span></p>
<h2>Der perfekte Blog: Content steht im Vordergrund</h2>
<figure>
					<img src="https://advitum.de/wp-content/uploads/2015/09/wordpress-1.jpg" alt="Ein neuer Beitrag in einer frischen WordPress-Installation." width="1043" height="583" class="size-full wp-image-2532" srcset="https://advitum.de/wp-content/uploads/2015/09/wordpress-1.jpg 1043w, https://advitum.de/wp-content/uploads/2015/09/wordpress-1-350x196.jpg 350w, https://advitum.de/wp-content/uploads/2015/09/wordpress-1-960x537.jpg 960w" sizes="(max-width: 1043px) 100vw, 1043px" />
					<figcaption>Ein neuer Beitrag in einer frischen WordPress-Installation.</figcaption>
				</figure>
<p>Ein Blick auf die Eingabefelder in einer frischen WordPress-Installation zeigt sehr schnell die grundlegende Struktur von WordPress-Beiträgen auf: <strong>Titel</strong, <strong>Inhalt</strong>, <strong>Beitragsbild</strong> und <strong>Kategorien und Schlagworte</strong>. Der Fokus liegt auf linearem Inhalt – fast wie in einem Magazin.</p>
<p>Das sind auch die idealen Anwendungsfälle für WordPress: Blogs, Magazine und News. Wichtig ist, dass der Inhalt der einzelnen Artikel möglichst simpel aufgebaut ist. Eine Kombination aus Fließtext und Bildern. Je weniger kompliziert der Inhalt, desto besser geeignet ist WordPress.</p>
<h2>Beispiel: siegerbrauckmann.de</h2>
<p>Die Webseite <a href="http://www.siegerbrauckmann.de">siegerbrauckmann.de</a> setzt gezielt auf diesen Magazin-Stil, den WordPress so gut beherrscht. Anhand der Referenzen wird dies am deutlichsten.</p>
<figure>
					<img src="https://advitum.de/wp-content/uploads/2015/09/wordpress-2.jpg" alt="Die Seite »Referenzen« besteht aus dem Beitrags-Blog von WordPress." width="1094" height="1552" class="size-full wp-image-2533" srcset="https://advitum.de/wp-content/uploads/2015/09/wordpress-2.jpg 1094w, https://advitum.de/wp-content/uploads/2015/09/wordpress-2-247x350.jpg 247w, https://advitum.de/wp-content/uploads/2015/09/wordpress-2-677x960.jpg 677w" sizes="(max-width: 1094px) 100vw, 1094px" />
					<figcaption>Die Seite »Referenzen« besteht aus dem Beitrags-Blog von WordPress.</figcaption>
				</figure>
<p>Jede Referenz ist ein Beitrag, die Seite »Referenzen« ist der Beitrags-Blog von WordPress.</p>
<p>Anhand einer vollständigen Referenz wird die Stärke von WordPress noch deutlicher:</p>
<figure>
					<img src="https://advitum.de/wp-content/uploads/2015/09/wordpress-3.jpg" alt="Der Inhalt ist linear – Text und Bild wechseln sich ab." width="960" height="2928" class="size-full wp-image-2536" srcset="https://advitum.de/wp-content/uploads/2015/09/wordpress-3.jpg 960w, https://advitum.de/wp-content/uploads/2015/09/wordpress-3-115x350.jpg 115w, https://advitum.de/wp-content/uploads/2015/09/wordpress-3-315x960.jpg 315w" sizes="(max-width: 960px) 100vw, 960px" />
					<figcaption>Der Inhalt ist linear – Text und Bild wechseln sich ab.</figcaption>
				</figure>
<p>Anhand des Screenshots wird der lineare Aufbau des Inhaltes sichtbar. Selbst auf den »normalen« Inhalts-Seiten wird dieser Aufbau beibehalten:</p>
<figure>
					<img src="https://advitum.de/wp-content/uploads/2015/09/wordpress-5.jpg" alt="Jede Inhalts-Seite nutzt den linearen Aufbau der WordPress-Inhalte." width="1408" height="2089" class="size-full wp-image-2538" srcset="https://advitum.de/wp-content/uploads/2015/09/wordpress-5.jpg 1408w, https://advitum.de/wp-content/uploads/2015/09/wordpress-5-236x350.jpg 236w, https://advitum.de/wp-content/uploads/2015/09/wordpress-5-647x960.jpg 647w" sizes="(max-width: 1408px) 100vw, 1408px" />
					<figcaption>Jede Inhalts-Seite nutzt den linearen Aufbau der WordPress-Inhalte.</figcaption>
				</figure>
<p>Warum dieser lineare Aufbau so wichtig ist, zeigt dieser Screenshot (aus meinem eigenen WordPress-Backend):</p>
<figure>
					<img src="https://advitum.de/wp-content/uploads/2015/09/wordpress-4.jpg" alt="Der Editor in WordPress funktioniert am Besten mit linearen Inhalten." width="1017" height="1464" class="size-full wp-image-2537" srcset="https://advitum.de/wp-content/uploads/2015/09/wordpress-4.jpg 1017w, https://advitum.de/wp-content/uploads/2015/09/wordpress-4-243x350.jpg 243w, https://advitum.de/wp-content/uploads/2015/09/wordpress-4-667x960.jpg 667w" sizes="(max-width: 1017px) 100vw, 1017px" />
					<figcaption>Der Editor in WordPress funktioniert am Besten mit linearen Inhalten.</figcaption>
				</figure>
<p>Der WordPress-Editor ist für Blog-Artikel gemacht. Lineare Inhalte funktionieren hier am Besten. Zwar gibt es einige Plugins, die mehr Flexibilität versprechen, aber so richtig sauber funktionieren diese nicht.</p>
<p>Auch die Kategorien von WordPress werden für die Referenzen aufgegriffen. Auf der Startseite findet sich eine Übersicht über die Kategorien, in welche die Referenzen fallen:</p>
<figure>
					<img src="https://advitum.de/wp-content/uploads/2015/09/wordpress-6.jpg" alt="Die Referenzen werden mit Beschreibung angezeigt." width="1305" height="422" class="size-full wp-image-2540" srcset="https://advitum.de/wp-content/uploads/2015/09/wordpress-6.jpg 1305w, https://advitum.de/wp-content/uploads/2015/09/wordpress-6-350x113.jpg 350w, https://advitum.de/wp-content/uploads/2015/09/wordpress-6-960x310.jpg 960w" sizes="(max-width: 1305px) 100vw, 1305px" />
					<figcaption>Die Referenzen werden mit Beschreibung angezeigt.</figcaption>
				</figure>
<p>Die Webseite konzentriert sich insgesamt auf die Stärken von WordPress und umschifft so die Schwächen. Ein schönes Beispiel für gute WordPress-Nutzung.</p>
<h2>Gib einer Person einen Hammer und sie sieht überall nur noch Nägel</h2>
<p>WordPress kann Blogging – für alles, was darüber hinaus geht, ist WordPress allerdings meist die falsche Wahl.</p>
<p>Wie oben erwähnt, bohren viele fertige Themes WordPress an viele Stellen auf, um mehr Funktionalität anzubieten. Dabei geht häufig der Fokus auf die Stärken von WordPress verloren, das Backend wird unübersichtlich und Plugins greifen nicht sauber ineinander.</p>
<p>Im folgenden nun typische Funktionen, welche <strong>nicht</strong> mit WordPress umgesetzt werden sollten.</p>
<h3>Mehrsprachigkeit</h3>
<p>Viele Content Management Systeme bringen tolle Funktionen für die Mehrsprachigkeit mit sich – Wordpress gehört nicht dazu. Einige Plugins rüsten die Mehrsprachigkeit nach. Sauber funktionieren sie alle nicht. Spätestens, wenn die Übersetzungen mit anderen Plugins ineinander greifen sollen, entstehen oft Baustellen. Warum mit Gewalt die Mehrsprachigkeit nachrüsten, wenn andere Systeme das viel besser können?</p>
<h3>Komplexe Seiten-Inhalte</h3>
<p>Die meisten Multi-Purpose-Themes bringen einen eigenen Inhalts-Editor mit. Inhalte werden komplexer, mehrspaltig, dynamisch. Der lineare Aufbau der Inhalte, wie WordPress ihn am Besten beherrscht, verschwindet.</p>
<p>Auch das bringt häufig Probleme mit sich: Stile werden oft direkt im Quellcode angewendet, der Redakteur muss die Inhalte von Hand setzen, viele WordPress-Funktionen (zum Beispiel der automatische Text-Auszug) funktionieren nicht mehr richtig. Die Code- und Seiten-Qualität leidet oft darunter.</p>
<p>Wenn die Inhalte, die auf der Webseite erscheinen sollen, sich nicht größtenteils im Standard-Editor von WordPress darstellen lassen, ist WordPress vermutlich nicht die richtige Wahl für diese Webseite.</p>
<h3>Online-Shop und andere Komplexitäten</h3>
<p>Auch für komplett neue Funktionen wie Online-Shops, Nutzerforen und vieles mehr gibt es diverse Plugins. Auch hier funktioniert (grade in Kombination mit anderen Plugins) nicht immer alles reibungslos.</p>
<p>WordPress als Online-Shop? Kann man machen, muss man aber nicht. Besser wäre es, einfach ein passendes System zu wählen.</p>
<h3>Gar keine News oder Beträge</h3>
<p>Auf manchen WordPress-Seiten finden sich gar keine Beiträge, lediglich »normale« Seiten. Dies ist vermutlich die häufigste Kuriosität der WordPress-Nutzungen. WordPress kann eben genau eine Sache gut: Blogging. Warum benutze ich dann für meine Seite WordPress, füge dutzende Plugins und Funktionen hinzu, um die komplexesten Inhalte darstellen zu können, <strong>verwende aber die eine Sache, die WordPress gut kann, überhaupt nicht</strong>? Warum dann überhaupt noch WordPress? Es gibt hunderte coole Content Management Systeme, die alles mögliche können.</p>
<p>Wieso den Blogging-King für eine blog-freie Webseite nutzen?</p>
<h2>Fazit: WordPress ist toll – zumindest als Blog</h2>
<p>Der Artikel ließt sich auf den ersten Blick extrem WordPress-kritisch. Das soll er gar nicht sein. Ich liebe WordPress! Dieser Blog läuft ja schließlich auch mit WordPress.</p>
<p>Was ich hingegen kritisiere, sind Multi-Purpose-Themes. Was diese Themes aus WordPress machen, ist nicht mehr elegant. Für mich war die WordPress-Philosophie immer »Content is King«. Bei den Themes steht nicht mehr der Inhalt im Vordergrund, sondern die Form.</p>
<p>Du willst Deine Webseite mit einem Multi-Purpose-Wordpress-Theme machen? Gerne. Ich habe kein Problem damit! Wenn es für Dich funktioniert, ist das toll.</p>
<p>Ich hoffe, dass ich mit diesem Artikel trotzdem einmal zeige, was WordPress im Kern eigentlich ist – und was nicht.</p>
<p>Und jetzt will ich eine schöne Kontroverse in den Kommentaren lesen!</p>
]]></content:encoded>
			<wfw:commentRss>https://advitum.de/2015/08/wordpress-ist-king-zumindest-fuer-blogs/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CSS Animation Tutorial: hypnotische Spirale</title>
		<link>https://advitum.de/2015/06/css-animation-tutorial-hypnotische-spirale/</link>
		<comments>https://advitum.de/2015/06/css-animation-tutorial-hypnotische-spirale/#respond</comments>
		<pubDate>Mon, 01 Jun 2015 07:00:39 +0000</pubDate>
		<dc:creator><![CDATA[Lars Ebert]]></dc:creator>
				<category><![CDATA[CSS]]></category>
		<category><![CDATA[Programmieren]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Animation]]></category>
		<category><![CDATA[Effekt]]></category>

		<guid isPermaLink="false">https://advitum.de/?p=2504</guid>
		<description><![CDATA[Vor ein paar Tagen habe ich ein interessantes Gif gesehen, eine Animation einiger konzentrischer Halbkreise, die unterschiedlich schnell rotieren. Ich war drauf und dran, den gleichen Effekt mit ein bisschen JavaScript nachzubauen, als ich bemerkte, dass der gleiche Effekt sehr einfach mit CSS erzielt werden kann. In diesem Artikel wende ich CSS-Animationen an, um einen [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Vor ein paar Tagen habe ich ein interessantes Gif gesehen, eine Animation einiger konzentrischer Halbkreise, die unterschiedlich schnell rotieren. Ich war drauf und dran, den gleichen Effekt mit ein bisschen JavaScript nachzubauen, als ich bemerkte, dass der gleiche Effekt sehr einfach mit CSS erzielt werden kann.</p>
<p>In diesem Artikel wende ich CSS-Animationen an, um einen interessanten Effekt zu erzielen.<span id="more-2504"></span></p>
<h2>Zuerst das Resultat</h2>
<div><iframe width="100%" height="600" src="/showcase/spiral/iframe.html" frameborder="0"></iframe></div>
<h2>Schritt 1: HTML</h2>
<p>Als erstes brauchen wir etwas HTML. Für jeden Halbkreis erstellen wir ein eigenes DIV.</p>
<pre><code>&lt;div id=&quot;spiral&quot;&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
	&lt;div&gt;&lt;/div&gt;
&lt;/div&gt;</code></pre>
<h2>Schritt 2: (S)CSS</h2>
<p>Ich benutze inzwischen fast überwiegend SCSS (und Compass), aber der gleiche Effekt lässt sich ohne weiteres auch mit einfachem CSS realisieren.</p>
<p>Zunächst definiere ich ein paar Variablen.</p>
<pre><code>$speed: 8s;
$count: 16;
$barWidth: 15px;
$gapWidth: 1px;</code></pre>
<p><code>$speed</code> ist die Zeit, die die gesamte Animation für einen Durchlauf benötigt. <code>$count</code> ist die Anzahl der DIVs, die wir in unserem HTML eingefügt haben. <code>$barWidth</code> und <code>$gapWidth</code> definieren die Breite der Rahmen und den Zwischenraum dazwischen.</p>
<pre><code>#spiral {
	position: absolute;
	top: 50%;
	left: 50%;
	width: 0;
	height: 0;
	
	div {
		@include transform(rotate(45deg));
		@include border-radius(50%);
		
		border: $barWidth solid transparent;
		border-top-color: #000000;
		border-left-color: #000000;
		position: absolute;
	}
}</code></pre>
<p>Hier sorgen wir zunächst dafür, dass die Animation im Zentrum steht. Anschließend geben wir jedem DIV den entsprechenden Rahmen. Allerdings braucht jedes DIV eine andere Breite und eine andere Position. Zum Glück sind beide Werte vom Index des DIV abhängig. Also iterieren wir mit SCSS über die 16 DIVs und definieren für jedes DIV die entsprechenden Werte.</p>
<pre><code>@for $i from 1 through $count {
	#spiral div:nth-child(#{$i}) {
		width: 2 * ($i - 1) * ($barWidth + $gapWidth);
		height: 2 * ($i - 1) * ($barWidth + $gapWidth);
		top: -1 * $i * ($barWidth + $gapWidth);
		left: -1 * $i * ($barWidth + $gapWidth);
	}
}</code></pre>
<p>Wichtig ist hier: Jedes DIV ist größer als das vorherige und muss entsprechend auch weiter nach links und oben versetzt werden.</p>
<h2>Schritt 3: Animation</h2>
<p>Als nächstes folgt die Animation. Auch hier wird für jedes DIV eine unterschiedliche Animation definiert. Das kleinste DIV rotiert während der Animation genau ein mal, das zweite DIV zwei mal und so weiter. So erweitern wir also unsere For-Schleife:</p>
<pre><code>@for $i from 1 through $count {
	@include keyframes(rotate-#{$i}) {
		0% {
			@include transform(rotate(45deg));
		}
		
		100% {
			@include transform(rotate(45deg - $i * 360deg));
		}
	}
	
	#spiral div:nth-child(#{$i}) {
		@include animation(rotate-#{$i} $speed ease-in-out infinite);
		
		width: 2 * ($i - 1) * ($barWidth + $gapWidth);
		height: 2 * ($i - 1) * ($barWidth + $gapWidth);
		top: -1 * $i * ($barWidth + $gapWidth);
		left: -1 * $i * ($barWidth + $gapWidth);
	}
}</code></pre>
<p>Das war auch schon alles.</p>
<p>Ganz ohne JavaScript lässt sich so eine interessante Animation erstellen. Vor einiger Zeit wäre es noch unumgänglich gewesen, um für solch einen Effekt JavaScript zu verwenden.</p>
<p>Natürlich war das nur eine kleine Einführung in CSS-Animationen. Ich wünsche viel Spaß beim Experimentieren!</p>
]]></content:encoded>
			<wfw:commentRss>https://advitum.de/2015/06/css-animation-tutorial-hypnotische-spirale/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Erstellen und Pflegen einer Webseite &#8211; Content MarkDown Tutorial</title>
		<link>https://advitum.de/2015/02/erstellen-und-pflegen-einer-webseite-content-markdown-tutorial/</link>
		<comments>https://advitum.de/2015/02/erstellen-und-pflegen-einer-webseite-content-markdown-tutorial/#comments</comments>
		<pubDate>Mon, 02 Feb 2015 08:00:01 +0000</pubDate>
		<dc:creator><![CDATA[Lars Ebert]]></dc:creator>
				<category><![CDATA[Content Management Systeme]]></category>
		<category><![CDATA[Programmieren]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Alpha]]></category>
		<category><![CDATA[Cmd]]></category>
		<category><![CDATA[CMS]]></category>
		<category><![CDATA[Content Management System]]></category>
		<category><![CDATA[Content MarkDown]]></category>
		<category><![CDATA[Markdown]]></category>
		<category><![CDATA[Tutorial]]></category>

		<guid isPermaLink="false">https://advitum.de/?p=2467</guid>
		<description><![CDATA[Seit einiger Zeit arbeite ich an einem eigenen Content Management System: Content MarkDown. Dieses CMS basiert, wie der Name vermuten lässt, auf Markdown, einer Text-Auszeichnungs-Sprache. Markdown ist darauf ausgelegt, schon im Text-Editor strukturiert auszusehen, so ist auch in Content MarkDown die Pflege der Inhalte direkt im Text-Editor sehr übersichtlich. In diesem Artikel erkläre ich die [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Seit einiger Zeit arbeite ich an einem eigenen Content Management System: Content MarkDown. Dieses CMS basiert, wie der Name vermuten lässt, auf Markdown, einer Text-Auszeichnungs-Sprache. Markdown ist darauf ausgelegt, schon im Text-Editor strukturiert auszusehen, so ist auch in Content MarkDown die Pflege der Inhalte direkt im Text-Editor sehr übersichtlich.</p>
<p>In diesem Artikel erkläre ich die Grundlagen von Content MarkDown und zeige, wie mit dem CMS schnell und einfach eine eigene Webseite erstellt und gepflegt werden kann.<span id="more-2467"></span></p>
<h2>Herunterladen und Installieren von Content MarkDown</h2>
<p><a href="https://github.com/advitum/Content-MarkDown">Auf GitHub kannst Du die aktuelle Version von Content MarkDown herunterladen</a>. Wenn Du schon mit Git(-Hub) arbeitest, kannst Du einfach das ganze Repository klonen, ansonsten kannst Du das System aber auch einfach als Zip-Datei herunterladen und ins Zielverzeichnis entpacken. Aktuell gibt es Content MarkDown in der Version 0.07 (alpha).</p>
<p>Nachdem Du den Quellcode heruntergeladen und auf den Server geladen hast, ruf die Seite direkt im Browser auf. Da Content MarkDown noch nicht konfiguriert wurde, erscheint nun als erstes das Setup-Tool.</p>
<figure>
					<img src="https://advitum.de/wp-content/uploads/2015/01/cmd1-1.jpg" alt="Das Setup-Tool von Content MarkDown." width="448" height="381" class="size-full wp-image-2470" srcset="https://advitum.de/wp-content/uploads/2015/01/cmd1-1.jpg 448w, https://advitum.de/wp-content/uploads/2015/01/cmd1-1-350x297.jpg 350w" sizes="(max-width: 448px) 100vw, 448px" />
					<figcaption>Das Setup-Tool von Content MarkDown.</figcaption>
				</figure>
<p>Im Setup-Tool kannst Du die Datenbank-Konfiguration eintragen. Content MarkDown prüft automatisch, ob mit den eingegebenen Daten eine Verbindung zur Datenbank hergestellt werden kann. Wenn alles klappt, wird die Konfiguration in der Datei <code>content/config.php</code> gespeichert und Du wirst zur Startseite Deiner Webseite weitergeleitet.</p>
<p>Hier findet sich bisher nur der Beispiel-Inhalt, der die grundlegenden Funktionen von Content MarkDown erläutert.</p>
<h2>Pflege über FTP oder das Backend</h2>
<p>Content MarkDown zeigt die Beispiel-Inhalte in einem einfachen Standard-Layout an. Das Layout kann komplett angepasst werden, dazu kommen wir aber in einem späteren Artikel. Erstmal schauen wir uns jetzt die Pflege der Inhalte an.</p>
<p>Es gibt zwei Möglichkeiten, den Inhalt zu pflegen: per FTP oder über das Cmd-Backend. Die Pflege per FTP hat den Vorteil, dass hier einem User Zugriff auf nur ein Unterverzeichnis ermöglicht werden kann. Die Pflege im Backend hat den Vorteil, dass hier auch direkt die Konfiguration von Plugins stattfinden kann, wenn ein Plugin eine Konfiguration anbietet.</p>
<figure>
					<img src="https://advitum.de/wp-content/uploads/2015/02/cmd1-2.jpg" alt="So sieht das Backend von Content MarkDown aus." width="1328" height="719" class="size-full wp-image-2480" srcset="https://advitum.de/wp-content/uploads/2015/02/cmd1-2.jpg 1328w, https://advitum.de/wp-content/uploads/2015/02/cmd1-2-350x189.jpg 350w, https://advitum.de/wp-content/uploads/2015/02/cmd1-2-960x519.jpg 960w" sizes="(max-width: 1328px) 100vw, 1328px" />
					<figcaption>So sieht das Backend von Content MarkDown aus.</figcaption>
				</figure>
<p>Wir schauen uns in diesem Artikel die Pflege über das Backend von Content MarkDown an. Um das Backend aufzurufen, hänge einfach an die Root URL der Webseite <code>/admin</code> an. Nach der Installation von Content MarkDown wird ein Standard-Benutzer mit Nutzernamen »admin« und Passwort »admin« angelegt. Mit diesen Daten solltest Du Dich am Backend anmelden können.</p>
<p>Nun landest Du auf der Startseite des Backends, wo Du auch direkt den Inhalt der Startseite bearbeiten kannst. Vorher schauen wir uns aber einmal die Navigation im Backend an.</p>
<p>Hier gibt es momentan drei Menüpunkte: Content, Users und Logout. Sobald ein Plugin eine Konfigurations-Seite benötigt, kommt hier ein vierter Punkt, Plugins, dazu. Nun klicken wir aber zuerst auf den Menüpunkt »Users -> List«. Hier können wir nämlich das Passwort des admin-Benutzers ändern. Klicke auf den Bearbeiten-Button hinter der Zeile für den Admin-Benutzer und gebe ein neues Passwort ein.</p>
<p>Jetzt können wir uns endlich der Hauptaufgabe von Content MarkDown widmen: dem Verwalten von Inhalten. Dafür klicken wir auf den Menüpunkt »Content -> Pages«. Die Ansicht »Pages« ist in den Seitenbaum und den Inhalt der aktiven Seite aufgeteilt.</p>
<h2>Seitenbaum und Navigation</h2>
<p>In Content MarkDown entspricht jede Seite auf der Webseite einer Markdown-Datei im content-Verzeichnis. Im Backend-Seitenbaum werden Dir alle Seiten angezeigt und Du kannst alle Seiten bearbeiten.</p>
<figure>
					<img src="https://advitum.de/wp-content/uploads/2015/02/cmd1-3.jpg" alt="Im Seitenbaum werden alle Seiten angezeigt und können bearbeitet werden." width="289" height="401" class="size-full wp-image-2482" srcset="https://advitum.de/wp-content/uploads/2015/02/cmd1-3.jpg 289w, https://advitum.de/wp-content/uploads/2015/02/cmd1-3-252x350.jpg 252w" sizes="(max-width: 289px) 100vw, 289px" />
					<figcaption>Im Seitenbaum werden alle Seiten angezeigt und können bearbeitet werden.</figcaption>
				</figure>
<p>Welche Seiten im Frontend sichtbar oder unsichtbar sind und welche Seiten in der Navigation in welcher Reihenfolge erscheinen ermittelt Content MarkDown anhand des Dateinamens. Alle Dateien (und Ordner), die mit einem Unterstrich anfangen, sind im Frontend nicht sichtbar. So kannst Du in aller Ruhe an Entwürfen arbeiten. Alle Seiten, die mit einer Zahl beginnen, erscheinen im Frontend in der Navigation, geordnet nach eben dieser Nummer.</p>
<p>Um Unterseiten zu erstellen kannst Du die Seiten einfach in Ordnern sortieren. Jeder Ordner (einschließlich des content-Ornders selbst) muss eine Datei <code>index.markdown</code> beinhalten, welche den Inhalt für diesen Ordner enthält. Den Unterstrich oder die Zahlen musst Du dann vor den Ordner-Namen, nicht vor den Namen der index-Datei schreiben.</p>
<p>Teste dieses Prinzip einfach mal aus, im Standard-Layout wird die Navigation schon in der Seitenleiste angezeigt, dort kannst Du Deine Änderungen direkt sehen. Das Backend gibt Dir hier alle nötigen Funktionen zum Anlegen neuer Ordner und Dateien, zum Umbenennen und zum Löschen an die Hand.</p>
<h2>Inhalt als Markdown</h2>
<p>Markdown ist eine Methode der Textformatierung, die darauf ausgelegt ist, schon im Text-Editor strukturiert zu sein. Wenn Du Dir den Standard-Inhalt im Backend ansiehst wird Dir schnell auffallen, dass die Struktur der Texte schon ohne die HTML-Formatierung des Frontends erkennbar ist. Das macht Markdown. <a href="http://daringfireball.net/projects/markdown/syntax">Die vollständige Syntax von Markdown findest Du hier</a>.</p>
<p>Im Standard-Inhalt findest Du dafür schon einige Beispiele. So könnte Dein Markdown-Inhalt zum Beispiel aussehen:</p>
<pre><code class="markdown">This is a headline
==================

This is some simple text. Just your basic paragraph.

This is a sub headline
----------------------

 * Here comes a list
 * and it really looks like one, too!

 1. This is a numbered list
 2. See how simple it is?</code></pre>
<p>Die Formatierung des Textes ergibt schon ohne HTML Sinn und lässt die Struktur des Inhaltes erkennen. So kannst Du den ganzen Inhalt als Markdown-Dateien direkt im Editor bearbeiten, Content MarkDown erstellt daraus im Frontent automatisch HTML.</p>
<p>Wenn Markdown an seine Grenzen stößt, kannst Du jederzeit innerhalb des Markdowns einen HTML-Block verwenden, der dann einfach so übernommen wird. Dies kannst Du im Standard-Inhalt auf der Changelog-Seite sehen. Dort habe ich einfach eine Tabelle als HTML-Markup eingefügt. Wer keine Lust auf HTML hat, könnte dafür auch einfach ein Plugin schreiben.</p>
<h2>Meta Informationen</h2>
<p>Im Kopf jeder Datei kannst Du einen Kommentar mit Meta-Informationen platzieren. Dies ist zum Beispiel nützlich, wenn Du einen alternativen Navigations-Titel oder Seiten-Titel verwenden willst. Die Syntax dafür sieht so aus:</p>
<pre><code class="markdown">/**
 * Author: Lars Ebert
 * Date: 2014/11/09
 * Title: Title of the page
 * NavTitle: Page X
 */</code></pre>
<h2>Plugins verwenden</h2>
<p>Momentan gibt es in Content MarkDown drei Plugins: Contact, Gallery und Map. Die Funktionsweise dieser Plugins wird auf der Seite Core-Plugins im Standard-Inhalt beschrieben.</p>
<p>Um ein Plugin einzubinden kannst Du einfach den Cmd-Tag <code>plugin</code> benutzen.</p>
<pre><code class="markdown">&lt;cmd:plugin plugin=&quot;Contact&quot; from=&quot;noreply@example.com&quot; to=&quot;me@example.com&quot; /&gt;</code></pre>
<p>Diese Zeile bindet zum Beispiel in Deine Seite ein Kontakt-Formular ein und übergibt auch direkt die Absender- und Empfänger-Adresse.</p>
<pre><code class="markdown">&lt;cmd:plugin plugin=&quot;Map&quot; markers='[{
    &quot;title&quot;: &quot;Example&quot;,
    &quot;position&quot;: [51.248855, 7.627476]
}]' options='{
    &quot;panControl&quot;: false,
    &quot;styles&quot;: [{
        &quot;stylers&quot;: [{ &quot;saturation&quot;: -100 }]
    }]
}' /&gt;</code></pre>
<p>Die Parameter können auch ein JSON-Objekt sein, wie zum Beispiel beim Aufruf des Map-Plugins, welches eine Google-Map im Inhalt platziert. Dabei ist wichtig, dass das JSON-Array mit einfachen Anführungszeichen umschlossen wird, damit innerhalb des Objektes doppelte Anführungszeichen verwendet werden können.</p>
<h2>Ausblick</h2>
<p>Soweit die Grundlagen von Content MarkDown. In kommenden Artikeln beschäftige ich mich weiter mit Content MarkDown, mit dem erstellen eigener Layouts und mit der Enwicklung eigener Plugins für Deine Cmd-Seite.</p>
<p>Beachte, dass das Projekt momentan im Alpha-Stadium ist. Fehler sind als nicht auszuschließen und ein paar Funktionen sind noch nicht ganz ausgereift. Deshalb freue ich mich auf Deine Ideen und Dein Feedback. Werde Teil des Projektes!</p>
]]></content:encoded>
			<wfw:commentRss>https://advitum.de/2015/02/erstellen-und-pflegen-einer-webseite-content-markdown-tutorial/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Regelmäßigkeit im Chaos &#8211; ein Canvas-Effekt mit Faszinations-Garantie</title>
		<link>https://advitum.de/2014/09/regelmaessigkeit-im-chaos-ein-canvas-effekt-mit-faszinations-garantie/</link>
		<comments>https://advitum.de/2014/09/regelmaessigkeit-im-chaos-ein-canvas-effekt-mit-faszinations-garantie/#comments</comments>
		<pubDate>Mon, 22 Sep 2014 07:00:42 +0000</pubDate>
		<dc:creator><![CDATA[Lars Ebert]]></dc:creator>
				<category><![CDATA[Design]]></category>
		<category><![CDATA[Inspiration]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Programmieren]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Canvas]]></category>
		<category><![CDATA[Effekt]]></category>
		<category><![CDATA[Mathematik]]></category>
		<category><![CDATA[Zykloide]]></category>

		<guid isPermaLink="false">https://advitum.de/?p=2442</guid>
		<description><![CDATA[In meinem letzten Artikel habe ich beschrieben, wie man mit ein wenig Physik, JavaScript und dem Canvas einen interaktiven Effekt erstellen kann. In diesem Artikel möchte ich einen neuen Effekt vorstellen, der diesmal zwar nicht interaktiv, aber nicht weniger interessant ist. Zuerst das Resultat Ein bisschen Theorie über Zykloide Auch in diesem Effekt brauchen wir [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>In meinem letzten Artikel habe ich beschrieben, <a href="https://advitum.de/2014/09/interaktiver-canvas-effekt-schwingungen-und-gravitation/" title="Interaktiver Canvas-Effekt: Schwingungen und Gravitation">wie man mit ein wenig Physik, JavaScript und dem Canvas einen interaktiven Effekt erstellen kann</a>. In diesem Artikel möchte ich einen neuen Effekt vorstellen, der diesmal zwar nicht interaktiv, aber nicht weniger interessant ist.<span id="more-2442"></span></p>
<h2>Zuerst das Resultat</h2>
<div><iframe width="100%" height="605" src="/showcase/cycloids/final/" frameborder="0"></iframe></div>
<h2>Ein bisschen Theorie über Zykloide</h2>
<p>Auch in diesem Effekt brauchen wir ein bisschen Theorie, jedoch nicht sehr viel. Obwohl die Bewegungen beinahe chaotisch wirken, steckt hinter ihnen ein mathematisches Muster. Punkt für Punkt wird das Muster aus der Rotation von Kreisen gegeneinander generiert. Klingt kompliziert, ist aber eigentlich ganz einfach. Gehen wir es Schritt für Schritt durch.</p>
<p>Als erstes denken wir uns einen Kreis in der Mitte. Dieser Kreis rotiert pro Iteration um einen bestimmten Winkel, zum Beispiel 2°.</p>
<div><iframe width="100%" height="605" src="/showcase/cycloids/single-circle/" frameborder="0"></iframe></div>
<p>Nun denken wir uns einen zweiten Kreis, der an dem Punkt auf dem ersten Kreis befestigt ist. Dieser Kreis ist halb so groß und rotiert doppelt so schnell, jedoch in die entgegengesetzte Richtung.</p>
<div><iframe width="100%" height="605" src="/showcase/cycloids/double-circle/" frameborder="0"></iframe></div>
<p>Wir können nun beliebig viele Kreise hinzufügen, so wird die entstehende Bewegung immer komplexer. So sieht die Bewegung zum Beispiel mit drei Kreisen aus.</p>
<div><iframe width="100%" height="605" src="/showcase/cycloids/triple-circle/" frameborder="0"></iframe></div>
<h2>Umsetzung I: HTML</h2>
<p>Bevor wir den Effekt mit JavaScript umsetzen müssen, brauchen wir zunächst ein HTML-Dokument mit einem Canvas. Hier binden wir auch die JavaScript-Datei ein.</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;

&lt;head&gt;
	&lt;meta http-equiv=&quot;content-type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
	
	&lt;title&gt;Cycloids&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;
	&lt;canvas&gt;&lt;/canvas&gt;
	&lt;script type=&quot;text/javascript&quot; src=&quot;js/main.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;</code></pre>
<h2>Umsetzung II: Javascript</h2>
<p>Nun können wir mit dem Erstellen des Effekts beginnen. Als erstes definieren wir ein paar Konstanten.</p>
<pre><code class="javascript">var width = 600, height = 600;
var numCircles = 5;
var ratio = 1.41;
var maxRadius = 290;
var step = Math.PI / 180;</code></pre>
<p><code>width</code> und <code>height</code> stellen die Größe des Canvas dar. <code>numCircles</code> definiert die Anzahl der Kreise, <code>ratio</code> ist das Verhältnis zwischen den Größen der einzelnen Kreise. In diesem Fall ist jeder Kreis 1,41 mal so klein wie der vorherige. <code>maxRadius</code> ist der maximale gesamte Radius aller Kreise. So passt das Muster immer in den Canvas. <code>step</code> ist der Winkel, um den sich der erste Kreis jede Iteration dreht.</p>
<pre><code class="javascript">document.body.setAttribute('style', 'margin: 0px; padding: 0px; background: #111111; text-align: center;');

var canvas = document.getElementsByTagName('canvas')[0];

canvas.setAttribute('style', 'margin: 0px auto;');
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);

var context = canvas.getContext('2d');</code></pre>
<p>Nun bereiten wir den Canvas vor. Zunächst weisen wir dem Body ein paar Stile vor, danach wählen wir den Canvas aus und geben ihm auch Stile, Breite und Höhe. Zuletzt fragen wir noch den Context ab.</p>
<pre><code class="javascript">var smallestRadius = maxRadius;
while(smallestRadius &gt; .01 &amp;&amp; accumulatedRadius(smallestRadius) &gt; maxRadius) {
	smallestRadius -= .01;
}</code></pre>
<p>Als nächstes ermitteln wir den Radius des kleinsten Kreises. Insgesamt dürfen alle Radien zusammen nicht größer sein als <code>maxRadius</code>. Also starten wir mit <code>maxRadius</code> und verkleinern den Radius so lange, bis alle Radien zusammen kleiner als <code>maxRadius</code> sind. Mit der Funktion <code>accumulatedRadius</code> berechnen wir den summierten Radius aller Kreise.</p>
<pre><code class="javascript">function accumulatedRadius(smallestRadius) {
	var result = 0;
	for(var i = 0; i &lt; numCircles; i++) {
		result += smallestRadius;
		smallestRadius *= ratio;
	}
	return result;
}</code></pre>
<p>In der Funktion <code>accumulatedRadius</code> gehen wir einfach alle Kreise durch und addieren den Radius zur Summe.</p>
<p>Nachdem wir nun den kleinsten Radius kennen, können wir die Kreise generieren und in einem Array speichern.</p>
<pre><code class="javascript">var circles = [];
for(var i = 0; i &lt; numCircles; i++) {
	circles.push({
		angle: 0,
		radius: smallestRadius * Math.pow(ratio, numCircles - i - 1)
	});
}</code></pre>
<p>Für jeden Kreis speichern wir im Array <code>circles</code> ein Objekt, dass den Winkel und Radius dieses Kreises enthält. Der Winkel ist zu Anfang bei allen Kreisen 0. Den Radius können wir aus dem kleinsten Radius generieren, indem wir pro Kreis den Radius mit <code>ratio</code> multiplizieren.</p>
<pre><code class="javascript">context.fillStyle = '#ffffff';
window.setInterval(function() {
	var position = {
		x: width / 2,
		y: height / 2
	};
	var deltaAngle = step;
	for(var i = 0; i &lt; numCircles; i++) {
		position.x += circles[i].radius * Math.sin(circles[i].angle);
		position.y += circles[i].radius * Math.cos(circles[i].angle);
		circles[i].angle += deltaAngle;
		deltaAngle *= -1 * ratio;
	}
	context.fillRect(position.x, position.y, 1, 1);
}, 1);</code></pre>
<p>Schließlich können wir die Iteration starten. Den <code>fillStyle</code> setzen wir auf <code>#ffffff</code>.</p>
<p>In jeder Iteration beginnen wir im Mittelpunkt des Canvas. Diesen speichern wir im Objekt <code>position</code>. In der Variable <code>deltaAngle</code> speichern wir den Winkel, um den der erste Kreis rotieren wird. Nun gehen wir alle Kreise durch.</p>
<p>Mit Sinus und Cosinus verschieben wir für jeden Kreis <code>position</code>. So berechnen wir in jeder Iteration den Endpunkt der aktuellen Kreiswinkel. Diesen Endpunkt zeichnen wir schließlich auf unserem Canvas ein. Nach jeder Iteration werden die für die Kreise gespeicherten Winkel um <code>deltaAngle</code> geändert, wobei <code>deltaAngle</code> selbst mit jedem Kreis wieder umgekehrt und mit <code>ratio</code> multipliziert wird, genau wie vorhin bei unseren drei Kreisen.</p>
<p>So entsteht Punkt für Punkt ein komplexes Muster. Obwohl dieses Muster zufällig wirkt, zeichnen sich mit der Zeit Regelmäßigkeiten ab. So oder so ist der Effekt jedenfalls interessant.</p>
<h2>Variationen und Spielerei</h2>
<p>Den Effekt selbst kann man über die Anzahl der Kreise und die Größenverhältnisse variieren. Dafür habe ich hier <a href="/showcase/cycloids/">eine interaktive Demo</a> hochgeladen.</p>
<p>Mit diesem Effekt zeige ich nur stellvertretend die vielen Möglichkeiten, die uns dank Canvas, JavaScript und ein bisschen Fantasie offen stehen. Hiermit fordere ich Dich auf, ein wenig mit dem Canvas herum zu experimentieren. Im Web gibt es schon extrem viele Canvas-Experimente, aber der Kreativität sind keine Grenzen gesetzt.</p>
]]></content:encoded>
			<wfw:commentRss>https://advitum.de/2014/09/regelmaessigkeit-im-chaos-ein-canvas-effekt-mit-faszinations-garantie/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Interaktiver Canvas-Effekt: Schwingungen und Gravitation</title>
		<link>https://advitum.de/2014/09/interaktiver-canvas-effekt-schwingungen-und-gravitation/</link>
		<comments>https://advitum.de/2014/09/interaktiver-canvas-effekt-schwingungen-und-gravitation/#comments</comments>
		<pubDate>Mon, 15 Sep 2014 07:00:04 +0000</pubDate>
		<dc:creator><![CDATA[Lars Ebert]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Programmieren]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Canvas]]></category>
		<category><![CDATA[Effekt]]></category>
		<category><![CDATA[Gravitation]]></category>
		<category><![CDATA[Physik]]></category>
		<category><![CDATA[Schwingungen]]></category>
		<category><![CDATA[Simulation]]></category>

		<guid isPermaLink="false">https://advitum.de/?p=2407</guid>
		<description><![CDATA[Achtung: Physik und Mathematik voraus! In diesem Artikel zeige ich Dir, wie man mit Physik und ein wenig JavaScript einem Canvas Leben einhauchen kann. Der entstandene Effekt kann, in abgewandelter Form, sehr schön in eine Webseite eingebunden werden. Was haben wir vor? Diese »Simulation« zeigt, wie man mit nur zwei physikalischen Prinzipien einen interessanten und [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Achtung: Physik und Mathematik voraus! In diesem Artikel zeige ich Dir, wie man mit Physik und ein wenig JavaScript einem Canvas Leben einhauchen kann. Der entstandene Effekt kann, in abgewandelter Form, sehr schön in eine Webseite eingebunden werden.<span id="more-2407"></span></p>
<h2>Was haben wir vor?</h2>
<div><iframe width="100%" height="505" src="/showcase/physics-swing/final/" frameborder="0"></iframe></div>
<p>Diese »Simulation« zeigt, wie man mit nur zwei physikalischen Prinzipien einen interessanten und interaktiven Effekt erzeugen kann. Im Verlauf des Artikels erkläre ich kurz die physikalischen Prinzipien, die hinter diesem Effekt stehen, danach machen wir uns ans Werk, den Effekt selbst zu erstellen.</p>
<h2>Physik I: Anziehung zwischen zwei Körpern</h2>
<p>Das erste physikalische Prinzip, welches ich in der Erstellung des Effektes genutzt habe, ist die <a href="http://de.wikipedia.org/wiki/Gravitation#Gravitation_in_der_klassischen_Mechanik">Anziehung zwischen zwei Körpern</a>. <em>Hinweis: Ich habe nicht Physik Studiert und meine Tage im Physik-Unterricht liegen auch schon ein paar Jahre zurück. Ich gehe hier nicht im Detail auf die Physik ein und vereinfache die Formeln an einigen Stellen.</em>.</p>
<p>Die Anziehungskraft zwischen zwei Körpern kann man deutlich in unserem Sonnensystem sehen. Sie ist die Kraft, die den Mond auf seiner Bahn um die Erde hält und die Erde auf der Umlaufbahn um die Sonne. Durch die gleiche Kraft kommt bei uns auf der Erde auch die Erdanziehungskraft zustande.</p>
<p>Schaut man sich noch einmal die Demo oben an, kann man sehen, dass die Punkte von dem Mauszeiger abgestoßen werden. Ich habe hier (physikalisch nicht ganz korrekt) die Anziehungskraft einfach umgekehrt, damit die Punkte abgestoßen werden, nicht angezogen. Dies funktioniert dann quasi, als wenn man zwei Magneten verkehrt herum aneinander hält&mdash;sie stoßen sich ab.</p>
<p>Die Anziehungskraft (oder in diesem Fall »Abstoßungskraft«) ist von drei Faktoren abhängig: der Masse der beiden Körper und dem Abstand zwischen den Körpern. Je größer die Masse der beiden Körper ist, desto stärker ist die Anziehungskraft. Je weiter die Körper auseinander sind, desto mehr nimmt die Kraft ab. Dies lässt sich durch die folgende Formel abbilden:</p>
<p>$$F_G = G \frac{m_1 * m_2}{r^2}$$</p>
<p>\(m_1\) und \(m_2\) sind die Massen der beiden Körper und \(r\) ist die Entfernung zwischen den beiden Körpern. \(G\) ist die universelle Gravitationskonstante, sie stellt eine Relation zwischen Masse, Entfernung und Kraft dar. In unserem Fall lassen wir die Konstante einfach weg. Physikalisch ist dies zwar sehr falsch, aber hier geht es nicht um perfekte Physik und das genaue Verhältnis zwischen Masse und Kraft ist in diesem Fall nicht so wichtig, denn wir definieren später die Masse der Punkte so oder so nach belieben. Die für uns relevante Formel ist also:</p>
<p>$$F_G = \frac{m_{Punkt} * m_{Cursor}}{r^2}$$</p>
<h2>Physik II: Von der Kraft zur Bewegung</h2>
<p>Als nächstes müssen wir mit dieser Kraft, die wir jetzt berechnen können, irgendwie die Strecke herausfinden, um die sich die Punkte bewegen. Dazu werfen wir erst einmal einen Blick auf den Zusammenhang zwischen Bewegung, Geschwindigkeit und Beschleunigung.</p>
<p>Wenn sich der Standpunkt \(s\) eines Körpers ändert, also eine Bewegung stattfindet, liegt eine Geschwindigkeit vor. Geschwindigkeit, Strecke und Zeit stehen im folgenden Verhältnis:</p>
<p>$$s = v * t$$</p>
<p>Das ist vergleichbar mit der Bewegung eines Autos: Wenn ein Auto 30 Sekunden lang eine Geschwindigkeit von 30 Metern pro Sekunde fährt, legt es insgesamt eine Strecke von \(s = 30 m/s * 30 s = 900 m\) zurück. Wenn ein Auto in 60 Sekunden 1200 Meter zurücklegt, beträgt die Geschwindigkeit \(v = \frac{1200 m}{60 s} = 20 m/s\). Soweit die Beziehung zwischen Strecke und Zeit.</p>
<p>Analog kann man eine ähnliche Beziehung zwischen der Geschwindigkeit und Beschleunigung herstellen:</p>
<p>$$v = a * t$$</p>
<p>Auch hier wieder der Vergleich mit einem Auto: Beschleunigt ein Auto von 0 Metern pro Sekunde auf 30 Meter pro Sekunde in 5 Sekunden, beträgt die Beschleunigung \(a = \frac{30 m/s}{5 s} = 6 m/s^2\). Beschleunigt ein Auto 20 Sekunden lang pro Sekunde um 3 Meter pro Sekunde, nimmt die Geschwindigkeit um \(v = 3 m/s^2 * 20 s = 60 m/s\) zu.</p>
<p>Als letztes brauchen wir noch die Beziehung zwischen der Kraft, mit der ein Körper beschleunigt wird, und der resultierenden Beschleunigung:</p>
<p>$$F = a * m$$</p>
<p>Hier kommt wieder die Masse des zu beschleunigenden Körpers, also unseres Punktes, ins Spiel. Je schwerer der Körper ist, desto mehr Kraft wird benötigt, um ihn in Bewegung zu versetzen. Das kannst Du leicht nachvollziehen: Einen Tennisball zu werfen ist viel leichter als eine Bowlingkugel zu werfen.</p>
<p>Diese verschiedenen Beziehungen können wir jetzt nutzen, um eine Abstoßung zwischen den Punkten und unserem Cursor zu simulieren.</p>
<h2>Grundgerüst aus HTML und JavaScript</h2>
<p>Bevor wir mit dem JavaScript-Code beginnen können, brauchen wir zunächst eine Leinwand:</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
	&lt;title&gt;Physik Swing&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
	&lt;canvas&gt;&lt;/canvas&gt;
	&lt;script type=&quot;text/javascript&quot; src=&quot;js/jquery-2.1.1.min.js&quot;&gt;&lt;/script&gt;
	&lt;script type=&quot;text/javascript&quot; src=&quot;js/main.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>Nun folgt unser JavaScript. Als erstes definieren wir einige Eigenschaften der Simulation, nämlich die Höhe und Breite der Leinwand, den Abstand und Radius der Punkte und die Anzahl der Reihen und Spalten von Punkten.</p>
<pre><code class="javascript">var width = 500, height = 500;
var rythm = 12, radius = 3;
var rows = 10, columns = 10;</code></pre>
<p>Nun erstellen wir eine Animation. Ich benutze dazu gerne die Funktion <code>window.requestAnimationFrame()</code>, denn damit kann man wesentlich flüssigere Animationen erreichen. Allerdings ist diese Funktion nicht in jedem Browser vorhanden, also brauchen wir ein Fallback.</p>
<pre><code class="javascript">if(!window.requestAnimationFrame) {
	window.requestAnimationFrame = ( function() {
		return window.webkitRequestAnimationFrame ||
		window.mozRequestAnimationFrame ||
		window.oRequestAnimationFrame ||
		window.msRequestAnimationFrame ||
		function(callback, element) {
			window.setTimeout(callback, 1000 / 60);
		};
	})();
}</code></pre>
<p>Nun bereiten wir schon einmal alles für die Animation vor.</p>
<pre><code class="javascript">var lastFrameTime = null;
var mousePosition = null;
var mouseDown = false;

var $canvas = $('canvas');
$canvas.attr('width', width).attr('height', height).css({
	margin: '0px auto'
});

var context = $canvas.get(0).getContext('2d');

$('body').css({
	textAlign: 'center',
	background: '#111111',
	margin: '0px'
}).mousemove(function(e) {
	var fixPoint = $canvas.offset();
	
	mousePosition = {
		x: e.pageX - fixPoint.left,
		y: e.pageY - fixPoint.top
	};
}).mousedown(function() {
	mouseDown = true;
}).mouseup(function() {
	mouseDown = false;
});</code></pre>
<p>Hier passiert eininges: Wir definieren uns ein paar Variablen, um den Zeitpunkt des letzten Frames und den Status der Maus zu speichern. Wir geben dem Canvas Breite und Höhe und legen ein bisschen CSS fest. Wir fragen den Context des Canvas ab. Abschließend geben wir dem Body noch einige CSS-Eigenschaften und binden an ihn drei Event-Handlers, die den Maus-Status speichern, sobald sich dieser ändert.</p>
<pre><code class="javascript">var dots = [];
for(var x = 0; x &lt; columns; x++) {
	for(var y = 0; y &lt; rows; y++) {
		var x0 = (width - rows * rythm) / 2 + x * rythm;
		var y0 = (height - columns * rythm) / 2 + y * rythm;
		
		dots.push({
			x: x0,
			y: y0,
			x0: x0,
			y0: y0,
			vx: 0,
			vy: 0
		});
	}
}

loop();</code></pre>
<p>Nun generieren wir ein Array mit den Punkten. Jeder Punkt bekommt eine Ursprungs-Koordinate \((x_0|y_0)\), eine aktuelle Position \((x|y)\) und einen Geschwindigkeits-Vektor \((v_x|v_y)\). Warum müssen wir die Geschwindigkeit auch speichern? Wenn der Cursor später eine Kraft auf einen Punkt ausübt, wird dieser beschleunigt, nimmt also an Geschwindigkeit zu. Bewegt man dann den Cursor weg, bleibt der Punkt natürlich nicht sofort regungslos stehen, sondern bewegt sich durch seine Trägheit weiter. Deshalb müssen wir hier die »angesammelte« Geschwindigkeit speichern.</p>
<p>In der letzten Zeile rufen wir schließlich die Funktion <code>loop()</code> auf. Diese Funktion stellt quasi die Animations schleife dar, für jeden Frame neu durchlaufen wird. Diese Funktion definieren wir jetzt.</p>
<pre><code class="javascript">function loop() {
	var thisFrameTime = new Date().getTime();
	if(lastFrameTime != null) {
		update(thisFrameTime - lastFrameTime);
	}
	lastFrameTime = thisFrameTime;
	
	draw();
	
	window.requestAnimationFrame(loop);
}</code></pre>
<p>Als erstes generieren wir die Zeit des aktuellen Frames, damit können wir die Zeit berechnen, die seit dem letzten Frame vergangen ist. Dies ist später die Zeit, die wir in unsere Formeln für Beschleunigung und Geschwindigkeit einsetzen können. Wenn <code>lastFrameTime</code> noch <code>null</code> ist, befinden wir uns grade im ersten Frame, deshalb muss die Funktion <code>update()</code> noch nicht ausgeführt werden. Ansonsten berechnet diese Funktion die Beschleunigung und Bewegung für den verstrichenen Zeitraum und aktualisieren die Punkte entsprechend. Anschließend wird die Funktion <code>draw()</code> ausgeführt, diese zeichnet den Canvas neu. Zuletzt bewirkt der Aufruf von <code>window.requestAnimationFrame(loop)</code>, dass die Funktion <code>loop()</code> vor dem nächsten aktualisieren des Bildschirms erneut aufgerufen wird.</p>
<pre><code class="javascript">function update(deltaTime) {
	
}

function draw() {
	context.clearRect(0, 0, width, height);
	
	for(var i = 0; i &lt; dots.length; i++) {
		context.fillStyle = '#ffffff';
		
		context.beginPath();
		context.arc(dots[i].x, dots[i].y, radius, 0, 2 * Math.PI);
		context.fill();
	}
}</code></pre>
<p>Die Funktion <code>update()</code> tut vorerst gar nichts, erst kümmern wir uns darum, dass die Punkte korrekt auf dem Canvas angezeigt werden. Dazu dient die Funktion <code>draw()</code>.</p>
<p>Hier wird zunächst die Leinwand geleert, danach wird jeder Punkt einzeln an seiner aktuellen Position \((x|y)\) ausgegeben.</p>
<h2>Bewegung und Beschleunigung</h2>
<div><iframe width="100%" height="505" src="/showcase/physics-swing/01-static/" frameborder="0"></iframe></div>
<p>So sieht unsere Simulation momentan aus. Die Punkte bewegen sich natürlich nicht, da wir die <code>update()</code>-Funktion noch nicht geschrieben haben. Das kommt jetzt! Als erstes definieren wir ein paar neue Konstanten:</p>
<pre><code class="javascript">var dotMass = 5, cursorMass = 1000000;
var frictionCoefficient = .95;</code></pre>
<p>Hier legen wir die Masse des Cursors und der einzelnen Punkte fest. Diese Werte habe ich durch ein wenig experimentieren herausgefunden. Durch Anpassen dieser Werte können wir das Verhalten der Simulation anpassen. Außerdem legen wir die Reibung fest, damit die Punkte auch wieder Geschwindigkeit verlieren.</p>
<p>Nun schreiben wir die <code>update()</code>-Funktion.</p>
<pre><code class="javascript">function update(deltaTime) {
	for(var i = 0; i &lt; dots.length; i++) {
		var mouseAccelerationVector = {
			x: 0,
			y: 0
		};
		if(mousePosition != null) {
			var distanceSquared = Math.pow(mousePosition.x - dots[i].x, 2) + Math.pow(mousePosition.y - dots[i].y, 2);
			var distance = Math.sqrt(distanceSquared);
			var mouseAcceleration = (mouseDown ? 4 : 1) * cursorMass / distanceSquared;
			mouseAccelerationVector.x = mouseAcceleration * (dots[i].x - mousePosition.x) / distance;
			mouseAccelerationVector.y = mouseAcceleration * (dots[i].y - mousePosition.y) / distance;
		}
		
		dots[i].vx += mouseAccelerationVector.x * deltaTime / 1000;
		dots[i].vy += mouseAccelerationVector.y * deltaTime / 1000;
		
		dots[i].vx *= frictionCoefficient;
		dots[i].vy *= frictionCoefficient;
		
		dots[i].x += dots[i].vx * deltaTime / 1000;
		dots[i].y += dots[i].vy * deltaTime / 1000;
	}
}</code></pre>
<p>Für jeden Punkt müssen wir die Änderungen getrennt berechnen. Wenn die Maus-Position bisher unbekannt ist, findet keine Beschleunigung statt. Kennen wir die Maus-Position jedoch, können wir die Beschleunigung einfach berechnen. Wir erinnern uns an die Formeln:</p>
<p>$$F = \frac{m_{cursor} * m_{punkt}}{r^2}$$<br />
$$F = a * m_punkt$$</p>
<p>\(F\) beschreibt hier in beiden Formeln die gleiche Kraft, und zwar die Kraft, die der Cursor auf einen Punkt auswirkt. Deshalb können wir die Formeln kombinieren, um eine Formel für die Beschleunigung \(a\) zu erhalten.</p>
<p>$$a = \frac{m_{cursor} * m_{punkt}}{r^2 * m_{punkt}} = \frac{m_{cursor}}{r^2}$$</p>
<p>Die Entfernung \(r\) können wir einfach aus den Koordinaten der Maus und des Punktes berechnen:</p>
<p>$$r^2 = (x_{maus} &#8211; x_{punkt})^2 + (y_{maus} &#8211; {y_punkt)^2$$</p>
<p>Das ist genau, was wir in der Variable <code>distanceSquared</code> speichern. Wir speichern hier das Quadrat der Entfernung, da wir die Entfernung später so oder so als Quadrat einsetzen müssen. Später brauchen wir jedoch die Entfernung auch noch unquadriert, deshalb ziehen wir in der nächsten Zeile aus <code>distanceSquared</code> die Wurzel.</p>
<p>Mit unserer Formel können wir jetzt einfach die Beschleunigung berechnen, diese speichern wir in <code>mouseAcceleration</code>. Wenn die Maus gedrückt ist, wird die Masse des Cursors vervierfacht, so kann der Nutzer hinterher auch noch per Klick mit dem Effekt interagieren.</p>
<p>Aus dieser Gesamt-Beschleunigung können wir jetzt die Beschleunigung in X- und Y-Richtung ermitteln. Da die Beschleunigung von der Entfernung abhängig ist, können wir uns folgender Formel bedienen:</p>
<p>$$\frac{x}{r} = \frac{a_x}{a}$$<br />
$$\frac{y}{r} = \frac{a_y}{a}$$</p>
<p>So können wir die Werte für <code>mouseAccelerationVector</code> berechnen.</p>
<p>Um welchen Betrag sich die Geschwindigkeit ändert, können wir mit dieser Formel berechnen:</p>
<p>$$v = a * t$$</p>
<p>Hier setzen wir als Zeit <code>deltaTime</code> in Sekunden ein. Die berechneten Werte addieren wir anschließend zu <code>dots[i].vx</code> und <code>dots[i].vy</code>.</p>
<p>Als nächstes multiplizieren wir die Geschwindigkeit mit dem Reibungswert, damit die Punkte sich nicht ewig weiter bewegen.</p>
<p>Zuletzt können wir die Änderung der Position mit dieser Formel berechnen, auch hier setzen wir wieder <code>deltaTime</code> ein.</p>
<p>$$s = v * t$$</p>
<h2>Physik III: Federschwingung</h2>
<div><iframe width="100%" height="505" src="/showcase/physics-swing/02-movement/" frameborder="0"></iframe></div>
<p>So, jetzt können wir mit dem Cursor die Punkte abstoßen. Es gibt nur ein Problem: die Punkte kommen nicht zurück. Um dies jetzt zu ändern, müssen wir uns wieder der Physik zuwenden.</p>
<p>Stellen wir uns einmal vor, die Punkte wären mit einem Gummiband oder einer Feder an ihrer Original-Position befestigt. Bewegt man die Punkte nun von dieser Position weg, zwingt die Feder sie zurück zu ihrer Original-Position. Allerdings vollzieht sich dieser Vorgang nicht linear, auf dem Weg zur Original-Position nimmt der Punkt an Geschwindigkeit zu und schießt so übers Ziel hinaus. So pendelt die Bewegung eine Weile um die Originalposition. Das klingt zwar sehr komplex, allerdings lässt sich dies durch eine sehr einfache Formel ausdrücken:</p>
<p>$$F = D * s$$</p>
<p>\(F\) ist hier die Kraft, die die Feder auf den Punkt ausübt und \(D\) ist die Federkonstante. Diese ist für jede Feder anders, wir können diese später einfach als Konstante definieren. \(s\) ist die Entfernung des Punktes von seiner Original-Position. Diese Formel sagt also einfach nur aus, dass die ausgeübte Kraft umso stärker ist, je weiter der Punkt von seiner Original-Position entfernt ist. Auch diese Formel kann man in der Praxis beobachten: Zieht man ein Gummiband oder eine Feder in die Länge, muss man um so stärker ziehen, je weiter das Band oder die Feder schon ausgedehnt ist.</p>
<h2>Anwendung der Feder-Kraft</h2>
<p>Da wir bereits wissen, wie wir aus einer Kraft eine Beschleunigung und daraus die Geschwindigkeit und Bewegung berechnen, können wir unsere <code>update()</code>-Funktion jetzt einfach anpassen, um den federnden Effekt zu realisieren.</p>
<p>Zunächst definieren wir die oben erwähnte Feder-Konstante.</p>
<pre><code class="javascript">var featherConstant = 300;</code></pre>
<p>Als nächstes arbeiten wir die Feder-Kraft in die <code>update()</code>-Funktion ein.</p>
<pre><code class="javascript">function update(deltaTime) {
	for(var i = 0; i &lt; dots.length; i++) {
		var mouseAccelerationVector = {
			x: 0,
			y: 0
		};
		if(mousePosition != null) {
			var distanceSquared = Math.pow(mousePosition.x - dots[i].x, 2) + Math.pow(mousePosition.y - dots[i].y, 2);
			var distance = Math.sqrt(distanceSquared);
			var mouseAcceleration = (mouseDown ? 4 : 1) * cursorMass / distanceSquared;
			mouseAccelerationVector.x = mouseAcceleration * (dots[i].x - mousePosition.x) / distance;
			mouseAccelerationVector.y = mouseAcceleration * (dots[i].y - mousePosition.y) / distance;
		}
		
		var featherAccelerationVector = {
			x: 0,
			y: 0
		};
		var amplitude = Math.sqrt(Math.pow(dots[i].x - dots[i].x0, 2) + Math.pow(dots[i].y - dots[i].y0, 2));
		if(amplitude != 0) {
			var featherAcceleration = featherConstant * amplitude / dotMass;
			featherAccelerationVector.x = featherAcceleration * (dots[i].x0 - dots[i].x) / amplitude;
			featherAccelerationVector.y = featherAcceleration * (dots[i].y0 - dots[i].y) / amplitude;
		}
		
		dots[i].vx += (mouseAccelerationVector.x + featherAccelerationVector.x) * deltaTime / 1000;
		dots[i].vy += (mouseAccelerationVector.y + featherAccelerationVector.y) * deltaTime / 1000;
		
		dots[i].vx *= frictionCoefficient;
		dots[i].vy *= frictionCoefficient;
		
		dots[i].x += dots[i].vx * deltaTime / 1000;
		dots[i].y += dots[i].vy * deltaTime / 1000;
	}
}</code></pre>
<p>Auch hier ist die Federbeschleunigung erst einmal 0, denn wenn der Punkt sich noch nicht vom Original-Punkt bewegt hat, wirkt auch noch keine Kraft. Anschließend berechnen wir die Amplitude, also die Entfernung vom Original-Punkt. Dazu können wir wieder \(r^2 = x^2 + y^2\) nutzen. Wenn die Amplitude nicht 0 ist, berechnen wir aus ihr die Kraft und daraus die Beschleunigung des Punktes und speichern dies in <code>featherAcceleration</code>. Anschließend teilen wir, wie schon bei der Maus-Beschleunigung, diese Beschleunigung in X- und Y-Komponente auf.</p>
<p>Bei der Änderung der Geschwindigkeit addieren wir nun die beiden wirkenden Beschleunigungen und bringen somit die Federschwingung in die Simulation ein.</p>
<h2>Einfache Formeln, viel Interaktivität</h2>
<p>Damit ist unsere Simulation komplett. Mit nur ein paar einfachen physikalischen Formeln konnten wir schon eine relativ komplexe Interaktion erreichen. Vielleicht hat dies nicht unbedingt viele praktische Anwendungsmöglichkeiten, aber mit dieser oder einer abgewandelten Technik kann man zumindest einen interessanten Effekt erreichen.</p>
<p>Nach einer viel zu langen Schreibpause ist dies mein erster Artikel. Ich freue mich auf Euer Feedback. Was macht ihr mit dem Canvas? Jetzt seid ihr dran!</p>
]]></content:encoded>
			<wfw:commentRss>https://advitum.de/2014/09/interaktiver-canvas-effekt-schwingungen-und-gravitation/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>FPDF-Tutorial &#8211; Rechnungs-Posten und mehrspaltiger Text</title>
		<link>https://advitum.de/2013/08/fpdf-tutorial-rechnungs-posten-und-mehrspaltiger-text/</link>
		<comments>https://advitum.de/2013/08/fpdf-tutorial-rechnungs-posten-und-mehrspaltiger-text/#comments</comments>
		<pubDate>Mon, 26 Aug 2013 07:00:10 +0000</pubDate>
		<dc:creator><![CDATA[Lars Ebert]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programmieren]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Betrag]]></category>
		<category><![CDATA[Euro]]></category>
		<category><![CDATA[FPDF]]></category>
		<category><![CDATA[PDF]]></category>
		<category><![CDATA[Posten]]></category>
		<category><![CDATA[Spalten]]></category>

		<guid isPermaLink="false">https://advitum.de/?p=2323</guid>
		<description><![CDATA[Im letzten Artikel dieser Serie hast Du gelernt, Blocksatz in FPDF zu generieren. Was uns jetzt für eine Rechnung noch fehlt, sind die Rechnungs-Posten. Diese wollen wir nun in zwei Spalten darstellen. Links wird der Artikel oder das Produkt aufgelistet und rechts der Preis. So können wir am Ende die Summe der Rechnung übersichtlich darstellen. [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Im letzten Artikel dieser Serie hast Du gelernt, Blocksatz in FPDF zu generieren. Was uns jetzt für eine Rechnung noch fehlt, sind die Rechnungs-Posten. Diese wollen wir nun in zwei Spalten darstellen. Links wird der Artikel oder das Produkt aufgelistet und rechts der Preis. So können wir am Ende die Summe der Rechnung übersichtlich darstellen.</p>
<p>Jetzt aber los!<span id="more-2323"></span></p>
<h2>Schritt 10: Eine neue Methode für Rechnungs-Posten</h2>
<p>Auch für die Posten werden wir eine neue Methode in unserer Invoice-Klasse bereistellen. So können wir später einfach die Posten in beliebiger Reihenfolge ausgeben und auch zwischendurch Text-Absätze einschieben. Um das zu testen, ergänze die Generierung des PDFs folgendermaßen:</p>
<pre><code title="index.php" highlight="37-43">&lt;?php
	
	$items = array(
		array(
			'name' =&gt; 'Produkt 1',
			'description' =&gt; 'Lorem ipsum ad qui amet dolore, vitae cetero quaerendum mel ea. Facilis fastidii duo no.',
			'unitprice' =&gt; 5.2,
			'quantity' =&gt; 3
		),
		array(
			'name' =&gt; 'Produkt 2',
			'description' =&gt; 'Lorem ipsum ad qui amet dolore, vitae cetero quaerendum mel ea. Facilis fastidii duo no.',
			'unitprice' =&gt; 1,
			'quantity' =&gt; 9
		),
		array(
			'name' =&gt; 'Produkt 3',
			'description' =&gt; 'Lorem ipsum ad qui amet dolore, vitae cetero quaerendum mel ea. Facilis fastidii duo no.',
			'unitprice' =&gt; .99,
			'quantity' =&gt; 18
		)
	);
	
	$invoiceNumber = date('Ymd') . '-' . rand(0, 100);
	$date = new DateTime();
	
	require_once('invoice.php');
	
	$invoice = new Invoice($invoiceNumber, $date);
	
	$invoice-&gt;Paragraph('Sehr geehrte Frau Mustermann,

vielen Dank für Ihren Einkauf bei Advitum.de. Dies ist Ihre Rechnung!

Lorem ipsum ad qui amet dolore, vitae cetero quaerendum mel ea. Facilis fastidii duo no. Viris partiendo ius no, alia animal nam at. Feugait imperdiet ius an, no quis facer lucilius vis. Aliquam saperet contentiones ex pro, id idque offendit ius. Fugit suavitate ad eam, ut essent debitis cum.');
	
	$invoice-&gt;Item(array_shift($items));
	
	$invoice-&gt;Paragraph('Lorem ipsum ad qui amet dolore, vitae cetero quaerendum mel ea. Facilis fastidii duo no. Viris partiendo ius no, alia animal nam at. Feugait imperdiet ius an, no quis facer lucilius vis. Aliquam saperet contentiones ex pro, id idque offendit ius. Fugit suavitate ad eam, ut essent debitis cum.');
	
	foreach($items as $item) {
		$invoice-&gt;Item($item);
	}
	
	$invoice-&gt;output();
	
?&gt;</code></pre>
<p>Die Methode <code>Item</code> wird später einen Rechnungs-Posten entgegennehmen und diesen Darstellen. Nach dem ersten Posten schieben wir hier einen Text-Absatz ein.</p>
<p>Nun müssen wir diese neue Methode auch in der Klasse bereitstellen.</p>
<pre><code title="invoice.php">public function Item($item) {
	$this-&gt;ln($this-&gt;rythm);
	
	$this-&gt;SetFont('Helvetica', 'B', 9);
	$this-&gt;Cell($this-&gt;leftColumnWidth + $this-&gt;gutter, 0, $item['name']);
	
	$this-&gt;SetFont('Helvetica', '', 7);
	$this-&gt;FitCell($item['quantity'] . ' á EUR ');
	$this-&gt;Currency($item['unitprice']);
	
	$this-&gt;ln($this-&gt;rythm);
	
	$this-&gt;SetX($this-&gt;rightColumnX);
	$this-&gt;FitCell('EUR ');
	
	$this-&gt;SetFont('Helvetica', 'B', 12);
	$this-&gt;Currency($item['quantity'] * $item['unitprice']);
	
	$this-&gt;sum += $item['quantity'] * $item['unitprice'];
	
	$this-&gt;SetX($this-&gt;leftMargin);
	$this-&gt;SetFont('Helvetica', '', 9);
	$this-&gt;BlockCell($item['description'], $this-&gt;leftColumnWidth);
	
	$this-&gt;ln($this-&gt;rythm * 3);
}</code></pre>
<p>Zunächst sorgen wir für einen Zeilenumbruch mit der Methode <code>ln</code>. Nun können wir den eigentlichen Posten ausgeben. In der linken Spalte geben wir zunächst fettgedruckt den Namen des Postens an. Die breite der Zelle wählen wir hier mit <code>$this->leftColumnWidth + $this->gutter</code> genau so, dass sie am linken Rand der rechten Spalte endet. So können wir nun direkt die erste Zeile der rechten Spalte ausgeben.</p>
<p>Hierzu gibt es zwei neue Methoden, die wir gleich noch erstellen müssen. Die Methode <code>FitCell</code> gibt den übergebenen Text in einer neuen Zelle aus, die genau so breit ist wie der Text. Die Methode <code>Currency</code> ist für die Darstellung des Betrags zuständig. Das lagere ich deshalb aus, da wir noch öfter auf der Rechnung beträge ausgeben müssen. So werden diese immer einheitlich dargestellt und wir müssen uns nicht wiederholen.</p>
<p>Nun aber weiter im Text. Als nächstes erzwingen wir wieder einen Zeilenbruch und springen dann wieder zur rechten Spalte (Zeile 13). Hier geben wir nun den Betrag des Postens aus, der sich aus dem Einzelpreis und der Stückzahl errechnet. Diesen Betrag addieren wir auch direkt zur Gesamtsumme der Rechnung, welche in <code>$this->sum</code> gespeichert ist.</p>
<p>Als letztes springen wir wieder in die linke Spalte und geben hier mittels <code>BlockCell</code> die Artikel-Beschreibung aus (Zeile 23). Am Ende generieren wir noch einen Zeilenumbruch.</p>
<p>Nun erstellen wir die beiden Methoden <code>FitCell</code> und <code>Currency</code></p>
<pre><code>private function FitCell($text) {
	$width = $this-&gt;GetStringWidth($text);
	$this-&gt;Cell($width, 0, $text);
}

private function Currency($amount) {
	$euro = floor($amount);
	$cent = round(($amount - $euro) * 100);
	
	if($cent &lt; 10) {
		$cent = '0' . $cent;
	}
	
	$this-&gt;FitCell($euro . ',' . $cent);
}</code></pre>
<p>Die Methode <code>FitCell</code> ermittelt zunöchst mit der Methode <code>GetStringWidth</code> die Breite des darzustellenden Strings. Anschließend wird dieser Text in einer Zelle mit genau dieser Breite dargestellt.</p>
<p>In der Methode <code>Currency</code> ermitteln wir zunächst die Euro- und Cent-Beträge, wobei wir hier auf den ganzen Cent runden. Anschließend sorgen wir dafür, dass der Centbetrag auf jeden Fall zweistellig ist, indem wir gegebenenfalls eine führende Null hinzufügen. Abschließend wird der Betrag mit der Methode <code>FitCell</code> ausgegeben.</p>
<p>Schauen wir uns die Rechnung nun erneut an, sehen wir die Darstellung der Posten. Hat alles Funktioniert? Wunderbar, dann schließen wir die Rechnung jetzt mit der Summe und einer Signatur ab!</p>
<h2>Schritt 11 &#8211; Ende der Rechnung mit Footer</h2>
<p>Erinnerst Du Dich noch an die Methode <code>Header</code>? Diese Methode wird von FPDF am Anfang jeder neuen Seite aufgerufen. Im ersten Teil dieser Serie haben wir diese dazu genutzt, um die Rechnungs-Nummer, das Datum etc. auf die Rechnung zu bringen. Nun werden wir das Äquivalent der Header-Methode für den Seiten-Abschluss, die Methode <code>Footer</code>, dazu nutzen, die Rechnung abzuschließen.</p>
<p>Dazu brauchen wir in der <code>index.php</code> nichts ändern, denn die Methode <code>Footer</code> wird von FPDF auch automatisch aufgerufen. Wir müssen diese jetzt lediglich in unserer Invoice-Klasse bereitstellen.</p>
<pre><code>public function Footer() {
	$this-&gt;SetLineWidth(.2);
	$this-&gt;Line($this-&gt;rightColumnX, $this-&gt;GetY(), $this-&gt;rightColumnX + 25, $this-&gt;GetY());
	
	$this-&gt;ln($this-&gt;rythm);
	$this-&gt;SetX($this-&gt;rightColumnX);
	
	$this-&gt;SetFont('Helvetica', '', 7);
	$this-&gt;FitCell('EUR ');
	
	$this-&gt;SetFont('Helvetica', 'B', 12);
	$this-&gt;Currency($this-&gt;sum);
	
	$this-&gt;ln($this-&gt;rythm);
	$this-&gt;SetX($this-&gt;rightColumnX);
	$this-&gt;SetFont('Helvetica', '', 7);
	$this-&gt;Cell(0, 0, 'Gesamtsumme');
	
	$this-&gt;ln($this-&gt;rythm);
	$this-&gt;SetFont('Helvetica', '', 9);
	
	$this-&gt;Paragraph('Wir wünschen Ihnen viel Spaß mit dem Produkt XYZ.


Mit freundlichen Grüßen,

Ihr Lars Ebert von Advitum.de');
}</code></pre>
<p>Als erstes Zeichen wir eine horizontale Linie in die rechte Spalte, als Summen-Strich. Dazu setzen wir die Linien-Breite in Zeile 2 auf 0,2 Millimeter. Mit der Methode <code>Line</code> erstellen wir die Linie selbst.</p>
<p>Anschließend f+gen wir einen Zeilenumbruch ein, springen in die rechte Spalte und geben hier die Gesamtsumme der Rechnung aus. Hier können wir wieder auf unsere Methode <code>Currency</code> zurückgreifen. Unter der Summe schreiben wir noch »Gesamtsumme« und springen anschließend wieder in die linke Spalte.</p>
<p>Nun geben wir mit der Methode <code>Paragraph</code> noch abschließende Worte und eine Signatur ein.</p>
<p>Schauen wir uns wieder die Rechnung im Frontend an, sehen wir direkt das Resultat. Am Ende wir nun die Rechnungs-Summe ausgegeben.</p>
<h2>Ausblick: Wie geht es weiter?</h2>
<p>Deine Rechnung ist nun fertig. Allerdings sind wir noch nicht am Ende dieser Artikelserie angekommen. Nun, wo Du Dich mit den grundlegenden Funktionen von FPDF vertraut gemacht hast, werden wir uns mit einigen Aspekten detaillierter beschäftigen. Denn die Flexibilität von FPDF hat den Preis, dass man viele Funktionen selbst erstellen muss, wie zum Beispiel den Blocksatz.</p>
<p>Einige Punkte, die wir in Zukunft genauer beleuchten werden, sind die Formatierung von Text mit Blocksatz, Hoch- und Tiefstellung usw. Hier werde ich jeweils im Detail darauf eingehen, wie Du alles sehr kleinschrittig anpassen kannst.</p>
<p>Zum Abschluss habe ich eine Frage an Dich: Welche Funktionen würdest Du gerne genauer behandeln? Schreib mir einfach einen Kommentar!</p>
]]></content:encoded>
			<wfw:commentRss>https://advitum.de/2013/08/fpdf-tutorial-rechnungs-posten-und-mehrspaltiger-text/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>FPDF-Tutorial: Blocksatz und Fließtext</title>
		<link>https://advitum.de/2013/08/fpdf-tutorial-blocksatz-und-fliesstext/</link>
		<comments>https://advitum.de/2013/08/fpdf-tutorial-blocksatz-und-fliesstext/#respond</comments>
		<pubDate>Mon, 19 Aug 2013 07:00:47 +0000</pubDate>
		<dc:creator><![CDATA[Lars Ebert]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programmieren]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Blocksatz]]></category>
		<category><![CDATA[Fließtext]]></category>
		<category><![CDATA[FPDF]]></category>
		<category><![CDATA[PDF]]></category>
		<category><![CDATA[Tutorial]]></category>
		<category><![CDATA[UTF-8]]></category>

		<guid isPermaLink="false">https://advitum.de/?p=2306</guid>
		<description><![CDATA[Im ersten Artikel dieser Serie haben wir angefangen, eine Rechnung automatisch mit PHP zu erstellen. Dazu benutzen wir FPDF. Du weißt jetzt schon, wie man grundsätzlich Text auf die Rechnung bringt. Als nächstes werden wir uns anschauen, wie wir Fließtext auf die Rechnung schreiben können. Im nächsten Artikel werden wir damit beginnnen, die Rechnungsposten anzuzeigen. [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><a href="https://advitum.de/2013/08/fpdf-tutorial-ein-pdf-erstellen-und-text-ausgeben/">Im ersten Artikel dieser Serie haben wir angefangen, eine Rechnung automatisch mit PHP zu erstellen. Dazu benutzen wir FPDF.</a> Du weißt jetzt schon, wie man grundsätzlich Text auf die Rechnung bringt. Als nächstes werden wir uns anschauen, wie wir Fließtext auf die Rechnung schreiben können. Im nächsten Artikel werden wir damit beginnnen, die Rechnungsposten anzuzeigen.<span id="more-2306"></span></p>
<h2>Schritt 7: Eine Methode für Absätze erstellen</h2>
<p>Später werden wir aus der Datei »index.php« heraus Textinhalte auf die Rechnung bringen wollen. Dazu erstellen wir uns jetzt eine neue Methode <code>Paragraph</code>, welche dafür sorgt, dass im PDF Absätze entstehen. Wir beginnen damit, zunächst den Text zu übergeben und dann die Methode selbst zu schreiben:</p>
<pre><code title="index.php" highlight="31-35">&lt;?php
	
	$items = array(
		array(
			'name' =&gt; 'Produkt 1',
			'description' =&gt; 'Lorem ipsum ad qui amet dolore, vitae cetero quaerendum mel ea. Facilis fastidii duo no.',
			'unitprice' =&gt; 5.2,
			'quantity' =&gt; 3
		),
		array(
			'name' =&gt; 'Produkt 2',
			'description' =&gt; 'Lorem ipsum ad qui amet dolore, vitae cetero quaerendum mel ea. Facilis fastidii duo no.',
			'unitprice' =&gt; 1,
			'quantity' =&gt; 9
		),
		array(
			'name' =&gt; 'Produkt 3',
			'description' =&gt; 'Lorem ipsum ad qui amet dolore, vitae cetero quaerendum mel ea. Facilis fastidii duo no.',
			'unitprice' =&gt; .99,
			'quantity' =&gt; 18
		)
	);
	
	$invoiceNumber = date('Ymd') . '-' . rand(0, 100);
	$date = new DateTime();
	
	require_once('invoice.php');
	
	$invoice = new Invoice($invoiceNumber, $date);
	
	$invoice-&gt;Paragraph('Sehr geehrte Frau Mustermann,

vielen Dank für Ihren Einkauf bei Advitum.de. Dies ist Ihre Rechnung!

Lorem ipsum ad qui amet dolore, vitae cetero quaerendum mel ea. Facilis fastidii duo no. Viris partiendo ius no, alia animal nam at. Feugait imperdiet ius an, no quis facer lucilius vis. Aliquam saperet contentiones ex pro, id idque offendit ius. Fugit suavitate ad eam, ut essent debitis cum.');
	
	$invoice-&gt;output();
	
?&gt;</code></pre>
<p>So soll später der Aufruf der Methode aussehen. Nun schreiben wir die Methode:</p>
<pre><code title="invoice.php">public function Paragraph($text) {
	$text = explode(&quot;\n&quot;, $text);
	$this-&gt;SetFont('Helvetica', '', 9);
	
	foreach($text as $cell) {
		$cell = trim($cell);
		
		if($cell != '') {
			$this-&gt;BlockCell($cell, $this-&gt;leftColumnWidth);
		}
		
		$this-&gt;ln($this-&gt;rythm);
	}
	
	$this-&gt;ln($this-&gt;rythm);
}</code></pre>
<p>Die Methode <code>Paragraph</code> teilt mittels <code>explode</code> den Text in einzelne Zeilen auf. Die Textgröße wird auf 9 Pt eingestellt.</p>
<p>Nun werden alle Zeilen durchlaufen. Wenn die Zeile nicht leer ist, wird ihr Text an eine weitere Methode <code>BlockCell</code> übergeben, zusammen mit der Breite der linken Spalte. Diese Methode gibt es momentan noch nicht, aber wir werden sie jetzt erstellen.</p>
<h2>Schritt 8: Blocksatz in FPDF</h2>
<p>Die Theorie hinter dem Blocksatz ist sehr einfach. Zunächst ermitteln wir, wie viele Wörter in die erste Zeile passen. Danach errechnen wir, wie viel Weißraum am Ende übrig bleibt und teilen diesen gleichmäßig auf alle Leerzeichen in der Zeile auf.</p>
<pre><code>private function BlockCell($text, $width) {
	$minSpace = 0.05 * $this-&gt;FontSizePt;
	$words = explode(' ', $text);
	
	$lineWords = array();
	$lineWidth = 0;
	while(count($words) &gt; 0) {
		$word = array_shift($words);
		$wordWidth = $this-&gt;GetStringWidth($word);
		if($lineWidth + $wordWidth + $minSpace &gt;= $width) {
			$deltaWidth = $width - $lineWidth;
			$deltaSpace = $deltaWidth / (count($lineWords) - 1);
			
			foreach($lineWords as $lineWord) {
				$lineWordWidth = $this-&gt;GetStringWidth($lineWord);
				$this-&gt;Cell($lineWordWidth + $minSpace + $deltaSpace, 0, $lineWord);
			}
			
			$lineWords = array();
			$lineWidth = 0;
			
			$this-&gt;ln($this-&gt;rythm);
		}
		
		$lineWords[] = $word;
		$lineWidth += $minSpace + $wordWidth;
	}
	
	$this-&gt;Cell(0, 0, implode(' ', $lineWords));
}</code></pre>
<p>Zunächst legen wir fest, wie breit ein Leerzeichen mindestens sein muss. Dies ist natürlich abhängig von der Schriftgroße, deshalb berechnen wir den Mindestabstand hier mit <code>.05 * $this->FontSizePt</code>. Anschließend teilen wir den String in einzelne Worte auf.</p>
<p>Jetzt gehen wir dieses Array Wort für Wort durch. Mit der Methode <code>GetStringWidth</code> können wir die Breite ermitteln, die ein Text bei aktueller Schriftart und -Größe einnehmen würde. Dazu nehmen wir die Mindestbreite für ein Leerzeichen. In der Variable <code>$lineWidth</code> summieren wir diese Werte auf. Sobald ein Wort nicht mehr in die aktuelle Zeile passt, halten wir inne, um die aktuellen Wörter in die Zeile zu bringen und anschließend eine neue Zeile anzufangen. Hierbei ermitteln wir in <code>$deltaWidth</code>, wie viel Platz uns am Ende der Zeile noch bleibt. In <code>$deltaSpace</code> rechnen wir diese verbleibende Breite nun noch auf die Anzahl der Leerzeichen herunter. Pro Wort rechnen wir auf die Zellbreite nun die Breite des Leerzeichens an.</p>
<p>Am Ende müssen alle Worte, die keine ganze Zeile einnehmen, linksbündig ausgegeben werden.</p>
<figure>
					<img src="https://advitum.de/wp-content/uploads/2013/08/fpdf-2-1.jpg" alt="Und schon ist unser Blocksatz fertig." width="998" height="475" class="size-full wp-image-2309" srcset="https://advitum.de/wp-content/uploads/2013/08/fpdf-2-1.jpg 998w, https://advitum.de/wp-content/uploads/2013/08/fpdf-2-1-350x166.jpg 350w, https://advitum.de/wp-content/uploads/2013/08/fpdf-2-1-960x456.jpg 960w" sizes="(max-width: 998px) 100vw, 998px" />
					<figcaption>Und schon ist unser Blocksatz fertig.</figcaption>
				</figure>
<h2>Schritt 9: Unterstützung von UTF-8</h2>
<p>Wie Dir in Deiner eigenen Rechnung und auf meinem Screenshot bestimmt aufgefallen ist, werden unsere Umlaute nicht korrekt angezeigt. Das liegt daran, dass FPDF noch nicht mit UTF-8 zurechtkommt. Dies können wir aber ganz schnell beheben. Wir müssen einfach dafür sorgen, dass die Texte von UTF-8 umgewandelt werden in einen Zeichensatz, den FPDF verstehen kann.</p>
<pre><code>private function Encode($text) {
	return mb_convert_encoding($text, &quot;ISO-8859-15&quot;, &quot;UTF-8&quot;);
}</code></pre>
<p>Die Funktion <code>mb_convert_encoding</code> sorgt dafür, dass der Text von UTF-8 nach ISO-8859-15 konvertiert wird. So können wir später auch noch weitere Sonderzeichen wie zum Beispiel das €-Zeichen darstellen. Diese Methode müssen wir nun überall aufrufen, wo mit dem Text gearbeitet wird. Konkret sind das die beiden Methoden <code>Cell</code> und <code>GetStringWidth</code>.</p>
<pre><code>public function Cell($w, $h=0, $text='', $border=0, $ln=0, $align='', $fill=false, $link='') {
	$text = $this-&gt;Encode($text);
	
	$startX = $this-&gt;GetX();
	$startY = $this-&gt;GetY();
	
	$this-&gt;SetY($startY - $this-&gt;FontSize / 2 * .6);
	$this-&gt;SetX($startX);
	
	parent::Cell($w, $h, $text, $border, $ln, $align, $fill, $link);
	
	$endX = $this-&gt;GetX();
	$endY = $this-&gt;GetY();
	
	$this-&gt;SetY($startY);
	$this-&gt;SetX($endX);
}

public function GetStringWidth($text) {
	$text = $this-&gt;Encode($text);
	return parent::GetStringWidth($text);
}</code></pre>
<p>In beiden Methoden wandle ich zuerst den Text um, bevor irgendwas sonst geschieht. Die Methode GetStringWidth habe ich weiter nicht verändert, sie ruft nach dem Umwandeln die entsprechende Methode in der Eltern-Klasse auf.</p>
<p>Schaust Du Dir nun Dein PDF erneut an, wirst Du feststellen, dass nun die Sonderzeichen korrekt angezeigt werden können.</p>
<h2>Wie geht es weiter?</h2>
<p>Im nächsten Artikel werden wir die Posten der Rechnung in das PDF schreiben. Dazu schauen wir uns genauer an, wie wir mehrspaltigen Text mit FPDF generieren können.</p>
]]></content:encoded>
			<wfw:commentRss>https://advitum.de/2013/08/fpdf-tutorial-blocksatz-und-fliesstext/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
