ESM: Ported MS and Entropy operations

feature-extract-files
n1474335 2018-05-16 11:39:30 +01:00
parent ebcc5bd9c8
commit 84df055888
10 changed files with 705 additions and 189 deletions

View File

@ -60,6 +60,12 @@ class Chef {
recipe.setBreakpoint(progress + 1, true);
}
// If the previously run operation presented a different value to its
// normal output, we need to recalculate it.
if (recipe.lastOpPresented(progress)) {
progress = 0;
}
// If stepping with flow control, we have to start from the beginning
// but still want to skip all previous breakpoints
if (progress > 0 && containsFc) {

View File

@ -81,9 +81,10 @@ class Operation {
* this behaviour.
*
* @param {*} data - The result of the run() function
* @param {Object[]} args - The operation's arguments
* @returns {*} - A human-readable version of the data
*/
present(data) {
present(data, args) {
return data;
}

View File

@ -212,7 +212,10 @@ class Recipe {
async present(dish) {
if (!this.lastRunOp) return;
const output = await this.lastRunOp.present(await dish.get(this.lastRunOp.outputType));
const output = await this.lastRunOp.present(
await dish.get(this.lastRunOp.outputType),
this.lastRunOp.ingValues
);
dish.set(output, this.lastRunOp.presentType);
}
@ -270,6 +273,18 @@ class Recipe {
return highlights;
}
/**
* Determines whether the previous operation has a different presentation type to its normal output.
*
* @param {number} progress
* @returns {boolean}
*/
lastOpPresented(progress) {
if (progress < 1) return false;
return this.opList[progress-1].presentType !== this.opList[progress-1].outputType;
}
}
export default Recipe;

204
src/core/lib/CanvasComponents.mjs Executable file
View File

@ -0,0 +1,204 @@
/**
* Various components for drawing diagrams on an HTML5 canvas.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
/**
* Draws a line from one point to another
*
* @param ctx
* @param startX
* @param startY
* @param endX
* @param endY
*/
export function drawLine(ctx, startX, startY, endX, endY) {
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
/**
* Draws a bar chart on the canvas.
*
* @param canvas
* @param scores
* @param xAxisLabel
* @param yAxisLabel
* @param numXLabels
* @param numYLabels
* @param fontSize
*/
export function drawBarChart(canvas, scores, xAxisLabel, yAxisLabel, numXLabels, numYLabels, fontSize) {
fontSize = fontSize || 15;
if (!numXLabels || numXLabels > Math.round(canvas.width / 50)) {
numXLabels = Math.round(canvas.width / 50);
}
if (!numYLabels || numYLabels > Math.round(canvas.width / 50)) {
numYLabels = Math.round(canvas.height / 50);
}
// Graph properties
const ctx = canvas.getContext("2d"),
leftPadding = canvas.width * 0.08,
rightPadding = canvas.width * 0.03,
topPadding = canvas.height * 0.08,
bottomPadding = canvas.height * 0.15,
graphHeight = canvas.height - topPadding - bottomPadding,
graphWidth = canvas.width - leftPadding - rightPadding,
base = topPadding + graphHeight,
ceil = topPadding;
ctx.font = fontSize + "px Arial";
// Draw axis
ctx.lineWidth = "1.0";
ctx.strokeStyle = "#444";
drawLine(ctx, leftPadding, base, graphWidth + leftPadding, base); // x
drawLine(ctx, leftPadding, base, leftPadding, ceil); // y
// Bar properties
const barPadding = graphWidth * 0.003,
barWidth = (graphWidth - (barPadding * scores.length)) / scores.length,
max = Math.max.apply(Math, scores);
let currX = leftPadding + barPadding;
// Draw bars
ctx.fillStyle = "green";
for (let i = 0; i < scores.length; i++) {
const h = scores[i] / max * graphHeight;
ctx.fillRect(currX, base - h, barWidth, h);
currX += barWidth + barPadding;
}
// Mark x axis
ctx.fillStyle = "black";
ctx.textAlign = "center";
currX = leftPadding + barPadding;
if (numXLabels >= scores.length) {
// Mark every score
for (let i = 0; i <= scores.length; i++) {
ctx.fillText(i, currX, base + (bottomPadding * 0.3));
currX += barWidth + barPadding;
}
} else {
// Mark some scores
for (let i = 0; i <= numXLabels; i++) {
const val = Math.ceil((scores.length / numXLabels) * i);
currX = (graphWidth / numXLabels) * i + leftPadding;
ctx.fillText(val, currX, base + (bottomPadding * 0.3));
}
}
// Mark y axis
ctx.textAlign = "right";
let currY;
if (numYLabels >= max) {
// Mark every increment
for (let i = 0; i <= max; i++) {
currY = base - (i / max * graphHeight) + fontSize / 3;
ctx.fillText(i, leftPadding * 0.8, currY);
}
} else {
// Mark some increments
for (let i = 0; i <= numYLabels; i++) {
const val = Math.ceil((max / numYLabels) * i);
currY = base - (val / max * graphHeight) + fontSize / 3;
ctx.fillText(val, leftPadding * 0.8, currY);
}
}
// Label x axis
if (xAxisLabel) {
ctx.textAlign = "center";
ctx.fillText(xAxisLabel, graphWidth / 2 + leftPadding, base + bottomPadding * 0.8);
}
// Label y axis
if (yAxisLabel) {
ctx.save();
const x = leftPadding * 0.3,
y = graphHeight / 2 + topPadding;
ctx.translate(x, y);
ctx.rotate(-Math.PI / 2);
ctx.textAlign = "center";
ctx.fillText(yAxisLabel, 0, 0);
ctx.restore();
}
}
/**
* Draws a scale bar on the canvas.
*
* @param canvas
* @param score
* @param max
* @param markings
*/
export function drawScaleBar(canvas, score, max, markings) {
// Bar properties
const ctx = canvas.getContext("2d"),
leftPadding = canvas.width * 0.01,
rightPadding = canvas.width * 0.01,
topPadding = canvas.height * 0.1,
bottomPadding = canvas.height * 0.3,
barHeight = canvas.height - topPadding - bottomPadding,
barWidth = canvas.width - leftPadding - rightPadding;
// Scale properties
const proportion = score / max;
// Draw bar outline
ctx.strokeRect(leftPadding, topPadding, barWidth, barHeight);
// Shade in up to proportion
const grad = ctx.createLinearGradient(leftPadding, 0, barWidth + leftPadding, 0);
grad.addColorStop(0, "green");
grad.addColorStop(0.5, "gold");
grad.addColorStop(1, "red");
ctx.fillStyle = grad;
ctx.fillRect(leftPadding, topPadding, barWidth * proportion, barHeight);
// Add markings
let x0, y0, x1, y1;
ctx.fillStyle = "black";
ctx.textAlign = "center";
ctx.font = "13px Arial";
for (let i = 0; i < markings.length; i++) {
// Draw min line down
x0 = barWidth / max * markings[i].min + leftPadding;
y0 = topPadding + barHeight + (bottomPadding * 0.1);
x1 = x0;
y1 = topPadding + barHeight + (bottomPadding * 0.3);
drawLine(ctx, x0, y0, x1, y1);
// Draw max line down
x0 = barWidth / max * markings[i].max + leftPadding;
x1 = x0;
drawLine(ctx, x0, y0, x1, y1);
// Join min and max lines
x0 = barWidth / max * markings[i].min + leftPadding;
y0 = topPadding + barHeight + (bottomPadding * 0.3);
x1 = barWidth / max * markings[i].max + leftPadding;
y1 = y0;
drawLine(ctx, x0, y0, x1, y1);
// Add label
if (markings[i].max >= max * 0.9) {
ctx.textAlign = "right";
x0 = x1;
} else if (markings[i].max <= max * 0.1) {
ctx.textAlign = "left";
} else {
x0 = x0 + (x1 - x0) / 2;
}
y0 = topPadding + barHeight + (bottomPadding * 0.8);
ctx.fillText(markings[i].label, x0, y0);
}
}

View File

@ -0,0 +1,53 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Chi Square operation
*/
class ChiSquare extends Operation {
/**
* ChiSquare constructor
*/
constructor() {
super();
this.name = "Chi Square";
this.module = "Default";
this.description = "Calculates the Chi Square distribution of values.";
this.inputType = "ArrayBuffer";
this.outputType = "number";
this.args = [];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {number}
*/
run(input, args) {
const data = new Uint8Array(input);
const distArray = new Array(256).fill(0);
let total = 0;
for (let i = 0; i < data.length; i++) {
distArray[data[i]]++;
}
for (let i = 0; i < distArray.length; i++) {
if (distArray[i] > 0) {
total += Math.pow(distArray[i] - data.length / 256, 2) / (data.length / 256);
}
}
return total;
}
}
export default ChiSquare;

View File

@ -0,0 +1,96 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
/**
* Entropy operation
*/
class Entropy extends Operation {
/**
* Entropy constructor
*/
constructor() {
super();
this.name = "Entropy";
this.module = "Default";
this.description = "Calculates the Shannon entropy of the input data which gives an idea of its randomness. 8 is the maximum.";
this.inputType = "byteArray";
this.outputType = "number";
this.presentType = "html";
this.args = [];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {number}
*/
run(input, args) {
const prob = [],
uniques = input.unique(),
str = Utils.byteArrayToChars(input);
let i;
for (i = 0; i < uniques.length; i++) {
prob.push(str.count(Utils.chr(uniques[i])) / input.length);
}
let entropy = 0,
p;
for (i = 0; i < prob.length; i++) {
p = prob[i];
entropy += p * Math.log(p) / Math.log(2);
}
return -entropy;
}
/**
* Displays the entropy as a scale bar for web apps.
*
* @param {number} entropy
* @returns {html}
*/
present(entropy) {
return `Shannon entropy: ${entropy}
<br><canvas id='chart-area'></canvas><br>
- 0 represents no randomness (i.e. all the bytes in the data have the same value) whereas 8, the maximum, represents a completely random string.
- Standard English text usually falls somewhere between 3.5 and 5.
- Properly encrypted or compressed data of a reasonable length should have an entropy of over 7.5.
The following results show the entropy of chunks of the input data. Chunks with particularly high entropy could suggest encrypted or compressed sections.
<br><script>
var canvas = document.getElementById("chart-area"),
parentRect = canvas.parentNode.getBoundingClientRect(),
entropy = ${entropy},
height = parentRect.height * 0.25;
canvas.width = parentRect.width * 0.95;
canvas.height = height > 150 ? 150 : height;
CanvasComponents.drawScaleBar(canvas, entropy, 8, [
{
label: "English text",
min: 3.5,
max: 5
},{
label: "Encrypted/compressed",
min: 7.5,
max: 8
}
]);
</script>`;
}
}
export default Entropy;

View File

@ -0,0 +1,110 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import OperationError from "../errors/OperationError";
/**
* Frequency distribution operation
*/
class FrequencyDistribution extends Operation {
/**
* FrequencyDistribution constructor
*/
constructor() {
super();
this.name = "Frequency distribution";
this.module = "Default";
this.description = "Displays the distribution of bytes in the data as a graph.";
this.inputType = "ArrayBuffer";
this.outputType = "json";
this.presentType = "html";
this.args = [
{
"name": "Show 0%s",
"type": "boolean",
"value": "Entropy.FREQ_ZEROS"
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {json}
*/
run(input, args) {
const data = new Uint8Array(input);
if (!data.length) throw new OperationError("No data");
const distrib = new Array(256).fill(0),
percentages = new Array(256),
len = data.length;
let i;
// Count bytes
for (i = 0; i < len; i++) {
distrib[data[i]]++;
}
// Calculate percentages
let repr = 0;
for (i = 0; i < 256; i++) {
if (distrib[i] > 0) repr++;
percentages[i] = distrib[i] / len * 100;
}
return {
"dataLength": len,
"percentages": percentages,
"distribution": distrib,
"bytesRepresented": repr
};
}
/**
* Displays the frequency distribution as a bar chart for web apps.
*
* @param {json} freq
* @returns {html}
*/
present(freq, args) {
const showZeroes = args[0];
// Print
let output = `<canvas id='chart-area'></canvas><br>
Total data length: ${freq.dataLength}
Number of bytes represented: ${freq.bytesRepresented}
Number of bytes not represented: ${256 - freq.bytesRepresented}
Byte Percentage
<script>
var canvas = document.getElementById("chart-area"),
parentRect = canvas.parentNode.getBoundingClientRect(),
scores = ${JSON.stringify(freq.percentages)};
canvas.width = parentRect.width * 0.95;
canvas.height = parentRect.height * 0.9;
CanvasComponents.drawBarChart(canvas, scores, "Byte", "Frequency %", 16, 6);
</script>`;
for (let i = 0; i < 256; i++) {
if (freq.distribution[i] || showZeroes) {
output += " " + Utils.hex(i, 2) + " (" +
(freq.percentages[i].toFixed(2).replace(".00", "") + "%)").padEnd(8, " ") +
Array(Math.ceil(freq.percentages[i])+1).join("|") + "\n";
}
}
return output;
}
}
export default FrequencyDistribution;

View File

@ -0,0 +1,217 @@
/**
* @author bmwhitn [brian.m.whitney@outlook.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* Microsoft Script Decoder operation
*/
class MicrosoftScriptDecoder extends Operation {
/**
* MicrosoftScriptDecoder constructor
*/
constructor() {
super();
this.name = "Microsoft Script Decoder";
this.module = "Default";
this.description = "Decodes Microsoft Encoded Script files that have been encoded with Microsoft's custom encoding. These are often VBS (Visual Basic Script) files that are encoded and renamed with a '.vbe' extention or JS (JScript) files renamed with a '.jse' extention.<br><br><b>Sample</b><br><br>Encoded:<br><code>#@~^RQAAAA==-mD~sX|:/TP{~J:+dYbxL~@!F@*@!+@*@!&amp;@*eEI@#@&amp;@#@&amp;.jm.raY 214Wv:zms/obI0xEAAA==^#~@</code><br><br>Decoded:<br><code>var my_msg = &#34;Testing <1><2><3>!&#34;;\n\nVScript.Echo(my_msg);</code>";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const matcher = /#@~\^.{6}==(.+).{6}==\^#~@/;
const encodedData = matcher.exec(input);
if (encodedData){
return MicrosoftScriptDecoder._decode(encodedData[1]);
} else {
return "";
}
}
/**
* Decodes Microsoft Encoded Script files that can be read and executed by cscript.exe/wscript.exe.
* This is a conversion of a Python script that was originally created by Didier Stevens
* (https://DidierStevens.com).
*
* @private
* @param {string} data
* @returns {string}
*/
static _decode(data) {
const result = [];
let index = -1;
data = data.replace(/@&/g, String.fromCharCode(10))
.replace(/@#/g, String.fromCharCode(13))
.replace(/@\*/g, ">")
.replace(/@!/g, "<")
.replace(/@\$/g, "@");
for (let i = 0; i < data.length; i++) {
const byte = data.charCodeAt(i);
let char = data.charAt(i);
if (byte < 128) {
index++;
}
if ((byte === 9 || byte > 31 && byte < 128) &&
byte !== 60 &&
byte !== 62 &&
byte !== 64) {
char = D_DECODE[byte].charAt(D_COMBINATION[index % 64]);
}
result.push(char);
}
return result.join("");
}
}
const D_DECODE = [
"",
"",
"",
"",
"",
"",
"",
"",
"",
"\x57\x6E\x7B",
"\x4A\x4C\x41",
"\x0B\x0B\x0B",
"\x0C\x0C\x0C",
"\x4A\x4C\x41",
"\x0E\x0E\x0E",
"\x0F\x0F\x0F",
"\x10\x10\x10",
"\x11\x11\x11",
"\x12\x12\x12",
"\x13\x13\x13",
"\x14\x14\x14",
"\x15\x15\x15",
"\x16\x16\x16",
"\x17\x17\x17",
"\x18\x18\x18",
"\x19\x19\x19",
"\x1A\x1A\x1A",
"\x1B\x1B\x1B",
"\x1C\x1C\x1C",
"\x1D\x1D\x1D",
"\x1E\x1E\x1E",
"\x1F\x1F\x1F",
"\x2E\x2D\x32",
"\x47\x75\x30",
"\x7A\x52\x21",
"\x56\x60\x29",
"\x42\x71\x5B",
"\x6A\x5E\x38",
"\x2F\x49\x33",
"\x26\x5C\x3D",
"\x49\x62\x58",
"\x41\x7D\x3A",
"\x34\x29\x35",
"\x32\x36\x65",
"\x5B\x20\x39",
"\x76\x7C\x5C",
"\x72\x7A\x56",
"\x43\x7F\x73",
"\x38\x6B\x66",
"\x39\x63\x4E",
"\x70\x33\x45",
"\x45\x2B\x6B",
"\x68\x68\x62",
"\x71\x51\x59",
"\x4F\x66\x78",
"\x09\x76\x5E",
"\x62\x31\x7D",
"\x44\x64\x4A",
"\x23\x54\x6D",
"\x75\x43\x71",
"\x4A\x4C\x41",
"\x7E\x3A\x60",
"\x4A\x4C\x41",
"\x5E\x7E\x53",
"\x40\x4C\x40",
"\x77\x45\x42",
"\x4A\x2C\x27",
"\x61\x2A\x48",
"\x5D\x74\x72",
"\x22\x27\x75",
"\x4B\x37\x31",
"\x6F\x44\x37",
"\x4E\x79\x4D",
"\x3B\x59\x52",
"\x4C\x2F\x22",
"\x50\x6F\x54",
"\x67\x26\x6A",
"\x2A\x72\x47",
"\x7D\x6A\x64",
"\x74\x39\x2D",
"\x54\x7B\x20",
"\x2B\x3F\x7F",
"\x2D\x38\x2E",
"\x2C\x77\x4C",
"\x30\x67\x5D",
"\x6E\x53\x7E",
"\x6B\x47\x6C",
"\x66\x34\x6F",
"\x35\x78\x79",
"\x25\x5D\x74",
"\x21\x30\x43",
"\x64\x23\x26",
"\x4D\x5A\x76",
"\x52\x5B\x25",
"\x63\x6C\x24",
"\x3F\x48\x2B",
"\x7B\x55\x28",
"\x78\x70\x23",
"\x29\x69\x41",
"\x28\x2E\x34",
"\x73\x4C\x09",
"\x59\x21\x2A",
"\x33\x24\x44",
"\x7F\x4E\x3F",
"\x6D\x50\x77",
"\x55\x09\x3B",
"\x53\x56\x55",
"\x7C\x73\x69",
"\x3A\x35\x61",
"\x5F\x61\x63",
"\x65\x4B\x50",
"\x46\x58\x67",
"\x58\x3B\x51",
"\x31\x57\x49",
"\x69\x22\x4F",
"\x6C\x6D\x46",
"\x5A\x4D\x68",
"\x48\x25\x7C",
"\x27\x28\x36",
"\x5C\x46\x70",
"\x3D\x4A\x6E",
"\x24\x32\x7A",
"\x79\x41\x2F",
"\x37\x3D\x5F",
"\x60\x5F\x4B",
"\x51\x4F\x5A",
"\x20\x42\x2C",
"\x36\x65\x57"
];
const D_COMBINATION = [
0, 1, 2, 0, 1, 2, 1, 2, 2, 1, 2, 1, 0, 2, 1, 2, 0, 2, 1, 2, 0, 0, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1,
0, 0, 2, 1, 2, 1, 2, 0, 2, 0, 0, 1, 2, 0, 2, 1, 0, 2, 1, 2, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 2, 1
];
export default MicrosoftScriptDecoder;

View File

@ -1,186 +0,0 @@
"use strict";
/**
* Various components for drawing diagrams on an HTML5 canvas.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*
* @constant
* @namespace
*/
const CanvasComponents = {
drawLine: function(ctx, startX, startY, endX, endY) {
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
},
drawBarChart: function(canvas, scores, xAxisLabel, yAxisLabel, numXLabels, numYLabels, fontSize) {
fontSize = fontSize || 15;
if (!numXLabels || numXLabels > Math.round(canvas.width / 50)) {
numXLabels = Math.round(canvas.width / 50);
}
if (!numYLabels || numYLabels > Math.round(canvas.width / 50)) {
numYLabels = Math.round(canvas.height / 50);
}
// Graph properties
var ctx = canvas.getContext("2d"),
leftPadding = canvas.width * 0.08,
rightPadding = canvas.width * 0.03,
topPadding = canvas.height * 0.08,
bottomPadding = canvas.height * 0.15,
graphHeight = canvas.height - topPadding - bottomPadding,
graphWidth = canvas.width - leftPadding - rightPadding,
base = topPadding + graphHeight,
ceil = topPadding;
ctx.font = fontSize + "px Arial";
// Draw axis
ctx.lineWidth = "1.0";
ctx.strokeStyle = "#444";
CanvasComponents.drawLine(ctx, leftPadding, base, graphWidth + leftPadding, base); // x
CanvasComponents.drawLine(ctx, leftPadding, base, leftPadding, ceil); // y
// Bar properties
var barPadding = graphWidth * 0.003,
barWidth = (graphWidth - (barPadding * scores.length)) / scores.length,
currX = leftPadding + barPadding,
max = Math.max.apply(Math, scores);
// Draw bars
ctx.fillStyle = "green";
for (var i = 0; i < scores.length; i++) {
var h = scores[i] / max * graphHeight;
ctx.fillRect(currX, base - h, barWidth, h);
currX += barWidth + barPadding;
}
// Mark x axis
ctx.fillStyle = "black";
ctx.textAlign = "center";
currX = leftPadding + barPadding;
if (numXLabels >= scores.length) {
// Mark every score
for (i = 0; i <= scores.length; i++) {
ctx.fillText(i, currX, base + (bottomPadding * 0.3));
currX += barWidth + barPadding;
}
} else {
// Mark some scores
for (i = 0; i <= numXLabels; i++) {
var val = Math.ceil((scores.length / numXLabels) * i);
currX = (graphWidth / numXLabels) * i + leftPadding;
ctx.fillText(val, currX, base + (bottomPadding * 0.3));
}
}
// Mark y axis
ctx.textAlign = "right";
var currY;
if (numYLabels >= max) {
// Mark every increment
for (i = 0; i <= max; i++) {
currY = base - (i / max * graphHeight) + fontSize / 3;
ctx.fillText(i, leftPadding * 0.8, currY);
}
} else {
// Mark some increments
for (i = 0; i <= numYLabels; i++) {
val = Math.ceil((max / numYLabels) * i);
currY = base - (val / max * graphHeight) + fontSize / 3;
ctx.fillText(val, leftPadding * 0.8, currY);
}
}
// Label x axis
if (xAxisLabel) {
ctx.textAlign = "center";
ctx.fillText(xAxisLabel, graphWidth / 2 + leftPadding, base + bottomPadding * 0.8);
}
// Label y axis
if (yAxisLabel) {
ctx.save();
var x = leftPadding * 0.3,
y = graphHeight / 2 + topPadding;
ctx.translate(x, y);
ctx.rotate(-Math.PI / 2);
ctx.textAlign = "center";
ctx.fillText(yAxisLabel, 0, 0);
ctx.restore();
}
},
drawScaleBar: function(canvas, score, max, markings) {
// Bar properties
var ctx = canvas.getContext("2d"),
leftPadding = canvas.width * 0.01,
rightPadding = canvas.width * 0.01,
topPadding = canvas.height * 0.1,
bottomPadding = canvas.height * 0.3,
barHeight = canvas.height - topPadding - bottomPadding,
barWidth = canvas.width - leftPadding - rightPadding;
// Scale properties
var proportion = score / max;
// Draw bar outline
ctx.strokeRect(leftPadding, topPadding, barWidth, barHeight);
// Shade in up to proportion
var grad = ctx.createLinearGradient(leftPadding, 0, barWidth + leftPadding, 0);
grad.addColorStop(0, "green");
grad.addColorStop(0.5, "gold");
grad.addColorStop(1, "red");
ctx.fillStyle = grad;
ctx.fillRect(leftPadding, topPadding, barWidth * proportion, barHeight);
// Add markings
var x0, y0, x1, y1;
ctx.fillStyle = "black";
ctx.textAlign = "center";
ctx.font = "13px Arial";
for (var i = 0; i < markings.length; i++) {
// Draw min line down
x0 = barWidth / max * markings[i].min + leftPadding;
y0 = topPadding + barHeight + (bottomPadding * 0.1);
x1 = x0;
y1 = topPadding + barHeight + (bottomPadding * 0.3);
CanvasComponents.drawLine(ctx, x0, y0, x1, y1);
// Draw max line down
x0 = barWidth / max * markings[i].max + leftPadding;
x1 = x0;
CanvasComponents.drawLine(ctx, x0, y0, x1, y1);
// Join min and max lines
x0 = barWidth / max * markings[i].min + leftPadding;
y0 = topPadding + barHeight + (bottomPadding * 0.3);
x1 = barWidth / max * markings[i].max + leftPadding;
y1 = y0;
CanvasComponents.drawLine(ctx, x0, y0, x1, y1);
// Add label
if (markings[i].max >= max * 0.9) {
ctx.textAlign = "right";
x0 = x1;
} else if (markings[i].max <= max * 0.1) {
ctx.textAlign = "left";
} else {
x0 = x0 + (x1 - x0) / 2;
}
y0 = topPadding + barHeight + (bottomPadding * 0.8);
ctx.fillText(markings[i].label, x0, y0);
}
},
};
export default CanvasComponents;

View File

@ -13,7 +13,7 @@ import "bootstrap";
import "bootstrap-switch";
import "bootstrap-colorpicker";
import moment from "moment-timezone";
import CanvasComponents from "../core/vendor/canvascomponents.js";
import * as CanvasComponents from "../core/lib/CanvasComponents";
// CyberChef
import App from "./App";