import * as d3 from "d3"; interface IColorInfo { wavelength: number; hexValue: string; } export interface IColors { indigo: IColorInfo; blue: IColorInfo; green: IColorInfo; yellow: IColorInfo; orange: IColorInfo; red: IColorInfo; } export interface IPadding { top: number, right: number, bottom: number, left: number } export enum YAxisSide { Right = 1, Left } export enum XAxisSide { Top = 1, Bottom } export class ChartHelpers { private static colors: IColors = { indigo: { wavelength: 380, hexValue: "#4c36a3" }, blue: { wavelength: 475, hexValue: "#3759c5" }, green: { wavelength: 510, hexValue: "#4cbd3b" }, yellow: { wavelength: 570, hexValue: "#eeed59" }, orange: { wavelength: 610, hexValue: "#ee783f" }, red: { wavelength: 750, hexValue: "#c22827" } }; public static buildColorGradient(svg, id, dataSet) { let gradient = svg .append("defs") .append("linearGradient") .attr("id", id + "gradient") .attr("x1", "0%") .attr("y1", "0%") .attr("x2", "100%") .attr("y2", "0%"); gradient.append("stop") .attr("offset", this.calculateGradientPercentage(0, dataSet)) .attr("stop-color", "#898a8a"); gradient.append("stop") .attr("offset", this.calculateGradientPercentage(this.colors.indigo.wavelength, dataSet)) .attr("stop-color", this.colors.indigo.hexValue); gradient.append("stop") .attr("offset", this.calculateGradientPercentage(this.colors.blue.wavelength, dataSet)) .attr("stop-color", this.colors.blue.hexValue); gradient.append("stop") .attr("offset", this.calculateGradientPercentage(this.colors.green.wavelength, dataSet)) .attr("stop-color", this.colors.green.hexValue); gradient.append("stop") .attr("offset", this.calculateGradientPercentage(this.colors.yellow.wavelength, dataSet)) .attr("stop-color", this.colors.yellow.hexValue); gradient.append("stop") .attr("offset", this.calculateGradientPercentage(this.colors.orange.wavelength, dataSet)) .attr("stop-color", this.colors.orange.hexValue); gradient.append("stop") .attr("offset", this.calculateGradientPercentage(this.colors.red.wavelength, dataSet)) .attr("stop-color", this.colors.red.hexValue); gradient.append("stop") .attr("offset", this.calculateGradientPercentage(820, dataSet)) .attr("stop-color", "#d2989b"); } public static buildOpacityMask(svg, id) { let defs = svg.select("defs"); let gradient = defs .append("linearGradient") .attr("id", id + "opacity-gradient") .attr("x1", "0%") .attr("y1", "0%") .attr("x2", "0%") .attr("y2", "100%"); gradient.append("stop") .attr("offset", "0%") .attr("stop-color", "white"); gradient.append("stop") .attr("offset", "80%") .attr("stop-color", "#aaa"); gradient.append("stop") .attr("offset", "100%") .attr("stop-color", "#666"); let mask = defs .append("mask") .attr("id", id + "opacity-mask") .attr("x", "0") .attr("y", "0") .attr("width", "100%") .attr("height", "100%"); mask.append("rect") .attr("id", "opacity-rect") .attr("fill", "url(#" + id + "opacity-gradient)") .attr("width", "100%") .attr("height", "100%"); } public static buildAreaGraph(svg, id, xScale, yScale, dataSet) : any { let area = d3.area() .x((dataPair: Array) => { return xScale(dataPair[0]); }) .y1((dataPair: Array) => { return yScale(dataPair[1]); }); area.y0(yScale(0)); let zoomAndPanTarget = svg .append("g") .attr("class", "zoom-pan-target"); zoomAndPanTarget.append("path") .attr("class", "area-graph") .datum(dataSet) .attr("stroke", "#000") .attr("stroke-width", 0.3) .attr("fill", "url(#"+id+"gradient)") .attr("mask", "url(#" + id + "opacity-mask)") .attr("d", area); return zoomAndPanTarget; } public static buildLineGraph(svg, xScale, yScale, dataSet, color) : any { let line = d3.line() .x((dataPair: Array) => { return xScale(dataPair[0]); }) .y((dataPair: Array) => { return yScale(dataPair[1]); }); let zoomAndPanTarget = svg .append("g") .attr("class", "zoom-pan-target"); zoomAndPanTarget.append("path") .attr("class", "line-graph") .datum(dataSet) .attr("stroke", color) .attr("stroke-width", 1) .attr("fill", 'none') .attr("d", line); return zoomAndPanTarget; } public static updateLineGraph(element, xScale, yScale, dataSet) : any { let line = d3.line() .x((dataPair: Array) => { return xScale(dataPair[0]); }) .y((dataPair: Array) => { return yScale(dataPair[1]); }); element.select(".line-graph") .datum(dataSet) .attr("d", line); } public static updateAreaGraph(svg, xScale, yScale, dataSet) : any { let area = d3.area() .x((dataPair: Array) => { return xScale(dataPair[0]); }) .y1((dataPair: Array) => { return yScale(dataPair[1]); }); area.y0(yScale(0)); svg.select(".area-graph") .datum(dataSet) .attr("d", area); } public static buildXAxis (svg, xScale, width, height, padding, axisSide: XAxisSide, xLabel) { if(axisSide == XAxisSide.Bottom){ let xAxis = d3.axisBottom(xScale); svg.append("rect") .attr("class", "x-mask") .attr("height", padding.bottom) .attr("width", "100%") .attr("transform", "translate(0," + (height - padding.bottom) + ")") .attr("fill", "white"); svg.append("rect") .attr("class", "x-mask") .attr("height", padding.top) .attr("width", width - padding.right + 1) .attr("fill", "white"); svg.append("g") .attr("class", "x-axis axis") .attr("transform", "translate(0," + (height - padding.bottom) + ")") .call(xAxis) .append("text") .text(xLabel) .attr("text-anchor", "middle") .attr("transform", "translate(" + (width/2 - padding.left) + ",32)") .attr("fill", "#5a5a5a"); return xAxis; } if(axisSide == XAxisSide.Top){ let xAxis = d3.axisTop(xScale); svg.append("rect") .attr("class", "x-mask") .attr("height", padding.top) .attr("width", width - padding.right) .attr("fill", "white"); svg.append("rect") .attr("class", "x-mask") .attr("height", "100%") .attr("width", padding.left) .attr("fill", "white"); svg.append("g") .attr("class", "x-axis axis") .attr("transform", "translate(0," + padding.top + ")") .call(xAxis) .append("text") .text(xLabel) .attr("text-anchor", "middle") .attr("transform", "translate(" + (width/2 - padding.left) + ",-32)") .attr("fill", "#5a5a5a"); svg.append("rect") .attr("class", "x-mask") .attr("height", padding.bottom) .attr("width", "100%") .attr("transform", "translate(0," + (height - padding.bottom) + ")") .attr("fill", "white"); return xAxis; } } public static buildYAxis (svg, yScale, width, height, padding, axisSide: YAxisSide, yLabel, addMasks) { if(axisSide == YAxisSide.Right){ let yAxis = d3.axisRight(yScale); if(addMasks) { svg.append("rect") .attr("class", "y-mask") .attr("height", "100%") .attr("width", padding.right) .attr("transform", "translate(" + (width - padding.right + 1) + ",0)") .attr("fill", "white"); } svg.append("g") .attr("class", "y-axis axis") .attr("transform", "translate(" + (width - padding.right) + ", 0)") .call(yAxis) .append("text") .text(yLabel) .attr("text-anchor", "middle") .attr("transform", "translate(" + padding.right*4/5 + "," + (height/2 - padding.top) + ") rotate(90)") .attr("fill", "#5a5a5a"); if(addMasks) { svg.append("rect") .attr("class", "y-mask") .attr("height", "100%") .attr("width", padding.left) .attr("fill", "white"); } return yAxis; } if(axisSide == YAxisSide.Left){ let yAxis = d3.axisLeft(yScale); svg.append("g") .attr("class", "y-axis axis") .attr("transform", "translate(" + (padding.left) + ", 0)") .call(yAxis) .append("text") .text(yLabel) .attr("text-anchor", "middle") .attr("transform", "translate(" + -padding.left*4/5 + "," + (height/2 - padding.top) + ") rotate(-90)") .attr("fill", "#000"); if(addMasks) { svg.append("rect") .attr("class", "y-mask") .attr("height", "100%") .attr("width", padding.right) .attr("transform", "translate(" + (width - padding.right + 5) + ",0)") .attr("fill", "white"); } return yAxis; } } public static removeXAxis(svg) { svg.selectAll(".x-axis").remove(); svg.selectAll(".x-mask").remove(); } public static removeYAxis(svg) { svg.selectAll(".y-axis").remove(); svg.selectAll(".y-mask").remove(); } public static drawGrid(svg, pixelWidth, pixelHeight, xAxisSide: XAxisSide, yAxisSide: YAxisSide) { if(yAxisSide == YAxisSide.Right) { svg.selectAll(".y-axis .tick line") .attr("x2", -2*pixelWidth) .attr("x1", 6); } if(yAxisSide == YAxisSide.Left) { svg.selectAll(".y-axis .tick line") .attr("x2", 2*pixelWidth) .attr("x1", -6); } if(xAxisSide == XAxisSide.Bottom) { svg.selectAll(".x-axis .tick line") .attr("y2", -2*pixelHeight) .attr("y1", 6); } if(xAxisSide == XAxisSide.Top) { svg.selectAll(".x-axis .tick line") .attr("y2", 2*pixelHeight) .attr("y1", -6); } svg.selectAll(".axis .tick line") .attr("opacity", 0.60) .attr("stroke-dasharray", "1,1") .attr("stroke", "#888"); } private static calculateGradientPercentage (wavelength: number, dataSet: Array>) : string { let minWavelength: number = d3.min(dataSet, (dataPair) => {return dataPair[0]}); let rangeOfWavelengths = d3.max(dataSet, (dataPair) => {return dataPair[0]}) - minWavelength; return (wavelength-minWavelength)/rangeOfWavelengths * 100 + "%"; } }