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) }); }