solar / FTV / bin / graph.js
graph.js
Raw
function drawGraph(container, points, smoothing, options, valuesScale) {

	const map = (value, inMin, inMax, outMin, outMax) => {

		return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;

	};

	const pointsPositionsCalc = (points, w, h, options) => points.map(e => {

		const x = map(e[0], options.xMin, options.xMax, 0, w)
		const y = map(e[1], options.yMin, options.yMax, h, 0)

		return [x, y]

	});

	const yAxisValuesOffset = 100;


	const svgRender = (w, h) => `<svg viewBox="0 0 ${w} ${h}" version="1.1" xmlns="http://www.w3.org/2000/svg">
			<svg class="g--content" viewBox="0 0 ${w - yAxisValuesOffset} ${h}"></svg>
	</svg>`

	const line = (pointA, pointB) => {

		const lengthX = pointB[0] - pointA[0]
		const lengthY = pointB[1] - pointA[1]

		return {
			length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
			angle: Math.atan2(lengthY, lengthX)
		}
	}

	const controlPoint = (line, smooth) => (current, previous, next, reverse) => {

		const p = previous || current
		const n = next || current
		const l = line(p, n)

		const angle = l.angle + (reverse ? Math.PI : 0)
		const length = l.length * smooth
		const x = current[0] + Math.cos(angle) * length
		const y = current[1] + Math.sin(angle) * length

		return [x, y]

	}

	const bezierCommand = (controlPoint) => (point, i, a) => {

		const cps = controlPoint(a[i - 1], a[i - 2], point)
		const cpe = controlPoint(point, a[i - 1], a[i + 1], true)
		const close = i === a.length - 1 ? ' z':''

		return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}${close}`

	}

	const svgPath = (points, command, h, name, style) => {

		const d = points.reduce((acc, e, i, a) => i === 0
				? `M ${a[a.length - 1][0]},${h} L ${e[0]},${h} L ${e[0]},${e[1]}`
				: `${acc} ${command(e, i, a)}`
		, '')

		var newPathElement = document.createElementNS("http://www.w3.org/2000/svg", 'path');
		newPathElement.setAttribute("d", d);
		newPathElement.setAttribute("style", 'fill: url(#' + name + '-grad); stroke: url(#' + name + '-solid-normal)');
		newPathElement.setAttribute("class", "svg-path");

		if(style.dashed == true) {

			newPathElement.setAttribute("stroke-dasharray", "5, 5");

		}

		return newPathElement;

	}

	function svgCircles(point, name, style) {

		let g = document.createElementNS("http://www.w3.org/2000/svg", 'g');
		g.classList.add('g--circle');

		if(style.labelVisibility == "pop") {
			g.classList.add("pop");
		}

		let newCircleElement = document.createElementNS("http://www.w3.org/2000/svg", 'circle');
		newCircleElement.setAttribute("cx", point[0]);
		newCircleElement.setAttribute("cy", point[1]);
		newCircleElement.setAttribute("r", "5");
		newCircleElement.setAttribute("v-for", "p in pointsPositions");
		newCircleElement.setAttribute("style", "fill: url(#" + name + "-solid-normal)");
		newCircleElement.setAttribute("class", "svg-circles");

		g.append(newCircleElement);

		return g;

	}

	function svgGPoints(points, vals, name, style, unit, precision, valuesScale) {

		let g = document.createElementNS("http://www.w3.org/2000/svg", 'g');
		g.classList.add("g--labels");

		const texts = points.forEach(function(point, i) {

			let gPoint = document.createElementNS("http://www.w3.org/2000/svg", 'g');

			if(style.label == "all" || i % style.label === 2) { // EVERY n-th ITEM SHOULD PASS

				let gPointContent = svgTextValues(point, vals[i], name, style, unit, precision, valuesScale);
				let gPointCircle  = svgCircles(point, name, style);

				gPoint.append(gPointCircle);
				gPoint.append(gPointContent);

				g.append(gPoint);

			}

		});

		return g;

	}

	const offsetX = -60;
	const offsetY = -20;
	const boxWidth = 140;
	let boxHeight = 0;
	function svgTextValues(point, val, name, style, unit, precision, valuesScale) {

		if(!precision) {
			precision = 1;
		}

		let g = document.createElementNS("http://www.w3.org/2000/svg", 'g');
		g.classList.add('g--label');

		let textStr1 = "";
		let textStr2 = ((val[1] / valuesScale).toFixed(precision)) + " " + unit;
		let text = "";

		let textWidth = getTextWidth(textStr2, "normal 16px sans-serif");

		let rectX = point[0] - 3 + offsetX;
		let rectY = point[1] - 15 + offsetY;

		let textX = point[0] + offsetX + (boxWidth - textWidth) / 2;
		let textY = point[1] + offsetY;

		if(val[2]) {

			text = '<text x="' + textX + '" y="' + textY + '">' + val[2] + '</text><text x="' + textX + '" y="' + (textY + 20) + '">' + textStr2 + "</text>";
			boxHeight = 40;

		}else{

			text = '<text x="' + textX + '" y="' + textY + '">' + textStr2 + "</text>";
			boxHeight = 20;

		}

		/*if(i == 0) {

			rectX = rectX + 50;

			textX = textX + 50;

		}*/

		var newRectElement = document.createElementNS("http://www.w3.org/2000/svg", 'rect');
		var newShadowElement = document.createElementNS("http://www.w3.org/2000/svg", 'rect');
		var newTextElement = document.createElementNS("http://www.w3.org/2000/svg", 'g');

		newRectElement.setAttribute("x", rectX);
		newRectElement.setAttribute("y", rectY);
		newRectElement.setAttribute("width", boxWidth);
		newRectElement.setAttribute("height", boxHeight);
		newRectElement.setAttribute("rx", "2");
		newRectElement.setAttribute("style", "fill: url(#" + name + "-solid-normal); stroke: url(#" + name + "-solid-normal)");
		newRectElement.setAttribute("class", "svg-rect");
		newRectElement.setAttribute("filter", "url(#f3)");

		newTextElement.setAttribute("x", textX);
		newTextElement.setAttribute("y", textY);
		newTextElement.setAttribute("class", "svg-text");
		newTextElement.innerHTML = text;

		g.append(newRectElement);
		g.append(newTextElement);

		return g;

	}

	function svgHLines(option, h, unit, precision, valuesScale) {

		if(!precision) {
			precision = 1;
		}

		let g = document.createElementNS("http://www.w3.org/2000/svg", 'g');
		let svgLine = document.createElementNS("http://www.w3.org/2000/svg", 'line');
		let svgText = document.createElementNS("http://www.w3.org/2000/svg", 'text');

		g.classList.add('g--h-lines');

		let nLines = 10;
		let yTextOffset = 5;
		let height = 400; //(h / (options.yMax - options.yMin) * options.yMax);
		let part   = height / nLines;
		let partR  = options.yMax / nLines;

		for(let l = 0; l < nLines; ++l) {

			let curLine = svgLine.cloneNode(true);
			let curText = svgText.cloneNode(true);

			curLine.setAttribute("x1", yAxisValuesOffset / 2);
			curLine.setAttribute("y1", height - part * l);
			curLine.setAttribute("x2", "100%");
			curLine.setAttribute("y2", height - part * l);

			curText.setAttribute("x", 0);
			curText.setAttribute("y", height - part * l + yTextOffset);
			curText.innerHTML = (partR * l / valuesScale).toFixed(precision);

			g.append(curLine);
			g.append(curText);

		}

		return g;

	}

	function svgLinearGradient(name, style) {

		let defs = document.createElementNS("http://www.w3.org/2000/svg", 'defs');

		defs.innerHTML = `<filter id="f3" x="-10%" y="-100%" width="200%" height="400%">
		      <feOffset result="offOut" in="SourceAlpha" dx="0" dy="0" />
		      <feColorMatrix result="matrixOut" in="offOut" type="matrix"
      values="0.2 0 0 0 0 0 0.2 0 0 0 0 0 0.2 0 0 0 0 0 0.2 0" />
		      <feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="4" />
		      <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
		    </filter>`;

		let color = style.color;
		let lighterColor = colorLuminance(color, 0.2);
		let darkerColor  = colorLuminance(color, -0.2);

		let solidN = document.createElementNS("http://www.w3.org/2000/svg", 'linearGradient');
			solidN.setAttribute("id", name + "-solid-normal");
		let solidStopN = document.createElementNS("http://www.w3.org/2000/svg", 'stop');
			solidStopN.setAttribute("stop-color", color);

		solidN.append(solidStopN);

		let solidD = document.createElementNS("http://www.w3.org/2000/svg", 'linearGradient');
			solidD.setAttribute("id", name + "-solid-darker");
		let solidStopD = document.createElementNS("http://www.w3.org/2000/svg", 'stop');
			solidStopD.setAttribute("stop-color", darkerColor);

		solidD.append(solidStopD);

		let solidL = document.createElementNS("http://www.w3.org/2000/svg", 'linearGradient');
			solidL.setAttribute("id", name + "-solid-lighter");
		let solidStopL = document.createElementNS("http://www.w3.org/2000/svg", 'stop');
			solidStopL.setAttribute("stop-color", lighterColor);

		solidL.append(solidStopL);

		let grad = document.createElementNS("http://www.w3.org/2000/svg", 'linearGradient');
			grad.setAttribute("id", name + "-grad");
			grad.setAttribute("x1", "0%");
			grad.setAttribute("y1", "20%");
			grad.setAttribute("x2", "0%");
			grad.setAttribute("y2", "80%");
		let gradStop1 = document.createElementNS("http://www.w3.org/2000/svg", 'stop');
			gradStop1.setAttribute("offset", "0%");
			gradStop1.setAttribute("stop-color", lighterColor);
		let gradStop2 = document.createElementNS("http://www.w3.org/2000/svg", 'stop');
			gradStop2.setAttribute("offset", "100%");
			gradStop2.setAttribute("stop-color", lighterColor + "66");

		grad.append(gradStop1);
		grad.append(gradStop2);

		defs.append(solidN);
		defs.append(solidD);
		defs.append(solidL);
		defs.append(grad);

		return defs;

	}
	
	const w = container.offsetWidth;
	const h = container.offsetHeight;
	
	container.append(parseHTML(svgRender(w, h)));

	const svgMain = container.firstElementChild;
	const svgContent = container.find('.g--content');

	if(!valuesScale) {
		valuesScale = 1;
	}

	const horizontalLines = svgHLines(options, h, "", 1, valuesScale);

	svgMain.append(horizontalLines);

	const resize = (data) => {

		let name   = data.name;
		let points = data.points;
		let style  = data.style;
		let unit   = data.unit;

		const linearGradients = svgLinearGradient(name, style);
		const pointsPositions = pointsPositionsCalc(points, w - yAxisValuesOffset / 2, h, options);
		const bezierCommandCalc = bezierCommand(controlPoint(line, smoothing));
		const path = svgPath(pointsPositions, bezierCommandCalc, h, name, style);

		svgContent.append(linearGradients);
		svgContent.append(path);

		if(data.style.label != "none") {

			const gPoints = svgGPoints(pointsPositions, points, name, style, unit, data.precision, valuesScale);
			//const circles = svgCircles(pointsPositions, name, style)
			//const texts   = svgTextValues(pointsPositions, points, name, style, unit, data.precision)

			//container.firstElementChild.append(circles);
			//container.firstElementChild.append(texts);
			svgContent.append(gPoints);

		}


	}

	//window.addEventListener('resize', resize)
	points.forEach(function(pointsSub) {

		resize(pointsSub)

	});
}