601 lines
18 KiB
JavaScript
601 lines
18 KiB
JavaScript
|
|
function newTimelineChart() {
|
|
|
|
const CONSOLE_LOG = false;
|
|
|
|
let activePlotters = [];
|
|
|
|
let timeLineCoordinateSystem = newTimeLineCoordinateSystem();
|
|
|
|
let timePeriod = INITIAL_TIME_PERIOD;
|
|
let datetime = INITIAL_DATE;
|
|
|
|
var thisObject = {
|
|
setDatetime: setDatetime,
|
|
container: undefined,
|
|
draw: draw,
|
|
getContainer: getContainer,
|
|
initialize: initialize
|
|
};
|
|
|
|
var container = newContainer();
|
|
container.initialize();
|
|
thisObject.container = container;
|
|
|
|
container.displacement.containerName = "Time Period Chart";
|
|
container.zoom.containerName = "Time Period Chart";
|
|
container.frame.containerName = "Time Period Chart";
|
|
|
|
let marketId;
|
|
let exchangeId;
|
|
|
|
let marketIndex;
|
|
|
|
let level;
|
|
|
|
let chartGrid;
|
|
|
|
let initializationReady = false;
|
|
|
|
let currentCandlePanel;
|
|
let currentVolumePanel;
|
|
let productsPanel;
|
|
let orderBookPanel;
|
|
|
|
return thisObject;
|
|
|
|
function initialize(exchange, market, currentCandlePanelToUse, currentVolumePanelToUse, pProductsPanel, orderBookPanelToUse, callBackFunction) {
|
|
|
|
/* Remember the Products Panel */
|
|
|
|
productsPanel = pProductsPanel;
|
|
|
|
/* Listen to the event of change of status */
|
|
|
|
productsPanel.container.eventHandler.listenToEvent("Product Card Status Changed", onProductCardStatusChanged);
|
|
|
|
/* Legacy code to clean */
|
|
|
|
currentCandlePanel = currentCandlePanelToUse;
|
|
currentVolumePanel = currentVolumePanelToUse;
|
|
orderBookPanel = orderBookPanelToUse;
|
|
|
|
marketId = market;
|
|
exchangeId = exchange;
|
|
|
|
chartGrid = newChartGrid();
|
|
|
|
marketIndex = newMarketIndex();
|
|
marketIndex.initialize(exchangeId, marketId, continueInitialization);
|
|
|
|
function continueInitialization() {
|
|
|
|
recalculateScale();
|
|
moveViewPortToCurrentDatetime();
|
|
|
|
/* Event Subscriptions - we need this events to be fired first here and then in active Plotters. */
|
|
|
|
viewPort.eventHandler.listenToEvent("Offset Changed", onOffsetChanged);
|
|
viewPort.eventHandler.listenToEvent("Zoom Changed", onZoomChanged);
|
|
canvas.eventHandler.listenToEvent("Drag Finished", onDragFinished);
|
|
|
|
initializeProductPlotters();
|
|
initializeCompetitionPlotters();
|
|
|
|
callBackFunction();
|
|
|
|
initializationReady = true;
|
|
}
|
|
}
|
|
|
|
function initializeCompetitionPlotters() {
|
|
|
|
/* At this current version of the platform, we will support only one competition with only one plotter. */
|
|
|
|
const COMPETITION_HOST = "AAArena";
|
|
const COMPETITION = "First-Closed-Door";
|
|
|
|
let objName = COMPETITION_HOST + "-" + COMPETITION;
|
|
let storage = newCompetitionStorage(objName);
|
|
|
|
let host = ecosystem.getHost(COMPETITION_HOST);
|
|
let competition = ecosystem.getCompetition(host, COMPETITION);
|
|
|
|
storage.initialize(host, competition, onStorageInitialized);
|
|
|
|
function onStorageInitialized() {
|
|
|
|
/* Now we have all the initial data loaded and ready to be delivered to the new instance of the plotter. */
|
|
|
|
let plotter = getNewPlotter(competition.plotter.host, competition.plotter.repo, competition.plotter.moduleName);
|
|
|
|
plotter.container.displacement.parentDisplacement = thisObject.container.displacement;
|
|
plotter.container.zoom.parentZoom = thisObject.container.zoom;
|
|
plotter.container.frame.parentFrame = thisObject.container.frame;
|
|
|
|
plotter.container.parentContainer = thisObject.container;
|
|
|
|
plotter.container.frame.width = thisObject.container.frame.width * 1;
|
|
plotter.container.frame.height = thisObject.container.frame.height * 1;
|
|
|
|
plotter.container.frame.position.x = thisObject.container.frame.width / 2 - plotter.container.frame.width / 2;
|
|
plotter.container.frame.position.y = thisObject.container.frame.height / 2 - plotter.container.frame.height / 2;
|
|
|
|
plotter.initialize(storage, datetime, timePeriod, onPlotterInizialized);
|
|
|
|
function onPlotterInizialized() {
|
|
|
|
if (CONSOLE_LOG === true) {
|
|
|
|
console.log(competition.plotter.host, competition.plotter.repo, competition.plotter.moduleName + " Initialized. ");
|
|
|
|
}
|
|
|
|
let activePlotter = {
|
|
productCard: undefined,
|
|
plotter: plotter,
|
|
storage: storage
|
|
};
|
|
|
|
/* Add the new Active Protter to the Array */
|
|
|
|
activePlotters.push(activePlotter);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
function initializeProductPlotters() {
|
|
|
|
/* Lets get all the cards that needs to be loaded. */
|
|
|
|
let loadingProductCards = productsPanel.getLoadingProductCards();
|
|
|
|
for (let i = 0; i < loadingProductCards.length; i++) {
|
|
|
|
/* For each one, we will initialize the associated plotter. */
|
|
|
|
initializeProductPlotter(loadingProductCards[i]);
|
|
|
|
}
|
|
}
|
|
|
|
function initializeProductPlotter(pProductCard) {
|
|
|
|
/* Before Initializing a Plotter, we need the Storage it will use, loaded with the files it will initially need. */
|
|
|
|
let objName = pProductCard.devTeam.codeName + "-" + pProductCard.bot.codeName + "-" + pProductCard.product.codeName;
|
|
let storage = newProductStorage(objName);
|
|
|
|
/*
|
|
|
|
Before Initializing the Storage, we will put the Product Card to listen to the events the storage will raise every time a file is loaded,
|
|
so that the UI can somehow show this. There are different types of events.
|
|
|
|
*/
|
|
|
|
for (let i = 0; i < pProductCard.product.sets.length; i++) {
|
|
|
|
let thisSet = pProductCard.product.sets[i];
|
|
|
|
switch (thisSet.type) {
|
|
case 'Market Files': {
|
|
|
|
storage.eventHandler.listenToEvent('Market File Loaded', pProductCard.onMarketFileLoaded);
|
|
|
|
}
|
|
break;
|
|
|
|
case 'Daily Files': {
|
|
|
|
storage.eventHandler.listenToEvent('Daily File Loaded', pProductCard.onDailyFileLoaded);
|
|
|
|
}
|
|
break;
|
|
|
|
case 'Single File': {
|
|
|
|
storage.eventHandler.listenToEvent('Single File Loaded', pProductCard.onSingleFileLoaded);;
|
|
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
storage.initialize(pProductCard.devTeam, pProductCard.bot, pProductCard.product, DEFAULT_EXCHANGE, DEFAULT_MARKET, datetime, timePeriod, onStorageInitialized) ;
|
|
|
|
function onStorageInitialized() {
|
|
|
|
/* Now we have all the initial data loaded and ready to be delivered to the new instance of the plotter. */
|
|
|
|
let plotter = getNewPlotter(pProductCard.product.plotter.devTeam, pProductCard.product.plotter.repo, pProductCard.product.plotter.moduleName);
|
|
|
|
plotter.container.displacement.parentDisplacement = thisObject.container.displacement;
|
|
plotter.container.zoom.parentZoom = thisObject.container.zoom;
|
|
plotter.container.frame.parentFrame = thisObject.container.frame;
|
|
|
|
plotter.container.parentContainer = thisObject.container;
|
|
|
|
plotter.container.frame.width = thisObject.container.frame.width * 1;
|
|
plotter.container.frame.height = thisObject.container.frame.height * 1;
|
|
|
|
plotter.container.frame.position.x = thisObject.container.frame.width / 2 - plotter.container.frame.width / 2;
|
|
plotter.container.frame.position.y = thisObject.container.frame.height / 2 - plotter.container.frame.height / 2;
|
|
|
|
plotter.initialize(storage, DEFAULT_EXCHANGE, DEFAULT_MARKET, datetime, timePeriod, onPlotterInizialized);
|
|
|
|
function onPlotterInizialized() {
|
|
|
|
if (CONSOLE_LOG === true) {
|
|
|
|
console.log(pProductCard.product.plotter.devTeam + '->' + pProductCard.product.plotter.repo + '->' + pProductCard.product.plotter.moduleName + " Initialized. ");
|
|
|
|
}
|
|
|
|
try {
|
|
plotter.positionAtDatetime(INITIAL_DATE);
|
|
} catch (err) {
|
|
// If the plotter does not implement this function its ok.
|
|
}
|
|
|
|
let activePlotter = {
|
|
productCard: pProductCard,
|
|
plotter: plotter,
|
|
storage: storage
|
|
};
|
|
|
|
/* Let the Plotter listen to the event of Cursor Files loaded, so that it can reack recalculating if needed. */
|
|
|
|
storage.eventHandler.listenToEvent('Daily File Loaded', plotter.onDailyFileLoaded);
|
|
|
|
/* Add the new Active Protter to the Array */
|
|
|
|
activePlotters.push(activePlotter);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
function onProductCardStatusChanged(pProductCard) {
|
|
|
|
if (pProductCard.status === PRODUCT_CARD_STATUS.LOADING) {
|
|
|
|
/* Lets see if we can find the Plotter of this card on our Active Plotters list, other wise we will initialize it */
|
|
|
|
let found = false;
|
|
|
|
for (let i = 0; i < activePlotters.length; i++) {
|
|
|
|
if (activePlotters[i].productCard.code === pProductCard.code) {
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (found === false) {
|
|
|
|
initializeProductPlotter(pProductCard);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pProductCard.status === PRODUCT_CARD_STATUS.OFF) {
|
|
|
|
/* If the plotter of this card is on our Active Plotters list, then we remove it. */
|
|
|
|
for (let i = 0; i < activePlotters.length; i++) {
|
|
|
|
if (activePlotters[i].productCard.code === pProductCard.code) {
|
|
|
|
activePlotters.splice(i, 1); // Delete item from array.
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function onZoomChanged(event) {
|
|
|
|
if (initializationReady === true) {
|
|
|
|
level = event.newLevel; // for debugging purposes only
|
|
|
|
let currentTimePeriod = timePeriod;
|
|
|
|
timePeriod = recalculatePeriod(event.newLevel);
|
|
|
|
/* If the period changes, we need to spread the word in cascade towards all the depending objects. */
|
|
|
|
if (timePeriod !== currentTimePeriod) {
|
|
|
|
for (let i = 0; i < activePlotters.length; i++) {
|
|
|
|
let activePlotter = activePlotters[i];
|
|
|
|
activePlotter.productCard.setTimePeriod(timePeriod);
|
|
activePlotter.storage.setTimePeriod(timePeriod);
|
|
activePlotter.plotter.setTimePeriod(timePeriod);
|
|
|
|
}
|
|
}
|
|
|
|
recalculateCurrentDatetime();
|
|
|
|
}
|
|
}
|
|
|
|
function onDragFinished() {
|
|
|
|
if (initializationReady === true) {
|
|
|
|
if (thisObject.container.frame.isInViewPort() && tooSmall() === false) {
|
|
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
function onOffsetChanged() {
|
|
|
|
if (initializationReady === true) {
|
|
|
|
if (thisObject.container.frame.isInViewPort()) {
|
|
|
|
recalculateCurrentDatetime();
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
function recalculateCurrentDatetime() {
|
|
|
|
/*
|
|
|
|
The view port was moved or the view port zoom level was changed and the center of the screen points to a different datetime that me
|
|
must calculate.
|
|
|
|
*/
|
|
|
|
let center = {
|
|
x: (viewPort.visibleArea.bottomRight.x - viewPort.visibleArea.bottomLeft.x) / 2,
|
|
y: (viewPort.visibleArea.bottomRight.y - viewPort.visibleArea.topRight.y) / 2
|
|
};
|
|
|
|
center = unTransformThisPoint(center, thisObject.container);
|
|
center = timeLineCoordinateSystem.unInverseTransform(center, thisObject.container.frame.height);
|
|
|
|
let newDate = new Date(0);
|
|
newDate.setUTCSeconds(center.x / 1000);
|
|
|
|
datetime = newDate;
|
|
|
|
for (var i = 0; i < activePlotters.length; i++) {
|
|
|
|
let activePlotter = activePlotters[i];
|
|
|
|
activePlotter.productCard.setDatetime(datetime);
|
|
activePlotter.storage.setDatetime(datetime);
|
|
activePlotter.plotter.setDatetime(datetime);
|
|
|
|
}
|
|
|
|
thisObject.container.eventHandler.raiseEvent("Datetime Changed", datetime);
|
|
|
|
}
|
|
|
|
function getContainer(point) {
|
|
|
|
var container;
|
|
|
|
/* First we check if this point is inside this space. */
|
|
|
|
if (this.container.frame.isThisPointHere(point) === true) {
|
|
|
|
return this.container;
|
|
|
|
} else {
|
|
|
|
/* This point does not belong to this space. */
|
|
|
|
return undefined;
|
|
}
|
|
|
|
}
|
|
|
|
function setDatetime(pDatetime) {
|
|
|
|
/* This function is used when the time is changed through the user interface, but without zooming or panning. */
|
|
/* No matter if the day changed or not, we need to inform all visible Plotters. */
|
|
|
|
if (thisObject.container.frame.isInViewPort()) {
|
|
|
|
for (var i = 0; i < activePlotters.length; i++) {
|
|
|
|
let activePlotter = activePlotters[i];
|
|
|
|
activePlotter.productCard.setDatetime(pDatetime);
|
|
activePlotter.storage.setDatetime(pDatetime);
|
|
activePlotter.plotter.setDatetime(pDatetime);
|
|
|
|
/* The time has changed, but the viewPort is still on the same place, so we request any of the plotters to reposition it. */
|
|
|
|
if (activePlotter.plotter.positionAtDatetime !== undefined) {
|
|
activePlotter.plotter.positionAtDatetime(pDatetime);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
function draw() {
|
|
|
|
if (activePlotters === undefined) { return; } // We need to wait
|
|
|
|
if (thisObject.container.frame.isInViewPort()) {
|
|
|
|
this.container.frame.draw();
|
|
|
|
drawBackground();
|
|
|
|
chartGrid.draw(thisObject.container, timeLineCoordinateSystem);
|
|
|
|
for (var i = 0; i < activePlotters.length; i++) {
|
|
|
|
let activePlotter = activePlotters[i];
|
|
activePlotter.plotter.draw();
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
function recalculateScale() {
|
|
|
|
var minValue = {
|
|
x: EARLIEST_DATE.valueOf(),
|
|
y: 0
|
|
};
|
|
|
|
var maxValue = {
|
|
x: MAX_PLOTABLE_DATE.valueOf(),
|
|
y: nextPorwerOf10(marketIndex.maxRate())
|
|
};
|
|
|
|
|
|
timeLineCoordinateSystem.initialize(
|
|
minValue,
|
|
maxValue,
|
|
thisObject.container.frame.width,
|
|
thisObject.container.frame.height
|
|
);
|
|
|
|
}
|
|
|
|
function tooTiny() {
|
|
|
|
if (viewPort.zoomLevel < Math.trunc(-28.25 * 100) / 100) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
function tooSmall() {
|
|
|
|
if (viewPort.zoomLevel < Math.trunc(-27.25 * 100) / 100) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
function drawBackground() {
|
|
|
|
let targetLabelFontSize = 150;
|
|
let fontSizeIncrement = 12.5;
|
|
let currentFontSize = 150;
|
|
|
|
var market = markets.get(marketId);
|
|
var label = market.assetA + " " + market.assetB;
|
|
|
|
//label = '' + level; // Math.trunc(timePeriod / 1000 / 60);
|
|
|
|
if (tooTiny() === true ) {
|
|
return;
|
|
}
|
|
|
|
let topPoint = {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
|
|
let bottomPoint = {
|
|
x: 0,
|
|
y: thisObject.container.frame.height
|
|
};
|
|
|
|
topPoint = transformThisPoint(topPoint, thisObject.container);
|
|
bottomPoint = transformThisPoint(bottomPoint, thisObject.container);
|
|
|
|
/* We want the label of the market to be always centered in the middle of the screen, unless the upper or lower border of the frame is visible on the screen */
|
|
|
|
let point;
|
|
|
|
if (targetLabelFontSize !== currentFontSize) {
|
|
if (targetLabelFontSize > currentFontSize) {
|
|
currentFontSize = currentFontSize + fontSizeIncrement;
|
|
} else {
|
|
currentFontSize = currentFontSize - fontSizeIncrement;
|
|
}
|
|
}
|
|
|
|
if (topPoint.y > viewPort.visibleArea.topLeft.y || bottomPoint.y < viewPort.visibleArea.bottomRight.y) {
|
|
|
|
targetLabelFontSize = 150 / 2;
|
|
|
|
point = {
|
|
x: 0,
|
|
y: thisObject.container.frame.height / 2
|
|
};
|
|
|
|
point = transformThisPoint(point, thisObject.container);
|
|
|
|
point = {
|
|
x: (viewPort.visibleArea.bottomRight.x - viewPort.visibleArea.bottomLeft.x) / 2 - label.length / 2 * currentFontSize * 0.60,
|
|
y: point.y + currentFontSize * 0.60 / 2
|
|
};
|
|
|
|
} else {
|
|
|
|
targetLabelFontSize = 150;
|
|
|
|
point = {
|
|
x: (viewPort.visibleArea.bottomRight.x - viewPort.visibleArea.bottomLeft.x) / 2 - label.length / 2 * currentFontSize * 0.60,
|
|
y: (viewPort.visibleArea.bottomLeft.y - viewPort.visibleArea.topLeft.y) / 2 + currentFontSize * 0.60 / 2
|
|
};
|
|
|
|
}
|
|
|
|
browserCanvasContext.font = currentFontSize + 'px Courier New';
|
|
browserCanvasContext.fillStyle = 'rgba(75, 86, 235, 0.07)';
|
|
browserCanvasContext.fillText(label, point.x, point.y);
|
|
|
|
}
|
|
|
|
function moveViewPortToCurrentDatetime() {
|
|
|
|
let targetPoint = {
|
|
x: datetime.valueOf(),
|
|
y: 0 // we wont touch the y axis here.
|
|
};
|
|
|
|
/* Lets put this point in the coordinate system of the viewPort */
|
|
|
|
targetPoint = timeLineCoordinateSystem.transformThisPoint(targetPoint);
|
|
targetPoint = transformThisPoint(targetPoint, thisObject.container);
|
|
|
|
/* Lets get the point on the viewPort coordinate system of the center of the visible screen */
|
|
|
|
let center = {
|
|
x: (viewPort.visibleArea.bottomRight.x - viewPort.visibleArea.bottomLeft.x) / 2,
|
|
y: (viewPort.visibleArea.bottomRight.y - viewPort.visibleArea.topRight.y) / 2
|
|
};
|
|
|
|
/* Lets calculate the displace vector, from the point we want at the center, to the current center. */
|
|
|
|
let displaceVector = {
|
|
x: center.x - targetPoint.x,
|
|
y: 0
|
|
};
|
|
|
|
viewPort.displace(displaceVector);
|
|
}
|
|
}
|
|
|