diff --git a/.vs/Plotters-Candles-Volumes/v15/.suo b/.vs/Plotters-Candles-Volumes/v15/.suo
index e96c3c535..1408ec49f 100644
Binary files a/.vs/Plotters-Candles-Volumes/v15/.suo and b/.vs/Plotters-Candles-Volumes/v15/.suo differ
diff --git a/CandlePanel.js b/CandlePanel.js
new file mode 100644
index 000000000..babd6f50d
--- /dev/null
+++ b/CandlePanel.js
@@ -0,0 +1,303 @@
+
+function newCandlePanel() {
+
+ var currentCandlePanel = {
+ onCurrentCandleChanged: onCurrentCandleChanged,
+ container: undefined,
+ draw: draw,
+ getContainer: getContainer,
+ initialize: initialize
+ };
+
+ var container = newContainer();
+ container.initialize();
+ currentCandlePanel.container = container;
+
+ container.displacement.containerName = "Current Candle Panel";
+ container.zoom.containerName = "Current Candle Panel";
+ container.frame.containerName = "Current Candle Panel";
+
+ let currentCandle;
+
+ return currentCandlePanel;
+
+ function initialize() {
+
+ currentCandlePanel.container.frame.width = 250;
+ currentCandlePanel.container.frame.height = 300;
+
+ currentCandlePanel.container.frame.position.x = viewPort.visibleArea.topRight.x - currentCandlePanel.container.frame.width - 180;
+ currentCandlePanel.container.frame.position.y = 0;
+
+
+ }
+
+
+
+ function getContainer(point) {
+
+ var container;
+
+ /* First we check if this point is inside this space. */
+
+ if (this.container.frame.isThisPointHere(point, true) === true) {
+
+ return this.container;
+
+ } else {
+
+ /* This point does not belong to this space. */
+
+ return undefined;
+ }
+
+ }
+
+
+ function onCurrentCandleChanged(lastCurrentCandle) {
+
+ currentCandle = lastCurrentCandle;
+
+ }
+
+
+ function draw() {
+
+ this.container.frame.draw(false, false, true);
+
+ plotCurrentCandleInfo();
+
+ }
+
+
+ function plotCurrentCandleInfo() {
+
+ if (currentCandle === undefined) { return; }
+ if (currentCandle.innerCandle === undefined) { return; }
+
+ const frameBodyHeight = currentCandlePanel.container.frame.getBodyHeight();
+ const frameTitleHeight = currentCandlePanel.container.frame.height - frameBodyHeight;
+
+ const X_AXIS = currentCandlePanel.container.frame.width / 2;
+ const Y_AXIS = frameTitleHeight + frameBodyHeight / 2;
+
+ var candlePoint1 = {
+ x: X_AXIS - currentCandle.bodyWidth / 2,
+ y: Y_AXIS - currentCandle.bodyHeight / 2
+ };
+
+ var candlePoint2 = {
+ x: X_AXIS + currentCandle.bodyWidth / 2,
+ y: Y_AXIS - currentCandle.bodyHeight / 2
+ };
+
+ var candlePoint3 = {
+ x: X_AXIS + currentCandle.bodyWidth / 2,
+ y: Y_AXIS + currentCandle.bodyHeight / 2
+ };
+
+ var candlePoint4 = {
+ x: X_AXIS - currentCandle.bodyWidth / 2,
+ y: Y_AXIS + currentCandle.bodyHeight / 2
+ };
+
+
+ var stickPoint1 = {
+ x: X_AXIS - currentCandle.stickWidth / 2,
+ y: candlePoint1.y - currentCandle.stickStart
+ };
+
+ var stickPoint2 = {
+ x: X_AXIS + currentCandle.stickWidth / 2,
+ y: candlePoint1.y - currentCandle.stickStart
+ };
+
+ var stickPoint3 = {
+ x: X_AXIS + currentCandle.stickWidth / 2,
+ y: candlePoint1.y - currentCandle.stickStart + currentCandle.stickHeight
+ };
+
+ var stickPoint4 = {
+ x: X_AXIS - currentCandle.stickWidth / 2,
+ y: candlePoint1.y - currentCandle.stickStart + currentCandle.stickHeight
+ };
+
+
+ /* Extra bounderies due to constrained space */
+
+ const upperLimit = frameTitleHeight + frameBodyHeight * 0.15;
+ const downLimit = frameTitleHeight + frameBodyHeight * 0.85;
+
+ if (candlePoint1.y < upperLimit) {
+
+ candlePoint1.y = upperLimit;
+ candlePoint2.y = upperLimit;
+
+ }
+
+ if (stickPoint1.y < upperLimit) {
+
+ stickPoint1.y = upperLimit;
+ stickPoint2.y = upperLimit;
+
+ }
+
+ if (candlePoint3.y < upperLimit) {
+
+ candlePoint3.y = upperLimit;
+ candlePoint4.y = upperLimit;
+
+ }
+
+ if (stickPoint3.y < upperLimit) {
+
+ stickPoint3.y = upperLimit;
+ stickPoint4.y = upperLimit;
+
+ }
+
+ if (candlePoint1.y > downLimit) {
+
+ candlePoint1.y = downLimit;
+ candlePoint2.y = downLimit;
+
+ }
+
+ if (stickPoint1.y > downLimit) {
+
+ stickPoint1.y = downLimit;
+ stickPoint2.y = downLimit;
+
+ }
+
+ if (candlePoint3.y > downLimit) {
+
+ candlePoint3.y = downLimit;
+ candlePoint4.y = downLimit;
+
+ }
+
+ if (stickPoint3.y > downLimit) {
+
+ stickPoint3.y = downLimit;
+ stickPoint4.y = downLimit;
+
+ }
+
+
+ candlePoint1 = currentCandlePanel.container.frame.frameThisPoint(candlePoint1);
+ candlePoint2 = currentCandlePanel.container.frame.frameThisPoint(candlePoint2);
+ candlePoint3 = currentCandlePanel.container.frame.frameThisPoint(candlePoint3);
+ candlePoint4 = currentCandlePanel.container.frame.frameThisPoint(candlePoint4);
+
+ stickPoint1 = currentCandlePanel.container.frame.frameThisPoint(stickPoint1);
+ stickPoint2 = currentCandlePanel.container.frame.frameThisPoint(stickPoint2);
+ stickPoint3 = currentCandlePanel.container.frame.frameThisPoint(stickPoint3);
+ stickPoint4 = currentCandlePanel.container.frame.frameThisPoint(stickPoint4);
+
+
+
+ browserCanvasContext.beginPath();
+
+ browserCanvasContext.moveTo(stickPoint1.x, stickPoint1.y);
+ browserCanvasContext.lineTo(stickPoint2.x, stickPoint2.y);
+ browserCanvasContext.lineTo(stickPoint3.x, stickPoint3.y);
+ browserCanvasContext.lineTo(stickPoint4.x, stickPoint4.y);
+
+ browserCanvasContext.closePath();
+ browserCanvasContext.fillStyle = 'rgba(54, 54, 54, 1)';
+ browserCanvasContext.fill();
+
+ browserCanvasContext.lineWidth = 1;
+ browserCanvasContext.stroke();
+
+ browserCanvasContext.beginPath();
+
+ browserCanvasContext.moveTo(candlePoint1.x, candlePoint1.y);
+ browserCanvasContext.lineTo(candlePoint2.x, candlePoint2.y);
+ browserCanvasContext.lineTo(candlePoint3.x, candlePoint3.y);
+ browserCanvasContext.lineTo(candlePoint4.x, candlePoint4.y);
+
+ browserCanvasContext.closePath();
+
+ const OPACITY = '1';
+
+ if (currentCandle.innerCandle.direction === 'up') { browserCanvasContext.strokeStyle = 'rgba(27, 105, 7, ' + OPACITY + ')'; }
+ if (currentCandle.innerCandle.direction === 'down') { browserCanvasContext.strokeStyle = 'rgba(130, 9, 9, ' + OPACITY + ')'; }
+ if (currentCandle.innerCandle.direction === 'side') { browserCanvasContext.strokeStyle = 'rgba(27, 7, 105, ' + OPACITY + ')'; }
+
+ if (currentCandle.innerCandle.direction === 'up') { browserCanvasContext.fillStyle = 'rgba(64, 217, 26, ' + OPACITY + ')'; }
+ if (currentCandle.innerCandle.direction === 'down') { browserCanvasContext.fillStyle = 'rgba(219, 18, 18, ' + OPACITY + ')'; }
+ if (currentCandle.innerCandle.direction === 'side') { browserCanvasContext.fillStyle = 'rgba(64, 26, 217, ' + OPACITY + ')'; }
+
+ browserCanvasContext.fill();
+
+ browserCanvasContext.lineWidth = 1;
+ browserCanvasContext.stroke();
+
+
+ browserCanvasContext.beginPath();
+
+ /* put the labels with the candles values */
+
+
+
+
+ let y;
+
+ printLabel('High', X_AXIS, frameTitleHeight + frameBodyHeight * 0.05, '1');
+ printLabel(currentCandle.innerCandle.max, X_AXIS, frameTitleHeight + frameBodyHeight * 0.10, '0.50');
+
+ printLabel('Low', X_AXIS, frameTitleHeight + frameBodyHeight * 0.95, '1');
+ printLabel(currentCandle.innerCandle.min, X_AXIS, frameTitleHeight + frameBodyHeight * 0.90, '0.50');
+
+
+ y = Y_AXIS - currentCandle.bodyHeight / 2;
+
+ if (y < frameTitleHeight + frameBodyHeight * 0.15) { y = frameTitleHeight + frameBodyHeight * 0.15; }
+ if (y > frameTitleHeight + frameBodyHeight * 0.80) { y = frameTitleHeight + frameBodyHeight * 0.80; }
+
+ printLabel('Open', X_AXIS * 1 / 2, y , '1');
+ printLabel(currentCandle.innerCandle.open, X_AXIS * 1 / 2, y + frameBodyHeight * 0.05, '0.50');
+
+ y = Y_AXIS + currentCandle.bodyHeight / 2;
+
+ if (y < frameTitleHeight + frameBodyHeight * 0.15) { y = frameTitleHeight + frameBodyHeight * 0.15; }
+ if (y > frameTitleHeight + frameBodyHeight * 0.80) { y = frameTitleHeight + frameBodyHeight * 0.80; }
+
+ printLabel('Close', X_AXIS * 3 / 2, y , '1');
+ printLabel(currentCandle.innerCandle.close, X_AXIS * 3 / 2, y + frameBodyHeight * 0.05, '0.50');
+
+
+
+ function printLabel(labelToPrint, x, y, opacity) {
+
+ let labelPoint;
+ let fontSize = 10;
+
+ browserCanvasContext.font = fontSize + 'px Courier New';
+
+ let label = '' + labelToPrint;
+
+ let xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO;
+
+ labelPoint = {
+ x: x - xOffset,
+ y: y
+ };
+
+ labelPoint = currentCandlePanel.container.frame.frameThisPoint(labelPoint);
+
+ browserCanvasContext.fillStyle = 'rgba(60, 60, 60, ' + opacity + ')';
+ browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y);
+
+ }
+
+ browserCanvasContext.closePath();
+ browserCanvasContext.fill();
+
+ }
+
+
+}
+
diff --git a/Candles.png b/Candles.png
new file mode 100644
index 000000000..f1e093cd5
Binary files /dev/null and b/Candles.png differ
diff --git a/Project.njsproj b/Project.njsproj
index ee3f8fe27..f8e8bdc92 100644
--- a/Project.njsproj
+++ b/Project.njsproj
@@ -17,11 +17,16 @@
+
+
Code
+
+
+
diff --git a/VolumePanel.js b/VolumePanel.js
new file mode 100644
index 000000000..4a2d08eb6
--- /dev/null
+++ b/VolumePanel.js
@@ -0,0 +1,224 @@
+
+function newVolumePanel() {
+
+ var currentVolumePanel = {
+ onCurrentVolumeChanged: onCurrentVolumeChanged,
+ container: undefined,
+ draw: draw,
+ getContainer: getContainer,
+ initialize: initialize
+ };
+
+ var container = newContainer();
+ container.initialize();
+ currentVolumePanel.container = container;
+
+ container.displacement.containerName = "Current Volume Panel";
+ container.zoom.containerName = "Current Volume Panel";
+ container.frame.containerName = "Current Volume Panel";
+
+ let currentVolume;
+
+ return currentVolumePanel;
+
+ function initialize() {
+
+ currentVolumePanel.container.frame.width = 250;
+ currentVolumePanel.container.frame.height = 300;
+
+ currentVolumePanel.container.frame.position.x = viewPort.visibleArea.topRight.x - currentVolumePanel.container.frame.width;
+ currentVolumePanel.container.frame.position.y = 0;
+
+
+ }
+
+
+
+ function getContainer(point) {
+
+ var container;
+
+ /* First we check if this point is inside this space. */
+
+ if (this.container.frame.isThisPointHere(point, true) === true) {
+
+ return this.container;
+
+ } else {
+
+ /* This point does not belong to this space. */
+
+ return undefined;
+ }
+
+ }
+
+
+ function onCurrentVolumeChanged(lastCurrentVolume) {
+
+ currentVolume = lastCurrentVolume;
+
+ }
+
+
+ function draw() {
+
+ this.container.frame.draw(false, false, true);
+
+ plotCurrentVolumeInfo();
+
+ }
+
+
+ function plotCurrentVolumeInfo() {
+
+ if (currentVolume === undefined) { return; }
+ if (currentVolume.innerVolumeBar === undefined) { return; }
+
+ const frameBodyHeight = currentVolumePanel.container.frame.getBodyHeight();
+ const frameTitleHeight = currentVolumePanel.container.frame.height - frameBodyHeight;
+
+ const X_AXIS = currentVolumePanel.container.frame.width / 2;
+ const Y_AXIS_BUY = frameTitleHeight + frameBodyHeight * 0.85;
+ const Y_AXIS_SELL = frameTitleHeight + frameBodyHeight * 0.15;
+
+ var buyVolumePoint1 = {
+ x: X_AXIS - currentVolume.buyInfo.baseWidth / 2,
+ y: Y_AXIS_BUY
+ };
+
+ var buyVolumePoint2 = {
+ x: X_AXIS - currentVolume.buyInfo.topWidth / 2,
+ y: Y_AXIS_BUY + currentVolume.buyInfo.height
+ };
+
+ var buyVolumePoint3 = {
+ x: X_AXIS + currentVolume.buyInfo.topWidth / 2,
+ y: Y_AXIS_BUY + currentVolume.buyInfo.height
+ };
+
+ var buyVolumePoint4 = {
+ x: X_AXIS + currentVolume.buyInfo.baseWidth / 2,
+ y: Y_AXIS_BUY
+ };
+
+ buyVolumePoint1 = currentVolumePanel.container.frame.fitIntoFrame(buyVolumePoint1);
+ buyVolumePoint2 = currentVolumePanel.container.frame.fitIntoFrame(buyVolumePoint2);
+ buyVolumePoint3 = currentVolumePanel.container.frame.fitIntoFrame(buyVolumePoint3);
+ buyVolumePoint4 = currentVolumePanel.container.frame.fitIntoFrame(buyVolumePoint4);
+
+ buyVolumePoint1 = currentVolumePanel.container.frame.frameThisPoint(buyVolumePoint1);
+ buyVolumePoint2 = currentVolumePanel.container.frame.frameThisPoint(buyVolumePoint2);
+ buyVolumePoint3 = currentVolumePanel.container.frame.frameThisPoint(buyVolumePoint3);
+ buyVolumePoint4 = currentVolumePanel.container.frame.frameThisPoint(buyVolumePoint4);
+
+ var sellVolumePoint1 = {
+ x: X_AXIS - currentVolume.buyInfo.baseWidth / 2,
+ y: Y_AXIS_SELL
+ };
+
+ var sellVolumePoint2 = {
+ x: X_AXIS - currentVolume.buyInfo.topWidth / 2,
+ y: Y_AXIS_SELL + currentVolume.sellInfo.height
+ };
+
+ var sellVolumePoint3 = {
+ x: X_AXIS + currentVolume.buyInfo.topWidth / 2,
+ y: Y_AXIS_SELL + currentVolume.sellInfo.height
+ };
+
+ var sellVolumePoint4 = {
+ x: X_AXIS + currentVolume.buyInfo.baseWidth / 2,
+ y: Y_AXIS_SELL
+ };
+
+ sellVolumePoint1 = currentVolumePanel.container.frame.fitIntoFrame(sellVolumePoint1);
+ sellVolumePoint2 = currentVolumePanel.container.frame.fitIntoFrame(sellVolumePoint2);
+ sellVolumePoint3 = currentVolumePanel.container.frame.fitIntoFrame(sellVolumePoint3);
+ sellVolumePoint4 = currentVolumePanel.container.frame.fitIntoFrame(sellVolumePoint4);
+
+ sellVolumePoint1 = currentVolumePanel.container.frame.frameThisPoint(sellVolumePoint1);
+ sellVolumePoint2 = currentVolumePanel.container.frame.frameThisPoint(sellVolumePoint2);
+ sellVolumePoint3 = currentVolumePanel.container.frame.frameThisPoint(sellVolumePoint3);
+ sellVolumePoint4 = currentVolumePanel.container.frame.frameThisPoint(sellVolumePoint4);
+
+ const OPACITY = '0.40';
+
+ browserCanvasContext.beginPath();
+
+ browserCanvasContext.moveTo(buyVolumePoint1.x, buyVolumePoint1.y);
+ browserCanvasContext.lineTo(buyVolumePoint2.x, buyVolumePoint2.y);
+ browserCanvasContext.lineTo(buyVolumePoint3.x, buyVolumePoint3.y);
+ browserCanvasContext.lineTo(buyVolumePoint4.x, buyVolumePoint4.y);
+
+ browserCanvasContext.closePath();
+
+ browserCanvasContext.strokeStyle = 'rgba(27, 105, 7, ' + OPACITY + ')';
+ browserCanvasContext.fillStyle = 'rgba(64, 217, 26, ' + OPACITY + ')';
+
+ browserCanvasContext.fill();
+
+ browserCanvasContext.lineWidth = 1;
+ browserCanvasContext.stroke();
+
+
+ browserCanvasContext.beginPath();
+
+ browserCanvasContext.moveTo(sellVolumePoint1.x, sellVolumePoint1.y);
+ browserCanvasContext.lineTo(sellVolumePoint2.x, sellVolumePoint2.y);
+ browserCanvasContext.lineTo(sellVolumePoint3.x, sellVolumePoint3.y);
+ browserCanvasContext.lineTo(sellVolumePoint4.x, sellVolumePoint4.y);
+
+ browserCanvasContext.closePath();
+
+ browserCanvasContext.strokeStyle = 'rgba(130, 9, 9, ' + OPACITY + ')';
+ browserCanvasContext.fillStyle = 'rgba(219, 18, 18, ' + OPACITY + ')';
+
+ browserCanvasContext.fill();
+
+ browserCanvasContext.lineWidth = 1;
+ browserCanvasContext.stroke();
+
+
+ /* put the labels with the volumes values */
+
+ browserCanvasContext.beginPath();
+
+ printLabel('Buy Volume', X_AXIS, frameTitleHeight + frameBodyHeight * 0.95, '1');
+ printLabel(currentVolume.innerVolumeBar.amountBuy, X_AXIS, frameTitleHeight + frameBodyHeight * 0.90, '0.50');
+
+ printLabel('Sell Volume', X_AXIS, frameTitleHeight + frameBodyHeight * 0.05, '1');
+ printLabel(currentVolume.innerVolumeBar.amountSell, X_AXIS, frameTitleHeight + frameBodyHeight * 0.10, '0.50');
+
+
+ function printLabel(labelToPrint, x, y, opacity) {
+
+ let labelPoint;
+ let fontSize = 10;
+
+ browserCanvasContext.font = fontSize + 'px Courier New';
+
+ let label = '' + labelToPrint;
+
+ let xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO;
+
+ labelPoint = {
+ x: x - xOffset,
+ y: y
+ };
+
+ labelPoint = currentVolumePanel.container.frame.frameThisPoint(labelPoint);
+
+ browserCanvasContext.fillStyle = 'rgba(60, 60, 60, ' + opacity + ')';
+ browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y);
+
+ }
+
+ browserCanvasContext.closePath();
+ browserCanvasContext.fill();
+
+ }
+
+
+}
+
diff --git a/this.plotter.config.json b/this.plotter.config.json
new file mode 100644
index 000000000..13724b2f2
--- /dev/null
+++ b/this.plotter.config.json
@@ -0,0 +1,27 @@
+{
+ "modules": [
+ {
+ "codeName": "Candles",
+ "moduleName": "Candles",
+ "description": "Plots traditional red and green candles.",
+ "profilePicture": "Candles.png",
+ "panels": [
+ {
+ "codeName": "Current Candle",
+ "moduleName": "CandlePanel"
+ }
+ ]
+ },
+ {
+ "codeName": "Volumes",
+ "description": "Plots volumes separated in buy and sell.",
+ "moduleName": "Volumes",
+ "panels": [
+ {
+ "codeName": "Current Volume",
+ "moduleName": "VolumePanel"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file