From fa89713f194e820e5cb4d4996f2a00cc80208853 Mon Sep 17 00:00:00 2001 From: toby Date: Tue, 30 May 2017 14:50:56 -0400 Subject: [PATCH 01/22] Add d3 as a dependency --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 4ad37565..09a08036 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,8 @@ "bootstrap-switch": "^3.3.4", "crypto-api": "^0.6.2", "crypto-js": "^3.1.9-1", + "d3": "^4.9.1", + "d3-hexbin": "^0.2.2", "diff": "^3.2.0", "escodegen": "^1.8.1", "esmangle": "^1.0.1", From 281d558111c5094b828583109fe8417f94abfb1c Mon Sep 17 00:00:00 2001 From: toby Date: Tue, 30 May 2017 14:53:32 -0400 Subject: [PATCH 02/22] Add hex density chart --- src/core/Utils.js | 1 + src/core/config/OperationConfig.js | 39 ++++++ src/core/operations/Charts.js | 205 +++++++++++++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100755 src/core/operations/Charts.js diff --git a/src/core/Utils.js b/src/core/Utils.js index 9b0d2a30..bb05ec3d 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -1021,6 +1021,7 @@ const Utils = { "Comma": ",", "Semi-colon": ";", "Colon": ":", + "Tab": "\t", "Line feed": "\n", "CRLF": "\r\n", "Forward slash": "/", diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 5fd5a9ee..f11809ad 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -5,6 +5,7 @@ import Base64 from "../operations/Base64.js"; import BitwiseOp from "../operations/BitwiseOp.js"; import ByteRepr from "../operations/ByteRepr.js"; import CharEnc from "../operations/CharEnc.js"; +import Charts from "../operations/Charts.js"; import Checksum from "../operations/Checksum.js"; import Cipher from "../operations/Cipher.js"; import Code from "../operations/Code.js"; @@ -3388,6 +3389,44 @@ const OperationConfig = { } ] }, + "Hex Density chart": { + description: [].join("\n"), + run: Charts.runHexDensityChart, + inputType: "string", + outputType: "html", + args: [ + { + name: "Record delimiter", + type: "option", + value: Charts.RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: Charts.FIELD_DELIMITER_OPTIONS, + }, + { + name: "Radius", + type: "number", + value: 25, + }, + { + name: "Use column headers as labels", + type: "boolean", + value: true, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Y label", + type: "string", + value: "", + }, + ] + } }; export default OperationConfig; diff --git a/src/core/operations/Charts.js b/src/core/operations/Charts.js new file mode 100755 index 00000000..a1ab9725 --- /dev/null +++ b/src/core/operations/Charts.js @@ -0,0 +1,205 @@ +import * as d3 from "d3"; +import {hexbin as d3hexbin} from "d3-hexbin"; +import Utils from "../Utils.js"; + +/** + * Charting operations. + * + * @author tlwr [toby@toby.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * @namespace + */ +const Charts = { + /** + * @constant + * @default + */ + RECORD_DELIMITER_OPTIONS: ["Line feed", "CRLF"], + + + /** + * @constant + * @default + */ + FIELD_DELIMITER_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Tab"], + + + /** + * Gets values from input for a scatter plot. + * + * @param {string} input + * @param {string} recordDelimiter + * @param {string} fieldDelimiter + * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record + * @returns {Object[]} + */ + _getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) { + let headings; + const values = []; + + input + .split(recordDelimiter) + .forEach((row, rowIndex) => { + let split = row.split(fieldDelimiter); + + if (split.length !== 2) throw "Each row must have length 2."; + + if (columnHeadingsAreIncluded && rowIndex === 0) { + headings = {}; + headings.x = split[0]; + headings.y = split[1]; + } else { + let x = split[0], + y = split[1]; + + x = parseFloat(x, 10); + if (Number.isNaN(x)) throw "Values must be numbers in base 10."; + + y = parseFloat(y, 10); + if (Number.isNaN(y)) throw "Values must be numbers in base 10."; + + values.push([x, y]); + } + }); + + return { headings, values}; + }, + + + /** + * Hex Bin chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + runHexDensityChart: function (input, args) { + const recordDelimiter = Utils.charRep[args[0]], + fieldDelimiter = Utils.charRep[args[1]], + radius = args[2], + columnHeadingsAreIncluded = args[3], + dimension = 500; + + let xLabel = args[4], + yLabel = args[5], + { headings, values } = Charts._getScatterValues( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + let margin = { + top: 0, + right: 0, + bottom: 30, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + let hexbin = d3hexbin() + .radius(radius) + .extent([0, 0], [width, height]); + + let hexPoints = hexbin(values), + maxCount = Math.max(...hexPoints.map(b => b.length)); + + let xExtent = d3.extent(hexPoints, d => d.x), + yExtent = d3.extent(hexPoints, d => d.y); + xExtent[0] -= 2 * radius; + xExtent[1] += 2 * radius; + yExtent[0] -= 2 * radius; + yExtent[1] += 2 * radius; + + let xAxis = d3.scaleLinear() + .domain(xExtent) + .range([0, width]); + let yAxis = d3.scaleLinear() + .domain(yExtent) + .range([height, 0]); + + let color = d3.scaleSequential(d3.interpolateLab("white", "steelblue")) + .domain([0, maxCount]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + marginedSpace.append("g") + .attr("class", "hexagon") + .attr("clip-path", "url(#clip)") + .selectAll("path") + .data(hexPoints) + .enter() + .append("path") + .attr("d", d => { + return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(radius * 0.75)}`; + }) + .attr("fill", (d) => color(d.length)) + .append("title") + .text(d => { + let count = d.length, + perc = 100.0 * d.length / values.length, + CX = d.x, + CY = d.y, + xMin = Math.min(...d.map(d => d[0])), + xMax = Math.max(...d.map(d => d[0])), + yMin = Math.min(...d.map(d => d[1])), + yMax = Math.max(...d.map(d => d[1])), + tooltip = `Count: ${count}\n + Percentage: ${perc.toFixed(2)}%\n + Center: ${CX.toFixed(2)}, ${CY.toFixed(2)}\n + Min X: ${xMin.toFixed(2)}\n + Max X: ${xMax.toFixed(2)}\n + Min Y: ${yMin.toFixed(2)}\n + Max Y: ${yMax.toFixed(2)} + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + }, +}; + +export default Charts; From 6cdc7d3966e19443c5d4d595ff1218eb04e6fc79 Mon Sep 17 00:00:00 2001 From: toby Date: Tue, 30 May 2017 15:24:23 -0400 Subject: [PATCH 03/22] Hex density: split radius into draw & pack radii --- src/core/config/OperationConfig.js | 7 ++++++- src/core/operations/Charts.js | 21 +++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index f11809ad..db7f5837 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -3406,10 +3406,15 @@ const OperationConfig = { value: Charts.FIELD_DELIMITER_OPTIONS, }, { - name: "Radius", + name: "Pack radius", type: "number", value: 25, }, + { + name: "Draw radius", + type: "number", + value: 15, + }, { name: "Use column headers as labels", type: "boolean", diff --git a/src/core/operations/Charts.js b/src/core/operations/Charts.js index a1ab9725..1c026fb7 100755 --- a/src/core/operations/Charts.js +++ b/src/core/operations/Charts.js @@ -78,12 +78,13 @@ const Charts = { runHexDensityChart: function (input, args) { const recordDelimiter = Utils.charRep[args[0]], fieldDelimiter = Utils.charRep[args[1]], - radius = args[2], - columnHeadingsAreIncluded = args[3], + packRadius = args[2], + drawRadius = args[3], + columnHeadingsAreIncluded = args[4], dimension = 500; - let xLabel = args[4], - yLabel = args[5], + let xLabel = args[5], + yLabel = args[6], { headings, values } = Charts._getScatterValues( input, recordDelimiter, @@ -114,7 +115,7 @@ const Charts = { .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); let hexbin = d3hexbin() - .radius(radius) + .radius(packRadius) .extent([0, 0], [width, height]); let hexPoints = hexbin(values), @@ -122,10 +123,10 @@ const Charts = { let xExtent = d3.extent(hexPoints, d => d.x), yExtent = d3.extent(hexPoints, d => d.y); - xExtent[0] -= 2 * radius; - xExtent[1] += 2 * radius; - yExtent[0] -= 2 * radius; - yExtent[1] += 2 * radius; + xExtent[0] -= 2 * packRadius; + xExtent[1] += 2 * packRadius; + yExtent[0] -= 2 * packRadius; + yExtent[1] += 2 * packRadius; let xAxis = d3.scaleLinear() .domain(xExtent) @@ -151,7 +152,7 @@ const Charts = { .enter() .append("path") .attr("d", d => { - return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(radius * 0.75)}`; + return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; }) .attr("fill", (d) => color(d.length)) .append("title") From dc642be1f53b270f8107b09405f79e5ecd012ef2 Mon Sep 17 00:00:00 2001 From: toby Date: Tue, 30 May 2017 15:49:22 -0400 Subject: [PATCH 04/22] Hex plot: add edge drawing & changing colour opts --- src/core/config/OperationConfig.js | 15 +++++++++++++++ src/core/operations/Charts.js | 22 ++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index db7f5837..ffb75a07 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -3430,6 +3430,21 @@ const OperationConfig = { type: "string", value: "", }, + { + name: "Draw hexagon edges", + type: "boolean", + value: false, + }, + { + name: "Min colour value", + type: "string", + value: Charts.COLOURS.min, + }, + { + name: "Max colour value", + type: "string", + value: Charts.COLOURS.max, + }, ] } }; diff --git a/src/core/operations/Charts.js b/src/core/operations/Charts.js index 1c026fb7..eb8c7efe 100755 --- a/src/core/operations/Charts.js +++ b/src/core/operations/Charts.js @@ -68,6 +68,19 @@ const Charts = { }, + /** + * Default from colour + * + * @constant + * @default + */ + COLOURS: { + min: "white", + max: "black", + }, + + + /** * Hex Bin chart operation. * @@ -81,6 +94,9 @@ const Charts = { packRadius = args[2], drawRadius = args[3], columnHeadingsAreIncluded = args[4], + drawEdges = args[7], + minColour = args[8], + maxColour = args[9], dimension = 500; let xLabel = args[5], @@ -135,7 +151,7 @@ const Charts = { .domain(yExtent) .range([height, 0]); - let color = d3.scaleSequential(d3.interpolateLab("white", "steelblue")) + let colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) .domain([0, maxCount]); marginedSpace.append("clipPath") @@ -154,7 +170,9 @@ const Charts = { .attr("d", d => { return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; }) - .attr("fill", (d) => color(d.length)) + .attr("fill", (d) => colour(d.length)) + .attr("stroke", drawEdges ? "black" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") .append("title") .text(d => { let count = d.length, From b4188db671ec1451c089b0f5416a9aeaf13805ec Mon Sep 17 00:00:00 2001 From: toby Date: Wed, 31 May 2017 14:56:03 -0400 Subject: [PATCH 05/22] Hexagon density: allow dense plotting of hexagons --- src/core/config/OperationConfig.js | 5 +++ src/core/operations/Charts.js | 56 ++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index ffb75a07..ab38b7cf 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -3445,6 +3445,11 @@ const OperationConfig = { type: "string", value: Charts.COLOURS.max, }, + { + name: "Draw empty hexagons within data boundaries", + type: "boolean", + value: false, + }, ] } }; diff --git a/src/core/operations/Charts.js b/src/core/operations/Charts.js index eb8c7efe..447d47b2 100755 --- a/src/core/operations/Charts.js +++ b/src/core/operations/Charts.js @@ -80,6 +80,36 @@ const Charts = { }, + /** + * Hex Bin chart operation. + * + * @param {Object[]} - centres + * @param {number} - radius + * @returns {Object[]} + */ + _getEmptyHexagons(centres, radius) { + const emptyCentres = []; + let boundingRect = [d3.extent(centres, d => d.x), d3.extent(centres, d => d.y)], + indent = false, + hexagonCenterToEdge = Math.cos(2 * Math.PI / 12) * radius, + hexagonEdgeLength = Math.sin(2 * Math.PI / 12) * radius; + + for (let y = boundingRect[1][0]; y <= boundingRect[1][1] + radius; y += hexagonEdgeLength + radius) { + for (let x = boundingRect[0][0]; x <= boundingRect[0][1] + radius; x += 2 * hexagonCenterToEdge) { + let cx = x, + cy = y; + + if (indent && x >= boundingRect[0][1]) break; + if (indent) cx += hexagonCenterToEdge; + + emptyCentres.push({x: cx, y: cy}); + } + indent = !indent; + } + + return emptyCentres; + }, + /** * Hex Bin chart operation. @@ -97,6 +127,7 @@ const Charts = { drawEdges = args[7], minColour = args[8], maxColour = args[9], + drawEmptyHexagons = args[10], dimension = 500; let xLabel = args[5], @@ -160,6 +191,31 @@ const Charts = { .attr("width", width) .attr("height", height); + if (drawEmptyHexagons) { + marginedSpace.append("g") + .attr("class", "empty-hexagon") + .selectAll("path") + .data(Charts._getEmptyHexagons(hexPoints, packRadius)) + .enter() + .append("path") + .attr("d", d => { + return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; + }) + .attr("fill", (d) => colour(0)) + .attr("stroke", drawEdges ? "black" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + let count = 0, + perc = 0, + tooltip = `Count: ${count}\n + Percentage: ${perc.toFixed(2)}%\n + Center: ${d.x.toFixed(2)}, ${d.y.toFixed(2)}\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + } + marginedSpace.append("g") .attr("class", "hexagon") .attr("clip-path", "url(#clip)") From 1c87707a76652642b544ed993a6289b2fc9a4053 Mon Sep 17 00:00:00 2001 From: toby Date: Mon, 5 Jun 2017 10:24:06 -0400 Subject: [PATCH 06/22] Add heatmap chart operation --- src/core/config/OperationConfig.js | 58 ++++++++++ src/core/operations/Charts.js | 176 +++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index ab38b7cf..62ba46e5 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -3451,6 +3451,64 @@ const OperationConfig = { value: false, }, ] + }, + "Heatmap chart": { + description: [].join("\n"), + run: Charts.runHeatmapChart, + inputType: "string", + outputType: "html", + args: [ + { + name: "Record delimiter", + type: "option", + value: Charts.RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: Charts.FIELD_DELIMITER_OPTIONS, + }, + { + name: "Number of vertical bins", + type: "number", + value: 25, + }, + { + name: "Number of horizontal bins", + type: "number", + value: 25, + }, + { + name: "Use column headers as labels", + type: "boolean", + value: true, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Y label", + type: "string", + value: "", + }, + { + name: "Draw bin edges", + type: "boolean", + value: false, + }, + { + name: "Min colour value", + type: "string", + value: Charts.COLOURS.min, + }, + { + name: "Max colour value", + type: "string", + value: Charts.COLOURS.max, + }, + ] } }; diff --git a/src/core/operations/Charts.js b/src/core/operations/Charts.js index 447d47b2..5a927ce3 100755 --- a/src/core/operations/Charts.js +++ b/src/core/operations/Charts.js @@ -275,6 +275,182 @@ const Charts = { return svg._groups[0][0].outerHTML; }, + + + /** + * Packs a list of x, y coordinates into a number of bins for use in a heatmap. + * + * @param {Object[]} points + * @param {number} number of vertical bins + * @param {number} number of horizontal bins + * @returns {Object[]} a list of bins (each bin is an Array) with x y coordinates, filled with the points + */ + _getHeatmapPacking(values, vBins, hBins) { + const xBounds = d3.extent(values, d => d[0]), + yBounds = d3.extent(values, d => d[1]), + bins = []; + + if (xBounds[0] === xBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum X coordinate."; + if (yBounds[0] === yBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum Y coordinate."; + + for (let y = 0; y < vBins; y++) { + bins.push([]); + for (let x = 0; x < hBins; x++) { + let item = []; + item.y = y; + item.x = x; + + bins[y].push(item); + } // x + } // y + + let epsilon = 0.000000001; // This is to clamp values that are exactly the maximum; + + values.forEach(v => { + let fractionOfY = (v[1] - yBounds[0]) / ((yBounds[1] + epsilon) - yBounds[0]), + fractionOfX = (v[0] - xBounds[0]) / ((xBounds[1] + epsilon) - xBounds[0]); + let y = Math.floor(vBins * fractionOfY), + x = Math.floor(hBins * fractionOfX); + + bins[y][x].push({x: v[0], y: v[1]}); + }); + + return bins; + }, + + + /** + * Heatmap chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + runHeatmapChart: function (input, args) { + const recordDelimiter = Utils.charRep[args[0]], + fieldDelimiter = Utils.charRep[args[1]], + vBins = args[2], + hBins = args[3], + columnHeadingsAreIncluded = args[4], + drawEdges = args[7], + minColour = args[8], + maxColour = args[9], + dimension = 500; + + if (vBins <= 0) throw "Number of vertical bins must be greater than 0"; + if (hBins <= 0) throw "Number of horizontal bins must be greater than 0"; + + let xLabel = args[5], + yLabel = args[6], + { headings, values } = Charts._getScatterValues( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + let margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + binWidth = width / hBins, + binHeight = height/ vBins, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + let bins = Charts._getHeatmapPacking(values, vBins, hBins), + maxCount = Math.max(...bins.map(row => { + let lengths = row.map(cell => cell.length); + return Math.max(...lengths); + })); + + let xExtent = d3.extent(values, d => d[0]), + yExtent = d3.extent(values, d => d[1]); + + let xAxis = d3.scaleLinear() + .domain(xExtent) + .range([0, width]); + let yAxis = d3.scaleLinear() + .domain(yExtent) + .range([height, 0]); + + let colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) + .domain([0, maxCount]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + marginedSpace.append("g") + .attr("class", "bins") + .attr("clip-path", "url(#clip)") + .selectAll("g") + .data(bins) + .enter() + .append("g") + .selectAll("rect") + .data(d => d) + .enter() + .append("rect") + .attr("x", (d) => binWidth * d.x) + .attr("y", (d) => (height - binHeight * (d.y + 1))) + .attr("width", binWidth) + .attr("height", binHeight) + .attr("fill", (d) => colour(d.length)) + .attr("stroke", drawEdges ? "rgba(0, 0, 0, 0.5)" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + let count = d.length, + perc = 100.0 * d.length / values.length, + tooltip = `Count: ${count}\n + Percentage: ${perc.toFixed(2)}%\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + }, }; export default Charts; From 594456856592d936711f52a5a6cde5cd937694d5 Mon Sep 17 00:00:00 2001 From: toby Date: Mon, 5 Jun 2017 10:24:15 -0400 Subject: [PATCH 07/22] Change margins in hex density chart --- src/core/operations/Charts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/operations/Charts.js b/src/core/operations/Charts.js index 5a927ce3..2202e0f1 100755 --- a/src/core/operations/Charts.js +++ b/src/core/operations/Charts.js @@ -151,9 +151,9 @@ const Charts = { .attr("viewBox", `0 0 ${dimension} ${dimension}`); let margin = { - top: 0, + top: 10, right: 0, - bottom: 30, + bottom: 40, left: 30, }, width = dimension - margin.left - margin.right, From 247e9bfbdeaa113b37ff1bea35c1db624a71a720 Mon Sep 17 00:00:00 2001 From: toby Date: Mon, 5 Jun 2017 21:47:32 -0400 Subject: [PATCH 08/22] Add "HTML to Text" operation --- src/core/config/OperationConfig.js | 8 ++++++++ src/core/operations/HTML.js | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 62ba46e5..cf8363f8 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -3509,6 +3509,14 @@ const OperationConfig = { value: Charts.COLOURS.max, }, ] + }, + "HTML to Text": { + description: [].join("\n"), + run: HTML.runHTMLToText, + inputType: "html", + outputType: "string", + args: [ + ] } }; diff --git a/src/core/operations/HTML.js b/src/core/operations/HTML.js index 601d6102..457124be 100755 --- a/src/core/operations/HTML.js +++ b/src/core/operations/HTML.js @@ -851,6 +851,16 @@ const HTML = { "diams" : 9830, }, + /** + * HTML to text operation + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runHTMLToText(input, args) { + return input; + }, }; export default HTML; From 49ea532cdc36cb6a7a52ede3cc04b40e771a3d24 Mon Sep 17 00:00:00 2001 From: toby Date: Tue, 6 Jun 2017 09:46:46 -0400 Subject: [PATCH 09/22] Tweak extent of hex density charts --- src/core/operations/Charts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/Charts.js b/src/core/operations/Charts.js index 2202e0f1..e47d26e2 100755 --- a/src/core/operations/Charts.js +++ b/src/core/operations/Charts.js @@ -171,7 +171,7 @@ const Charts = { let xExtent = d3.extent(hexPoints, d => d.x), yExtent = d3.extent(hexPoints, d => d.y); xExtent[0] -= 2 * packRadius; - xExtent[1] += 2 * packRadius; + xExtent[1] += 3 * packRadius; yExtent[0] -= 2 * packRadius; yExtent[1] += 2 * packRadius; From 39ab60088774f5375206209d59cadfbf2e2a84e8 Mon Sep 17 00:00:00 2001 From: toby Date: Tue, 6 Jun 2017 14:01:23 -0400 Subject: [PATCH 10/22] Add scatter plot operation --- src/core/config/OperationConfig.js | 48 ++++++ src/core/operations/Charts.js | 241 +++++++++++++++++++++++++---- 2 files changed, 258 insertions(+), 31 deletions(-) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index cf8363f8..d0565e7c 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -3510,6 +3510,54 @@ const OperationConfig = { }, ] }, + "Scatter chart": { + description: [].join("\n"), + run: Charts.runScatterChart, + inputType: "string", + outputType: "html", + args: [ + { + name: "Record delimiter", + type: "option", + value: Charts.RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: Charts.FIELD_DELIMITER_OPTIONS, + }, + { + name: "Use column headers as labels", + type: "boolean", + value: true, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Y label", + type: "string", + value: "", + }, + { + name: "Colour", + type: "string", + value: Charts.COLOURS.max, + }, + { + name: "Point radius", + type: "number", + value: 10, + }, + { + name: "Use colour from third column", + type: "boolean", + value: false, + }, + ] + }, "HTML to Text": { description: [].join("\n"), run: HTML.runHTMLToText, diff --git a/src/core/operations/Charts.js b/src/core/operations/Charts.js index e47d26e2..06a3cb62 100755 --- a/src/core/operations/Charts.js +++ b/src/core/operations/Charts.js @@ -26,6 +26,49 @@ const Charts = { FIELD_DELIMITER_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Tab"], + /** + * Default from colour + * + * @constant + * @default + */ + COLOURS: { + min: "white", + max: "black", + }, + + + /** + * Gets values from input for a plot. + * + * @param {string} input + * @param {string} recordDelimiter + * @param {string} fieldDelimiter + * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record + * @returns {Object[]} + */ + _getValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded, length) { + let headings; + const values = []; + + input + .split(recordDelimiter) + .forEach((row, rowIndex) => { + let split = row.split(fieldDelimiter); + + if (split.length !== length) throw `Each row must have length ${length}.`; + + if (columnHeadingsAreIncluded && rowIndex === 0) { + headings = split; + } else { + values.push(split); + } + }); + + return { headings, values}; + }, + + /** * Gets values from input for a scatter plot. * @@ -36,47 +79,64 @@ const Charts = { * @returns {Object[]} */ _getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) { - let headings; - const values = []; + let { headings, values } = Charts._getValues( + input, + recordDelimiter, fieldDelimiter, + columnHeadingsAreIncluded, + 2 + ); - input - .split(recordDelimiter) - .forEach((row, rowIndex) => { - let split = row.split(fieldDelimiter); + if (headings) { + headings = {x: headings[0], y: headings[1]}; + } - if (split.length !== 2) throw "Each row must have length 2."; + values = values.map(row => { + let x = parseFloat(row[0], 10), + y = parseFloat(row[1], 10); - if (columnHeadingsAreIncluded && rowIndex === 0) { - headings = {}; - headings.x = split[0]; - headings.y = split[1]; - } else { - let x = split[0], - y = split[1]; + if (Number.isNaN(x)) throw "Values must be numbers in base 10."; + if (Number.isNaN(y)) throw "Values must be numbers in base 10."; - x = parseFloat(x, 10); - if (Number.isNaN(x)) throw "Values must be numbers in base 10."; + return [x, y]; + }); - y = parseFloat(y, 10); - if (Number.isNaN(y)) throw "Values must be numbers in base 10."; - - values.push([x, y]); - } - }); - - return { headings, values}; + return { headings, values }; }, - + /** - * Default from colour + * Gets values from input for a scatter plot with colour from the third column. * - * @constant - * @default + * @param {string} input + * @param {string} recordDelimiter + * @param {string} fieldDelimiter + * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record + * @returns {Object[]} */ - COLOURS: { - min: "white", - max: "black", + _getScatterValuesWithColour(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) { + let { headings, values } = Charts._getValues( + input, + recordDelimiter, fieldDelimiter, + columnHeadingsAreIncluded, + 3 + ); + + if (headings) { + headings = {x: headings[0], y: headings[1]}; + } + + values = values.map(row => { + let x = parseFloat(row[0], 10), + y = parseFloat(row[1], 10), + colour = row[2]; + + if (Number.isNaN(x)) throw "Values must be numbers in base 10."; + if (Number.isNaN(y)) throw "Values must be numbers in base 10."; + + return [x, y, colour]; + }); + + return { headings, values }; }, @@ -451,6 +511,125 @@ const Charts = { return svg._groups[0][0].outerHTML; }, + + + /** + * Scatter chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + runScatterChart: function (input, args) { + const recordDelimiter = Utils.charRep[args[0]], + fieldDelimiter = Utils.charRep[args[1]], + columnHeadingsAreIncluded = args[2], + fillColour = args[5], + radius = args[6], + colourInInput = args[7], + dimension = 500; + + let xLabel = args[3], + yLabel = args[4]; + + let dataFunction = colourInInput ? Charts._getScatterValuesWithColour : Charts._getScatterValues; + + let { headings, values } = dataFunction( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + let margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + let xExtent = d3.extent(values, d => d[0]), + xDelta = xExtent[1] - xExtent[0], + yExtent = d3.extent(values, d => d[1]), + yDelta = yExtent[1] - yExtent[0], + xAxis = d3.scaleLinear() + .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)]) + .range([0, width]), + yAxis = d3.scaleLinear() + .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)]) + .range([height, 0]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + marginedSpace.append("g") + .attr("class", "points") + .attr("clip-path", "url(#clip)") + .selectAll("circle") + .data(values) + .enter() + .append("circle") + .attr("cx", (d) => xAxis(d[0])) + .attr("cy", (d) => yAxis(d[1])) + .attr("r", d => radius) + .attr("fill", d => { + return colourInInput ? d[2] : fillColour; + }) + .attr("stroke", "rgba(0, 0, 0, 0.5)") + .attr("stroke-width", "0.5") + .append("title") + .text(d => { + let x = d[0], + y = d[1], + tooltip = `X: ${x}\n + Y: ${y}\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + }, }; export default Charts; From 6784a1c0276c83e7e35020f3953a6b839c67239d Mon Sep 17 00:00:00 2001 From: toby Date: Tue, 20 Jun 2017 15:25:16 -0400 Subject: [PATCH 11/22] Add Series chart operation --- src/core/config/OperationConfig.js | 33 +++++ src/core/operations/Charts.js | 208 ++++++++++++++++++++++++++++- 2 files changed, 240 insertions(+), 1 deletion(-) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index d0565e7c..f9b5937d 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -3558,6 +3558,39 @@ const OperationConfig = { }, ] }, + "Series chart": { + description: [].join("\n"), + run: Charts.runSeriesChart, + inputType: "string", + outputType: "html", + args: [ + { + name: "Record delimiter", + type: "option", + value: Charts.RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: Charts.FIELD_DELIMITER_OPTIONS, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Point radius", + type: "number", + value: 1, + }, + { + name: "Series colours", + type: "string", + value: "mediumseagreen, dodgerblue, tomato", + }, + ] + }, "HTML to Text": { description: [].join("\n"), run: HTML.runHTMLToText, diff --git a/src/core/operations/Charts.js b/src/core/operations/Charts.js index 06a3cb62..2ce084d0 100755 --- a/src/core/operations/Charts.js +++ b/src/core/operations/Charts.js @@ -103,7 +103,7 @@ const Charts = { return { headings, values }; }, - + /** * Gets values from input for a scatter plot with colour from the third column. * @@ -140,6 +140,50 @@ const Charts = { }, + /** + * Gets values from input for a time series plot. + * + * @param {string} input + * @param {string} recordDelimiter + * @param {string} fieldDelimiter + * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record + * @returns {Object[]} + */ + _getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) { + let { headings, values } = Charts._getValues( + input, + recordDelimiter, fieldDelimiter, + false, + 3 + ); + + let xValues = new Set(), + series = {}; + + values = values.forEach(row => { + let serie = row[0], + xVal = row[1], + val = parseFloat(row[2], 10); + + if (Number.isNaN(val)) throw "Values must be numbers in base 10."; + + xValues.add(xVal); + if (typeof series[serie] === "undefined") series[serie] = {}; + series[serie][xVal] = val; + }); + + xValues = new Array(...xValues); + + const seriesList = []; + for (let seriesName in series) { + let serie = series[seriesName]; + seriesList.push({name: seriesName, data: serie}); + } + + return { xValues, series: seriesList }; + }, + + /** * Hex Bin chart operation. * @@ -630,6 +674,168 @@ const Charts = { return svg._groups[0][0].outerHTML; }, + + + /** + * Series chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + runSeriesChart(input, args) { + const recordDelimiter = Utils.charRep[args[0]], + fieldDelimiter = Utils.charRep[args[1]], + xLabel = args[2], + pipRadius = args[3], + seriesColours = args[4].split(","), + svgWidth = 500, + interSeriesPadding = 20, + xAxisHeight = 50, + seriesLabelWidth = 50, + seriesHeight = 100, + seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding; + + let { xValues, series } = Charts._getSeriesValues(input, recordDelimiter, fieldDelimiter), + allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight), + svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding; + + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); + + let xAxis = d3.scalePoint() + .domain(xValues) + .range([0, seriesWidth]); + + svg.append("g") + .attr("class", "axis axis--x") + .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`) + .call( + d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => { + return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0; + })) + ); + + svg.append("text") + .attr("x", svgWidth / 2) + .attr("y", xAxisHeight / 2) + .style("text-anchor", "middle") + .text(xLabel); + + let tooltipText = {}, + tooltipAreaWidth = seriesWidth / xValues.length; + + xValues.forEach(x => { + let tooltip = []; + + series.forEach(serie => { + let y = serie.data[x]; + if (typeof y === "undefined") return; + + tooltip.push(`${serie.name}: ${y}`); + }); + + tooltipText[x] = tooltip.join("\n"); + }); + + let chartArea = svg.append("g") + .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`); + + chartArea + .append("g") + .selectAll("rect") + .data(xValues) + .enter() + .append("rect") + .attr("x", x => { + return xAxis(x) - (tooltipAreaWidth / 2); + }) + .attr("y", 0) + .attr("width", tooltipAreaWidth) + .attr("height", allSeriesHeight) + .attr("stroke", "none") + .attr("fill", "transparent") + .append("title") + .text(x => { + return `${x}\n + --\n + ${tooltipText[x]}\n + `.replace(/\s{2,}/g, "\n"); + }); + + let yAxesArea = svg.append("g") + .attr("transform", `translate(0, ${xAxisHeight})`); + + series.forEach((serie, seriesIndex) => { + let yExtent = d3.extent(Object.values(serie.data)), + yAxis = d3.scaleLinear() + .domain(yExtent) + .range([seriesHeight, 0]); + + let seriesGroup = chartArea + .append("g") + .attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`); + + let path = ""; + xValues.forEach((x, xIndex) => { + let nextX = xValues[xIndex + 1], + y = serie.data[x], + nextY= serie.data[nextX]; + + if (typeof y === "undefined" || typeof nextY === "undefined") return; + + x = xAxis(x); nextX = xAxis(nextX); + y = yAxis(y); nextY = yAxis(nextY); + + path += `M ${x} ${y} L ${nextX} ${nextY} z `; + }); + + seriesGroup + .append("path") + .attr("d", path) + .attr("fill", "none") + .attr("stroke", seriesColours[seriesIndex % seriesColours.length]) + .attr("stroke-width", "1"); + + xValues.forEach(x => { + let y = serie.data[x]; + if (typeof y === "undefined") return; + + seriesGroup + .append("circle") + .attr("cx", xAxis(x)) + .attr("cy", yAxis(y)) + .attr("r", pipRadius) + .attr("fill", seriesColours[seriesIndex % seriesColours.length]) + .append("title") + .text(d => { + return `${x}\n + --\n + ${tooltipText[x]}\n + `.replace(/\s{2,}/g, "\n"); + }); + }); + + yAxesArea + .append("g") + .attr("transform", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).ticks(5)); + + yAxesArea + .append("g") + .attr("transform", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) + .append("text") + .style("text-anchor", "middle") + .attr("transform", "rotate(-90)") + .text(serie.name); + }); + + return svg._groups[0][0].outerHTML; + }, }; export default Charts; From da2d5674a5a48637cd4f3e530f491aa91257b8e5 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 23 Feb 2019 00:41:19 +0000 Subject: [PATCH 12/22] Ported heatmap and hex density chart ops --- package-lock.json | 414 ++++++++++-- src/core/lib/Charts.mjs | 177 +++++ src/core/operations/Charts.js | 841 ------------------------ src/core/operations/HeatmapChart.mjs | 260 ++++++++ src/core/operations/HexDensityChart.mjs | 287 ++++++++ src/core/operations/legacy/Charts.js | 297 +++++++++ 6 files changed, 1362 insertions(+), 914 deletions(-) create mode 100644 src/core/lib/Charts.mjs delete mode 100755 src/core/operations/Charts.js create mode 100644 src/core/operations/HeatmapChart.mjs create mode 100644 src/core/operations/HexDensityChart.mjs create mode 100755 src/core/operations/legacy/Charts.js diff --git a/package-lock.json b/package-lock.json index 55ad6303..207a3058 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1631,7 +1631,7 @@ }, "array-equal": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, @@ -1716,7 +1716,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -1864,7 +1864,7 @@ }, "axios": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", "dev": true, "requires": { @@ -2334,7 +2334,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -2371,7 +2371,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -2436,7 +2436,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -2590,7 +2590,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -2639,7 +2639,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { "ansi-styles": "^2.2.1", @@ -3172,7 +3172,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -3185,7 +3185,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -3332,7 +3332,7 @@ }, "css-select": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { @@ -3440,6 +3440,266 @@ "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", "dev": true }, + "d3": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-4.13.0.tgz", + "integrity": "sha512-l8c4+0SldjVKLaE2WG++EQlqD7mh/dmQjvi2L2lKPadAVC+TbJC4ci7Uk9bRi+To0+ansgsS0iWfPjD7DBy+FQ==", + "requires": { + "d3-array": "1.2.1", + "d3-axis": "1.0.8", + "d3-brush": "1.0.4", + "d3-chord": "1.0.4", + "d3-collection": "1.0.4", + "d3-color": "1.0.3", + "d3-dispatch": "1.0.3", + "d3-drag": "1.2.1", + "d3-dsv": "1.0.8", + "d3-ease": "1.0.3", + "d3-force": "1.1.0", + "d3-format": "1.2.2", + "d3-geo": "1.9.1", + "d3-hierarchy": "1.1.5", + "d3-interpolate": "1.1.6", + "d3-path": "1.0.5", + "d3-polygon": "1.0.3", + "d3-quadtree": "1.0.3", + "d3-queue": "3.0.7", + "d3-random": "1.1.0", + "d3-request": "1.0.6", + "d3-scale": "1.0.7", + "d3-selection": "1.3.0", + "d3-shape": "1.2.0", + "d3-time": "1.0.8", + "d3-time-format": "2.1.1", + "d3-timer": "1.0.7", + "d3-transition": "1.1.1", + "d3-voronoi": "1.1.2", + "d3-zoom": "1.7.1" + } + }, + "d3-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz", + "integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw==" + }, + "d3-axis": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.8.tgz", + "integrity": "sha1-MacFoLU15ldZ3hQXOjGTMTfxjvo=" + }, + "d3-brush": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.4.tgz", + "integrity": "sha1-AMLyOAGfJPbAoZSibUGhUw/+e8Q=", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "d3-chord": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.4.tgz", + "integrity": "sha1-fexPC6iG9xP+ERxF92NBT290yiw=", + "requires": { + "d3-array": "1", + "d3-path": "1" + } + }, + "d3-collection": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.4.tgz", + "integrity": "sha1-NC39EoN8kJdPM/HMCnha6lcNzcI=" + }, + "d3-color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.0.3.tgz", + "integrity": "sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs=" + }, + "d3-dispatch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz", + "integrity": "sha1-RuFJHqqbWMNY/OW+TovtYm54cfg=" + }, + "d3-drag": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.1.tgz", + "integrity": "sha512-Cg8/K2rTtzxzrb0fmnYOUeZHvwa4PHzwXOLZZPwtEs2SKLLKLXeYwZKBB+DlOxUvFmarOnmt//cU4+3US2lyyQ==", + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-dsv": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.8.tgz", + "integrity": "sha512-IVCJpQ+YGe3qu6odkPQI0KPqfxkhbP/oM1XhhE/DFiYmcXKfCRub4KXyiuehV1d4drjWVXHUWx4gHqhdZb6n/A==", + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "d3-ease": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz", + "integrity": "sha1-aL+8NJM4o4DETYrMT7wzBKotjA4=" + }, + "d3-force": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.0.tgz", + "integrity": "sha512-2HVQz3/VCQs0QeRNZTYb7GxoUCeb6bOzMp/cGcLa87awY9ZsPvXOGeZm0iaGBjXic6I1ysKwMn+g+5jSAdzwcg==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "d3-format": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.2.2.tgz", + "integrity": "sha512-zH9CfF/3C8zUI47nsiKfD0+AGDEuM8LwBIP7pBVpyR4l/sKkZqITmMtxRp04rwBrlshIZ17XeFAaovN3++wzkw==" + }, + "d3-geo": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.9.1.tgz", + "integrity": "sha512-l9wL/cEQkyZQYXw3xbmLsH3eQ5ij+icNfo4r0GrLa5rOCZR/e/3am45IQ0FvQ5uMsv+77zBRunLc9ufTWSQYFA==", + "requires": { + "d3-array": "1" + } + }, + "d3-hexbin": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/d3-hexbin/-/d3-hexbin-0.2.2.tgz", + "integrity": "sha1-nFg32s/UcasFM3qeke8Qv8T5iDE=" + }, + "d3-hierarchy": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz", + "integrity": "sha1-ochFxC+Eoga88cAcAQmOpN2qeiY=" + }, + "d3-interpolate": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.1.6.tgz", + "integrity": "sha512-mOnv5a+pZzkNIHtw/V6I+w9Lqm9L5bG3OTXPM5A+QO0yyVMQ4W1uZhR+VOJmazaOZXri2ppbiZ5BUNWT0pFM9A==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.5.tgz", + "integrity": "sha1-JB6xhJvZ6egCHA0KeZ+KDo5EF2Q=" + }, + "d3-polygon": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.3.tgz", + "integrity": "sha1-FoiOkCZGCTPysXllKtN4Ik04LGI=" + }, + "d3-quadtree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz", + "integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg=" + }, + "d3-queue": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz", + "integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg=" + }, + "d3-random": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.0.tgz", + "integrity": "sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM=" + }, + "d3-request": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-request/-/d3-request-1.0.6.tgz", + "integrity": "sha512-FJj8ySY6GYuAJHZMaCQ83xEYE4KbkPkmxZ3Hu6zA1xxG2GD+z6P+Lyp+zjdsHf0xEbp2xcluDI50rCS855EQ6w==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-dsv": "1", + "xmlhttprequest": "1" + } + }, + "d3-scale": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz", + "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-color": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-selection": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.0.tgz", + "integrity": "sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA==" + }, + "d3-shape": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.0.tgz", + "integrity": "sha1-RdAVOPBkuv0F6j1tLLdI/YxB93c=", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.8.tgz", + "integrity": "sha512-YRZkNhphZh3KcnBfitvF3c6E0JOFGikHZ4YqD+Lzv83ZHn1/u6yGenRU1m+KAk9J1GnZMnKcrtfvSktlA1DXNQ==" + }, + "d3-time-format": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.1.tgz", + "integrity": "sha512-8kAkymq2WMfzW7e+s/IUNAtN/y3gZXGRrdGfo6R8NKPAA85UBTxZg5E61bR6nLwjPjj4d3zywSQe1CkYLPFyrw==", + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.7.tgz", + "integrity": "sha512-vMZXR88XujmG/L5oB96NNKH5lCWwiLM/S2HyyAQLcjWJCloK5shxta4CwOFYLZoY3AWX73v8Lgv4cCAdWtRmOA==" + }, + "d3-transition": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.1.tgz", + "integrity": "sha512-xeg8oggyQ+y5eb4J13iDgKIjUcEfIOZs2BqV/eEmXm2twx80wTzJ4tB4vaZ5BKfz7XsI/DFmQL5me6O27/5ykQ==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", + "integrity": "sha1-Fodmfo8TotFYyAwUgMWinLDYlzw=" + }, + "d3-zoom": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.7.1.tgz", + "integrity": "sha512-sZHQ55DGq5BZBFGnRshUT8tm2sfhPHFnOlmPbbwTkAoPeVdRTkB4Xsf9GCY0TSHrTD8PeJPZGmP/TpGicwJDJQ==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -3700,7 +3960,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -3764,7 +4024,7 @@ "dependencies": { "domelementtype": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", "dev": true }, @@ -3969,7 +4229,7 @@ }, "entities": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", "dev": true }, @@ -4392,7 +4652,7 @@ }, "eventemitter2": { "version": "0.4.14", - "resolved": "http://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", "dev": true }, @@ -4404,7 +4664,7 @@ }, "events": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "dev": true }, @@ -4821,7 +5081,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { @@ -5057,7 +5317,7 @@ }, "fs-extra": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { @@ -5726,7 +5986,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, @@ -5868,7 +6128,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -5945,7 +6205,7 @@ }, "grunt-cli": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", "dev": true, "requires": { @@ -5993,7 +6253,7 @@ "dependencies": { "shelljs": { "version": "0.5.3", - "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", "integrity": "sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM=", "dev": true } @@ -6013,7 +6273,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -6058,7 +6318,7 @@ }, "grunt-contrib-jshint": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz", "integrity": "sha1-Np2QmyWTxA6L55lAshNAhQx5Oaw=", "dev": true, "requires": { @@ -6157,7 +6417,7 @@ "dependencies": { "colors": { "version": "1.1.2", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true } @@ -6221,7 +6481,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -6538,7 +6798,7 @@ }, "htmlparser2": { "version": "3.8.3", - "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", "dev": true, "requires": { @@ -6557,7 +6817,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -6607,7 +6867,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { @@ -6689,7 +6949,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -7053,7 +7312,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -7614,7 +7873,7 @@ }, "jsonfile": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { @@ -7725,7 +7984,7 @@ }, "kew": { "version": "0.7.0", - "resolved": "http://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", "dev": true }, @@ -7844,7 +8103,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -7857,7 +8116,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -8221,7 +8480,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -8280,7 +8539,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { @@ -8501,7 +8760,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -8542,7 +8801,7 @@ "dependencies": { "commander": { "version": "2.15.1", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true, "optional": true @@ -8711,7 +8970,7 @@ }, "ncp": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", "integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=", "dev": true }, @@ -8810,7 +9069,7 @@ "dependencies": { "semver": { "version": "5.3.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true } @@ -8993,7 +9252,7 @@ "dependencies": { "colors": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" }, "underscore": { @@ -9287,13 +9546,13 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, "os-locale": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -9302,7 +9561,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, @@ -9526,7 +9785,7 @@ }, "parse-asn1": { "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { @@ -9612,7 +9871,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, @@ -9653,7 +9912,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -9836,7 +10095,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -10207,7 +10466,7 @@ }, "progress": { "version": "1.1.8", - "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=" }, "promise-inflight": { @@ -10232,13 +10491,13 @@ "dependencies": { "async": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", "dev": true }, "winston": { "version": "2.1.1", - "resolved": "http://registry.npmjs.org/winston/-/winston-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", "dev": true, "requires": { @@ -10253,7 +10512,7 @@ "dependencies": { "colors": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true }, @@ -10476,7 +10735,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -10665,7 +10924,7 @@ "dependencies": { "jsesc": { "version": "0.5.0", - "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true } @@ -10716,7 +10975,7 @@ }, "htmlparser2": { "version": "3.3.0", - "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", "dev": true, "requires": { @@ -10728,7 +10987,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -10973,6 +11232,11 @@ "aproba": "^1.1.1" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, "rxjs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", @@ -10995,7 +11259,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -11005,8 +11269,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sanitize-html": { "version": "1.19.1", @@ -11315,7 +11578,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -11359,7 +11622,7 @@ }, "shelljs": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", "dev": true }, @@ -12080,7 +12343,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -12097,7 +12360,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, @@ -12190,7 +12453,7 @@ }, "tar": { "version": "2.2.1", - "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "dev": true, "requires": { @@ -12348,7 +12611,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -13008,7 +13271,7 @@ "dependencies": { "async": { "version": "0.9.2", - "resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", "dev": true }, @@ -13034,7 +13297,7 @@ }, "valid-data-url": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.6.tgz", "integrity": "sha512-FXg2qXMzfAhZc0y2HzELNfUeiOjPr+52hU1DNBWiJJ2luXD+dD1R9NA48Ug5aj0ibbxroeGDc/RJv6ThiGgkDw==", "dev": true }, @@ -13050,7 +13313,7 @@ }, "validator": { "version": "9.4.1", - "resolved": "http://registry.npmjs.org/validator/-/validator-9.4.1.tgz", + "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==", "dev": true }, @@ -13582,7 +13845,7 @@ }, "webpack-node-externals": { "version": "1.7.2", - "resolved": "http://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", "integrity": "sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg==", "dev": true }, @@ -13736,14 +13999,14 @@ "dependencies": { "async": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", "dev": true, "optional": true }, "colors": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true, "optional": true @@ -13776,7 +14039,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -13885,6 +14148,11 @@ "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + }, "xpath": { "version": "0.0.27", "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", diff --git a/src/core/lib/Charts.mjs b/src/core/lib/Charts.mjs new file mode 100644 index 00000000..8cb9d224 --- /dev/null +++ b/src/core/lib/Charts.mjs @@ -0,0 +1,177 @@ +/** + * @author tlwr [toby@toby.codes] - Original + * @author Matt C [matt@artemisbot.uk] - Conversion to new format + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError"; + + /** + * @constant + * @default + */ +export const RECORD_DELIMITER_OPTIONS = ["Line feed", "CRLF"]; + + +/** + * @constant + * @default + */ +export const FIELD_DELIMITER_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Tab"]; + + +/** + * Default from colour + * + * @constant + * @default + */ +export const COLOURS = { + min: "white", + max: "black" +}; + + +/** + * Gets values from input for a plot. + * + * @param {string} input + * @param {string} recordDelimiter + * @param {string} fieldDelimiter + * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record + * @returns {Object[]} + */ +export function getValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded, length) { + let headings; + const values = []; + + input + .split(recordDelimiter) + .forEach((row, rowIndex) => { + const split = row.split(fieldDelimiter); + + if (split.length !== length) throw new OperationError(`Each row must have length ${length}.`); + + if (columnHeadingsAreIncluded && rowIndex === 0) { + headings = split; + } else { + values.push(split); + } + }); + + return { headings, values}; +} + + +/** + * Gets values from input for a scatter plot. + * + * @param {string} input + * @param {string} recordDelimiter + * @param {string} fieldDelimiter + * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record + * @returns {Object[]} + */ +export function getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) { + let { headings, values } = getValues( + input, + recordDelimiter, fieldDelimiter, + columnHeadingsAreIncluded, + 2 + ); + + if (headings) { + headings = {x: headings[0], y: headings[1]}; + } + + values = values.map(row => { + const x = parseFloat(row[0], 10), + y = parseFloat(row[1], 10); + + if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10."); + if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10."); + + return [x, y]; + }); + + return { headings, values }; +} + +/** + * Gets values from input for a scatter plot with colour from the third column. + * + * @param {string} input + * @param {string} recordDelimiter + * @param {string} fieldDelimiter + * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record + * @returns {Object[]} + */ +export function getScatterValuesWithColour(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) { + let { headings, values } = getValues( + input, + recordDelimiter, fieldDelimiter, + columnHeadingsAreIncluded, + 3 + ); + + if (headings) { + headings = {x: headings[0], y: headings[1]}; + } + + values = values.map(row => { + const x = parseFloat(row[0], 10), + y = parseFloat(row[1], 10), + colour = row[2]; + + if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10."); + if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10."); + + return [x, y, colour]; + }); + + return { headings, values }; +} + +/** + * Gets values from input for a time series plot. + * + * @param {string} input + * @param {string} recordDelimiter + * @param {string} fieldDelimiter + * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record + * @returns {Object[]} + */ +export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) { + const { values } = getValues( + input, + recordDelimiter, fieldDelimiter, + false, + 3 + ); + + let xValues = new Set(); + const series = {}; + + values.forEach(row => { + const serie = row[0], + xVal = row[1], + val = parseFloat(row[2], 10); + + if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10."); + + xValues.add(xVal); + if (typeof series[serie] === "undefined") series[serie] = {}; + series[serie][xVal] = val; + }); + + xValues = new Array(...xValues); + + const seriesList = []; + for (const seriesName in series) { + const serie = series[seriesName]; + seriesList.push({name: seriesName, data: serie}); + } + + return { xValues, series: seriesList }; +} diff --git a/src/core/operations/Charts.js b/src/core/operations/Charts.js deleted file mode 100755 index 2ce084d0..00000000 --- a/src/core/operations/Charts.js +++ /dev/null @@ -1,841 +0,0 @@ -import * as d3 from "d3"; -import {hexbin as d3hexbin} from "d3-hexbin"; -import Utils from "../Utils.js"; - -/** - * Charting operations. - * - * @author tlwr [toby@toby.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace - */ -const Charts = { - /** - * @constant - * @default - */ - RECORD_DELIMITER_OPTIONS: ["Line feed", "CRLF"], - - - /** - * @constant - * @default - */ - FIELD_DELIMITER_OPTIONS: ["Space", "Comma", "Semi-colon", "Colon", "Tab"], - - - /** - * Default from colour - * - * @constant - * @default - */ - COLOURS: { - min: "white", - max: "black", - }, - - - /** - * Gets values from input for a plot. - * - * @param {string} input - * @param {string} recordDelimiter - * @param {string} fieldDelimiter - * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record - * @returns {Object[]} - */ - _getValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded, length) { - let headings; - const values = []; - - input - .split(recordDelimiter) - .forEach((row, rowIndex) => { - let split = row.split(fieldDelimiter); - - if (split.length !== length) throw `Each row must have length ${length}.`; - - if (columnHeadingsAreIncluded && rowIndex === 0) { - headings = split; - } else { - values.push(split); - } - }); - - return { headings, values}; - }, - - - /** - * Gets values from input for a scatter plot. - * - * @param {string} input - * @param {string} recordDelimiter - * @param {string} fieldDelimiter - * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record - * @returns {Object[]} - */ - _getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) { - let { headings, values } = Charts._getValues( - input, - recordDelimiter, fieldDelimiter, - columnHeadingsAreIncluded, - 2 - ); - - if (headings) { - headings = {x: headings[0], y: headings[1]}; - } - - values = values.map(row => { - let x = parseFloat(row[0], 10), - y = parseFloat(row[1], 10); - - if (Number.isNaN(x)) throw "Values must be numbers in base 10."; - if (Number.isNaN(y)) throw "Values must be numbers in base 10."; - - return [x, y]; - }); - - return { headings, values }; - }, - - - /** - * Gets values from input for a scatter plot with colour from the third column. - * - * @param {string} input - * @param {string} recordDelimiter - * @param {string} fieldDelimiter - * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record - * @returns {Object[]} - */ - _getScatterValuesWithColour(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) { - let { headings, values } = Charts._getValues( - input, - recordDelimiter, fieldDelimiter, - columnHeadingsAreIncluded, - 3 - ); - - if (headings) { - headings = {x: headings[0], y: headings[1]}; - } - - values = values.map(row => { - let x = parseFloat(row[0], 10), - y = parseFloat(row[1], 10), - colour = row[2]; - - if (Number.isNaN(x)) throw "Values must be numbers in base 10."; - if (Number.isNaN(y)) throw "Values must be numbers in base 10."; - - return [x, y, colour]; - }); - - return { headings, values }; - }, - - - /** - * Gets values from input for a time series plot. - * - * @param {string} input - * @param {string} recordDelimiter - * @param {string} fieldDelimiter - * @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record - * @returns {Object[]} - */ - _getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) { - let { headings, values } = Charts._getValues( - input, - recordDelimiter, fieldDelimiter, - false, - 3 - ); - - let xValues = new Set(), - series = {}; - - values = values.forEach(row => { - let serie = row[0], - xVal = row[1], - val = parseFloat(row[2], 10); - - if (Number.isNaN(val)) throw "Values must be numbers in base 10."; - - xValues.add(xVal); - if (typeof series[serie] === "undefined") series[serie] = {}; - series[serie][xVal] = val; - }); - - xValues = new Array(...xValues); - - const seriesList = []; - for (let seriesName in series) { - let serie = series[seriesName]; - seriesList.push({name: seriesName, data: serie}); - } - - return { xValues, series: seriesList }; - }, - - - /** - * Hex Bin chart operation. - * - * @param {Object[]} - centres - * @param {number} - radius - * @returns {Object[]} - */ - _getEmptyHexagons(centres, radius) { - const emptyCentres = []; - let boundingRect = [d3.extent(centres, d => d.x), d3.extent(centres, d => d.y)], - indent = false, - hexagonCenterToEdge = Math.cos(2 * Math.PI / 12) * radius, - hexagonEdgeLength = Math.sin(2 * Math.PI / 12) * radius; - - for (let y = boundingRect[1][0]; y <= boundingRect[1][1] + radius; y += hexagonEdgeLength + radius) { - for (let x = boundingRect[0][0]; x <= boundingRect[0][1] + radius; x += 2 * hexagonCenterToEdge) { - let cx = x, - cy = y; - - if (indent && x >= boundingRect[0][1]) break; - if (indent) cx += hexagonCenterToEdge; - - emptyCentres.push({x: cx, y: cy}); - } - indent = !indent; - } - - return emptyCentres; - }, - - - /** - * Hex Bin chart operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runHexDensityChart: function (input, args) { - const recordDelimiter = Utils.charRep[args[0]], - fieldDelimiter = Utils.charRep[args[1]], - packRadius = args[2], - drawRadius = args[3], - columnHeadingsAreIncluded = args[4], - drawEdges = args[7], - minColour = args[8], - maxColour = args[9], - drawEmptyHexagons = args[10], - dimension = 500; - - let xLabel = args[5], - yLabel = args[6], - { headings, values } = Charts._getScatterValues( - input, - recordDelimiter, - fieldDelimiter, - columnHeadingsAreIncluded - ); - - if (headings) { - xLabel = headings.x; - yLabel = headings.y; - } - - let svg = document.createElement("svg"); - svg = d3.select(svg) - .attr("width", "100%") - .attr("height", "100%") - .attr("viewBox", `0 0 ${dimension} ${dimension}`); - - let margin = { - top: 10, - right: 0, - bottom: 40, - left: 30, - }, - width = dimension - margin.left - margin.right, - height = dimension - margin.top - margin.bottom, - marginedSpace = svg.append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - let hexbin = d3hexbin() - .radius(packRadius) - .extent([0, 0], [width, height]); - - let hexPoints = hexbin(values), - maxCount = Math.max(...hexPoints.map(b => b.length)); - - let xExtent = d3.extent(hexPoints, d => d.x), - yExtent = d3.extent(hexPoints, d => d.y); - xExtent[0] -= 2 * packRadius; - xExtent[1] += 3 * packRadius; - yExtent[0] -= 2 * packRadius; - yExtent[1] += 2 * packRadius; - - let xAxis = d3.scaleLinear() - .domain(xExtent) - .range([0, width]); - let yAxis = d3.scaleLinear() - .domain(yExtent) - .range([height, 0]); - - let colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) - .domain([0, maxCount]); - - marginedSpace.append("clipPath") - .attr("id", "clip") - .append("rect") - .attr("width", width) - .attr("height", height); - - if (drawEmptyHexagons) { - marginedSpace.append("g") - .attr("class", "empty-hexagon") - .selectAll("path") - .data(Charts._getEmptyHexagons(hexPoints, packRadius)) - .enter() - .append("path") - .attr("d", d => { - return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; - }) - .attr("fill", (d) => colour(0)) - .attr("stroke", drawEdges ? "black" : "none") - .attr("stroke-width", drawEdges ? "0.5" : "none") - .append("title") - .text(d => { - let count = 0, - perc = 0, - tooltip = `Count: ${count}\n - Percentage: ${perc.toFixed(2)}%\n - Center: ${d.x.toFixed(2)}, ${d.y.toFixed(2)}\n - `.replace(/\s{2,}/g, "\n"); - return tooltip; - }); - } - - marginedSpace.append("g") - .attr("class", "hexagon") - .attr("clip-path", "url(#clip)") - .selectAll("path") - .data(hexPoints) - .enter() - .append("path") - .attr("d", d => { - return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; - }) - .attr("fill", (d) => colour(d.length)) - .attr("stroke", drawEdges ? "black" : "none") - .attr("stroke-width", drawEdges ? "0.5" : "none") - .append("title") - .text(d => { - let count = d.length, - perc = 100.0 * d.length / values.length, - CX = d.x, - CY = d.y, - xMin = Math.min(...d.map(d => d[0])), - xMax = Math.max(...d.map(d => d[0])), - yMin = Math.min(...d.map(d => d[1])), - yMax = Math.max(...d.map(d => d[1])), - tooltip = `Count: ${count}\n - Percentage: ${perc.toFixed(2)}%\n - Center: ${CX.toFixed(2)}, ${CY.toFixed(2)}\n - Min X: ${xMin.toFixed(2)}\n - Max X: ${xMax.toFixed(2)}\n - Min Y: ${yMin.toFixed(2)}\n - Max Y: ${yMax.toFixed(2)} - `.replace(/\s{2,}/g, "\n"); - return tooltip; - }); - - marginedSpace.append("g") - .attr("class", "axis axis--y") - .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); - - svg.append("text") - .attr("transform", "rotate(-90)") - .attr("y", -margin.left) - .attr("x", -(height / 2)) - .attr("dy", "1em") - .style("text-anchor", "middle") - .text(yLabel); - - marginedSpace.append("g") - .attr("class", "axis axis--x") - .attr("transform", "translate(0," + height + ")") - .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); - - svg.append("text") - .attr("x", width / 2) - .attr("y", dimension) - .style("text-anchor", "middle") - .text(xLabel); - - return svg._groups[0][0].outerHTML; - }, - - - /** - * Packs a list of x, y coordinates into a number of bins for use in a heatmap. - * - * @param {Object[]} points - * @param {number} number of vertical bins - * @param {number} number of horizontal bins - * @returns {Object[]} a list of bins (each bin is an Array) with x y coordinates, filled with the points - */ - _getHeatmapPacking(values, vBins, hBins) { - const xBounds = d3.extent(values, d => d[0]), - yBounds = d3.extent(values, d => d[1]), - bins = []; - - if (xBounds[0] === xBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum X coordinate."; - if (yBounds[0] === yBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum Y coordinate."; - - for (let y = 0; y < vBins; y++) { - bins.push([]); - for (let x = 0; x < hBins; x++) { - let item = []; - item.y = y; - item.x = x; - - bins[y].push(item); - } // x - } // y - - let epsilon = 0.000000001; // This is to clamp values that are exactly the maximum; - - values.forEach(v => { - let fractionOfY = (v[1] - yBounds[0]) / ((yBounds[1] + epsilon) - yBounds[0]), - fractionOfX = (v[0] - xBounds[0]) / ((xBounds[1] + epsilon) - xBounds[0]); - let y = Math.floor(vBins * fractionOfY), - x = Math.floor(hBins * fractionOfX); - - bins[y][x].push({x: v[0], y: v[1]}); - }); - - return bins; - }, - - - /** - * Heatmap chart operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runHeatmapChart: function (input, args) { - const recordDelimiter = Utils.charRep[args[0]], - fieldDelimiter = Utils.charRep[args[1]], - vBins = args[2], - hBins = args[3], - columnHeadingsAreIncluded = args[4], - drawEdges = args[7], - minColour = args[8], - maxColour = args[9], - dimension = 500; - - if (vBins <= 0) throw "Number of vertical bins must be greater than 0"; - if (hBins <= 0) throw "Number of horizontal bins must be greater than 0"; - - let xLabel = args[5], - yLabel = args[6], - { headings, values } = Charts._getScatterValues( - input, - recordDelimiter, - fieldDelimiter, - columnHeadingsAreIncluded - ); - - if (headings) { - xLabel = headings.x; - yLabel = headings.y; - } - - let svg = document.createElement("svg"); - svg = d3.select(svg) - .attr("width", "100%") - .attr("height", "100%") - .attr("viewBox", `0 0 ${dimension} ${dimension}`); - - let margin = { - top: 10, - right: 0, - bottom: 40, - left: 30, - }, - width = dimension - margin.left - margin.right, - height = dimension - margin.top - margin.bottom, - binWidth = width / hBins, - binHeight = height/ vBins, - marginedSpace = svg.append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - let bins = Charts._getHeatmapPacking(values, vBins, hBins), - maxCount = Math.max(...bins.map(row => { - let lengths = row.map(cell => cell.length); - return Math.max(...lengths); - })); - - let xExtent = d3.extent(values, d => d[0]), - yExtent = d3.extent(values, d => d[1]); - - let xAxis = d3.scaleLinear() - .domain(xExtent) - .range([0, width]); - let yAxis = d3.scaleLinear() - .domain(yExtent) - .range([height, 0]); - - let colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) - .domain([0, maxCount]); - - marginedSpace.append("clipPath") - .attr("id", "clip") - .append("rect") - .attr("width", width) - .attr("height", height); - - marginedSpace.append("g") - .attr("class", "bins") - .attr("clip-path", "url(#clip)") - .selectAll("g") - .data(bins) - .enter() - .append("g") - .selectAll("rect") - .data(d => d) - .enter() - .append("rect") - .attr("x", (d) => binWidth * d.x) - .attr("y", (d) => (height - binHeight * (d.y + 1))) - .attr("width", binWidth) - .attr("height", binHeight) - .attr("fill", (d) => colour(d.length)) - .attr("stroke", drawEdges ? "rgba(0, 0, 0, 0.5)" : "none") - .attr("stroke-width", drawEdges ? "0.5" : "none") - .append("title") - .text(d => { - let count = d.length, - perc = 100.0 * d.length / values.length, - tooltip = `Count: ${count}\n - Percentage: ${perc.toFixed(2)}%\n - `.replace(/\s{2,}/g, "\n"); - return tooltip; - }); - - marginedSpace.append("g") - .attr("class", "axis axis--y") - .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); - - svg.append("text") - .attr("transform", "rotate(-90)") - .attr("y", -margin.left) - .attr("x", -(height / 2)) - .attr("dy", "1em") - .style("text-anchor", "middle") - .text(yLabel); - - marginedSpace.append("g") - .attr("class", "axis axis--x") - .attr("transform", "translate(0," + height + ")") - .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); - - svg.append("text") - .attr("x", width / 2) - .attr("y", dimension) - .style("text-anchor", "middle") - .text(xLabel); - - return svg._groups[0][0].outerHTML; - }, - - - /** - * Scatter chart operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runScatterChart: function (input, args) { - const recordDelimiter = Utils.charRep[args[0]], - fieldDelimiter = Utils.charRep[args[1]], - columnHeadingsAreIncluded = args[2], - fillColour = args[5], - radius = args[6], - colourInInput = args[7], - dimension = 500; - - let xLabel = args[3], - yLabel = args[4]; - - let dataFunction = colourInInput ? Charts._getScatterValuesWithColour : Charts._getScatterValues; - - let { headings, values } = dataFunction( - input, - recordDelimiter, - fieldDelimiter, - columnHeadingsAreIncluded - ); - - if (headings) { - xLabel = headings.x; - yLabel = headings.y; - } - - let svg = document.createElement("svg"); - svg = d3.select(svg) - .attr("width", "100%") - .attr("height", "100%") - .attr("viewBox", `0 0 ${dimension} ${dimension}`); - - let margin = { - top: 10, - right: 0, - bottom: 40, - left: 30, - }, - width = dimension - margin.left - margin.right, - height = dimension - margin.top - margin.bottom, - marginedSpace = svg.append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - let xExtent = d3.extent(values, d => d[0]), - xDelta = xExtent[1] - xExtent[0], - yExtent = d3.extent(values, d => d[1]), - yDelta = yExtent[1] - yExtent[0], - xAxis = d3.scaleLinear() - .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)]) - .range([0, width]), - yAxis = d3.scaleLinear() - .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)]) - .range([height, 0]); - - marginedSpace.append("clipPath") - .attr("id", "clip") - .append("rect") - .attr("width", width) - .attr("height", height); - - marginedSpace.append("g") - .attr("class", "points") - .attr("clip-path", "url(#clip)") - .selectAll("circle") - .data(values) - .enter() - .append("circle") - .attr("cx", (d) => xAxis(d[0])) - .attr("cy", (d) => yAxis(d[1])) - .attr("r", d => radius) - .attr("fill", d => { - return colourInInput ? d[2] : fillColour; - }) - .attr("stroke", "rgba(0, 0, 0, 0.5)") - .attr("stroke-width", "0.5") - .append("title") - .text(d => { - let x = d[0], - y = d[1], - tooltip = `X: ${x}\n - Y: ${y}\n - `.replace(/\s{2,}/g, "\n"); - return tooltip; - }); - - marginedSpace.append("g") - .attr("class", "axis axis--y") - .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); - - svg.append("text") - .attr("transform", "rotate(-90)") - .attr("y", -margin.left) - .attr("x", -(height / 2)) - .attr("dy", "1em") - .style("text-anchor", "middle") - .text(yLabel); - - marginedSpace.append("g") - .attr("class", "axis axis--x") - .attr("transform", "translate(0," + height + ")") - .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); - - svg.append("text") - .attr("x", width / 2) - .attr("y", dimension) - .style("text-anchor", "middle") - .text(xLabel); - - return svg._groups[0][0].outerHTML; - }, - - - /** - * Series chart operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} - */ - runSeriesChart(input, args) { - const recordDelimiter = Utils.charRep[args[0]], - fieldDelimiter = Utils.charRep[args[1]], - xLabel = args[2], - pipRadius = args[3], - seriesColours = args[4].split(","), - svgWidth = 500, - interSeriesPadding = 20, - xAxisHeight = 50, - seriesLabelWidth = 50, - seriesHeight = 100, - seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding; - - let { xValues, series } = Charts._getSeriesValues(input, recordDelimiter, fieldDelimiter), - allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight), - svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding; - - let svg = document.createElement("svg"); - svg = d3.select(svg) - .attr("width", "100%") - .attr("height", "100%") - .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); - - let xAxis = d3.scalePoint() - .domain(xValues) - .range([0, seriesWidth]); - - svg.append("g") - .attr("class", "axis axis--x") - .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`) - .call( - d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => { - return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0; - })) - ); - - svg.append("text") - .attr("x", svgWidth / 2) - .attr("y", xAxisHeight / 2) - .style("text-anchor", "middle") - .text(xLabel); - - let tooltipText = {}, - tooltipAreaWidth = seriesWidth / xValues.length; - - xValues.forEach(x => { - let tooltip = []; - - series.forEach(serie => { - let y = serie.data[x]; - if (typeof y === "undefined") return; - - tooltip.push(`${serie.name}: ${y}`); - }); - - tooltipText[x] = tooltip.join("\n"); - }); - - let chartArea = svg.append("g") - .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`); - - chartArea - .append("g") - .selectAll("rect") - .data(xValues) - .enter() - .append("rect") - .attr("x", x => { - return xAxis(x) - (tooltipAreaWidth / 2); - }) - .attr("y", 0) - .attr("width", tooltipAreaWidth) - .attr("height", allSeriesHeight) - .attr("stroke", "none") - .attr("fill", "transparent") - .append("title") - .text(x => { - return `${x}\n - --\n - ${tooltipText[x]}\n - `.replace(/\s{2,}/g, "\n"); - }); - - let yAxesArea = svg.append("g") - .attr("transform", `translate(0, ${xAxisHeight})`); - - series.forEach((serie, seriesIndex) => { - let yExtent = d3.extent(Object.values(serie.data)), - yAxis = d3.scaleLinear() - .domain(yExtent) - .range([seriesHeight, 0]); - - let seriesGroup = chartArea - .append("g") - .attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`); - - let path = ""; - xValues.forEach((x, xIndex) => { - let nextX = xValues[xIndex + 1], - y = serie.data[x], - nextY= serie.data[nextX]; - - if (typeof y === "undefined" || typeof nextY === "undefined") return; - - x = xAxis(x); nextX = xAxis(nextX); - y = yAxis(y); nextY = yAxis(nextY); - - path += `M ${x} ${y} L ${nextX} ${nextY} z `; - }); - - seriesGroup - .append("path") - .attr("d", path) - .attr("fill", "none") - .attr("stroke", seriesColours[seriesIndex % seriesColours.length]) - .attr("stroke-width", "1"); - - xValues.forEach(x => { - let y = serie.data[x]; - if (typeof y === "undefined") return; - - seriesGroup - .append("circle") - .attr("cx", xAxis(x)) - .attr("cy", yAxis(y)) - .attr("r", pipRadius) - .attr("fill", seriesColours[seriesIndex % seriesColours.length]) - .append("title") - .text(d => { - return `${x}\n - --\n - ${tooltipText[x]}\n - `.replace(/\s{2,}/g, "\n"); - }); - }); - - yAxesArea - .append("g") - .attr("transform", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) - .attr("class", "axis axis--y") - .call(d3.axisLeft(yAxis).ticks(5)); - - yAxesArea - .append("g") - .attr("transform", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) - .append("text") - .style("text-anchor", "middle") - .attr("transform", "rotate(-90)") - .text(serie.name); - }); - - return svg._groups[0][0].outerHTML; - }, -}; - -export default Charts; diff --git a/src/core/operations/HeatmapChart.mjs b/src/core/operations/HeatmapChart.mjs new file mode 100644 index 00000000..047ce054 --- /dev/null +++ b/src/core/operations/HeatmapChart.mjs @@ -0,0 +1,260 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import * as d3 from "d3"; +import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; + + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import Utils from "../Utils"; + +/** + * Heatmap chart operation + */ +class HeatmapChart extends Operation { + + /** + * HeatmapChart constructor + */ + constructor() { + super(); + + this.name = "Heatmap chart"; + this.module = "Charts"; + this.description = ""; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "Record delimiter", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "Number of vertical bins", + type: "number", + value: 25, + }, + { + name: "Number of horizontal bins", + type: "number", + value: 25, + }, + { + name: "Use column headers as labels", + type: "boolean", + value: true, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Y label", + type: "string", + value: "", + }, + { + name: "Draw bin edges", + type: "boolean", + value: false, + }, + { + name: "Min colour value", + type: "string", + value: COLOURS.min, + }, + { + name: "Max colour value", + type: "string", + value: COLOURS.max, + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep[args[0]], + fieldDelimiter = Utils.charRep[args[1]], + vBins = args[2], + hBins = args[3], + columnHeadingsAreIncluded = args[4], + drawEdges = args[7], + minColour = args[8], + maxColour = args[9], + dimension = 500; + + if (vBins <= 0) throw new OperationError("Number of vertical bins must be greater than 0"); + if (hBins <= 0) throw new OperationError("Number of horizontal bins must be greater than 0"); + + let xLabel = args[5], + yLabel = args[6]; + const { headings, values } = getScatterValues( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + const margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + binWidth = width / hBins, + binHeight = height/ vBins, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + const bins = this.getHeatmapPacking(values, vBins, hBins), + maxCount = Math.max(...bins.map(row => { + const lengths = row.map(cell => cell.length); + return Math.max(...lengths); + })); + + const xExtent = d3.extent(values, d => d[0]), + yExtent = d3.extent(values, d => d[1]); + + const xAxis = d3.scaleLinear() + .domain(xExtent) + .range([0, width]); + const yAxis = d3.scaleLinear() + .domain(yExtent) + .range([height, 0]); + + const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) + .domain([0, maxCount]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + marginedSpace.append("g") + .attr("class", "bins") + .attr("clip-path", "url(#clip)") + .selectAll("g") + .data(bins) + .enter() + .append("g") + .selectAll("rect") + .data(d => d) + .enter() + .append("rect") + .attr("x", (d) => binWidth * d.x) + .attr("y", (d) => (height - binHeight * (d.y + 1))) + .attr("width", binWidth) + .attr("height", binHeight) + .attr("fill", (d) => colour(d.length)) + .attr("stroke", drawEdges ? "rgba(0, 0, 0, 0.5)" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + let count = d.length, + perc = 100.0 * d.length / values.length, + tooltip = `Count: ${count}\n + Percentage: ${perc.toFixed(2)}%\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + } + + /** + * Packs a list of x, y coordinates into a number of bins for use in a heatmap. + * + * @param {Object[]} points + * @param {number} number of vertical bins + * @param {number} number of horizontal bins + * @returns {Object[]} a list of bins (each bin is an Array) with x y coordinates, filled with the points + */ + getHeatmapPacking(values, vBins, hBins) { + const xBounds = d3.extent(values, d => d[0]), + yBounds = d3.extent(values, d => d[1]), + bins = []; + + if (xBounds[0] === xBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum X coordinate."; + if (yBounds[0] === yBounds[1]) throw "Cannot pack points. There is no difference between the minimum and maximum Y coordinate."; + + for (let y = 0; y < vBins; y++) { + bins.push([]); + for (let x = 0; x < hBins; x++) { + const item = []; + item.y = y; + item.x = x; + + bins[y].push(item); + } // x + } // y + + const epsilon = 0.000000001; // This is to clamp values that are exactly the maximum; + + values.forEach(v => { + const fractionOfY = (v[1] - yBounds[0]) / ((yBounds[1] + epsilon) - yBounds[0]), + fractionOfX = (v[0] - xBounds[0]) / ((xBounds[1] + epsilon) - xBounds[0]), + y = Math.floor(vBins * fractionOfY), + x = Math.floor(hBins * fractionOfX); + + bins[y][x].push({x: v[0], y: v[1]}); + }); + + return bins; + } + +} + +export default HeatmapChart; diff --git a/src/core/operations/HexDensityChart.mjs b/src/core/operations/HexDensityChart.mjs new file mode 100644 index 00000000..3d010f13 --- /dev/null +++ b/src/core/operations/HexDensityChart.mjs @@ -0,0 +1,287 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import * as d3 from "d3"; +import * as d3hexbin from "d3-hexbin"; +import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Hex Density chart operation + */ +class HexDensityChart extends Operation { + + /** + * HexDensityChart constructor + */ + constructor() { + super(); + + this.name = "Hex Density chart"; + this.module = "Charts"; + this.description = ""; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "Record delimiter", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "Pack radius", + type: "number", + value: 25, + }, + { + name: "Draw radius", + type: "number", + value: 15, + }, + { + name: "Use column headers as labels", + type: "boolean", + value: true, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Y label", + type: "string", + value: "", + }, + { + name: "Draw hexagon edges", + type: "boolean", + value: false, + }, + { + name: "Min colour value", + type: "string", + value: COLOURS.min, + }, + { + name: "Max colour value", + type: "string", + value: COLOURS.max, + }, + { + name: "Draw empty hexagons within data boundaries", + type: "boolean", + value: false, + } + ]; + } + + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep[args[0]], + fieldDelimiter = Utils.charRep[args[1]], + packRadius = args[2], + drawRadius = args[3], + columnHeadingsAreIncluded = args[4], + drawEdges = args[7], + minColour = args[8], + maxColour = args[9], + drawEmptyHexagons = args[10], + dimension = 500; + + let xLabel = args[5], + yLabel = args[6]; + const { headings, values } = getScatterValues( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + const margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + const hexbin = d3hexbin.hexbin() + .radius(packRadius) + .extent([0, 0], [width, height]); + + const hexPoints = hexbin(values), + maxCount = Math.max(...hexPoints.map(b => b.length)); + + const xExtent = d3.extent(hexPoints, d => d.x), + yExtent = d3.extent(hexPoints, d => d.y); + xExtent[0] -= 2 * packRadius; + xExtent[1] += 3 * packRadius; + yExtent[0] -= 2 * packRadius; + yExtent[1] += 2 * packRadius; + + const xAxis = d3.scaleLinear() + .domain(xExtent) + .range([0, width]); + const yAxis = d3.scaleLinear() + .domain(yExtent) + .range([height, 0]); + + const colour = d3.scaleSequential(d3.interpolateLab(minColour, maxColour)) + .domain([0, maxCount]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + if (drawEmptyHexagons) { + marginedSpace.append("g") + .attr("class", "empty-hexagon") + .selectAll("path") + .data(this.getEmptyHexagons(hexPoints, packRadius)) + .enter() + .append("path") + .attr("d", d => { + return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; + }) + .attr("fill", (d) => colour(0)) + .attr("stroke", drawEdges ? "black" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + const count = 0, + perc = 0, + tooltip = `Count: ${count}\n + Percentage: ${perc.toFixed(2)}%\n + Center: ${d.x.toFixed(2)}, ${d.y.toFixed(2)}\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + } + + marginedSpace.append("g") + .attr("class", "hexagon") + .attr("clip-path", "url(#clip)") + .selectAll("path") + .data(hexPoints) + .enter() + .append("path") + .attr("d", d => { + return `M${xAxis(d.x)},${yAxis(d.y)} ${hexbin.hexagon(drawRadius)}`; + }) + .attr("fill", (d) => colour(d.length)) + .attr("stroke", drawEdges ? "black" : "none") + .attr("stroke-width", drawEdges ? "0.5" : "none") + .append("title") + .text(d => { + const count = d.length, + perc = 100.0 * d.length / values.length, + CX = d.x, + CY = d.y, + xMin = Math.min(...d.map(d => d[0])), + xMax = Math.max(...d.map(d => d[0])), + yMin = Math.min(...d.map(d => d[1])), + yMax = Math.max(...d.map(d => d[1])), + tooltip = `Count: ${count}\n + Percentage: ${perc.toFixed(2)}%\n + Center: ${CX.toFixed(2)}, ${CY.toFixed(2)}\n + Min X: ${xMin.toFixed(2)}\n + Max X: ${xMax.toFixed(2)}\n + Min Y: ${yMin.toFixed(2)}\n + Max Y: ${yMax.toFixed(2)} + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + } + + + /** + * Hex Bin chart operation. + * + * @param {Object[]} - centres + * @param {number} - radius + * @returns {Object[]} + */ + getEmptyHexagons(centres, radius) { + const emptyCentres = [], + boundingRect = [d3.extent(centres, d => d.x), d3.extent(centres, d => d.y)], + hexagonCenterToEdge = Math.cos(2 * Math.PI / 12) * radius, + hexagonEdgeLength = Math.sin(2 * Math.PI / 12) * radius; + let indent = false; + + for (let y = boundingRect[1][0]; y <= boundingRect[1][1] + radius; y += hexagonEdgeLength + radius) { + for (let x = boundingRect[0][0]; x <= boundingRect[0][1] + radius; x += 2 * hexagonCenterToEdge) { + let cx = x; + const cy = y; + + if (indent && x >= boundingRect[0][1]) break; + if (indent) cx += hexagonCenterToEdge; + + emptyCentres.push({x: cx, y: cy}); + } + indent = !indent; + } + + return emptyCentres; + } + +} + +export default HexDensityChart; diff --git a/src/core/operations/legacy/Charts.js b/src/core/operations/legacy/Charts.js new file mode 100755 index 00000000..1d4a5a3b --- /dev/null +++ b/src/core/operations/legacy/Charts.js @@ -0,0 +1,297 @@ +import * as d3 from "d3"; +import Utils from "../Utils.js"; + +/** + * Charting operations. + * + * @author tlwr [toby@toby.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * @namespace + */ +const Charts = { + + + /** + * Scatter chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + runScatterChart: function (input, args) { + const recordDelimiter = Utils.charRep[args[0]], + fieldDelimiter = Utils.charRep[args[1]], + columnHeadingsAreIncluded = args[2], + fillColour = args[5], + radius = args[6], + colourInInput = args[7], + dimension = 500; + + let xLabel = args[3], + yLabel = args[4]; + + let dataFunction = colourInInput ? Charts._getScatterValuesWithColour : Charts._getScatterValues; + + let { headings, values } = dataFunction( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + let margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + let xExtent = d3.extent(values, d => d[0]), + xDelta = xExtent[1] - xExtent[0], + yExtent = d3.extent(values, d => d[1]), + yDelta = yExtent[1] - yExtent[0], + xAxis = d3.scaleLinear() + .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)]) + .range([0, width]), + yAxis = d3.scaleLinear() + .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)]) + .range([height, 0]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + marginedSpace.append("g") + .attr("class", "points") + .attr("clip-path", "url(#clip)") + .selectAll("circle") + .data(values) + .enter() + .append("circle") + .attr("cx", (d) => xAxis(d[0])) + .attr("cy", (d) => yAxis(d[1])) + .attr("r", d => radius) + .attr("fill", d => { + return colourInInput ? d[2] : fillColour; + }) + .attr("stroke", "rgba(0, 0, 0, 0.5)") + .attr("stroke-width", "0.5") + .append("title") + .text(d => { + let x = d[0], + y = d[1], + tooltip = `X: ${x}\n + Y: ${y}\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + }, + + + /** + * Series chart operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + runSeriesChart(input, args) { + const recordDelimiter = Utils.charRep[args[0]], + fieldDelimiter = Utils.charRep[args[1]], + xLabel = args[2], + pipRadius = args[3], + seriesColours = args[4].split(","), + svgWidth = 500, + interSeriesPadding = 20, + xAxisHeight = 50, + seriesLabelWidth = 50, + seriesHeight = 100, + seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding; + + let { xValues, series } = Charts._getSeriesValues(input, recordDelimiter, fieldDelimiter), + allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight), + svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding; + + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); + + let xAxis = d3.scalePoint() + .domain(xValues) + .range([0, seriesWidth]); + + svg.append("g") + .attr("class", "axis axis--x") + .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`) + .call( + d3.axisTop(xAxis).tickValues(xValues.filter((x, i) => { + return [0, Math.round(xValues.length / 2), xValues.length -1].indexOf(i) >= 0; + })) + ); + + svg.append("text") + .attr("x", svgWidth / 2) + .attr("y", xAxisHeight / 2) + .style("text-anchor", "middle") + .text(xLabel); + + let tooltipText = {}, + tooltipAreaWidth = seriesWidth / xValues.length; + + xValues.forEach(x => { + let tooltip = []; + + series.forEach(serie => { + let y = serie.data[x]; + if (typeof y === "undefined") return; + + tooltip.push(`${serie.name}: ${y}`); + }); + + tooltipText[x] = tooltip.join("\n"); + }); + + let chartArea = svg.append("g") + .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`); + + chartArea + .append("g") + .selectAll("rect") + .data(xValues) + .enter() + .append("rect") + .attr("x", x => { + return xAxis(x) - (tooltipAreaWidth / 2); + }) + .attr("y", 0) + .attr("width", tooltipAreaWidth) + .attr("height", allSeriesHeight) + .attr("stroke", "none") + .attr("fill", "transparent") + .append("title") + .text(x => { + return `${x}\n + --\n + ${tooltipText[x]}\n + `.replace(/\s{2,}/g, "\n"); + }); + + let yAxesArea = svg.append("g") + .attr("transform", `translate(0, ${xAxisHeight})`); + + series.forEach((serie, seriesIndex) => { + let yExtent = d3.extent(Object.values(serie.data)), + yAxis = d3.scaleLinear() + .domain(yExtent) + .range([seriesHeight, 0]); + + let seriesGroup = chartArea + .append("g") + .attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`); + + let path = ""; + xValues.forEach((x, xIndex) => { + let nextX = xValues[xIndex + 1], + y = serie.data[x], + nextY= serie.data[nextX]; + + if (typeof y === "undefined" || typeof nextY === "undefined") return; + + x = xAxis(x); nextX = xAxis(nextX); + y = yAxis(y); nextY = yAxis(nextY); + + path += `M ${x} ${y} L ${nextX} ${nextY} z `; + }); + + seriesGroup + .append("path") + .attr("d", path) + .attr("fill", "none") + .attr("stroke", seriesColours[seriesIndex % seriesColours.length]) + .attr("stroke-width", "1"); + + xValues.forEach(x => { + let y = serie.data[x]; + if (typeof y === "undefined") return; + + seriesGroup + .append("circle") + .attr("cx", xAxis(x)) + .attr("cy", yAxis(y)) + .attr("r", pipRadius) + .attr("fill", seriesColours[seriesIndex % seriesColours.length]) + .append("title") + .text(d => { + return `${x}\n + --\n + ${tooltipText[x]}\n + `.replace(/\s{2,}/g, "\n"); + }); + }); + + yAxesArea + .append("g") + .attr("transform", `translate(${seriesLabelWidth - interSeriesPadding}, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).ticks(5)); + + yAxesArea + .append("g") + .attr("transform", `translate(0, ${seriesHeight / 2 + seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`) + .append("text") + .style("text-anchor", "middle") + .attr("transform", "rotate(-90)") + .text(serie.name); + }); + + return svg._groups[0][0].outerHTML; + }, +}; + +export default Charts; From 4ae875601a317e8499c28e093cde61b27ce8e29c Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 10 Mar 2019 10:33:47 +0000 Subject: [PATCH 13/22] Ported final two chart operations --- src/core/lib/Charts.mjs | 2 +- src/core/operations/ScatterChart.mjs | 192 +++++++++++++++++ .../{legacy/Charts.js => SeriesChart.mjs} | 203 ++++++------------ 3 files changed, 256 insertions(+), 141 deletions(-) create mode 100644 src/core/operations/ScatterChart.mjs rename src/core/operations/{legacy/Charts.js => SeriesChart.mjs} (53%) mode change 100755 => 100644 diff --git a/src/core/lib/Charts.mjs b/src/core/lib/Charts.mjs index 8cb9d224..52585505 100644 --- a/src/core/lib/Charts.mjs +++ b/src/core/lib/Charts.mjs @@ -1,6 +1,6 @@ /** * @author tlwr [toby@toby.codes] - Original - * @author Matt C [matt@artemisbot.uk] - Conversion to new format + * @author Matt C [me@mitt.dev] - Conversion to new format * @copyright Crown Copyright 2019 * @license Apache-2.0 */ diff --git a/src/core/operations/ScatterChart.mjs b/src/core/operations/ScatterChart.mjs new file mode 100644 index 00000000..acd527cf --- /dev/null +++ b/src/core/operations/ScatterChart.mjs @@ -0,0 +1,192 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import * as d3 from "d3"; +import { getScatterValues, getScatterValuesWithColour, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; + +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Scatter chart operation + */ +class ScatterChart extends Operation { + + /** + * ScatterChart constructor + */ + constructor() { + super(); + + this.name = "Scatter chart"; + this.module = "Charts"; + this.description = ""; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "Record delimiter", + type: "option", + value: RECORD_DELIMITER_OPTIONS, + }, + { + name: "Field delimiter", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "Use column headers as labels", + type: "boolean", + value: true, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Y label", + type: "string", + value: "", + }, + { + name: "Colour", + type: "string", + value: COLOURS.max, + }, + { + name: "Point radius", + type: "number", + value: 10, + }, + { + name: "Use colour from third column", + type: "boolean", + value: false, + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const recordDelimiter = Utils.charRep[args[0]], + fieldDelimiter = Utils.charRep[args[1]], + columnHeadingsAreIncluded = args[2], + fillColour = args[5], + radius = args[6], + colourInInput = args[7], + dimension = 500; + + let xLabel = args[3], + yLabel = args[4]; + + const dataFunction = colourInInput ? getScatterValuesWithColour : getScatterValues; + + const { headings, values } = dataFunction( + input, + recordDelimiter, + fieldDelimiter, + columnHeadingsAreIncluded + ); + + if (headings) { + xLabel = headings.x; + yLabel = headings.y; + } + + let svg = document.createElement("svg"); + svg = d3.select(svg) + .attr("width", "100%") + .attr("height", "100%") + .attr("viewBox", `0 0 ${dimension} ${dimension}`); + + const margin = { + top: 10, + right: 0, + bottom: 40, + left: 30, + }, + width = dimension - margin.left - margin.right, + height = dimension - margin.top - margin.bottom, + marginedSpace = svg.append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + const xExtent = d3.extent(values, d => d[0]), + xDelta = xExtent[1] - xExtent[0], + yExtent = d3.extent(values, d => d[1]), + yDelta = yExtent[1] - yExtent[0], + xAxis = d3.scaleLinear() + .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)]) + .range([0, width]), + yAxis = d3.scaleLinear() + .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)]) + .range([height, 0]); + + marginedSpace.append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + + marginedSpace.append("g") + .attr("class", "points") + .attr("clip-path", "url(#clip)") + .selectAll("circle") + .data(values) + .enter() + .append("circle") + .attr("cx", (d) => xAxis(d[0])) + .attr("cy", (d) => yAxis(d[1])) + .attr("r", d => radius) + .attr("fill", d => { + return colourInInput ? d[2] : fillColour; + }) + .attr("stroke", "rgba(0, 0, 0, 0.5)") + .attr("stroke-width", "0.5") + .append("title") + .text(d => { + const x = d[0], + y = d[1], + tooltip = `X: ${x}\n + Y: ${y}\n + `.replace(/\s{2,}/g, "\n"); + return tooltip; + }); + + marginedSpace.append("g") + .attr("class", "axis axis--y") + .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); + + svg.append("text") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left) + .attr("x", -(height / 2)) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text(yLabel); + + marginedSpace.append("g") + .attr("class", "axis axis--x") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); + + svg.append("text") + .attr("x", width / 2) + .attr("y", dimension) + .style("text-anchor", "middle") + .text(xLabel); + + return svg._groups[0][0].outerHTML; + } + +} + +export default ScatterChart; diff --git a/src/core/operations/legacy/Charts.js b/src/core/operations/SeriesChart.mjs old mode 100755 new mode 100644 similarity index 53% rename from src/core/operations/legacy/Charts.js rename to src/core/operations/SeriesChart.mjs index 1d4a5a3b..34dd6ec0 --- a/src/core/operations/legacy/Charts.js +++ b/src/core/operations/SeriesChart.mjs @@ -1,145 +1,67 @@ +/** + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + import * as d3 from "d3"; -import Utils from "../Utils.js"; +import { getSeriesValues, RECORD_DELIMITER_OPTIONS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; + +import Operation from "../Operation"; +import Utils from "../Utils"; /** - * Charting operations. - * - * @author tlwr [toby@toby.com] - * @copyright Crown Copyright 2016 - * @license Apache-2.0 - * - * @namespace + * Series chart operation */ -const Charts = { - +class SeriesChart extends Operation { /** - * Scatter chart operation. - * - * @param {string} input - * @param {Object[]} args - * @returns {html} + * SeriesChart constructor */ - runScatterChart: function (input, args) { - const recordDelimiter = Utils.charRep[args[0]], - fieldDelimiter = Utils.charRep[args[1]], - columnHeadingsAreIncluded = args[2], - fillColour = args[5], - radius = args[6], - colourInInput = args[7], - dimension = 500; + constructor() { + super(); - let xLabel = args[3], - yLabel = args[4]; - - let dataFunction = colourInInput ? Charts._getScatterValuesWithColour : Charts._getScatterValues; - - let { headings, values } = dataFunction( - input, - recordDelimiter, - fieldDelimiter, - columnHeadingsAreIncluded - ); - - if (headings) { - xLabel = headings.x; - yLabel = headings.y; - } - - let svg = document.createElement("svg"); - svg = d3.select(svg) - .attr("width", "100%") - .attr("height", "100%") - .attr("viewBox", `0 0 ${dimension} ${dimension}`); - - let margin = { - top: 10, - right: 0, - bottom: 40, - left: 30, + this.name = "Series chart"; + this.module = "Charts"; + this.description = ""; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "html"; + this.args = [ + { + name: "Record delimiter", + type: "option", + value: RECORD_DELIMITER_OPTIONS, }, - width = dimension - margin.left - margin.right, - height = dimension - margin.top - margin.bottom, - marginedSpace = svg.append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - let xExtent = d3.extent(values, d => d[0]), - xDelta = xExtent[1] - xExtent[0], - yExtent = d3.extent(values, d => d[1]), - yDelta = yExtent[1] - yExtent[0], - xAxis = d3.scaleLinear() - .domain([xExtent[0] - (0.1 * xDelta), xExtent[1] + (0.1 * xDelta)]) - .range([0, width]), - yAxis = d3.scaleLinear() - .domain([yExtent[0] - (0.1 * yDelta), yExtent[1] + (0.1 * yDelta)]) - .range([height, 0]); - - marginedSpace.append("clipPath") - .attr("id", "clip") - .append("rect") - .attr("width", width) - .attr("height", height); - - marginedSpace.append("g") - .attr("class", "points") - .attr("clip-path", "url(#clip)") - .selectAll("circle") - .data(values) - .enter() - .append("circle") - .attr("cx", (d) => xAxis(d[0])) - .attr("cy", (d) => yAxis(d[1])) - .attr("r", d => radius) - .attr("fill", d => { - return colourInInput ? d[2] : fillColour; - }) - .attr("stroke", "rgba(0, 0, 0, 0.5)") - .attr("stroke-width", "0.5") - .append("title") - .text(d => { - let x = d[0], - y = d[1], - tooltip = `X: ${x}\n - Y: ${y}\n - `.replace(/\s{2,}/g, "\n"); - return tooltip; - }); - - marginedSpace.append("g") - .attr("class", "axis axis--y") - .call(d3.axisLeft(yAxis).tickSizeOuter(-width)); - - svg.append("text") - .attr("transform", "rotate(-90)") - .attr("y", -margin.left) - .attr("x", -(height / 2)) - .attr("dy", "1em") - .style("text-anchor", "middle") - .text(yLabel); - - marginedSpace.append("g") - .attr("class", "axis axis--x") - .attr("transform", "translate(0," + height + ")") - .call(d3.axisBottom(xAxis).tickSizeOuter(-height)); - - svg.append("text") - .attr("x", width / 2) - .attr("y", dimension) - .style("text-anchor", "middle") - .text(xLabel); - - return svg._groups[0][0].outerHTML; - }, - + { + name: "Field delimiter", + type: "option", + value: FIELD_DELIMITER_OPTIONS, + }, + { + name: "X label", + type: "string", + value: "", + }, + { + name: "Point radius", + type: "number", + value: 1, + }, + { + name: "Series colours", + type: "string", + value: "mediumseagreen, dodgerblue, tomato", + }, + ]; + } /** - * Series chart operation. - * * @param {string} input * @param {Object[]} args * @returns {html} */ - runSeriesChart(input, args) { + run(input, args) { const recordDelimiter = Utils.charRep[args[0]], fieldDelimiter = Utils.charRep[args[1]], xLabel = args[2], @@ -152,7 +74,7 @@ const Charts = { seriesHeight = 100, seriesWidth = svgWidth - seriesLabelWidth - interSeriesPadding; - let { xValues, series } = Charts._getSeriesValues(input, recordDelimiter, fieldDelimiter), + const { xValues, series } = getSeriesValues(input, recordDelimiter, fieldDelimiter), allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight), svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding; @@ -162,7 +84,7 @@ const Charts = { .attr("height", "100%") .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`); - let xAxis = d3.scalePoint() + const xAxis = d3.scalePoint() .domain(xValues) .range([0, seriesWidth]); @@ -181,14 +103,14 @@ const Charts = { .style("text-anchor", "middle") .text(xLabel); - let tooltipText = {}, + const tooltipText = {}, tooltipAreaWidth = seriesWidth / xValues.length; xValues.forEach(x => { - let tooltip = []; + const tooltip = []; series.forEach(serie => { - let y = serie.data[x]; + const y = serie.data[x]; if (typeof y === "undefined") return; tooltip.push(`${serie.name}: ${y}`); @@ -197,7 +119,7 @@ const Charts = { tooltipText[x] = tooltip.join("\n"); }); - let chartArea = svg.append("g") + const chartArea = svg.append("g") .attr("transform", `translate(${seriesLabelWidth}, ${xAxisHeight})`); chartArea @@ -222,16 +144,16 @@ const Charts = { `.replace(/\s{2,}/g, "\n"); }); - let yAxesArea = svg.append("g") + const yAxesArea = svg.append("g") .attr("transform", `translate(0, ${xAxisHeight})`); series.forEach((serie, seriesIndex) => { - let yExtent = d3.extent(Object.values(serie.data)), + const yExtent = d3.extent(Object.values(serie.data)), yAxis = d3.scaleLinear() .domain(yExtent) .range([seriesHeight, 0]); - let seriesGroup = chartArea + const seriesGroup = chartArea .append("g") .attr("transform", `translate(0, ${seriesHeight * seriesIndex + interSeriesPadding * (seriesIndex + 1)})`); @@ -257,7 +179,7 @@ const Charts = { .attr("stroke-width", "1"); xValues.forEach(x => { - let y = serie.data[x]; + const y = serie.data[x]; if (typeof y === "undefined") return; seriesGroup @@ -291,7 +213,8 @@ const Charts = { }); return svg._groups[0][0].outerHTML; - }, -}; + } -export default Charts; +} + +export default SeriesChart; From f8874fc58665cee30b7f77f49323a20fdb453d87 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 10 Mar 2019 11:44:02 +0000 Subject: [PATCH 14/22] Actually made operations work (and made the module 8MB) Unfortunately they need jsdom --- package-lock.json | 376 ++++++++++++------------ package.json | 3 +- src/core/lib/Charts.mjs | 2 - src/core/operations/HeatmapChart.mjs | 6 +- src/core/operations/HexDensityChart.mjs | 6 +- src/core/operations/ScatterChart.mjs | 8 +- src/core/operations/SeriesChart.mjs | 6 +- webpack.config.js | 5 +- 8 files changed, 211 insertions(+), 201 deletions(-) diff --git a/package-lock.json b/package-lock.json index 207a3058..b2311d3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1334,8 +1334,7 @@ "abab": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", - "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", - "dev": true + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" }, "abbrev": { "version": "1.1.1", @@ -1374,6 +1373,12 @@ "validator": "^9.4.1" }, "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1394,6 +1399,46 @@ "supports-color": "^5.3.0" } }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1408,14 +1453,33 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } } } }, "acorn": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", - "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", - "dev": true + "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==" }, "acorn-dynamic-import": { "version": "3.0.0", @@ -1438,7 +1502,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", - "dev": true, "requires": { "acorn": "^6.0.1", "acorn-walk": "^6.0.1" @@ -1453,8 +1516,7 @@ "acorn-walk": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", - "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", - "dev": true + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==" }, "agent-base": { "version": "4.2.1", @@ -1480,7 +1542,6 @@ "version": "6.5.5", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", - "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -1632,8 +1693,7 @@ "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" }, "array-find-index": { "version": "1.0.2", @@ -1683,7 +1743,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -1728,8 +1787,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { "version": "1.1.0", @@ -1779,14 +1837,12 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.2", @@ -1853,14 +1909,12 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "axios": { "version": "0.18.0", @@ -2098,7 +2152,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, "requires": { "tweetnacl": "^0.14.3" }, @@ -2106,8 +2159,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" } } }, @@ -2322,8 +2374,7 @@ "browser-process-hrtime": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", - "dev": true + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" }, "browser-stdout": { "version": "1.3.1", @@ -2607,8 +2658,7 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "catharsis": { "version": "0.8.9", @@ -2894,7 +2944,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -3133,8 +3182,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "4.0.0", @@ -3400,16 +3448,14 @@ "dev": true }, "cssom": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", - "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", - "dev": true + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==" }, "cssstyle": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", - "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", - "dev": true, + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.1.tgz", + "integrity": "sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A==", "requires": { "cssom": "0.3.x" } @@ -3704,7 +3750,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -3719,24 +3764,10 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, "requires": { "abab": "^2.0.0", "whatwg-mimetype": "^2.2.0", "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } } }, "datauri": { @@ -3916,8 +3947,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", @@ -4057,7 +4087,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, "requires": { "webidl-conversions": "^4.0.2" } @@ -4140,7 +4169,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -4149,8 +4177,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" } } }, @@ -4815,8 +4842,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -4930,8 +4956,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "eyes": { "version": "0.1.8", @@ -4942,14 +4967,12 @@ "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-levenshtein": { "version": "2.0.6", @@ -5238,14 +5261,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -6052,7 +6073,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -6516,14 +6536,12 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -6714,7 +6732,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, "requires": { "whatwg-encoding": "^1.0.1" } @@ -6881,7 +6898,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -7511,8 +7527,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-url": { "version": "1.2.4", @@ -7570,8 +7585,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jimp": { "version": "0.6.0", @@ -7753,44 +7767,46 @@ "dev": true }, "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "dev": true, + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-14.0.0.tgz", + "integrity": "sha512-/VkyPmdtbwqpJSkwDx3YyJ3U1oawYNB/h5z8vTUZGAzjtu2OHTeFRfnJqyMHsJ5Cyes23trOmvUpM1GfHH1leA==", "requires": { "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", + "acorn": "^6.0.4", + "acorn-globals": "^4.3.0", "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", + "cssom": "^0.3.4", + "cssstyle": "^1.1.1", + "data-urls": "^1.1.0", "domexception": "^1.0.1", - "escodegen": "^1.9.1", + "escodegen": "^1.11.0", "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", + "nwsapi": "^2.0.9", + "parse5": "5.1.0", "pn": "^1.1.0", - "request": "^2.87.0", + "request": "^2.88.0", "request-promise-native": "^1.0.5", - "sax": "^1.2.4", + "saxes": "^3.1.5", "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", + "tough-cookie": "^2.5.0", "w3c-hr-time": "^1.0.1", + "w3c-xmlserializer": "^1.0.1", "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^7.0.0", + "ws": "^6.1.2", "xml-name-validator": "^3.0.0" }, "dependencies": { - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } } } }, @@ -7835,14 +7851,12 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7853,8 +7867,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json3": { "version": "3.3.2", @@ -7925,7 +7938,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -8354,8 +8366,7 @@ "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, "lodash.tail": { "version": "4.1.1", @@ -8607,14 +8618,12 @@ "mime-db": { "version": "1.37.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", - "dev": true + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" }, "mime-types": { "version": "2.1.21", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", - "dev": true, "requires": { "mime-db": "~1.37.0" } @@ -9351,16 +9360,14 @@ "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==" }, "nwsapi": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.9.tgz", - "integrity": "sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==", - "dev": true + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.1.tgz", + "integrity": "sha512-T5GaA1J/d34AC8mkrFD2O0DR17kwJ702ZOtJOsS8RpbsQZVOC2/xYFb1i/cw+xdM54JIlMuojjDOYct8GIWtwg==" }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", @@ -9834,10 +9841,9 @@ } }, "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" }, "parseurl": { "version": "1.3.2", @@ -9940,8 +9946,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pgp-utils": { "version": "0.0.34", @@ -10069,8 +10074,7 @@ "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" }, "pngjs": { "version": "3.3.3", @@ -10590,8 +10594,7 @@ "psl": { "version": "1.1.29", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", - "dev": true + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" }, "public-encrypt": { "version": "4.0.3", @@ -10631,8 +10634,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "purepack": { "version": "1.0.4", @@ -10647,8 +10649,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystring": { "version": "0.2.0", @@ -11024,7 +11025,6 @@ "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -11058,23 +11058,21 @@ } }, "request-promise-core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", - "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", - "dev": true, + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", "requires": { - "lodash": "^4.13.1" + "lodash": "^4.17.11" } }, "request-promise-native": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", - "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", - "dev": true, + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", "requires": { - "request-promise-core": "1.1.1", - "stealthy-require": "^1.1.0", - "tough-cookie": ">=2.3.3" + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" } }, "require-directory": { @@ -11408,6 +11406,14 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, + "saxes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.9.tgz", + "integrity": "sha512-FZeKhJglhJHk7eWG5YM0z46VHmI3KJpMBAQm3xa9meDvd+wevB5GuBB0wc0exPInZiBBHqi00DbS8AcvCGCFMw==", + "requires": { + "xmlchars": "^1.3.1" + } + }, "schema-utils": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", @@ -12075,7 +12081,6 @@ "version": "1.15.2", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", - "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -12091,14 +12096,12 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" } } }, @@ -12196,8 +12199,7 @@ "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, "stream-browserify": { "version": "2.0.1", @@ -12410,8 +12412,7 @@ "symbol-tree": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", - "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", - "dev": true + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" }, "table": { "version": "5.2.2", @@ -12791,7 +12792,6 @@ "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -12800,8 +12800,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" } } }, @@ -12809,7 +12808,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -12875,7 +12873,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -13136,7 +13133,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -13292,8 +13288,7 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "valid-data-url": { "version": "0.1.6", @@ -13327,7 +13322,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -13352,11 +13346,20 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", - "dev": true, "requires": { "browser-process-hrtime": "^0.1.2" } }, + "w3c-xmlserializer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.0.1.tgz", + "integrity": "sha512-XZGI1OH/OLQr/NaJhhPmzhngwcAnZDLytsvXnRmlYeRkmbb0I7sqFFA22erq4WQR0sUu17ZSQOAV9mFwCqKRNg==", + "requires": { + "domexception": "^1.0.1", + "webidl-conversions": "^4.0.2", + "xml-name-validator": "^3.0.0" + } + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", @@ -13444,8 +13447,7 @@ "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" }, "webpack": { "version": "4.28.3", @@ -13936,22 +13938,19 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, "requires": { "iconv-lite": "0.4.24" } }, "whatwg-mimetype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz", - "integrity": "sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw==", - "dev": true + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" }, "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", "requires": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -14085,10 +14084,9 @@ } }, "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.0.tgz", + "integrity": "sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==", "requires": { "async-limiter": "~1.0.0" } @@ -14107,8 +14105,7 @@ "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" }, "xml-parse-from-string": { "version": "1.0.1", @@ -14137,6 +14134,11 @@ "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", "dev": true }, + "xmlchars": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-1.3.1.tgz", + "integrity": "sha512-tGkGJkN8XqCod7OT+EvGYK5Z4SfDQGD30zAa58OcnAa0RRWgzUEK72tkXhsX1FZd+rgnhRxFtmO+ihkp8LHSkw==" + }, "xmlcreate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", diff --git a/package.json b/package.json index 065adba4..f71f7709 100644 --- a/package.json +++ b/package.json @@ -89,9 +89,9 @@ "chi-squared": "^1.1.0", "crypto-api": "^0.8.3", "crypto-js": "^3.1.9-1", + "ctph.js": "0.0.5", "d3": "^4.9.1", "d3-hexbin": "^0.2.2", - "ctph.js": "0.0.5", "diff": "^3.5.0", "es6-promisify": "^6.0.1", "escodegen": "^1.11.0", @@ -105,6 +105,7 @@ "jquery": "^3.3.1", "js-crc": "^0.2.0", "js-sha3": "^0.8.0", + "jsdom": "^14.0.0", "jsesc": "^2.5.2", "jsonpath": "^1.0.0", "jsonwebtoken": "^8.4.0", diff --git a/src/core/lib/Charts.mjs b/src/core/lib/Charts.mjs index 52585505..1b9be128 100644 --- a/src/core/lib/Charts.mjs +++ b/src/core/lib/Charts.mjs @@ -50,7 +50,6 @@ export function getValues(input, recordDelimiter, fieldDelimiter, columnHeadings .split(recordDelimiter) .forEach((row, rowIndex) => { const split = row.split(fieldDelimiter); - if (split.length !== length) throw new OperationError(`Each row must have length ${length}.`); if (columnHeadingsAreIncluded && rowIndex === 0) { @@ -59,7 +58,6 @@ export function getValues(input, recordDelimiter, fieldDelimiter, columnHeadings values.push(split); } }); - return { headings, values}; } diff --git a/src/core/operations/HeatmapChart.mjs b/src/core/operations/HeatmapChart.mjs index 047ce054..eda20fcc 100644 --- a/src/core/operations/HeatmapChart.mjs +++ b/src/core/operations/HeatmapChart.mjs @@ -5,6 +5,7 @@ */ import * as d3 from "d3"; +import jsdom from "jsdom"; import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; @@ -89,8 +90,8 @@ class HeatmapChart extends Operation { * @returns {html} */ run(input, args) { - const recordDelimiter = Utils.charRep[args[0]], - fieldDelimiter = Utils.charRep[args[1]], + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), vBins = args[2], hBins = args[3], columnHeadingsAreIncluded = args[4], @@ -116,6 +117,7 @@ class HeatmapChart extends Operation { yLabel = headings.y; } + const document = new jsdom.JSDOM().window.document; let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") diff --git a/src/core/operations/HexDensityChart.mjs b/src/core/operations/HexDensityChart.mjs index 3d010f13..56be19c3 100644 --- a/src/core/operations/HexDensityChart.mjs +++ b/src/core/operations/HexDensityChart.mjs @@ -6,6 +6,7 @@ import * as d3 from "d3"; import * as d3hexbin from "d3-hexbin"; +import jsdom from "jsdom"; import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; import Operation from "../Operation"; @@ -94,8 +95,8 @@ class HexDensityChart extends Operation { * @returns {html} */ run(input, args) { - const recordDelimiter = Utils.charRep[args[0]], - fieldDelimiter = Utils.charRep[args[1]], + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), packRadius = args[2], drawRadius = args[3], columnHeadingsAreIncluded = args[4], @@ -119,6 +120,7 @@ class HexDensityChart extends Operation { yLabel = headings.y; } + const document = new jsdom.JSDOM().window.document; let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") diff --git a/src/core/operations/ScatterChart.mjs b/src/core/operations/ScatterChart.mjs index acd527cf..bdec508a 100644 --- a/src/core/operations/ScatterChart.mjs +++ b/src/core/operations/ScatterChart.mjs @@ -5,6 +5,7 @@ */ import * as d3 from "d3"; +import jsdom from "jsdom"; import { getScatterValues, getScatterValuesWithColour, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; import Operation from "../Operation"; @@ -77,8 +78,8 @@ class ScatterChart extends Operation { * @returns {html} */ run(input, args) { - const recordDelimiter = Utils.charRep[args[0]], - fieldDelimiter = Utils.charRep[args[1]], + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), columnHeadingsAreIncluded = args[2], fillColour = args[5], radius = args[6], @@ -89,7 +90,6 @@ class ScatterChart extends Operation { yLabel = args[4]; const dataFunction = colourInInput ? getScatterValuesWithColour : getScatterValues; - const { headings, values } = dataFunction( input, recordDelimiter, @@ -101,7 +101,7 @@ class ScatterChart extends Operation { xLabel = headings.x; yLabel = headings.y; } - + const document = new jsdom.JSDOM().window.document; let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") diff --git a/src/core/operations/SeriesChart.mjs b/src/core/operations/SeriesChart.mjs index 34dd6ec0..fbdca826 100644 --- a/src/core/operations/SeriesChart.mjs +++ b/src/core/operations/SeriesChart.mjs @@ -5,6 +5,7 @@ */ import * as d3 from "d3"; +import jsdom from "jsdom"; import { getSeriesValues, RECORD_DELIMITER_OPTIONS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; import Operation from "../Operation"; @@ -62,8 +63,8 @@ class SeriesChart extends Operation { * @returns {html} */ run(input, args) { - const recordDelimiter = Utils.charRep[args[0]], - fieldDelimiter = Utils.charRep[args[1]], + const recordDelimiter = Utils.charRep(args[0]), + fieldDelimiter = Utils.charRep(args[1]), xLabel = args[2], pipRadius = args[3], seriesColours = args[4].split(","), @@ -78,6 +79,7 @@ class SeriesChart extends Operation { allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight), svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding; + const document = new jsdom.JSDOM().window.document; let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") diff --git a/webpack.config.js b/webpack.config.js index 054152b2..43e277ee 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -130,7 +130,10 @@ module.exports = { ], }, node: { - fs: "empty" + fs: "empty", + "child_process": "empty", + net: "empty", + tls: "empty" }, performance: { hints: false From 0019a4e1db88d94c221f6072a54ae3b3c142a7f9 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 10 Mar 2019 12:03:53 +0000 Subject: [PATCH 15/22] Found a different dom implementation that removes 6MB --- package-lock.json | 4 ++++ package.json | 1 + src/core/operations/HeatmapChart.mjs | 4 ++-- src/core/operations/HexDensityChart.mjs | 4 ++-- src/core/operations/ScatterChart.mjs | 5 +++-- src/core/operations/SeriesChart.mjs | 4 ++-- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index b2311d3b..a1136b70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9250,6 +9250,10 @@ } } }, + "nodom": { + "version": "github:ptytb/nodom#f041f9f85a6e21adb8e48273e6dd84474c773a01", + "from": "github:ptytb/nodom" + }, "nomnom": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz", diff --git a/package.json b/package.json index f71f7709..2ff2dfbb 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "ngeohash": "^0.6.3", "node-forge": "^0.7.6", "node-md6": "^0.1.0", + "nodom": "github:ptytb/nodom", "notepack.io": "^2.2.0", "nwmatcher": "^1.4.4", "otp": "^0.1.3", diff --git a/src/core/operations/HeatmapChart.mjs b/src/core/operations/HeatmapChart.mjs index eda20fcc..9852a4ad 100644 --- a/src/core/operations/HeatmapChart.mjs +++ b/src/core/operations/HeatmapChart.mjs @@ -5,7 +5,7 @@ */ import * as d3 from "d3"; -import jsdom from "jsdom"; +import * as nodom from "nodom"; import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; @@ -117,7 +117,7 @@ class HeatmapChart extends Operation { yLabel = headings.y; } - const document = new jsdom.JSDOM().window.document; + const document = new nodom.Document(); let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") diff --git a/src/core/operations/HexDensityChart.mjs b/src/core/operations/HexDensityChart.mjs index 56be19c3..dc04bf33 100644 --- a/src/core/operations/HexDensityChart.mjs +++ b/src/core/operations/HexDensityChart.mjs @@ -6,7 +6,7 @@ import * as d3 from "d3"; import * as d3hexbin from "d3-hexbin"; -import jsdom from "jsdom"; +import * as nodom from "nodom"; import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; import Operation from "../Operation"; @@ -120,7 +120,7 @@ class HexDensityChart extends Operation { yLabel = headings.y; } - const document = new jsdom.JSDOM().window.document; + const document = new nodom.Document(); let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") diff --git a/src/core/operations/ScatterChart.mjs b/src/core/operations/ScatterChart.mjs index bdec508a..6898a91e 100644 --- a/src/core/operations/ScatterChart.mjs +++ b/src/core/operations/ScatterChart.mjs @@ -5,7 +5,7 @@ */ import * as d3 from "d3"; -import jsdom from "jsdom"; +import * as nodom from "nodom"; import { getScatterValues, getScatterValuesWithColour, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; import Operation from "../Operation"; @@ -101,7 +101,8 @@ class ScatterChart extends Operation { xLabel = headings.x; yLabel = headings.y; } - const document = new jsdom.JSDOM().window.document; + + const document = new nodom.Document(); let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") diff --git a/src/core/operations/SeriesChart.mjs b/src/core/operations/SeriesChart.mjs index fbdca826..548aa7f2 100644 --- a/src/core/operations/SeriesChart.mjs +++ b/src/core/operations/SeriesChart.mjs @@ -5,7 +5,7 @@ */ import * as d3 from "d3"; -import jsdom from "jsdom"; +import * as nodom from "nodom"; import { getSeriesValues, RECORD_DELIMITER_OPTIONS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; import Operation from "../Operation"; @@ -79,7 +79,7 @@ class SeriesChart extends Operation { allSeriesHeight = Object.keys(series).length * (interSeriesPadding + seriesHeight), svgHeight = allSeriesHeight + xAxisHeight + interSeriesPadding; - const document = new jsdom.JSDOM().window.document; + const document = new nodom.Document(); let svg = document.createElement("svg"); svg = d3.select(svg) .attr("width", "100%") From 650145442486f56abcd956b515dbe807f3b536f4 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 10 Mar 2019 13:08:47 +0000 Subject: [PATCH 16/22] Cleanup --- src/core/config/Categories.json | 6 +++++- src/core/operations/HeatmapChart.mjs | 11 ++++++----- src/core/operations/HexDensityChart.mjs | 5 +++-- src/core/operations/ScatterChart.mjs | 6 ++++-- src/core/operations/SeriesChart.mjs | 5 +++-- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 8235ab10..f8f29be9 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -359,7 +359,11 @@ "Play Media", "Remove EXIF", "Extract EXIF", - "Split Colour Channels" + "Split Colour Channels", + "Hex Density chart", + "Scatter chart", + "Series chart", + "Heatmap chart" ] }, { diff --git a/src/core/operations/HeatmapChart.mjs b/src/core/operations/HeatmapChart.mjs index 9852a4ad..6620e7aa 100644 --- a/src/core/operations/HeatmapChart.mjs +++ b/src/core/operations/HeatmapChart.mjs @@ -26,8 +26,8 @@ class HeatmapChart extends Operation { this.name = "Heatmap chart"; this.module = "Charts"; - this.description = ""; - this.infoURL = ""; + this.description = "A heatmap is a graphical representation of data where the individual values contained in a matrix are represented as colors."; + this.infoURL = "https://wikipedia.org/wiki/Heat_map"; this.inputType = "string"; this.outputType = "html"; this.args = [ @@ -85,6 +85,8 @@ class HeatmapChart extends Operation { } /** + * Heatmap chart operation. + * * @param {string} input * @param {Object[]} args * @returns {html} @@ -99,7 +101,6 @@ class HeatmapChart extends Operation { minColour = args[8], maxColour = args[9], dimension = 500; - if (vBins <= 0) throw new OperationError("Number of vertical bins must be greater than 0"); if (hBins <= 0) throw new OperationError("Number of horizontal bins must be greater than 0"); @@ -182,7 +183,7 @@ class HeatmapChart extends Operation { .attr("stroke-width", drawEdges ? "0.5" : "none") .append("title") .text(d => { - let count = d.length, + const count = d.length, perc = 100.0 * d.length / values.length, tooltip = `Count: ${count}\n Percentage: ${perc.toFixed(2)}%\n @@ -218,7 +219,7 @@ class HeatmapChart extends Operation { /** * Packs a list of x, y coordinates into a number of bins for use in a heatmap. - * + * * @param {Object[]} points * @param {number} number of vertical bins * @param {number} number of horizontal bins diff --git a/src/core/operations/HexDensityChart.mjs b/src/core/operations/HexDensityChart.mjs index dc04bf33..c9912599 100644 --- a/src/core/operations/HexDensityChart.mjs +++ b/src/core/operations/HexDensityChart.mjs @@ -25,8 +25,7 @@ class HexDensityChart extends Operation { this.name = "Hex Density chart"; this.module = "Charts"; - this.description = ""; - this.infoURL = ""; + this.description = "Hex density charts are used in a similar way to scatter charts, however rather than rendering tens of thousands of points, it groups the points into a few hundred hexagons to show the distribution."; this.inputType = "string"; this.outputType = "html"; this.args = [ @@ -90,6 +89,8 @@ class HexDensityChart extends Operation { /** + * Hex Bin chart operation. + * * @param {string} input * @param {Object[]} args * @returns {html} diff --git a/src/core/operations/ScatterChart.mjs b/src/core/operations/ScatterChart.mjs index 6898a91e..fa642449 100644 --- a/src/core/operations/ScatterChart.mjs +++ b/src/core/operations/ScatterChart.mjs @@ -24,8 +24,8 @@ class ScatterChart extends Operation { this.name = "Scatter chart"; this.module = "Charts"; - this.description = ""; - this.infoURL = ""; + this.description = "Plots two-variable data as single points on a graph."; + this.infoURL = "https://en.wikipedia.org/wiki/Scatter_plot"; this.inputType = "string"; this.outputType = "html"; this.args = [ @@ -73,6 +73,8 @@ class ScatterChart extends Operation { } /** + * Scatter chart operation. + * * @param {string} input * @param {Object[]} args * @returns {html} diff --git a/src/core/operations/SeriesChart.mjs b/src/core/operations/SeriesChart.mjs index 548aa7f2..bccbc7ed 100644 --- a/src/core/operations/SeriesChart.mjs +++ b/src/core/operations/SeriesChart.mjs @@ -24,8 +24,7 @@ class SeriesChart extends Operation { this.name = "Series chart"; this.module = "Charts"; - this.description = ""; - this.infoURL = ""; + this.description = "A time series graph is a line graph of repeated measurements taken over regular time intervals."; this.inputType = "string"; this.outputType = "html"; this.args = [ @@ -58,6 +57,8 @@ class SeriesChart extends Operation { } /** + * Series chart operation. + * * @param {string} input * @param {Object[]} args * @returns {html} From ca6d472e5db13d564b508548f17b7e379d6e806f Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 10 Mar 2019 16:07:14 +0000 Subject: [PATCH 17/22] Update nodom --- package-lock.json | 5 +++-- package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a1136b70..424b18a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9251,8 +9251,9 @@ } }, "nodom": { - "version": "github:ptytb/nodom#f041f9f85a6e21adb8e48273e6dd84474c773a01", - "from": "github:ptytb/nodom" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nodom/-/nodom-2.1.0.tgz", + "integrity": "sha512-DKhDki0J0pY0LWeA6tsZpqdkEwD1SRhXc2vPjgU/f9hwJptLzIthU8oiT5d2VrHQuqs9hzykqgTLPyMPJjiZbw==" }, "nomnom": { "version": "1.5.2", diff --git a/package.json b/package.json index 2ff2dfbb..093bf185 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "ngeohash": "^0.6.3", "node-forge": "^0.7.6", "node-md6": "^0.1.0", - "nodom": "github:ptytb/nodom", + "nodom": "^2.1.0", "notepack.io": "^2.2.0", "nwmatcher": "^1.4.4", "otp": "^0.1.3", From fd7fd9ca35b11a09bbe8d04693d2628e44af4b61 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 11 Mar 2019 11:55:44 +0000 Subject: [PATCH 18/22] Remove jsdom from dependencies --- package-lock.json | 244 ++++++++++++++++++++++------------------------ package.json | 1 - 2 files changed, 119 insertions(+), 126 deletions(-) diff --git a/package-lock.json b/package-lock.json index 424b18a4..35420aa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1334,7 +1334,8 @@ "abab": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", - "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true }, "abbrev": { "version": "1.1.1", @@ -1479,7 +1480,8 @@ "acorn": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", - "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==" + "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", + "dev": true }, "acorn-dynamic-import": { "version": "3.0.0", @@ -1502,6 +1504,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", + "dev": true, "requires": { "acorn": "^6.0.1", "acorn-walk": "^6.0.1" @@ -1516,7 +1519,8 @@ "acorn-walk": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", - "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==" + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true }, "agent-base": { "version": "4.2.1", @@ -1542,6 +1546,7 @@ "version": "6.5.5", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", + "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -1693,7 +1698,8 @@ "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true }, "array-find-index": { "version": "1.0.2", @@ -1743,6 +1749,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -1787,7 +1794,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "assertion-error": { "version": "1.1.0", @@ -1837,12 +1845,14 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, "atob": { "version": "2.1.2", @@ -1909,12 +1919,14 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true }, "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true }, "axios": { "version": "0.18.0", @@ -2152,6 +2164,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { "tweetnacl": "^0.14.3" }, @@ -2159,7 +2172,8 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true } } }, @@ -2374,7 +2388,8 @@ "browser-process-hrtime": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true }, "browser-stdout": { "version": "1.3.1", @@ -2658,7 +2673,8 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true }, "catharsis": { "version": "0.8.9", @@ -2944,6 +2960,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -3182,7 +3199,8 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "cosmiconfig": { "version": "4.0.0", @@ -3450,12 +3468,14 @@ "cssom": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", - "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==" + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", + "dev": true }, "cssstyle": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.1.tgz", "integrity": "sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A==", + "dev": true, "requires": { "cssom": "0.3.x" } @@ -3750,6 +3770,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -3764,6 +3785,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, "requires": { "abab": "^2.0.0", "whatwg-mimetype": "^2.2.0", @@ -3947,7 +3969,8 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "delegates": { "version": "1.0.0", @@ -4087,6 +4110,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, "requires": { "webidl-conversions": "^4.0.2" } @@ -4169,6 +4193,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -4177,7 +4202,8 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true } } }, @@ -4842,7 +4868,8 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "extend-shallow": { "version": "3.0.2", @@ -4956,7 +4983,8 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, "eyes": { "version": "0.1.8", @@ -4967,12 +4995,14 @@ "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", @@ -5261,12 +5291,14 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -6073,6 +6105,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -6536,12 +6569,14 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -6732,6 +6767,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, "requires": { "whatwg-encoding": "^1.0.1" } @@ -6898,6 +6934,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -7527,7 +7564,8 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true }, "is-url": { "version": "1.2.4", @@ -7585,7 +7623,8 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true }, "jimp": { "version": "0.6.0", @@ -7766,50 +7805,6 @@ "integrity": "sha1-hCRCjVtWOtjFx/vsB5uaiwnI3Po=", "dev": true }, - "jsdom": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-14.0.0.tgz", - "integrity": "sha512-/VkyPmdtbwqpJSkwDx3YyJ3U1oawYNB/h5z8vTUZGAzjtu2OHTeFRfnJqyMHsJ5Cyes23trOmvUpM1GfHH1leA==", - "requires": { - "abab": "^2.0.0", - "acorn": "^6.0.4", - "acorn-globals": "^4.3.0", - "array-equal": "^1.0.0", - "cssom": "^0.3.4", - "cssstyle": "^1.1.1", - "data-urls": "^1.1.0", - "domexception": "^1.0.1", - "escodegen": "^1.11.0", - "html-encoding-sniffer": "^1.0.2", - "nwsapi": "^2.0.9", - "parse5": "5.1.0", - "pn": "^1.1.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.5", - "saxes": "^3.1.5", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.5.0", - "w3c-hr-time": "^1.0.1", - "w3c-xmlserializer": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^7.0.0", - "ws": "^6.1.2", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -7851,12 +7846,14 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7867,7 +7864,8 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json3": { "version": "3.3.2", @@ -7938,6 +7936,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -8366,7 +8365,8 @@ "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true }, "lodash.tail": { "version": "4.1.1", @@ -8618,12 +8618,14 @@ "mime-db": { "version": "1.37.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true }, "mime-types": { "version": "2.1.21", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, "requires": { "mime-db": "~1.37.0" } @@ -9367,12 +9369,14 @@ "nwsapi": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.1.tgz", - "integrity": "sha512-T5GaA1J/d34AC8mkrFD2O0DR17kwJ702ZOtJOsS8RpbsQZVOC2/xYFb1i/cw+xdM54JIlMuojjDOYct8GIWtwg==" + "integrity": "sha512-T5GaA1J/d34AC8mkrFD2O0DR17kwJ702ZOtJOsS8RpbsQZVOC2/xYFb1i/cw+xdM54JIlMuojjDOYct8GIWtwg==", + "dev": true }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true }, "object-assign": { "version": "4.1.1", @@ -9845,11 +9849,6 @@ "error-ex": "^1.2.0" } }, - "parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" - }, "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", @@ -9951,7 +9950,8 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true }, "pgp-utils": { "version": "0.0.34", @@ -10079,7 +10079,8 @@ "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true }, "pngjs": { "version": "3.3.3", @@ -10599,7 +10600,8 @@ "psl": { "version": "1.1.29", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true }, "public-encrypt": { "version": "4.0.3", @@ -10639,7 +10641,8 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true }, "purepack": { "version": "1.0.4", @@ -10654,7 +10657,8 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true }, "querystring": { "version": "0.2.0", @@ -11030,6 +11034,7 @@ "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -11066,6 +11071,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "dev": true, "requires": { "lodash": "^4.17.11" } @@ -11074,6 +11080,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "dev": true, "requires": { "request-promise-core": "1.1.2", "stealthy-require": "^1.1.1", @@ -11411,14 +11418,6 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, - "saxes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.9.tgz", - "integrity": "sha512-FZeKhJglhJHk7eWG5YM0z46VHmI3KJpMBAQm3xa9meDvd+wevB5GuBB0wc0exPInZiBBHqi00DbS8AcvCGCFMw==", - "requires": { - "xmlchars": "^1.3.1" - } - }, "schema-utils": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", @@ -12086,6 +12085,7 @@ "version": "1.15.2", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -12101,12 +12101,14 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true } } }, @@ -12204,7 +12206,8 @@ "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true }, "stream-browserify": { "version": "2.0.1", @@ -12417,7 +12420,8 @@ "symbol-tree": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", - "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true }, "table": { "version": "5.2.2", @@ -12797,6 +12801,7 @@ "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -12805,7 +12810,8 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true } } }, @@ -12813,6 +12819,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -12878,6 +12885,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -13138,6 +13146,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -13293,7 +13302,8 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true }, "valid-data-url": { "version": "0.1.6", @@ -13327,6 +13337,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -13351,20 +13362,11 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, "requires": { "browser-process-hrtime": "^0.1.2" } }, - "w3c-xmlserializer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.0.1.tgz", - "integrity": "sha512-XZGI1OH/OLQr/NaJhhPmzhngwcAnZDLytsvXnRmlYeRkmbb0I7sqFFA22erq4WQR0sUu17ZSQOAV9mFwCqKRNg==", - "requires": { - "domexception": "^1.0.1", - "webidl-conversions": "^4.0.2", - "xml-name-validator": "^3.0.0" - } - }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", @@ -13452,7 +13454,8 @@ "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true }, "webpack": { "version": "4.28.3", @@ -13943,6 +13946,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, "requires": { "iconv-lite": "0.4.24" } @@ -13950,12 +13954,14 @@ "whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true }, "whatwg-url": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, "requires": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -14088,14 +14094,6 @@ "mkdirp": "^0.5.1" } }, - "ws": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.0.tgz", - "integrity": "sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==", - "requires": { - "async-limiter": "~1.0.0" - } - }, "xhr": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", @@ -14110,7 +14108,8 @@ "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true }, "xml-parse-from-string": { "version": "1.0.1", @@ -14139,11 +14138,6 @@ "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", "dev": true }, - "xmlchars": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-1.3.1.tgz", - "integrity": "sha512-tGkGJkN8XqCod7OT+EvGYK5Z4SfDQGD30zAa58OcnAa0RRWgzUEK72tkXhsX1FZd+rgnhRxFtmO+ihkp8LHSkw==" - }, "xmlcreate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", diff --git a/package.json b/package.json index 093bf185..a1956eea 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,6 @@ "jquery": "^3.3.1", "js-crc": "^0.2.0", "js-sha3": "^0.8.0", - "jsdom": "^14.0.0", "jsesc": "^2.5.2", "jsonpath": "^1.0.0", "jsonwebtoken": "^8.4.0", From cd22985f110801f51276a5494efa8a4aef9a5d86 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 11 Mar 2019 12:09:29 +0000 Subject: [PATCH 19/22] Fix categories JSON issue --- src/core/config/Categories.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index f670d600..39d7c877 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -365,7 +365,7 @@ "Hex Density chart", "Scatter chart", "Series chart", - "Heatmap chart" + "Heatmap chart", "Rotate Image", "Resize Image", "Blur Image", From 768fef502d89eb37a4e2a195ffcbb78120214039 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 14 Mar 2019 11:39:46 +0000 Subject: [PATCH 20/22] Changed version of nodom to actually functioning fork --- package-lock.json | 13 ++++--------- package.json | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index b33ab0eb..e5026aec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5438,14 +5438,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5465,8 +5463,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -5614,7 +5611,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -9257,9 +9253,8 @@ } }, "nodom": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nodom/-/nodom-2.1.0.tgz", - "integrity": "sha512-DKhDki0J0pY0LWeA6tsZpqdkEwD1SRhXc2vPjgU/f9hwJptLzIthU8oiT5d2VrHQuqs9hzykqgTLPyMPJjiZbw==" + "version": "github:artemisbot/nodom#0071b2fa25cbc74e14c7d911cda9b03ea26eac7b", + "from": "github:artemisbot/nodom" }, "nomnom": { "version": "1.5.2", diff --git a/package.json b/package.json index cca45580..61e9d4ed 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "ngeohash": "^0.6.3", "node-forge": "^0.7.6", "node-md6": "^0.1.0", - "nodom": "^2.1.0", + "nodom": "github:artemisbot/nodom", "notepack.io": "^2.2.0", "nwmatcher": "^1.4.4", "otp": "^0.1.3", From 3ad5f889a0621bdc523452f2d912f45418933b11 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 14 Mar 2019 13:37:11 +0000 Subject: [PATCH 21/22] Wrote some tests, fixed imports for node --- src/core/lib/Charts.mjs | 4 +- src/core/operations/HTMLToText.mjs | 41 ++++++++++++++++++ src/core/operations/HeatmapChart.mjs | 8 +++- src/core/operations/HexDensityChart.mjs | 12 ++++-- src/core/operations/ScatterChart.mjs | 9 +++- src/core/operations/SeriesChart.mjs | 8 +++- tests/operations/index.mjs | 1 + tests/operations/tests/Charts.mjs | 55 +++++++++++++++++++++++++ 8 files changed, 127 insertions(+), 11 deletions(-) create mode 100644 src/core/operations/HTMLToText.mjs create mode 100644 tests/operations/tests/Charts.mjs diff --git a/src/core/lib/Charts.mjs b/src/core/lib/Charts.mjs index 1b9be128..fa3e5137 100644 --- a/src/core/lib/Charts.mjs +++ b/src/core/lib/Charts.mjs @@ -1,6 +1,6 @@ /** - * @author tlwr [toby@toby.codes] - Original - * @author Matt C [me@mitt.dev] - Conversion to new format + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ diff --git a/src/core/operations/HTMLToText.mjs b/src/core/operations/HTMLToText.mjs new file mode 100644 index 00000000..a47ffc46 --- /dev/null +++ b/src/core/operations/HTMLToText.mjs @@ -0,0 +1,41 @@ +/** + * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; + +/** + * HTML To Text operation + */ +class HTMLToText extends Operation { + + /** + * HTMLToText constructor + */ + constructor() { + super(); + + this.name = "HTML To Text"; + this.module = "Default"; + this.description = "Converts a HTML ouput from an operation to a readable string instead of being rendered in the DOM."; + this.infoURL = ""; + this.inputType = "html"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {html} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return input; + } + +} + +export default HTMLToText; diff --git a/src/core/operations/HeatmapChart.mjs b/src/core/operations/HeatmapChart.mjs index 6620e7aa..4cde1f30 100644 --- a/src/core/operations/HeatmapChart.mjs +++ b/src/core/operations/HeatmapChart.mjs @@ -1,11 +1,12 @@ /** * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ -import * as d3 from "d3"; -import * as nodom from "nodom"; +import * as d3temp from "d3"; +import * as nodomtemp from "nodom"; import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; @@ -13,6 +14,9 @@ import Operation from "../Operation"; import OperationError from "../errors/OperationError"; import Utils from "../Utils"; +const d3 = d3temp.default ? d3temp.default : d3temp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + /** * Heatmap chart operation */ diff --git a/src/core/operations/HexDensityChart.mjs b/src/core/operations/HexDensityChart.mjs index c9912599..6414d97a 100644 --- a/src/core/operations/HexDensityChart.mjs +++ b/src/core/operations/HexDensityChart.mjs @@ -1,17 +1,23 @@ /** * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ -import * as d3 from "d3"; -import * as d3hexbin from "d3-hexbin"; -import * as nodom from "nodom"; +import * as d3temp from "d3"; +import * as d3hexbintemp from "d3-hexbin"; +import * as nodomtemp from "nodom"; import { getScatterValues, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; import Operation from "../Operation"; import Utils from "../Utils"; +const d3 = d3temp.default ? d3temp.default : d3temp; +const d3hexbin = d3hexbintemp.default ? d3hexbintemp.default : d3hexbintemp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + + /** * Hex Density chart operation */ diff --git a/src/core/operations/ScatterChart.mjs b/src/core/operations/ScatterChart.mjs index fa642449..e6d0ec9d 100644 --- a/src/core/operations/ScatterChart.mjs +++ b/src/core/operations/ScatterChart.mjs @@ -1,16 +1,21 @@ /** * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ -import * as d3 from "d3"; -import * as nodom from "nodom"; +import * as d3temp from "d3"; +import * as nodomtemp from "nodom"; + import { getScatterValues, getScatterValuesWithColour, RECORD_DELIMITER_OPTIONS, COLOURS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; import Operation from "../Operation"; import Utils from "../Utils"; +const d3 = d3temp.default ? d3temp.default : d3temp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + /** * Scatter chart operation */ diff --git a/src/core/operations/SeriesChart.mjs b/src/core/operations/SeriesChart.mjs index bccbc7ed..cdae32b7 100644 --- a/src/core/operations/SeriesChart.mjs +++ b/src/core/operations/SeriesChart.mjs @@ -1,16 +1,20 @@ /** * @author tlwr [toby@toby.codes] + * @author Matt C [me@mitt.dev] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ -import * as d3 from "d3"; -import * as nodom from "nodom"; +import * as d3temp from "d3"; +import * as nodomtemp from "nodom"; import { getSeriesValues, RECORD_DELIMITER_OPTIONS, FIELD_DELIMITER_OPTIONS } from "../lib/Charts"; import Operation from "../Operation"; import Utils from "../Utils"; +const d3 = d3temp.default ? d3temp.default : d3temp; +const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp; + /** * Series chart operation */ diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index fb68ed9c..817529c8 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -33,6 +33,7 @@ import "./tests/BitwiseOp"; import "./tests/ByteRepr"; import "./tests/CartesianProduct"; import "./tests/CharEnc"; +import "./tests/Charts"; import "./tests/Checksum"; import "./tests/Ciphers"; import "./tests/Code"; diff --git a/tests/operations/tests/Charts.mjs b/tests/operations/tests/Charts.mjs new file mode 100644 index 00000000..3bd5c4fd --- /dev/null +++ b/tests/operations/tests/Charts.mjs @@ -0,0 +1,55 @@ +/** + * Chart tests. + * + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ +import TestRegister from "../TestRegister"; + +TestRegister.addTests([ + { + name: "Scatter chart", + input: "100 100\n200 200\n300 300\n400 400\n500 500", + expectedMatch: /^ Date: Tue, 19 Mar 2019 11:24:29 +0000 Subject: [PATCH 22/22] Updated nodom dependency to upstream --- package-lock.json | 27 +++++++++++++++++++-------- package.json | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index e5026aec..fca640e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5438,12 +5438,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5458,17 +5460,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5585,7 +5590,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5597,6 +5603,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5611,6 +5618,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5722,7 +5730,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5855,6 +5864,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9253,8 +9263,9 @@ } }, "nodom": { - "version": "github:artemisbot/nodom#0071b2fa25cbc74e14c7d911cda9b03ea26eac7b", - "from": "github:artemisbot/nodom" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nodom/-/nodom-2.2.0.tgz", + "integrity": "sha512-+W3jlsobV3NNkO15xQXkWoboeq1RPa/SKi8NMHmWF33SCMX4ALcM5dpPLEnUs69Gu+uZoCX9wcWXy866LXvd8w==" }, "nomnom": { "version": "1.5.2", diff --git a/package.json b/package.json index 61e9d4ed..dda5a279 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "ngeohash": "^0.6.3", "node-forge": "^0.7.6", "node-md6": "^0.1.0", - "nodom": "github:artemisbot/nodom", + "nodom": "^2.2.0", "notepack.io": "^2.2.0", "nwmatcher": "^1.4.4", "otp": "^0.1.3",