' : '
', {
target: '_blank',
'class': 'chartWrapperTitleInner'
}));
// set the title above the chart
self.updateChartTitle
?self.updateChartTitle(titleDivInner)
:titleDivInner.text(driverName);
prompt.div.hide();
mainDiv.show();
$.each(outputListeners, function(i, listener) {
listener(driverId);
});
}
/**
* @param {Array of ForecastModel} models
* @return {int}
*/
function getIndexOfUserModel(models) {
var result = -1;
$.each(models, function(i, model) {
if(modelManager.get('active') == model)
result = i;
});
return result;
}
/**
* Creates the addition CSS classes for the toggles. Returns a two-element array where the first element is the class for the first
* toggle period and the second one is the class for the second period.
*/
function createAdditionalToggleClasses(togglePeriods, baseModelChartData) {
return $.map(togglePeriods, function(periodType) {
var chartDataFilteredByPeriodType = pickPeriod(baseModelChartData, periodType);
if(_.find(chartDataFilteredByPeriodType.array, function(point){ return !point.Fixed && point.AffectsAnyOutputs !== undefined && !point.AffectsAnyOutputs; })) {
if(tf.showRedOnInspector()) {
return 'red';
} else {
return '';
}
} else {
return '';
};
});
}
/**
* @param {String} driverId
* @return {boolean} whether the user model has modified values for this driver
*/
function getIsUserLineModified(driverId) {
var usersModel = modelManager.get('active');
// if this is an output instead of a driver, we want to exit
if (!usersModel.companyData.getChartData(driverId)){
return false;
}
if(usersModel.isModifiedDriver(driverId))
return true;
// calculate if any of the driver points have different values in the user's model compared to the base model
// iterate through all the modified drivers, and for each point check against the base model for any difference
var anyDriverPointsDifferentThanBaseModel = false;
$.each (usersModel.companyData.getChartData(driverId).array, function( index, driverPoint ) {
// fb3111: the values from the user model may be undefined if they're constants and just aren't recalculated
// by the model, so don't consider undefined values as differences
if (usersModel.cs.values[driverPoint.Identifier] !== undefined && usersModel.cs.values[driverPoint.Identifier] != driverPoint.Value) {
if (!MathUtil.equalsWithinTolerance(usersModel.cs.values[driverPoint.Identifier], driverPoint.Value, 0.01)){
anyDriverPointsDifferentThanBaseModel = true;
return false; // this is how we 'break' with $.each
}
}
});
if(anyDriverPointsDifferentThanBaseModel)
return true;
return false;
}
/**
* @return {Function} a function that can be called to reset the data onto the DraggableChart. The function should be
* called with a single argument: the current period type (an instance of the tf.period.type enum).
* The returned function effectively caches the state of the arguments like chartData, terminalGrowthDaat, etc., so they
* can be reset on the charts with a different period type.
*/
function createSetDataForPeriodType(chartData, terminalGrowthData, ids, labels, colors, bubbleCallOuts, editableIndex, isUserLineModified,
driverName, showingTerminalGrowth, companyData, driverId, isIntegerValuesOnly, gridViewMode, eventData) {
return function(periodType) {
selectedPeriodType = periodType;
var periodTypeFilteredChartData = $.map(chartData, function(datum){return pickPeriod(datum, periodType)});
mainChart.setData(
periodTypeFilteredChartData,
ids,
labels,
colors,
bubbleCallOuts,
editableIndex,
isUserLineModified,
driverName,
(showingTerminalGrowth || companyData.isDriverNonNegative(driverId)) ? 0 : companyData.getDriverMinAllowedValue(driverId),
companyData.getDriverMaxAllowedValue(driverId),
isIntegerValuesOnly,
gridViewMode,
companyData.md.yAxisLabels ? companyData.md.yAxisLabels[driverId] : null,
eventData
);
// in case the user model has any values, immediately update them on the chart. This way,
// if the user modifies a quarterly line then switches the period type to annuals,
// the annual data will reflect the new values that the user modified
mainChart.updateData(modelManager.get('active').cs.values);
tgrDiv.toggle(showingTerminalGrowth);
if(showingTerminalGrowth) {
tgrChart.setData(
terminalGrowthData,
ids,
[], // the terminal growth chart never has labels
[], // or colors
[], // or bubble callouts
editableIndex,
"Terminal Growth Rate",
true,
mainChart.getScaledValue(0) * 0.9,
false);
tgrChart.updateData(modelManager.get('active').cs.values);
}
// now that the charts have rendered, we need to re-position the orange prompt
//
positionPrompt();
}
}
/**
* @param {Object} chartDatum
* @param {int} periodType
* @return {Object} a copy of the given chartDatum, but with the data points filtered
* so only the periods that match the given type will be included
*/
function pickPeriod(chartDatum, periodType) {
var result = $.extend(true, {}, chartDatum);
result.array = $.grep(chartDatum.array, function(point, i) {
return tf.period.typeOf(point.Year) == periodType;
});
return result;
}
/**
* Gives the abbreviation to be used for the given period type in the toggle.
* The string also includes a count of how many data points there are of that type.
*
* @param {Object} chartDatum default chart data
* @param {int} periodType
* @return {String}
*/
function abbreviateType(chartDatum, periodType) {
return tf.period.typeAbbrevs[periodType] + ' (' + tf.period.getCountMap(chartDatum.array)[periodType] + ')';
}
/**
* Helper function that creates a new instance of chart data with modified values
*
* @param {Object} trefisChartData the trefis chart data object from the company model data
* @param {String} newName the name/title of the resulting series
* @param {int}newType the type (see ChartType) of the resulting series
* @param {Object} valueProvider provides any values that differ from the Trefis chart line;
* must have the getValue function
*/
function copyChartData(trefisChartData, newName, newType, valueProvider) {
var result = $.extend(true, {}, trefisChartData, {title: newName, type: newType}),
value;
if(valueProvider) {
$.each(result.array, function(pointIndex, point) {
value = valueProvider.getValue(point.Identifier);
if(value !== undefined)
point.Value = value;
});
}
return result;
}
/**
* registers a new series with the chart. Useful for adding competition lines, etc.
* Should only be called after setData is called with the appropriate driver
*
* @param {String} id the unique id of this series; used for referencing the series in calls to e.g. setSeriesVisible
* @param {String} label the user-visible label which should be applied to this series
* @param {Object} chartDatum the actual data of the series
* @param {int} competitionIndexForColor the index of this series, if it's a competition series; used for determining
* the color of the line. If this isn't a competition or benchmark series, can be undefined.
* @return the color of the registered line
* @type String
*/
function registerSeries(id, label, chartDatum, competitionIndexForColor) {
if(chartDatum.hasTerminalGrowth) {
var extracted = extractTerminalGrowth(chartDatum);
terminalGrowthChart.registerSeries(id, null, extracted[1], competitionIndexForColor);
return mainChart.registerSeries(id, label, extracted[0], competitionIndexForColor);
} else {
return mainChart.registerSeries(id, label, chartDatum, competitionIndexForColor);
}
}
/**
* Sets a particular series to be visible/invisible. The series must have been added in setData
* or registered afterwards with registerSeries. Setting a series visible/invisible will also set
* its label visible/invisible.
*
* @param {String} id the unique id of the series
* @param {boolean} visible whether the series should be visible
*/
function setSeriesVisible(id, visible) {
mainChart.setSeriesVisible(id, visible);
if(showingTerminalGrowth)
terminalGrowthChart.setSeriesVisible(id, visible);
}
/** Check whether the series by id is visible. */
function isSeriesVisible(id) {
return mainChart.isSeriesVisible(id);
}
/**
* Helper function that splits chart data into data for the main chart and data for the TGR chart.
* Assumes the chart data actually has a TGR value in it.
*
* @param {Object} data chart data
* @return an array whose 0th element is the main chart data object, and whose 1st element is the
* TGR chart data objects
* @type Array
*/
function extractTerminalGrowth(data) {
// copy the data so we don't mess up the original
var discountRateData = $.merge(true, {}, data),
terminalGrowthData = $.merge(true, {}, data);
delete discountRateData.array[discountRateData.array.length - 1];
terminalGrowthData.array = [ terminalGrowthData.array[terminalGrowthData.array.length - 1] ];
return [discountRateData, terminalGrowthData];
}
/**
* @return a map from year identifiers to values, giving the values of the editable line. Can return undefined
* if there is no editable line. If there is an editable line, but it defaulted to the Trefis values and hasn't been
* changed yet, this method will return a map that includes the Trefis values.
* @type Object
*/
function getData() {
// because there are two charts, we need to combine the data
// from each. Start with the data from the mainChart, then
// add in the data from the terminal growth chart
return showingTerminalGrowth ? $.merge(mainChart.getData(), terminalGrowthChart.getData()) : mainChart.getData();
}
/**
* Resets the editable line back to the Trefis default
*/
var analystWarningDisplayReset = true
function reset() {
// set the data in the chart back to the Trefis default
mainChart.updateData(defaultData);
if(showingTerminalGrowth)
tgrChart.updateData(defaultData);
mainChart.stopDispatchTimer();
if(showingTerminalGrowth)
tgrChart.stopDispatchTimer();
// since the discount rate may have changed by resetting, we need to update the maximum on the TGR chart
if(showingTerminalGrowth)
tgrChart.setMaxAllowedValue(mainChart.getScaledValue(0) * 0.9);
// undo the editable series change
//
if (!gridViewMode)
mainChart.undoEditableSeriesTypeChange();
if(showingTerminalGrowth)
tgrChart.undoEditableSeriesTypeChange();
mainChart.setLabelVisible("trefis", false);
mainChart.setLabelVisible("yours", false);
// this signals to the gridview that this chart has been reset by the user,
// so the gridview will update colors
// gridview will reset this value
chartReseted = true;
// recompute
modelManager.get('active').doComputation(self.getData(), currentDivision, currentDriver, true, true);
modelManager.trigger('change:active');
}
/**
* Updates the heights and Y-coordinates of the charts and other divs. Should be called whenever
* the heights of the chart title or container div change.
*/
function updateHeights() {
var chartY = (gapAboveChart ? 20 : 0)+ (titleDiv.is(":visible") ? titleDiv.height() : 0),
totalHeight = containerDiv.height();
if(shareOnChart) {
$(shareManager.linkDiv)
.addClass("shareLinkOnChart")
.removeClass("shareLinkAboveChart")
.add(shareManager.dropdownDiv)
.css({
top: chartY - 12 + (shareLinkOffset || 0)
});
} else {
$(shareManager.linkDiv)
.addClass("shareLinkAboveChart")
.removeClass("shareLinkOnChart");
$(shareManager.dropdownDiv).css({top: 10});
}
$.each(chartPositionListeners, function(i, listener) {
listener(chartY - 12);
});
mainDiv
.add(tgrDiv)
.css({
width:'100%',
height: totalHeight - chartY,
top: chartY
});
if(mainChart) {
mainChart.reflow();
tgrChart.reflow();
mainChart.tooltipDiv.css({top: chartY - (noElevatedMouseover ? 0 : 10)});
}
if(chartY != lastChartY) {
lastChartY = chartY;
if(mainChart)
setTimeout(mainChart.updateLabelPositions, 20);
}
}
/**
* Positions the "drag the trendline below" prompt appropriately on the chart, and sets it
* to be visible/invisible appropriately. Should be called whenever the screen position
* of the editable trendline may have moved, or when the visibility of the prompt may need to be changed.
*/
function positionPrompt() {
prompt.div.toggle(!!promptEnabled);
if(promptEnabled) {
var numPoints = mainChart.getNumPoints();
if(promptOnChart && numPoints > 5) {
var leftPoint = mainChart.getGlobalPosition(Math.round(numPoints/2)),
rightPoint = mainChart.getGlobalPosition(Math.round(numPoints/4)),
useRight,
pointToUse;
if(leftPoint && rightPoint) {
useRight = leftPoint.y < rightPoint.y;
pointToUse = useRight ? rightPoint : leftPoint;
prompt.caratOnRight(useRight);
prompt.setText(promptOverrideText || "Drag or click the trendline");
prompt.div.css({
top: 'auto',
bottom: pointToUse.y + 50,
left: pointToUse.x + (useRight ? 38 - prompt.div.width() : -20)
});
}
} else {
prompt.setText(promptOverrideText || "Drag the trendline below");
prompt.div.css({
top: (titleDiv.is(":visible") ? titleDiv.height() : 0) + 4,
bottom: 'auto',
left: (containerDiv.width() - prompt.div.width()) / 2
});
}
prompt.div.addClass(promptClass);
}
// this trigger is used to create an event to be used by Jasmine
$( "html" ).trigger( "positionPromptExecuted" );
}
/**
* Adds a listener which is called with no arguments whenever the user grabs (i.e., mouses down on) the editable line
*
* @param {Function} listener
*/
function addMouseDownListener(listener) {
mouseDownListeners.push(listener);
}
/**
* Adds a listener which is called whenever the y positions of the chart or driver label change.
*
* @param {Function} listener called with one argument, which is the approximate y coordinate of the
* top of the gap between the chart and the driver label
*/
function addChartPositionListener(listener) {
chartPositionListeners.push(listener);
}
function addDriverListener(listener) {
driverListeners.push(listener);
}
function addOutputListener(listener) {
outputListeners.push(listener);
}
/**
* Sets whether the chart is in smoothing mode (line chart) or not (bar chart)
*
* @param {boolean} smoothing
*/
function setSmoothing(smoothing) {
mainDiv.hide();
mainChart.setSmoothing(smoothing);
mainDiv.show();
// TGR chart only has one element, so it doesn't care about smoothing
}
/**
* @return the CompanyData used to render the current chart
* @type CompanyData
*/
function getCompanyData() {
return currentCompanyData;
}
/**
* @return the driver which the chart currently shows. For ouptut charts this will
* be the referenced collection identifier and may not be a real driver
* @type String
*/
function getDriver() {
return currentDriver;
}
function getDivision() {
return currentDivision;
}
function setEditable(val) {
editable = val;
promptEnabled = editable;
positionPrompt();
}
function setPromptOnChart(val) {
promptOnChart = val;
positionPrompt();
}
function setShareOnChart(val) {
shareOnChart = val;
updateHeights();
}
function setGapAboveChart(val) {
gapAboveChart = val;
updateHeights();
}
function setSuppressTypeChange(newSuppressTypeChange) {
suppressTypeChange = newSuppressTypeChange;
}
function onPeriodTypeChange(selectedValue) {
userSelectedPeriodType = selectedValue;
setDataForPeriodType(selectedValue);
}
function setPeriodSelectionManager(newPeriodSelectionManager) {
mainChart.setPeriodSelectionManager(newPeriodSelectionManager);
newPeriodSelectionManager.addChangeListener(updateSelectedPeriod);
}
function updateSelectedPeriod() {
// ideally we could just update the orange/blue dots on the editable series of the chart,
// but highcharts seems to have a bug where the point markers don't update correctly. So
// instead, when the selected period changes, we re-render the whole chart, which is inefficient
// but it works. Note that the easiest way to re-render the chart is just to use the setDataForPeriodType
// function and pass it the same period type it's using currently
if(setDataForPeriodType && selectedPeriodType)
setDataForPeriodType(selectedPeriodType);
}
function updateDataForCurrentDriver() {
mainChart.updateData(modelManager.get('active').cs.values);
if(getIsUserLineModified(currentDriver))
mainChart.doEditableSeriesTypeChange();
}
function setChartReseted(inputChartReseted) {
chartReseted = inputChartReseted;
}
function getChartReseted() {
return chartReseted;
}
function updateNonEditableData(usersValueProvider, modelId) {
mainChart.updateNonEditableData(usersValueProvider, modelId);
}
function setModelsAndColors(models, colors) {
allModels = models;
allColors = colors;
}
function getCurrentPeriodType(){
if(outputPeriodType!=null)
return outputPeriodType;
var p = mainChart.getChartData()['base']['array'][0]['Year'];
return tf.period.typeOf(p)
}
/** Refactor chart data creation from SOLR style doc. */
function createChartData(doc) {
var currentPeriodType = this.getCurrentPeriodType();
var trefisChartData = $.parseJSON(doc.trefisData).chartData[0]; //trefisData is json stored as a string in solr, here we parse the strign back to json
trefisChartData = this.pickPeriod(trefisChartData, currentPeriodType);
trefisChartData.type=9; //set chart type as benchmark
trefisChartData.title = doc.title;
trefisChartData.modelName = doc.modelName;
if(doc.lead_symbol) {
trefisChartData.title += ' for ' + doc.lead_symbol;
}
if(doc.providerString) { // Capitalize provider
trefisChartData.title += '(' + doc.providerString.charAt(0).toUpperCase() + doc.providerString.slice(1) + ')';
}
return trefisChartData;
}
$.extend(self, {
mainChart: mainChart,
tgrChart: tgrChart,
setData: setData,
setOutputData: setOutputData,
getData: getData,
registerSeries: registerSeries,
setSeriesVisible: setSeriesVisible,
isSeriesVisible: isSeriesVisible,
copyChartData: copyChartData,
addMouseDownListener: addMouseDownListener,
addChartPositionListener: addChartPositionListener,
addDriverListener: addDriverListener,
addOutputListener: addOutputListener,
setSmoothing: setSmoothing,
reset: reset,
getCompanyData: getCompanyData,
getDriver: getDriver,
getDivision: getDivision,
setEditable:setEditable,
setPromptOnChart: setPromptOnChart,
setShareOnChart: setShareOnChart,
setGapAboveChart: setGapAboveChart,
setSuppressTypeChange: setSuppressTypeChange,
divId: containerDivId,
div: containerDiv,
mainDiv: mainDiv,
modelManager: modelManager,
updateHeights: updateHeights,
setPeriodSelectionManager: setPeriodSelectionManager,
updateDataForCurrentDriver: updateDataForCurrentDriver,
setChartReseted: setChartReseted,
getChartReseted: getChartReseted,
updateNonEditableData: updateNonEditableData,
setModelsAndColors: setModelsAndColors,
getCurrentPeriodType: getCurrentPeriodType,
pickPeriod: pickPeriod,
createChartData: createChartData
});
}
/**
* Constants indicating various types of chart lines.
*/
ChartType = {
yours: 0,
trefis: 1,
community: 2,
competition: 3,
otherUser: 6,
comparable: 7,
comparisonOtherUser: 8,
benchmark: 9
};
;;
// lib/jquery.min.js
/**
* Manages driver selection. Keeps track of a list of drivers, displays the left/right arrows,
* and fires off events when the driver is changed.
*
* @constructor
* @param {ChartWrapper} chartWrapper the chart in which the filmstrip should be embeded.
* The chart is only used for inserting a div; it isn't used for any control logic. Cannot be null.
* @param {WidgetTracker} tracker the filmstrip will track driver changes using the given tracker.
* Cannot be null.
*/
function DriverFilmstrip(chartWrapper, tracker, baseClassOverride) {
var self = this,
baseClass = baseClassOverride || "widgetFilmstrip",
div, // containing div
leftButton, // left arrow button
rightButton, // right arrow button
label, // "1 of 7" label span
driverList, // array of drivers (Strings)
currentPosition, // int; index into driverList array giving currently selected driver
listeners = [] // array of callback functions to be called when driver changes
;
/*
* Constructor
*/
$("#" + chartWrapper.divId)
.prepend(div = $("", {
"class": baseClass
})
.append(rightButton = $("", {
"class": baseClass + "-right",
text: "Next",
click: function() {
changeDriver(currentPosition + 1);
tracker.driverChanged(driverList[currentPosition]);
return false;
}
}))
.append(leftButton = $("
", {
"class": baseClass + "-left",
text: "Previous",
click: function() {
changeDriver(currentPosition - 1);
tracker.driverChanged(driverList[currentPosition]);
return false;
}
}))
.append(label = $("
")));
/*
* Methods
*/
/**
* Sets the driver list of the filmstrip.
*
* @param {Array} drivers array of identifiers (Strings) for the driver list
* @param {String} driver default driver to be selected; can be null, in which case the first driver
* from the resulting list is selected by default
*/
function setDrivers(drivers, driver) {
driverList = drivers;
div.toggle(drivers.length > 1);
changeDriver(driver || 0);
}
/**
* Changes the currently selected driver. Does not change the list of available drivers.
*
* @param {String or int} driver the driver to be selected, or the index into the current driver
* list that should be selected
*/
function changeDriver(driver) {
if("string" == typeof driver)
driver = $.inArray(driverList, driver);
if(driver >= 0 && driver < driverList.length) {
currentPosition = driver;
self.driver = driverList[currentPosition];
updateView();
$.each(listeners, function(i, listener) {
listener(driverList[currentPosition]);
});
}
}
/**
* updates the arrows, label, etc. according to the current state (currentPosition and driverList)
*/
function updateView() {
label.text((currentPosition + 1) + " of " + driverList.length);
leftButton.toggleClass(baseClass + "-left-disabled", !currentPosition);
rightButton.toggleClass(baseClass + "-right-disabled", currentPosition == driverList.length - 1);
}
/**
* Adds a listener for when the driver changes. Listeners will be called with one argument: the
* String identifier of the newly selected driver.
*/
function addListener(callback) {
listeners.push(callback);
}
$.extend(self, {
setDrivers: setDrivers,
changeDriver: changeDriver,
addListener: addListener,
getPosition: function () {return currentPosition},
getDrivers: function(){return driverList}
});
}
;;
// @include lib/underscore.js
// @include lib/json2.js
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function(){
// Initial Setup
// -------------
// Save a reference to the global object (`window` in the browser, `global`
// on the server).
var root = this;
// Save the previous value of the `Backbone` variable, so that it can be
// restored later on, if `noConflict` is used.
var previousBackbone = root.Backbone;
// Create a local reference to slice/splice.
var slice = Array.prototype.slice;
var splice = Array.prototype.splice;
// The top-level namespace. All public Backbone classes and modules will
// be attached to this. Exported for both CommonJS and the browser.
var Backbone;
if (typeof exports !== 'undefined') {
Backbone = exports;
} else {
Backbone = root.Backbone = {};
}
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '0.9.2';
// Require Underscore, if we're on the server, and it's not already present.
var _ = root._;
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
// For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
var $ = root.jQuery || root.Zepto || root.ender;
// Set the JavaScript library that will be used for DOM manipulation and
// Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
// Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
// alternate JavaScript library (or a mock library for testing your views
// outside of a browser).
Backbone.setDomLibrary = function(lib) {
$ = lib;
};
// Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
// to its previous owner. Returns a reference to this Backbone object.
Backbone.noConflict = function() {
root.Backbone = previousBackbone;
return this;
};
// Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
// will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
// set a `X-Http-Method-Override` header.
Backbone.emulateHTTP = false;
// Turn on `emulateJSON` to support legacy servers that can't deal with direct
// `application/json` requests ... will encode the body as
// `application/x-www-form-urlencoded` instead and will send the model in a
// form param named `model`.
Backbone.emulateJSON = false;
// Backbone.Events
// -----------------
// Regular expression used to split event strings
var eventSplitter = /\s+/;
// A module that can be mixed in to *any object* in order to provide it with
// custom events. You may bind with `on` or remove with `off` callback functions
// to an event; trigger`-ing an event fires all callbacks in succession.
//
// var object = {};
// _.extend(object, Backbone.Events);
// object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
var Events = Backbone.Events = {
// Bind one or more space separated events, `events`, to a `callback`
// function. Passing `"all"` will bind the callback to all events fired.
on: function(events, callback, context) {
var calls, event, node, tail, list;
if (!callback) return this;
events = events.split(eventSplitter);
calls = this._callbacks || (this._callbacks = {});
// Create an immutable callback list, allowing traversal during
// modification. The tail is an empty object that will always be used
// as the next node.
while (event = events.shift()) {
list = calls[event];
node = list ? list.tail : {};
node.next = tail = {};
node.context = context;
node.callback = callback;
calls[event] = {tail: tail, next: list ? list.next : node};
}
return this;
},
// Remove one or many callbacks. If `context` is null, removes all callbacks
// with that function. If `callback` is null, removes all callbacks for the
// event. If `events` is null, removes all bound callbacks for all events.
off: function(events, callback, context) {
var event, calls, node, tail, cb, ctx;
// No events, or removing *all* events.
if (!(calls = this._callbacks)) return;
if (!(events || callback || context)) {
delete this._callbacks;
return this;
}
// Loop through the listed events and contexts, splicing them out of the
// linked list of callbacks if appropriate.
events = events ? events.split(eventSplitter) : _.keys(calls);
while (event = events.shift()) {
node = calls[event];
delete calls[event];
if (!node || !(callback || context)) continue;
// Create a new list, omitting the indicated callbacks.
tail = node.tail;
while ((node = node.next) !== tail) {
cb = node.callback;
ctx = node.context;
if ((callback && cb !== callback) || (context && ctx !== context)) {
this.on(event, cb, ctx);
}
}
}
return this;
},
// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
trigger: function(events) {
var event, node, calls, tail, args, all, rest;
if (!(calls = this._callbacks)) return this;
all = calls.all;
events = events.split(eventSplitter);
rest = slice.call(arguments, 1);
// For each event, walk through the linked list of callbacks twice,
// first to trigger the event, then to trigger any `"all"` callbacks.
while (event = events.shift()) {
if (node = calls[event]) {
tail = node.tail;
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, rest);
}
}
if (node = all) {
tail = node.tail;
args = [event].concat(rest);
while ((node = node.next) !== tail) {
node.callback.apply(node.context || this, args);
}
}
}
return this;
}
};
// Aliases for backwards compatibility.
Events.bind = Events.on;
Events.unbind = Events.off;
// Backbone.Model
// --------------
// Create a new model, with defined attributes. A client id (`cid`)
// is automatically generated and assigned for you.
var Model = Backbone.Model = function(attributes, options) {
var defaults;
attributes || (attributes = {});
if (options && options.parse) attributes = this.parse(attributes);
if (defaults = getValue(this, 'defaults')) {
attributes = _.extend({}, defaults, attributes);
}
if (options && options.collection) this.collection = options.collection;
this.attributes = {};
this._escapedAttributes = {};
this.cid = _.uniqueId('c');
this.changed = {};
this._silent = {};
this._pending = {};
this.set(attributes, {silent: true});
// Reset change tracking.
this.changed = {};
this._silent = {};
this._pending = {};
this._previousAttributes = _.clone(this.attributes);
this.initialize.apply(this, arguments);
};
// Attach all inheritable methods to the Model prototype.
_.extend(Model.prototype, Events, {
// A hash of attributes whose current and previous value differ.
changed: null,
// A hash of attributes that have silently changed since the last time
// `change` was called. Will become pending attributes on the next call.
_silent: null,
// A hash of attributes that have changed since the last `'change'` event
// began.
_pending: null,
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
// CouchDB users may want to set this to `"_id"`.
idAttribute: 'id',
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// Return a copy of the model's `attributes` object.
toJSON: function(options) {
return _.clone(this.attributes);
},
// Get the value of an attribute.
get: function(attr) {
return this.attributes[attr];
},
// Get the HTML-escaped value of an attribute.
escape: function(attr) {
var html;
if (html = this._escapedAttributes[attr]) return html;
var val = this.get(attr);
return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
},
// Returns `true` if the attribute contains a value that is not null
// or undefined.
has: function(attr) {
return this.get(attr) != null;
},
// Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it.
set: function(key, value, options) {
var attrs, attr, val;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
// Extract attributes and options.
options || (options = {});
if (!attrs) return this;
if (attrs instanceof Model) attrs = attrs.attributes;
if (options.unset) for (attr in attrs) attrs[attr] = void 0;
// Run validation.
if (!this._validate(attrs, options)) return false;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
var changes = options.changes = {};
var now = this.attributes;
var escaped = this._escapedAttributes;
var prev = this._previousAttributes || {};
// For each `set` attribute...
for (attr in attrs) {
val = attrs[attr];
// If the new and current value differ, record the change.
if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
delete escaped[attr];
(options.silent ? this._silent : changes)[attr] = true;
}
// Update or delete the current value.
options.unset ? delete now[attr] : now[attr] = val;
// If the new and previous value differ, record the change. If not,
// then remove changes for this attribute.
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
this.changed[attr] = val;
if (!options.silent) this._pending[attr] = true;
} else {
delete this.changed[attr];
delete this._pending[attr];
}
}
// Fire the `"change"` events.
if (!options.silent) this.change(options);
return this;
},
// Remove an attribute from the model, firing `"change"` unless you choose
// to silence it. `unset` is a noop if the attribute doesn't exist.
unset: function(attr, options) {
(options || (options = {})).unset = true;
return this.set(attr, null, options);
},
// Clear all attributes on the model, firing `"change"` unless you choose
// to silence it.
clear: function(options) {
(options || (options = {})).unset = true;
return this.set(_.clone(this.attributes), options);
},
// Fetch the model from the server. If the server's representation of the
// model differs from its current attributes, they will be overriden,
// triggering a `"change"` event.
fetch: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
if (!model.set(model.parse(resp, xhr), options)) return false;
if (success) success(model, resp);
};
options.error = Backbone.wrapError(options.error, model, options);
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
// Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save: function(key, value, options) {
var attrs, current;
// Handle both `("key", value)` and `({key: value})` -style calls.
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
options = options ? _.clone(options) : {};
// If we're "wait"-ing to set changed attributes, validate early.
if (options.wait) {
if (!this._validate(attrs, options)) return false;
current = _.clone(this.attributes);
}
// Regular saves `set` attributes before persisting to the server.
var silentOptions = _.extend({}, options, {silent: true});
if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
return false;
}
// After a successful server-side save, the client is (optionally)
// updated with the server-side state.
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
var serverAttrs = model.parse(resp, xhr);
if (options.wait) {
delete options.wait;
serverAttrs = _.extend(attrs || {}, serverAttrs);
}
if (!model.set(serverAttrs, options)) return false;
if (success) {
success(model, resp);
} else {
model.trigger('sync', model, resp, options);
}
};
// Finish configuring and sending the Ajax request.
options.error = Backbone.wrapError(options.error, model, options);
var method = this.isNew() ? 'create' : 'update';
var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
if (options.wait) this.set(current, silentOptions);
return xhr;
},
// Destroy this model on the server if it was already persisted.
// Optimistically removes the model from its collection, if it has one.
// If `wait: true` is passed, waits for the server to respond before removal.
destroy: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var success = options.success;
var triggerDestroy = function() {
model.trigger('destroy', model, model.collection, options);
};
if (this.isNew()) {
triggerDestroy();
return false;
}
options.success = function(resp) {
if (options.wait) triggerDestroy();
if (success) {
success(model, resp);
} else {
model.trigger('sync', model, resp, options);
}
};
options.error = Backbone.wrapError(options.error, model, options);
var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
if (!options.wait) triggerDestroy();
return xhr;
},
// Default URL for the model's representation on the server -- if you're
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url: function() {
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
// **parse** converts a response into the hash of attributes to be `set` on
// the model. The default implementation is just to pass the response along.
parse: function(resp, xhr) {
return resp;
},
// Create a new model with identical attributes to this one.
clone: function() {
return new this.constructor(this.attributes);
},
// A model is new if it has never been saved to the server, and lacks an id.
isNew: function() {
return this.id == null;
},
// Call this method to manually fire a `"change"` event for this model and
// a `"change:attribute"` event for each changed attribute.
// Calling this will cause all objects observing the model to update.
change: function(options) {
options || (options = {});
var changing = this._changing;
this._changing = true;
// Silent changes become pending changes.
for (var attr in this._silent) this._pending[attr] = true;
// Silent changes are triggered.
var changes = _.extend({}, options.changes, this._silent);
this._silent = {};
for (var attr in changes) {
this.trigger('change:' + attr, this, this.get(attr), options);
}
if (changing) return this;
// Continue firing `"change"` events while there are pending changes.
while (!_.isEmpty(this._pending)) {
this._pending = {};
this.trigger('change', this, options);
// Pending and silent changes still remain.
for (var attr in this.changed) {
if (this._pending[attr] || this._silent[attr]) continue;
delete this.changed[attr];
}
this._previousAttributes = _.clone(this.attributes);
}
this._changing = false;
return this;
},
// Determine if the model has changed since the last `"change"` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged: function(attr) {
if (!arguments.length) return !_.isEmpty(this.changed);
return _.has(this.changed, attr);
},
// Return an object containing all the attributes that have changed, or
// false if there are no changed attributes. Useful for determining what
// parts of a view need to be updated and/or what attributes need to be
// persisted to the server. Unset attributes will be set to undefined.
// You can also pass an attributes object to diff against the model,
// determining if there *would be* a change.
changedAttributes: function(diff) {
if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
var val, changed = false, old = this._previousAttributes;
for (var attr in diff) {
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
(changed || (changed = {}))[attr] = val;
}
return changed;
},
// Get the previous value of an attribute, recorded at the time the last
// `"change"` event was fired.
previous: function(attr) {
if (!arguments.length || !this._previousAttributes) return null;
return this._previousAttributes[attr];
},
// Get all of the attributes of the model at the time of the previous
// `"change"` event.
previousAttributes: function() {
return _.clone(this._previousAttributes);
},
// Check if the model is currently in a valid state. It's only possible to
// get into an *invalid* state if you're using silent changes.
isValid: function() {
return !this.validate(this.attributes);
},
// Run validation against the next complete set of model attributes,
// returning `true` if all is well. If a specific `error` callback has
// been passed, call that instead of firing the general `"error"` event.
_validate: function(attrs, options) {
if (options.silent || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
var error = this.validate(attrs, options);
if (!error) return true;
if (options && options.error) {
options.error(this, error, options);
} else {
this.trigger('error', this, error, options);
}
return false;
}
});
// Backbone.Collection
// -------------------
// Provides a standard collection class for our sets of models, ordered
// or unordered. If a `comparator` is specified, the Collection will maintain
// its models in sort order, as they're added and removed.
var Collection = Backbone.Collection = function(models, options) {
options || (options = {});
if (options.model) this.model = options.model;
if (options.comparator) this.comparator = options.comparator;
this._reset();
this.initialize.apply(this, arguments);
if (models) this.reset(models, {silent: true, parse: options.parse});
};
// Define the Collection's inheritable methods.
_.extend(Collection.prototype, Events, {
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model: Model,
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// The JSON representation of a Collection is an array of the
// models' attributes.
toJSON: function(options) {
return this.map(function(model){ return model.toJSON(options); });
},
// Add a model, or list of models to the set. Pass **silent** to avoid
// firing the `add` event for every new model.
add: function(models, options) {
var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
// Begin by turning bare objects into model references, and preventing
// invalid models or duplicate models from being added.
for (i = 0, length = models.length; i < length; i++) {
if (!(model = models[i] = this._prepareModel(models[i], options))) {
throw new Error("Can't add an invalid model to a collection");
}
cid = model.cid;
id = model.id;
if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
dups.push(i);
continue;
}
cids[cid] = ids[id] = model;
}
// Remove duplicates.
i = dups.length;
while (i--) {
models.splice(dups[i], 1);
}
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
for (i = 0, length = models.length; i < length; i++) {
(model = models[i]).on('all', this._onModelEvent, this);
this._byCid[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
// Insert models into the collection, re-sorting if needed, and triggering
// `add` events unless silenced.
this.length += length;
index = options.at != null ? options.at : this.models.length;
splice.apply(this.models, [index, 0].concat(models));
if (this.comparator) this.sort({silent: true});
if (options.silent) return this;
for (i = 0, length = this.models.length; i < length; i++) {
if (!cids[(model = this.models[i]).cid]) continue;
options.index = i;
model.trigger('add', model, this, options);
}
return this;
},
// Remove a model, or a list of models from the set. Pass silent to avoid
// firing the `remove` event for every model removed.
remove: function(models, options) {
var i, l, index, model;
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
for (i = 0, l = models.length; i < l; i++) {
model = this.getByCid(models[i]) || this.get(models[i]);
if (!model) continue;
delete this._byId[model.id];
delete this._byCid[model.cid];
index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
this._removeReference(model);
}
return this;
},
// Add a model to the end of the collection.
push: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, options);
return model;
},
// Remove a model from the end of the collection.
pop: function(options) {
var model = this.at(this.length - 1);
this.remove(model, options);
return model;
},
// Add a model to the beginning of the collection.
unshift: function(model, options) {
model = this._prepareModel(model, options);
this.add(model, _.extend({at: 0}, options));
return model;
},
// Remove a model from the beginning of the collection.
shift: function(options) {
var model = this.at(0);
this.remove(model, options);
return model;
},
// Get a model from the set by id.
get: function(id) {
if (id == null) return void 0;
return this._byId[id.id != null ? id.id : id];
},
// Get a model from the set by client id.
getByCid: function(cid) {
return cid && this._byCid[cid.cid || cid];
},
// Get the model at the given index.
at: function(index) {
return this.models[index];
},
// Return models with matching attributes. Useful for simple cases of `filter`.
where: function(attrs) {
if (_.isEmpty(attrs)) return [];
return this.filter(function(model) {
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
},
// Force the collection to re-sort itself. You don't need to call this under
// normal circumstances, as the set will maintain sort order as each item
// is added.
sort: function(options) {
options || (options = {});
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
var boundComparator = _.bind(this.comparator, this);
if (this.comparator.length == 1) {
this.models = this.sortBy(boundComparator);
} else {
this.models.sort(boundComparator);
}
if (!options.silent) this.trigger('reset', this, options);
return this;
},
// Pluck an attribute from each model in the collection.
pluck: function(attr) {
return _.map(this.models, function(model){ return model.get(attr); });
},
// When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing
// any `add` or `remove` events. Fires `reset` when finished.
reset: function(models, options) {
models || (models = []);
options || (options = {});
for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
this._reset();
this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
return this;
},
// Fetch the default set of models for this collection, resetting the
// collection when they arrive. If `add: true` is passed, appends the
// models to the collection instead of resetting.
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === undefined) options.parse = true;
var collection = this;
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp);
};
options.error = Backbone.wrapError(options.error, collection, options);
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
// Create a new instance of a model in this collection. Add the model to the
// collection immediately, unless `wait: true` is passed, in which case we
// wait for the server to agree.
create: function(model, options) {
var coll = this;
options = options ? _.clone(options) : {};
model = this._prepareModel(model, options);
if (!model) return false;
if (!options.wait) coll.add(model, options);
var success = options.success;
options.success = function(nextModel, resp, xhr) {
if (options.wait) coll.add(nextModel, options);
if (success) {
success(nextModel, resp);
} else {
nextModel.trigger('sync', model, resp, options);
}
};
model.save(null, options);
return model;
},
// **parse** converts a response into a list of models to be added to the
// collection. The default implementation is just to pass it through.
parse: function(resp, xhr) {
return resp;
},
// Proxy to _'s chain. Can't be proxied the same way the rest of the
// underscore methods are proxied because it relies on the underscore
// constructor.
chain: function () {
return _(this.models).chain();
},
// Reset all internal state. Called when the collection is reset.
_reset: function(options) {
this.length = 0;
this.models = [];
this._byId = {};
this._byCid = {};
},
// Prepare a model or hash of attributes to be added to this collection.
_prepareModel: function(model, options) {
options || (options = {});
if (!(model instanceof Model)) {
var attrs = model;
options.collection = this;
model = new this.model(attrs, options);
if (!model._validate(model.attributes, options)) model = false;
} else if (!model.collection) {
model.collection = this;
}
return model;
},
// Internal method to remove a model's ties to a collection.
_removeReference: function(model) {
if (this == model.collection) {
delete model.collection;
}
model.off('all', this._onModelEvent, this);
},
// Internal method called every time a model in the set fires an event.
// Sets need to update their indexes when models change ids. All other
// events simply proxy through. "add" and "remove" events that originate
// in other collections are ignored.
_onModelEvent: function(event, model, collection, options) {
if ((event == 'add' || event == 'remove') && collection != this) return;
if (event == 'destroy') {
this.remove(model, options);
}
if (model && event === 'change:' + model.idAttribute) {
delete this._byId[model.previous(model.idAttribute)];
this._byId[model.id] = model;
}
this.trigger.apply(this, arguments);
}
});
// Underscore methods that we want to implement on the Collection.
var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
// Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) {
Collection.prototype[method] = function() {
return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
};
});
// Backbone.Router
// -------------------
// Routers map faux-URLs to actions, and fire events when routes are
// matched. Creating a new one sets its `routes` hash, if not set statically.
var Router = Backbone.Router = function(options) {
options || (options = {});
if (options.routes) this.routes = options.routes;
this._bindRoutes();
this.initialize.apply(this, arguments);
};
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
var namedParam = /:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
// Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Router.prototype, Events, {
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// Manually bind a single named route to a callback. For example:
//
// this.route('search/:query/p:num', 'search', function(query, num) {
// ...
// });
//
route: function(route, name, callback) {
Backbone.history || (Backbone.history = new History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment) {
var args = this._extractParameters(route, fragment);
callback && callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
Backbone.history.trigger('route', this, name, args);
}, this));
return this;
},
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options);
},
// Bind all defined routes to `Backbone.history`. We have to reverse the
// order of the routes here to support behavior where the most general
// routes can be defined at the bottom of the route map.
_bindRoutes: function() {
if (!this.routes) return;
var routes = [];
for (var route in this.routes) {
routes.unshift([route, this.routes[route]]);
}
for (var i = 0, l = routes.length; i < l; i++) {
this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
}
},
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp: function(route) {
route = route.replace(escapeRegExp, '\\$&')
.replace(namedParam, '([^\/]+)')
.replace(splatParam, '(.*?)');
return new RegExp('^' + route + '$');
},
// Given a route, and a URL fragment that it matches, return the array of
// extracted parameters.
_extractParameters: function(route, fragment) {
return route.exec(fragment).slice(1);
}
});
// Backbone.History
// ----------------
// Handles cross-browser history management, based on URL fragments. If the
// browser does not support `onhashchange`, falls back to polling.
var History = Backbone.History = function() {
this.handlers = [];
_.bindAll(this, 'checkUrl');
};
// Cached regex for cleaning leading hashes and slashes .
var routeStripper = /^[#\/]/;
// Cached regex for detecting MSIE.
var isExplorer = /msie [\w.]+/;
// Has the history handling already been started?
History.started = false;
// Set up all inheritable **Backbone.History** properties and methods.
_.extend(History.prototype, Events, {
// The default interval to poll for hash changes, if necessary, is
// twenty times a second.
interval: 50,
// Gets the true hash value. Cannot use location.hash directly due to bug
// in Firefox where location.hash will always be decoded.
getHash: function(windowOverride) {
var loc = windowOverride ? windowOverride.location : window.location;
var match = loc.href.match(/#(.*)$/);
return match ? match[1] : '';
},
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
getFragment: function(fragment, forcePushState) {
if (fragment == null) {
if (this._hasPushState || forcePushState) {
fragment = window.location.pathname;
var search = window.location.search;
if (search) fragment += search;
} else {
fragment = this.getHash();
}
}
if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
return fragment.replace(routeStripper, '');
},
// Start the hash change handling, returning `true` if the current URL matches
// an existing route, and `false` otherwise.
start: function(options) {
if (History.started) throw new Error("Backbone.history has already been started");
History.started = true;
// Figure out the initial configuration. Do we need an iframe?
// Is pushState desired ... is it available?
this.options = _.extend({}, {root: '/'}, this.options, options);
this._wantsHashChange = this.options.hashChange !== false;
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
var fragment = this.getFragment();
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
if (oldIE) {
this.iframe = $('
').hide().appendTo('body')[0].contentWindow;
this.navigate(fragment);
}
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) {
$(window).bind('popstate', this.checkUrl);
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
$(window).bind('hashchange', this.checkUrl);
} else if (this._wantsHashChange) {
this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
}
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this.fragment = fragment;
var loc = window.location;
var atRoot = loc.pathname == this.options.root;
// If we've started off with a route from a `pushState`-enabled browser,
// but we're currently in a browser that doesn't support it...
if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
this.fragment = this.getFragment(null, true);
window.location.replace(this.options.root + '#' + this.fragment);
// Return immediately as browser will do redirect to new url
return true;
// Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
this.fragment = this.getHash().replace(routeStripper, '');
window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
}
if (!this.options.silent) {
return this.loadUrl();
}
},
// Disable Backbone.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers.
stop: function() {
$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
clearInterval(this._checkUrlInterval);
History.started = false;
},
// Add a route to be tested when the fragment changes. Routes added later
// may override previous routes.
route: function(route, callback) {
this.handlers.unshift({route: route, callback: callback});
},
// Checks the current URL to see if it has changed, and if it has,
// calls `loadUrl`, normalizing across the hidden iframe.
checkUrl: function(e) {
var current = this.getFragment();
if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
if (current == this.fragment) return false;
if (this.iframe) this.navigate(current);
this.loadUrl() || this.loadUrl(this.getHash());
},
// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl: function(fragmentOverride) {
var fragment = this.fragment = this.getFragment(fragmentOverride);
var matched = _.any(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
return true;
}
});
return matched;
},
// Save a fragment into the hash history, or replace the URL state if the
// 'replace' option is passed. You are responsible for properly URL-encoding
// the fragment in advance.
//
// The options object can contain `trigger: true` if you wish to have the
// route callback be fired (not usually desirable), or `replace: true`, if
// you wish to modify the current URL without adding an entry to the history.
navigate: function(fragment, options) {
if (!History.started) return false;
if (!options || options === true) options = {trigger: options};
var frag = (fragment || '').replace(routeStripper, '');
if (this.fragment == frag) return;
// If pushState is available, we use it to set the fragment as a real URL.
if (this._hasPushState) {
if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
this.fragment = frag;
window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
} else if (this._wantsHashChange) {
this.fragment = frag;
this._updateHash(window.location, frag, options.replace);
if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
// Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
// When replace is true, we don't want this.
if(!options.replace) this.iframe.document.open().close();
this._updateHash(this.iframe.location, frag, options.replace);
}
// If you've told us that you explicitly don't want fallback hashchange-
// based history, then `navigate` becomes a page refresh.
} else {
window.location.assign(this.options.root + fragment);
}
if (options.trigger) this.loadUrl(fragment);
},
// Update the hash location, either replacing the current entry, or adding
// a new one to the browser history.
_updateHash: function(location, fragment, replace) {
if (replace) {
location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
} else {
location.hash = fragment;
}
}
});
// Backbone.View
// -------------
// Creating a Backbone.View creates its initial element outside of the DOM,
// if an existing element is not provided...
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
this._ensureElement();
this.initialize.apply(this, arguments);
this.delegateEvents();
};
// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
// List of view options to be merged as properties.
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
// Set up all inheritable **Backbone.View** properties and methods.
_.extend(View.prototype, Events, {
// The default `tagName` of a View's element is `"div"`.
tagName: 'div',
// jQuery delegate for element lookup, scoped to DOM elements within the
// current view. This should be prefered to global lookups where possible.
$: function(selector) {
return this.$el.find(selector);
},
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// **render** is the core function that your view should override, in order
// to populate its element (`this.el`), with the appropriate HTML. The
// convention is for **render** to always return `this`.
render: function() {
return this;
},
// Remove this view from the DOM. Note that the view isn't present in the
// DOM by default, so calling this method may be a no-op.
remove: function() {
this.$el.remove();
return this;
},
// For small amounts of DOM Elements, where a full-blown template isn't
// needed, use **make** to manufacture elements, one at a time.
//
// var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
//
make: function(tagName, attributes, content) {
var el = document.createElement(tagName);
if (attributes) $(el).attr(attributes);
if (content) $(el).html(content);
return el;
},
// Change the view's element (`this.el` property), including event
// re-delegation.
setElement: function(element, delegate) {
if (this.$el) this.undelegateEvents();
this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0];
if (delegate !== false) this.delegateEvents();
return this;
},
// Set callbacks, where `this.events` is a hash of
//
// *{"event selector": "callback"}*
//
// {
// 'mousedown .title': 'edit',
// 'click .button': 'save'
// 'click .open': function(e) { ... }
// }
//
// pairs. Callbacks will be bound to the view, with `this` set properly.
// Uses event delegation for efficiency.
// Omitting the selector binds the event to `this.el`.
// This only works for delegate-able events: not `focus`, `blur`, and
// not `change`, `submit`, and `reset` in Internet Explorer.
delegateEvents: function(events) {
if (!(events || (events = getValue(this, 'events')))) return;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
var match = key.match(delegateEventSplitter);
var eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateEvents' + this.cid;
if (selector === '') {
this.$el.bind(eventName, method);
} else {
this.$el.delegate(selector, eventName, method);
}
}
},
// Clears all callbacks previously bound to the view with `delegateEvents`.
// You usually don't need to use this, but may wish to if you have multiple
// Backbone views attached to the same DOM element.
undelegateEvents: function() {
this.$el.unbind('.delegateEvents' + this.cid);
},
// Performs the initial configuration of a View with a set of options.
// Keys with special meaning *(model, collection, id, className)*, are
// attached directly to the view.
_configure: function(options) {
if (this.options) options = _.extend({}, this.options, options);
for (var i = 0, l = viewOptions.length; i < l; i++) {
var attr = viewOptions[i];
if (options[attr]) this[attr] = options[attr];
}
this.options = options;
},
// Ensure that the View has a DOM element to render into.
// If `this.el` is a string, pass it through `$()`, take the first
// matching element, and re-assign it to `el`. Otherwise, create
// an element from the `id`, `className` and `tagName` properties.
_ensureElement: function() {
if (!this.el) {
var attrs = getValue(this, 'attributes') || {};
if (this.id) attrs.id = this.id;
if (this.className) attrs['class'] = this.className;
this.setElement(this.make(this.tagName, attrs), false);
} else {
this.setElement(this.el, false);
}
}
});
// The self-propagating extend function that Backbone classes use.
var extend = function (protoProps, classProps) {
var child = inherits(this, protoProps, classProps);
child.extend = this.extend;
return child;
};
// Set up inheritance for the model, collection, and view.
Model.extend = Collection.extend = Router.extend = View.extend = extend;
// Backbone.sync
// -------------
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var methodMap = {
'create': 'POST',
'update': 'PUT',
'delete': 'DELETE',
'read': 'GET'
};
// Override this function to change the manner in which Backbone persists
// models to the server. You will be passed the type of request, and the
// model in question. By default, makes a RESTful Ajax request
// to the model's `url()`. Some possible customizations could be:
//
// * Use `setTimeout` to batch rapid-fire updates into a single request.
// * Send up the models as XML instead of JSON.
// * Persist models via WebSockets instead of Ajax.
//
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
// as `POST`, with a `_method` parameter containing the true HTTP method,
// as well as all requests with the body as `application/x-www-form-urlencoded`
// instead of `application/json` with the model in a param named `model`.
// Useful when interfacing with server-side languages like **PHP** that make
// it difficult to read the body of `PUT` requests.
Backbone.sync = function(method, model, options) {
var type = methodMap[method];
// Default options, unless specified.
options || (options = {});
// Default JSON-request options.
var params = {type: type, dataType: 'json'};
// Ensure that we have a URL.
if (!options.url) {
params.url = getValue(model, 'url') || urlError();
}
// Ensure that we have the appropriate request data.
if (!options.data && model && (method == 'create' || method == 'update')) {
params.contentType = 'application/json';
params.data = JSON.stringify(model.toJSON());
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if (Backbone.emulateJSON) {
params.contentType = 'application/x-www-form-urlencoded';
params.data = params.data ? {model: params.data} : {};
}
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
if (Backbone.emulateHTTP) {
if (type === 'PUT' || type === 'DELETE') {
if (Backbone.emulateJSON) params.data._method = type;
params.type = 'POST';
params.beforeSend = function(xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
};
}
}
// Don't process data on a non-GET request.
if (params.type !== 'GET' && !Backbone.emulateJSON) {
params.processData = false;
}
// Make the request, allowing the user to override any Ajax options.
return $.ajax(_.extend(params, options));
};
// Wrap an optional error callback with a fallback error event.
Backbone.wrapError = function(onError, originalModel, options) {
return function(model, resp) {
resp = model === originalModel ? resp : model;
if (onError) {
onError(originalModel, resp, options);
} else {
originalModel.trigger('error', originalModel, resp, options);
}
};
};
// Helpers
// -------
// Shared empty constructor function to aid in prototype-chain creation.
var ctor = function(){};
// Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
var inherits = function(parent, protoProps, staticProps) {
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
child = function(){ parent.apply(this, arguments); };
}
// Inherit class (static) properties from parent.
_.extend(child, parent);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
ctor.prototype = parent.prototype;
child.prototype = new ctor();
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);
// Add static properties to the constructor function, if supplied.
if (staticProps) _.extend(child, staticProps);
// Correctly set child's `prototype.constructor`.
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed later.
child.__super__ = parent.prototype;
return child;
};
// Helper function to get a value from a Backbone object as a property
// or as a function.
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
// Throw an error when a URL is needed, and none is supplied.
var urlError = function() {
throw new Error('A "url" property or function must be specified');
};
}).call(this);
;;
;(function (global, factory) {
if (typeof define === "function" && define.amd) {
define('Reselect', ['exports'], factory);
} else if (typeof exports !== "undefined") {
factory(exports);
} else {
var mod = {
exports: {}
};
factory(mod.exports);
global.Reselect = mod.exports;
}
})(this, function (exports) {
'use strict';
exports.__esModule = true;
exports.defaultMemoize = defaultMemoize;
exports.createSelectorCreator = createSelectorCreator;
exports.createStructuredSelector = createStructuredSelector;
function defaultEqualityCheck(a, b) {
return a === b;
}
function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
if (prev === null || next === null || prev.length !== next.length) {
return false;
}
// Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
var length = prev.length;
for (var i = 0; i < length; i++) {
if (!equalityCheck(prev[i], next[i])) {
return false;
}
}
return true;
}
function defaultMemoize(func) {
var equalityCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultEqualityCheck;
var lastArgs = null;
var lastResult = null;
// we reference arguments instead of spreading them for performance reasons
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments);
}
lastArgs = arguments;
return lastResult;
};
}
function getDependencies(funcs) {
var dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs;
if (!dependencies.every(function (dep) {
return typeof dep === 'function';
})) {
var dependencyTypes = dependencies.map(function (dep) {
return typeof dep;
}).join(', ');
throw new Error('Selector creators expect all input-selectors to be functions, ' + ('instead received the following types: [' + dependencyTypes + ']'));
}
return dependencies;
}
function createSelectorCreator(memoize) {
for (var _len = arguments.length, memoizeOptions = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
memoizeOptions[_key - 1] = arguments[_key];
}
return function () {
for (var _len2 = arguments.length, funcs = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
funcs[_key2] = arguments[_key2];
}
var recomputations = 0;
var resultFunc = funcs.pop();
var dependencies = getDependencies(funcs);
var memoizedResultFunc = memoize.apply(undefined, [function () {
recomputations++;
// apply arguments instead of spreading for performance.
return resultFunc.apply(null, arguments);
}].concat(memoizeOptions));
// If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
var selector = defaultMemoize(function () {
var params = [];
var length = dependencies.length;
for (var i = 0; i < length; i++) {
// apply arguments instead of spreading and mutate a local list of params for performance.
params.push(dependencies[i].apply(null, arguments));
}
// apply arguments instead of spreading for performance.
return memoizedResultFunc.apply(null, params);
});
selector.resultFunc = resultFunc;
selector.recomputations = function () {
return recomputations;
};
selector.resetRecomputations = function () {
return recomputations = 0;
};
return selector;
};
}
var createSelector = exports.createSelector = createSelectorCreator(defaultMemoize);
function createStructuredSelector(selectors) {
var selectorCreator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : createSelector;
if (typeof selectors !== 'object') {
throw new Error('createStructuredSelector expects first argument to be an object ' + ('where each property is a selector, instead received a ' + typeof selectors));
}
var objectKeys = Object.keys(selectors);
return selectorCreator(objectKeys.map(function (key) {
return selectors[key];
}), function () {
for (var _len3 = arguments.length, values = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
values[_key3] = arguments[_key3];
}
return values.reduce(function (composition, value, index) {
composition[objectKeys[index]] = value;
return composition;
}, {});
});
}
});
;;
FpoDataUtils = {
"sheetIndexNumber": 0,
"code": "5.DhJAv",
"modelId": 1296,
"type": "ROW",
"tags": {
"SECONDARY": true
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A5.0BpE6",
"Year": "-1781726935|0|5||A",
"Column": "A",
"Value": "BULLWHIP(1, LINE), SECONDARY, DECIMAL_POINTS(1)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B5.3MeWU",
"Year": "-1781726935|1|5||B",
"Column": "B",
"Value": "Value per Share (2022)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "C5.To8Jm",
"Year": "-1781726935|2|5||C",
"Column": "C",
"Value": "$",
"Hidden": true,
"SortOrder": "C",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D5.Lwikp",
"Year": "-1781726935|3|1|2014",
"Column": "D",
"Value": 82.08452226758979,
"Hidden": false,
"SortOrder": "D",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E5.zGDzo",
"Year": "-1781726935|4|1|2015",
"Column": "E",
"Value": 77.00794428658524,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "F5.VBp7f",
"Year": "-1781726935|5|1|2016",
"Column": "F",
"Value": 90.12224471197842,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "G5.VdMh1",
"Year": "-1781726935|6|1|2017",
"Column": "G",
"Value": 95.67522035065414,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "H5.PkYOL",
"Year": "-1781726935|7|1|2018",
"Column": "H",
"Value": 111.82866519759699,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "I5.byd4M",
"Year": "-1781726935|8|1|2019",
"Column": "I",
"Value": 131.4,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "J5.LqG66",
"Year": "-1781726935|9|1|2020",
"Column": "J",
"Value": 150.36093794765316,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K5.6AvlD",
"Year": "-1781726935|10|1|2021",
"Column": "K",
"Value": 172.8350877835848,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L5.mUCJI",
"Year": "-1781726935|11|1|2022",
"Column": "L",
"Value": 196.5940815516959,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M5.OZm6J",
"Year": "-1781726935|12|1|2023",
"Column": "M",
"Value": 220.32873057257208,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N5.JaHZ9",
"Year": "-1781726935|13|1|2024",
"Column": "N",
"Value": 249.94919088283112,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O5.RiDye",
"Year": "-1781726935|14|5|2022-06-30",
"Column": "O",
"Value": 196.5940815516959,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"components": [
{
"isNegative": false,
"code": "4.hm8cp",
"sheetIndexNumber": 1,
"modelId": 1302,
"type": "ROW",
"tags": {
"sections": [
{
"id": "distribution",
"order": 0
}
]
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A4.moKGF",
"Year": "-1819591982|0|5|",
"Column": "A",
"Value": "Revenue 1",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "B4.vyjMR",
"Year": "-1819591982|1|5|",
"Column": "B",
"Value": "DRIVER_GROUP(a),SECTION(distribution,1)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C4.Ve4rR",
"Year": "-1819591982|2|1|2018",
"Column": "C",
"Value": 50,
"Hidden": false,
"SortOrder": "C",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D4.GVbFf",
"Year": "-1819591982|3|1|2019",
"Column": "D",
"Value": 50,
"Hidden": false,
"SortOrder": "D",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "E4.A7z8S",
"Year": "-1819591982|4|1|2020",
"Column": "E",
"Value": 50,
"Hidden": false,
"SortOrder": "E",
"Historical": false,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "KfrnvUgCtIqyt3SBjjnUnxEa|4.hm8cp",
"rowType": "OTHER",
"unit": "$",
"name": "Revenue 1",
"sheet": "Sheet1",
"id": "KfrnvUgCtIqyt3SBjjnUnxEa|4.hm8cp",
"rowNumber": "4"
},
{
"isNegative": false,
"code": "5.zhWCY",
"sheetIndexNumber": 1,
"modelId": 1302,
"type": "ROW",
"tags": {
"sections": [
{
"id": "distribution",
"order": -1
}
]
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A5.yU4iS",
"Year": "-1819591982|0|5|",
"Column": "A",
"Value": "Revenue 2",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "B5.ZvhNA",
"Year": "-1819591982|1|5|",
"Column": "B",
"Value": "DRIVER_GROUP(a),SECTION(distribution)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C5.JlJKh",
"Year": "-1819591982|2|1|2018",
"Column": "C",
"Value": 50,
"Hidden": false,
"SortOrder": "C",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D5.FwuOS",
"Year": "-1819591982|3|1|2019",
"Column": "D",
"Value": 50,
"Hidden": false,
"SortOrder": "D",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "E5.P4VKt",
"Year": "-1819591982|4|1|2020",
"Column": "E",
"Value": 50,
"Hidden": false,
"SortOrder": "E",
"Historical": false,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "KfrnvUgCtIqyt3SBjjnUnxEa|5.zhWCY",
"rowType": "OTHER",
"unit": "$",
"name": "Revenue 2",
"sheet": "Sheet1",
"id": "KfrnvUgCtIqyt3SBjjnUnxEa|5.zhWCY",
"rowNumber": "5"
},
{
"isNegative": false,
"code": "6.VHkVZ",
"sheetIndexNumber": 1,
"modelId": 1302,
"type": "ROW",
"tags": {
"sections": [
{
"id": "distribution",
"order": 1
}
]
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A6.mecrY",
"Year": "-1819591982|0|5|",
"Column": "A",
"Value": "Revenue 3",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "B6.MW4Hq",
"Year": "-1819591982|1|5|",
"Column": "B",
"Value": "DRIVER_GROUP(a),SECTION(distribution,2),MIN(25),MAX(35)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C6.diFV9",
"Year": "-1819591982|2|1|2018",
"Column": "C",
"Value": 25,
"Hidden": false,
"SortOrder": "C",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D6.a0DOR",
"Year": "-1819591982|3|1|2019",
"Column": "D",
"Value": 35,
"Hidden": false,
"SortOrder": "D",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "E6.uJ7Wz",
"Year": "-1819591982|4|1|2020",
"Column": "E",
"Value": 45,
"Hidden": false,
"SortOrder": "E",
"Historical": false,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "KfrnvUgCtIqyt3SBjjnUnxEa|6.VHkVZ",
"rowType": "OTHER",
"unit": "$",
"name": "Revenue 3",
"sheet": "Sheet1",
"id": "KfrnvUgCtIqyt3SBjjnUnxEa|6.VHkVZ",
"rowNumber": "6"
},
{
"isNegative": false,
"code": "7.BsI50",
"sheetIndexNumber": 1,
"modelId": 1302,
"type": "ROW",
"tags": {
"sections": [
{
"id": "distribution",
"order": 3
}
]
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A7.aaxLH",
"Year": "-1819591982|0|5|",
"Column": "A",
"Value": "Revenue 4",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "B7.8lF9o",
"Year": "-1819591982|1|5|",
"Column": "B",
"Value": "DRIVER_GROUP(a),SECTION(distribution,4),MIN(10),MAX(50)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C7.wVbyZ",
"Year": "-1819591982|2|1|2018",
"Column": "C",
"Value": 35,
"Hidden": false,
"SortOrder": "C",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D7.4Bhfg",
"Year": "-1819591982|3|1|2019",
"Column": "D",
"Value": 45,
"Hidden": false,
"SortOrder": "D",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "E7.vU4xQ",
"Year": "-1819591982|4|1|2020",
"Column": "E",
"Value": 55,
"Hidden": false,
"SortOrder": "E",
"Historical": false,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "KfrnvUgCtIqyt3SBjjnUnxEa|7.BsI50",
"rowType": "OTHER",
"unit": "$",
"name": "Revenue 4",
"sheet": "Sheet1",
"id": "KfrnvUgCtIqyt3SBjjnUnxEa|7.BsI50",
"rowNumber": "7"
},
{
"isNegative": false,
"code": "8.ILczK",
"sheetIndexNumber": 1,
"modelId": 1302,
"type": "ROW",
"tags": {
"sections": [
{
"id": "distribution",
"order": -1
}
]
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A8.QIt1i",
"Year": "-1819591982|0|5|",
"Column": "A",
"Value": "Revenue 5 min/max limits",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "B8.2Nafq",
"Year": "-1819591982|1|5|",
"Column": "B",
"Value": "MIN(15), MAX(85), DRIVER_GROUP(a),SECTION(distribution)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C8.IiwMp",
"Year": "-1819591982|2|1|2018",
"Column": "C",
"Value": 47,
"Hidden": false,
"SortOrder": "C",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D8.WvKG8",
"Year": "-1819591982|3|1|2019",
"Column": "D",
"Value": 50,
"Hidden": false,
"SortOrder": "D",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "E8.xBPg2",
"Year": "-1819591982|4|1|2020",
"Column": "E",
"Value": 65,
"Hidden": false,
"SortOrder": "E",
"Historical": false,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "KfrnvUgCtIqyt3SBjjnUnxEa|8.ILczK",
"rowType": "OTHER",
"unit": "$",
"name": "Revenue 5 min/max limits",
"sheet": "Sheet1",
"id": "KfrnvUgCtIqyt3SBjjnUnxEa|8.ILczK",
"rowNumber": "8"
}
],
"uid": "PO62pShaGaJk6cSovNiqwd9L|5.DhJAv",
"rowType": "OTHER",
"unit": "$",
"name": "",
"bullwhipCharts": [
{
"lo": {
"sheetIndexNumber": 1,
"code": "310.H1J8o",
"modelId": 1296,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A310.rfXry",
"Year": "3211434|0|5||A",
"Column": "A",
"Value": "BULLWHIP(1, LOW)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "E310.4H6nl",
"Year": "3211434|4|5||FYE Date",
"Column": "E",
"Value": "Low",
"Hidden": true,
"SortOrder": "E",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "I310.wSQfb",
"Year": "3211434|8|1|2010",
"Column": "I",
"Value": 22,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "J310.qjEQ6",
"Year": "3211434|9|1|2011",
"Column": "J",
"Value": 22.73,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "K310.inreO",
"Year": "3211434|10|1|2012",
"Column": "K",
"Value": 23.79,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "L310.M55Em",
"Year": "3211434|11|1|2013",
"Column": "L",
"Value": 26.26,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "M310.Gz56i",
"Year": "3211434|12|1|2014",
"Column": "M",
"Value": 30.84,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "N310.Xp4SF",
"Year": "3211434|13|1|2015",
"Column": "N",
"Value": 40.12,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "O310.NFFRI",
"Year": "3211434|14|1|2016",
"Column": "O",
"Value": 39.72,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "P310.OxdJr",
"Year": "3211434|15|1|2017",
"Column": "P",
"Value": 50.39,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "Q310.n2Uc2",
"Year": "3211434|16|1|2018",
"Column": "Q",
"Value": 68.02,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "R310.s1lMa",
"Year": "3211434|17|1|2019",
"Column": "R",
"Value": 93.96,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "S310.QGAj4",
"Year": "3211434|18|1|2020",
"Column": "S",
"Value": "--",
"Hidden": true,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "PO62pShaGaJk6cSovNiqwd9L|310.H1J8o",
"rowType": "OTHER",
"unit": "",
"name": "Low",
"sheet": "iTSR",
"id": "PO62pShaGaJk6cSovNiqwd9L|310.H1J8o",
"rowNumber": "310"
},
"line": {
"sheetIndexNumber": 0,
"code": "5.DhJAv",
"modelId": 1296,
"type": "ROW",
"tags": {
"SECONDARY": true
},
"chartData": [
{
"unit": "$",
"array": [
{
"Type": "STRING",
"Identifier": "A5.0BpE6",
"Year": "-1781726935|0|5||A",
"Column": "A",
"Value": "BULLWHIP(1, LINE), SECONDARY, DECIMAL_POINTS(1)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B5.3MeWU",
"Year": "-1781726935|1|5||B",
"Column": "B",
"Value": "Value per Share (2022)",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "C5.To8Jm",
"Year": "-1781726935|2|5||C",
"Column": "C",
"Value": "$",
"Hidden": true,
"SortOrder": "C",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "D5.Lwikp",
"Year": "-1781726935|3|1|2014",
"Column": "D",
"Value": 82.08452226758979,
"Hidden": false,
"SortOrder": "D",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E5.zGDzo",
"Year": "-1781726935|4|1|2015",
"Column": "E",
"Value": 77.00794428658524,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "F5.VBp7f",
"Year": "-1781726935|5|1|2016",
"Column": "F",
"Value": 90.12224471197842,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "G5.VdMh1",
"Year": "-1781726935|6|1|2017",
"Column": "G",
"Value": 95.67522035065414,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "H5.PkYOL",
"Year": "-1781726935|7|1|2018",
"Column": "H",
"Value": 111.82866519759699,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "I5.byd4M",
"Year": "-1781726935|8|1|2019",
"Column": "I",
"Value": 131.4,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "J5.LqG66",
"Year": "-1781726935|9|1|2020",
"Column": "J",
"Value": 150.36093794765316,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K5.6AvlD",
"Year": "-1781726935|10|1|2021",
"Column": "K",
"Value": 172.8350877835848,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L5.mUCJI",
"Year": "-1781726935|11|1|2022",
"Column": "L",
"Value": 196.5940815516959,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M5.OZm6J",
"Year": "-1781726935|12|1|2023",
"Column": "M",
"Value": 220.32873057257208,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N5.JaHZ9",
"Year": "-1781726935|13|1|2024",
"Column": "N",
"Value": 249.94919088283112,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O5.RiDye",
"Year": "-1781726935|14|5|2022-06-30",
"Column": "O",
"Value": 196.5940815516959,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "PO62pShaGaJk6cSovNiqwd9L|5.DhJAv",
"rowType": "OTHER",
"unit": "$",
"name": "Value per Share (2022)",
"bullwhipCharts": [
{
"lo": "310.H1J8o",
"line": "5.DhJAv",
"range": "309.EI2VL",
"yearEnd": "311.R7G9f"
}
],
"sheet": "Trefis",
"id": "PO62pShaGaJk6cSovNiqwd9L|5.DhJAv",
"rowNumber": "5"
},
"range": {
"sheetIndexNumber": 1,
"components": [
{
"isNegative": true,
"code": "310.H1J8o"
},
{
"isNegative": false,
"code": "441.jMH2C"
}
],
"code": "309.EI2VL",
"modelId": 1296,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A309.Q5iWH",
"Year": "3211434|0|5||A",
"Column": "A",
"Value": "BULLWHIP(1, RANGE)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "E309.Gi8G6",
"Year": "3211434|4|5||FYE Date",
"Column": "E",
"Value": "Historical Trading Range",
"Hidden": true,
"SortOrder": "E",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "I309.QNf8j",
"Year": "3211434|8|1|2010",
"Column": "I",
"Value": 9.579999999999998,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "J309.SdgGc",
"Year": "3211434|9|1|2011",
"Column": "J",
"Value": 6.73,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "K309.KCx6p",
"Year": "3211434|10|1|2012",
"Column": "K",
"Value": 9.160000000000004,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "L309.i4dYK",
"Year": "3211434|11|1|2013",
"Column": "L",
"Value": 9.52,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "M309.L6m6K",
"Year": "3211434|12|1|2014",
"Column": "M",
"Value": 11.45,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "N309.ypW2c",
"Year": "3211434|13|1|2015",
"Column": "N",
"Value": 9.925000000000004,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "O309.xhK9j",
"Year": "3211434|14|1|2016",
"Column": "O",
"Value": 17.130000000000003,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "P309.qQTs1",
"Year": "3211434|15|1|2017",
"Column": "P",
"Value": 22.5,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "Q309.kNiWv",
"Year": "3211434|16|1|2018",
"Column": "Q",
"Value": 34.67,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "R309.2ZMsK",
"Year": "3211434|17|1|2019",
"Column": "R",
"Value": 38.290000000000006,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": false,
"Fixed": true
}
],
"type": 1
}
],
"uid": "PO62pShaGaJk6cSovNiqwd9L|309.EI2VL",
"rowType": "OTHER",
"unit": "",
"name": "Historical Trading Range",
"sheet": "iTSR",
"id": "PO62pShaGaJk6cSovNiqwd9L|309.EI2VL",
"rowNumber": "309"
},
"yearEnd": {
"sheetIndexNumber": 1,
"code": "311.R7G9f",
"modelId": 1296,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A311.y8lRV",
"Year": "3211434|0|5||A",
"Column": "A",
"Value": "BULLWHIP(1, YEAREND)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "E311.t7aIW",
"Year": "3211434|4|5||FYE Date",
"Column": "E",
"Value": "Fiscal Year Close Price",
"Hidden": true,
"SortOrder": "E",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "I311.Mx5q3",
"Year": "3211434|8|1|2010",
"Column": "I",
"Value": 23.01,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "J311.ThEcC",
"Year": "3211434|9|1|2011",
"Column": "J",
"Value": 26,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "K311.GXBtv",
"Year": "3211434|10|1|2012",
"Column": "K",
"Value": 30.59,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "L311.CPXSN",
"Year": "3211434|11|1|2013",
"Column": "L",
"Value": 34.545,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "M311.iYZuA",
"Year": "3211434|12|1|2014",
"Column": "M",
"Value": 41.7,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "N311.p45O7",
"Year": "3211434|13|1|2015",
"Column": "N",
"Value": 44.15,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "O311.qqYKa",
"Year": "3211434|14|1|2016",
"Column": "O",
"Value": 51.17,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "P311.VtadZ",
"Year": "3211434|15|1|2017",
"Column": "P",
"Value": 68.93,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "Q311.G8q4Z",
"Year": "3211434|16|1|2018",
"Column": "Q",
"Value": 98.61,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "R311.TnYut",
"Year": "3211434|17|1|2019",
"Column": "R",
"Value": 131.4,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "S311.yDZQM",
"Year": "3211434|18|1|2020",
"Column": "S",
"Value": "--",
"Hidden": true,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "PO62pShaGaJk6cSovNiqwd9L|311.R7G9f",
"rowType": "OTHER",
"unit": "",
"name": "Fiscal Year Close Price",
"sheet": "iTSR",
"id": "PO62pShaGaJk6cSovNiqwd9L|311.R7G9f",
"rowNumber": "311"
}
}
],
"sheet": "Trefis",
"id": "PO62pShaGaJk6cSovNiqwd9L|5.DhJAv",
"rowNumber": "5",
"waveCharts": [
[
[
{
"sheetIndexNumber": 0,
"code": "10.9w9aX",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A10.enPDp",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,2)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B10.KLga3",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "23",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C10.m83H4",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.25,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D10.MkcEb",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E10.6yqxo",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 1.44761858348852,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F10.DaHfK",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 1.12510445167724,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G10.wSWXx",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.20606276326206,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H10.VT0Bn",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.26701414217157,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I10.C2hlw",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.31510152628277,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J10.EYy7n",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 3.4887497888584,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K10.Psmcx",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.01789887383127,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L10.wltS4",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 3.87839329817433,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M10.sueCc",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.10287407399784,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N10.QQ112",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 3.89643401165626,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O10.iGyw5",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 3.78390600798921,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P10.ftfCm",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.03264392797899,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q10.gfAMi",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 3.86467822104392,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R10.624Hm",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 3.94619846562543,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S10.GA8u7",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 3.65892111950984,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T10.hXEm4",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 3.51597859136862,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U10.b2wO1",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 3.53469750634482,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V10.GdWde",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 3.44799496331684,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W10.CXY1c",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 3.43400967753191,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X10.qFF9C",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 3.41634330293017,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y10.5Sb7Q",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 3.58646142467766,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z10.k5q34",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 3.70266356967258,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|10.9w9aX",
"rowType": "OTHER",
"unit": "",
"name": "23",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|10.9w9aX",
"rowNumber": "10"
},
{
"sheetIndexNumber": 0,
"code": "15.TmHcR",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "%",
"array": [
{
"Type": "STRING",
"Identifier": "A15.ZQZif",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,2)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B15.yTfiB",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "50% Certainty Band",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C15.Y8Mhn",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.75,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D15.akpSc",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E15.gWJwE",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 2.38542958074896,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F15.C07jL",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 2.0114682568435,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G15.JHrwD",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.98871682001293,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H15.8I2A4",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 4.08786666543376,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I15.3jcjo",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 4.17225232246308,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J15.6fl0D",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 4.42546578467505,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K15.KraDH",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.9504652937651,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L15.1PGPw",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.73152148798984,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M15.NRa8I",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.90462136713127,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N15.aKJSc",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 4.79272663949795,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O15.RVtOT",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 4.68816791291389,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P15.0pw8v",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.8554583614677,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q15.qAC2P",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.74424118551776,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R15.hU8Q8",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 4.85866777651238,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S15.JNxcp",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 4.60001630096601,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T15.VG1Bj",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 4.53634037055152,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U15.UQXS3",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 4.60911975794198,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V15.KB876",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 4.48887166165155,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W15.6qwh0",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 4.62374400872323,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X15.hBkto",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 4.66887857544031,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y15.vPRFZ",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 4.8772084752401,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z15.ysbxe",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 5.057109281652,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|15.TmHcR",
"rowType": "OTHER",
"unit": "%",
"name": "50% Certainty Band",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|15.TmHcR",
"rowNumber": "15"
}
],
[
{
"sheetIndexNumber": 0,
"code": "11.G4RGz",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A11.VCouV",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,3)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B11.R5o9v",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "25",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C11.1U6BC",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.35,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D11.zvF5x",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E11.QyyW6",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 1.65049934177424,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F11.zZiCV",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 1.34369270886142,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G11.VMBTT",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.39718622228391,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H11.3F0Wy",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.44964827107964,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I11.GLSd3",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.49655996357102,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J11.erkBn",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 3.6929458197106,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K11.GCi64",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.22873957636848,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L11.wBkE8",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.06688341580177,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M11.FLUss",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.27472794226095,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N11.uqYoh",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 4.10978805112646,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O11.uVHDN",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 3.97009322425243,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P11.K095v",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.21081641902462,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q11.k2Oc4",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.01741969717194,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R11.18iVu",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 4.17062144138834,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S11.jxnpa",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 3.85538002004597,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T11.BNRRc",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 3.7509323016142,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U11.vgWVp",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 3.74527631771809,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V11.i2ICK",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 3.6513675369645,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W11.Mzl06",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 3.64182439495674,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X11.pKJch",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 3.68167603541352,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y11.dwcLh",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 3.86026520660245,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z11.g2mLZ",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 3.93433272864492,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|11.G4RGz",
"rowType": "OTHER",
"unit": "",
"name": "25",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|11.G4RGz",
"rowNumber": "11"
},
{
"sheetIndexNumber": 0,
"code": "14.NE0PQ",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "%",
"array": [
{
"Type": "STRING",
"Identifier": "A14.Ycw6T",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,3)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B14.2mBRE",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "30% Certainty Band",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C14.sjMY3",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.65,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D14.tNLLQ",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E14.Y1oMK",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 2.18475320699325,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F14.EnkGg",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 1.84632493878424,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G14.VlidV",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.83367289365126,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H14.hmbOd",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.91735375440642,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I14.B0qcY",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.97606059170247,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J14.YgVEq",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 4.26405931445818,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K14.JfNgT",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.77972642471915,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L14.sZoGo",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.55594477583989,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M14.NBAYQ",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.69756459206451,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N14.o8tcN",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 4.59117127731521,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O14.VF7CP",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 4.48972328410019,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P14.ONf5J",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.67157689228952,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q14.Notlp",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.52567295377768,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R14.iRxrE",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 4.67667074223868,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S14.vTdKz",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 4.41202785350189,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T14.lKMRH",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 4.29938497318696,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U14.d2bHL",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 4.41337729316858,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V14.e2jVN",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 4.26179300775642,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W14.EN0rF",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 4.33931014291839,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X14.lTYfj",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 4.38619163278406,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y14.OqdE5",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 4.60286250277338,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z14.ApYHU",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 4.69336505393097,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|14.NE0PQ",
"rowType": "OTHER",
"unit": "%",
"name": "30% Certainty Band",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|14.NE0PQ",
"rowNumber": "14"
}
],
[
{
"sheetIndexNumber": 0,
"code": "13.NyAsa",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "%",
"array": [
{
"Type": "STRING",
"Identifier": "A13.zZPOl",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,4)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B13.2dlry",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "10% Certainty Band",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C13.OGKhw",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.55,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D13.qaUX5",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E13.KqwWD",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 2.01914872987542,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F13.W7wyv",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 1.67490962335373,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G13.Z74Pa",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.69612416301491,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H13.IzkaM",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.76542533769233,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I13.KRXDO",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.83379937554498,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J13.XP03s",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 4.09204726052771,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K13.ZtSJu",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.60360267619505,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L13.B6ISE",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.3992453039724,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M13.eruea",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.55368823129437,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N13.rU7H1",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 4.42988753153423,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O13.vPgzE",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 4.32074387187421,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P13.jGDHg",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.53208591883676,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q13.7lqx2",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.32203078099272,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R13.C6xTh",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 4.50190404366357,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S13.GyaJF",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 4.24277029256447,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T13.SRbUu",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 4.14618118966009,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U13.7JaOi",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 4.21708437889921,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V13.aG2Za",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 4.06496320230981,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W13.BJFSi",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 4.1061153501189,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X13.cm0vo",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 4.12028680700074,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y13.lrEJG",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 4.33197478139822,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z13.0KVk2",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 4.49364656425163,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|13.NyAsa",
"rowType": "OTHER",
"unit": "%",
"name": "10% Certainty Band",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|13.NyAsa",
"rowNumber": "13"
},
{
"sheetIndexNumber": 0,
"code": "12.67Qxh",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A12.1D1GZ",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,4)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B12.XPk0B",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "27",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C12.xkia4",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.45,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D12.WZ4iL",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E12.V5h6R",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 1.84969908875024,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F12.urAwk",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 1.5276162667769,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G12.MKpFX",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 2.5528644198955,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H12.WDtSb",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.6395157223555,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I12.Dhp65",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.68182882051294,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J12.WuTVJ",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 3.89974774395862,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K12.vYLuL",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 4.41868001666331,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L12.gUyEV",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.23512404902589,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M12.a5wXw",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 4.4246910590542,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N12.t9X10",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 4.28162268884231,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O12.jVVCI",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 4.17262140132989,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P12.938fp",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 4.33142485719705,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q12.CqQ0h",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.17137100621889,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R12.a8MLh",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 4.32362608065492,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S12.rqJLL",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 4.02929754012317,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T12.Rhbrt",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 3.96376197209218,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U12.jmrWS",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 4.01131770235969,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V12.7xkYd",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 3.86885399425363,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W12.DCyZj",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 3.87662751477671,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X12.QIucg",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 3.9441904196527,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y12.IUoyM",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 4.05593716412018,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z12.pHi6Z",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 4.18380330819419,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|12.67Qxh",
"rowType": "OTHER",
"unit": "",
"name": "27",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|12.67Qxh",
"rowNumber": "12"
}
],
[
{
"sheetIndexNumber": 0,
"code": "8.qUsJV",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A8.Po3ry",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,0)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B8.ZJUbs",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "18",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C8.GU1Vz",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.05,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D8.dI9rC",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E8.iPuLv",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 0.792290562781484,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F8.6NGdY",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 0.471460136184987,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G8.ACW8Y",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 1.58343739651616,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H8.17xMq",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 2.65668897981891,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I8.4Ym4d",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 2.74455438983238,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J8.ezIFp",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 2.84678451246267,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K8.fEcqN",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 3.3491330341183,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L8.s0cvu",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 3.18890740448467,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M8.SnsfJ",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 3.36144207766492,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N8.3Tdz1",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 3.20241690866086,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O8.LeANw",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 3.14825117348123,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P8.tQEiH",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 3.45808314614641,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q8.AaY5W",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 3.20289574083373,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R8.kavzB",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 3.23872220464816,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S8.u3n9t",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 2.94668105064933,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T8.h9n9x",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 2.86679503872486,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U8.Bluq0",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 2.81164406384391,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V8.FUYav",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 2.65090007188553,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W8.6LFy3",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 2.55918385006273,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X8.SbPv2",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 2.5278759666361,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y8.7agzy",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 2.62129780734608,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z8.kPqFm",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 2.65223600365525,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|8.qUsJV",
"rowType": "OTHER",
"unit": "",
"name": "18",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|8.qUsJV",
"rowNumber": "8"
},
{
"sheetIndexNumber": 0,
"code": "17.kjxWW",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "%",
"array": [
{
"Type": "STRING",
"Identifier": "A17.IYJib",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,0)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B17.X6U3I",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "90% Certainty Band",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C17.8qgsv",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.95,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D17.HwxI8",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E17.jgn4d",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 3.02823612420651,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F17.VtQz3",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 2.74451739454087,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G17.sqLnQ",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 3.62847344136357,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H17.gpibI",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 4.69951180860983,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I17.rcx1y",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 4.84150270740232,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J17.XiyNm",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 5.00358698475334,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K17.NXSRE",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 5.51176668161677,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L17.cDhS3",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 5.37414568333028,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M17.OBBOs",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 5.55968598082442,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N17.Ce3X9",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 5.533467523549,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O17.NlBWF",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 5.32184661859603,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P17.glwic",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 5.45303953913678,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q17.5krZB",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 5.33651667593516,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R17.YUPtQ",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 5.57117683425008,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S17.lXcIi",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 5.3348503096546,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T17.oKxIP",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 5.27626912528891,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U17.Jjfdj",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 5.33652059543849,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V17.Nwdfh",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 5.33010881163937,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W17.GBxzR",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 5.42547320747939,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X17.SnyhP",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 5.64147382805981,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y17.jHeSm",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 5.81580392450568,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z17.Z1lbz",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 5.99261319211615,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|17.kjxWW",
"rowType": "OTHER",
"unit": "%",
"name": "90% Certainty Band",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|17.kjxWW",
"rowNumber": "17"
}
],
[
{
"sheetIndexNumber": 0,
"code": "9.TSKQY",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "",
"array": [
{
"Type": "STRING",
"Identifier": "A9.nfnBx",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,1)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B9.afCYZ",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "21",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C9.34kDa",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.15,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D9.GwitE",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E9.5NBJp",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 1.23574824023722,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F9.BAkSa",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 0.827463318918346,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G9.GpFeH",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 1.97847082556786,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H9.6LJqz",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 3.05895245686113,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I9.BdbWP",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 3.07885879648688,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J9.XoLf2",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 3.21887371716507,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K9.V5P4k",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 3.79007392020362,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L9.11J3k",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 3.64969644654652,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M9.942KA",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 3.85017892073477,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N9.EQDPq",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 3.63471687120925,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O9.83Rzn",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 3.61035185880034,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P9.8Jse6",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 3.83018983964336,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q9.QLeGr",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 3.58199798825209,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R9.UrR95",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 3.65775156764448,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S9.7xhSS",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 3.38081072107724,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T9.FlIDH",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 3.27793259509821,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U9.hD8Oo",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 3.26320466422842,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V9.VdtFC",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 3.19398699960968,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W9.vz08J",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 3.13003458407333,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X9.kGb2l",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 3.10275461544927,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y9.U8mER",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 3.17976599414498,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z9.VfGig",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 3.27453432479056,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|9.TSKQY",
"rowType": "OTHER",
"unit": "",
"name": "21",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|9.TSKQY",
"rowNumber": "9"
},
{
"sheetIndexNumber": 0,
"code": "16.AU4GM",
"modelId": 1305,
"type": "ROW",
"tags": {},
"chartData": [
{
"unit": "%",
"array": [
{
"Type": "STRING",
"Identifier": "A16.x7Ftm",
"Year": "-46092688|0|5|",
"Column": "A",
"Value": "WAVE_CHART(A,1)",
"Hidden": true,
"SortOrder": "A",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "STRING",
"Identifier": "B16.zDKlL",
"Year": "-46092688|1|5|",
"Column": "B",
"Value": "70% Certainty Band",
"Hidden": true,
"SortOrder": "B",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "C16.VqsGr",
"Year": "-46092688|2|5|",
"Column": "C",
"Value": 0.85,
"Hidden": false,
"SortOrder": "C",
"Historical": false,
"Modifiable": true,
"Fixed": false
},
{
"Type": "STRING",
"Identifier": "D16.GzQeo",
"Year": "-46092688|3|5|",
"Column": "D",
"Value": "UNITLESS",
"Hidden": true,
"SortOrder": "D",
"Historical": false,
"Modifiable": false,
"Fixed": true
},
{
"Type": "NUMBER",
"Identifier": "E16.P6DiP",
"Year": "-46092688|4|1|2015",
"Column": "E",
"Value": 2.5902958762612,
"Hidden": false,
"SortOrder": "E",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "F16.U0Rr9",
"Year": "-46092688|5|1|2016",
"Column": "F",
"Value": 2.27119147965474,
"Hidden": false,
"SortOrder": "F",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "G16.2qs9y",
"Year": "-46092688|6|1|2017",
"Column": "G",
"Value": 3.24109602743678,
"Hidden": false,
"SortOrder": "G",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "H16.McLTG",
"Year": "-46092688|7|1|2018",
"Column": "H",
"Value": 4.3133431608711,
"Hidden": false,
"SortOrder": "H",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "I16.f6VC5",
"Year": "-46092688|8|1|2019",
"Column": "I",
"Value": 4.37734803021363,
"Hidden": false,
"SortOrder": "I",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "J16.3IEb6",
"Year": "-46092688|9|1|2020",
"Column": "J",
"Value": 4.63063353556282,
"Hidden": false,
"SortOrder": "J",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "K16.iKIks",
"Year": "-46092688|10|1|2021",
"Column": "K",
"Value": 5.21050473085229,
"Hidden": false,
"SortOrder": "K",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "L16.EuXbu",
"Year": "-46092688|11|1|2022",
"Column": "L",
"Value": 4.97426427051326,
"Hidden": false,
"SortOrder": "L",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "M16.MfUT8",
"Year": "-46092688|12|1|2023",
"Column": "M",
"Value": 5.13940275876477,
"Hidden": false,
"SortOrder": "M",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "N16.8EjI3",
"Year": "-46092688|13|1|2024",
"Column": "N",
"Value": 5.05821236053366,
"Hidden": false,
"SortOrder": "N",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "O16.3weST",
"Year": "-46092688|14|0|2018Q1",
"Column": "O",
"Value": 4.93988336546871,
"Hidden": false,
"SortOrder": "O",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "P16.sl7dp",
"Year": "-46092688|15|0|2018Q2",
"Column": "P",
"Value": 5.06576652769494,
"Hidden": false,
"SortOrder": "P",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Q16.vbuhB",
"Year": "-46092688|16|0|2018Q3",
"Column": "Q",
"Value": 4.98626986981291,
"Hidden": false,
"SortOrder": "Q",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "R16.oMKWs",
"Year": "-46092688|17|0|2018Q4",
"Column": "R",
"Value": 5.13252016236625,
"Hidden": false,
"SortOrder": "R",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "S16.Hm9uT",
"Year": "-46092688|18|0|2019Q1",
"Column": "S",
"Value": 4.93065222023955,
"Hidden": false,
"SortOrder": "S",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "T16.kFCEp",
"Year": "-46092688|19|0|2019Q2",
"Column": "T",
"Value": 4.80044052543481,
"Hidden": false,
"SortOrder": "T",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "U16.EqkVB",
"Year": "-46092688|20|0|2019Q3",
"Column": "U",
"Value": 4.8712805447744,
"Hidden": false,
"SortOrder": "U",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "V16.aF2dM",
"Year": "-46092688|21|0|2019Q4",
"Column": "V",
"Value": 4.81098634272072,
"Hidden": false,
"SortOrder": "V",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "W16.UdISJ",
"Year": "-46092688|22|0|2020Q1",
"Column": "W",
"Value": 4.916883701702,
"Hidden": false,
"SortOrder": "W",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "X16.GgyOZ",
"Year": "-46092688|23|0|2020Q2",
"Column": "X",
"Value": 5.05757805893093,
"Hidden": false,
"SortOrder": "X",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Y16.L0EQE",
"Year": "-46092688|24|0|2020Q3",
"Column": "Y",
"Value": 5.20282095287024,
"Hidden": false,
"SortOrder": "Y",
"Historical": true,
"Modifiable": true,
"Fixed": false
},
{
"Type": "NUMBER",
"Identifier": "Z16.Voh9X",
"Year": "-46092688|25|0|2020Q4",
"Column": "Z",
"Value": 5.39532090229855,
"Hidden": false,
"SortOrder": "Z",
"Historical": true,
"Modifiable": true,
"Fixed": false
}
],
"type": 1
}
],
"uid": "htBzIyU6EttZzYmBJZ47LfTu|16.AU4GM",
"rowType": "OTHER",
"unit": "%",
"name": "70% Certainty Band",
"sheet": "Trefis_Summary",
"id": "htBzIyU6EttZzYmBJZ47LfTu|16.AU4GM",
"rowNumber": "16"
}
]
]
]
}
;;
// @include presentations/utils/fpoDataUtils.js
var Service = {
saveState: function (state, modelId) {
var self = this;
var $dfd = $.Deferred();
if (window.properties.isTemplateAuthoring()) {
$dfd.resolve({ presentationId: -1, status: "success" })
} else if (state.get("persistedId") < 0){
$dfd.resolve({ presentationId: -1, status: "success" })
} else {
if(!modelId)modelId=properties.modelId();
var payload = $.extend(true, {}, {
state: JSON.stringify(state.toJSON()),
timestamp: new Date().getTime(),
modelId: modelId,
presentationId: state.get("persistedId")
},self.getMetaData(state))
var req = $.ajax({
data: payload,
url: getHost() + "/api/presentations/save",
type: "POST",
})
req.done(function (resp) {
// we are removing the translated content form the presentation when we save the the translated state for a unlink dashboard
if (window.translatedContent){
self.removeTranslatedState(state.get("persistedId"));
}
$dfd.resolve(resp);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
}
return $dfd.promise()
},
removeTranslatedState: function (presentationId) {
var req = $.ajax({
url: properties.host() + "/api/presentations/deleteTranslatedContent?presentationId="+presentationId,
type: "GET",
})
req.done(function (resp) {
window.translatedContent = undefined;
}).fail(function (error) {
console.error(error);
});
},
unlinkDashboard: function(presentationId,copy){
var self = this;
var $dfd = $.Deferred();
// TODO: whatever the unlink service is - populate it here. SHould only need a presentation ID
var req = $.ajax({
method: 'POST',
data:{
timestamp: new Date().getTime(),
modelId: properties.modelId(),
presentationId:presentationId,
copy:copy,
},
url: properties.host() + "/api/presentations/unlink",
dataType: "json"
});
req.done(function (resp) {
if(resp.isCopy){
// updating the presentation id
var newPresentationId = resp.newPresentationId;
var newModelId = resp.newModelId;
var newState = toPersistState(store.getState()).set("persistedId", newPresentationId);
self.saveState(newState, newModelId).done(function(){
window.location = resp.url;
})
} else {
// non eed to resolve if its isCopy - we're off to somewhere else
$dfd.resolve({});
}
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
//$dfd.resolve({persistedId:presentationId});
return $dfd.promise();
},
// we are storing the meta keywords, meta description, templateTopicTags and title outside of the
// state - so that we don't have to parse the state to go looking for the,
// TODO: we will also store the 'translation' request here as well at a later date
getMetaData: function (state) {
var self = this;
return $.extend({}, self.getMetaDescription(state), {
title: state.getIn(['title', 'text'])
})
},
getMetaDescription: function (state) {
var metaData = state.getIn(['meta']) ? state.getIn(['meta']).toJS() : {};
if (metaData && metaData.description && metaData.description != 'null') {
return metaData;
} else {
metaData.description = PresentUtil.getDefaultMetaDescription(state);
return metaData;
}
},
getVerifyBlock: function(state){
return {
// heades up - this function available throgugh javascript funciton hoisting and not
// through explicitly ordering of scripts...
verifyBlock:JSON.stringify(getVerifiedDynamicContentsRequest(state))
}
},
getCacheBlock: function(state){
return {
// heades up - this function available throgugh javascript funciton hoisting and not
// through explicitly ordering of scripts...
cacheBlock:JSON.stringify(getDynamicContentsRequest(state))
}
},
saveTemplate: function (template) {
var state = store.getState();
var metaData = this.getMetaData(state);
var verifyBlock = this.getVerifyBlock(state);
var cacheBlock = this.getCacheBlock(state);
var $dfd = $.Deferred();
var payload = $.extend(true, {}, {
execution: properties.executionString(),
name: template.name,
template: JSON.stringify(template.json.toJS()),
id: !template.id ? '' : template.id,
isGlobal: template.isGlobal
}, metaData, verifyBlock, cacheBlock); // adding our rmetaData and verifyBLock FET-153, 154, 155, 156..
var req = $.ajax({
method: 'POST',
data: payload,
url: properties.host() + "/api/presentations/template/saveas",
dataType: "json"
});
req.done(function (resp) {
$dfd.resolve(resp.data);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
return $dfd.promise();
},
saveTemplateSlug: function (template, overWrite) {
var $dfd = $.Deferred();
var state = store.getState();
var templateId=state.getIn(['ui', 'currentTemplate', 'id']);
var req = $.ajax({
method: 'POST',
data: {
id: templateId,
templateSlug:template.templateSlug,
},
url: properties.host() + "/api/presentations/template/saveSlug",
dataType: "json"
});
req.done(function (resp) {
$dfd.resolve(resp);
if(resp.status && resp.status=="error"){
alert(resp.message);
}
}).fail(function (a, b, c) {
$dfd.reject(a, b, c);
});
return $dfd.promise();
},
getTemplateState: function (id) {
var $dfd = $.Deferred();
var req = $.ajax({
method: 'GET',
url: properties.host() + "/api/presentations/template/state?id=" + id
});
req.done(function (resp) {
$dfd.resolve(resp);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
return $dfd.promise()
},
getTemplates: function () {
var $dfd = $.Deferred();
var req = $.ajax({
data: { execution: properties.executionString() },
url: properties.host() + "/api/presentations/template/list",
dataType: "json",
});
req.done(function (resp) {
$dfd.resolve(resp);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
return $dfd.promise()
},
getTemplateById: function (id) {
var $dfd = $.Deferred();
var req = $.ajax({
data: { execution: properties.executionString() },
url: properties.host() + "/api/presentations/template/"+id,
dataType: "json",
});
req.done(function (resp) {
$dfd.resolve(resp);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
return $dfd.promise()
},
// When results come back as rows:{{code:data},...} we need to stitch the 'resources onto them'
// we have mergeResources, getResourceIds, etc
addResourceData: function (data) {
var _data = $.extend(true, {}, data);
var dataRows = _data.rows;
var self = this;
var updatedData = Object.keys(dataRows).reduce(function (acc, plotCode) {
var plotData = $.extend(true, {}, dataRows[plotCode]);
acc[plotCode] = self.mergeResources(plotData, dataRows)
return acc;
}, {});
_data.rows = updatedData;
return _data;
},
// modelId - take into account the model they are from. ie, only this model
getAllPlotCodes: function (skipResources,modelId) {
var self = this;
var codes = store.getState().getIn(['components']).reduce(function (acc, component, i) {
// first get all the chart codes
// if we pass in a a modelId, make sure the component is from the modelId
// important to check isFromCurrent model and NOT component.get('modelId') === modelId
// to be able to support reuploads, we don't store the modelId on components FROM the model
// only store them if they are FROM DIFFERENT models...
if (component.get('type') === 'chart' && (modelId ? PlotUtils.isFromCurrentModel(component) : true)) {
acc.push(component.get('code'))
// now, check if this chart, has components. If so, we might as well request that data now
if (!skipResources) {
// self.getResourceIds(component)
var plot = component.getIn(['ui', 'plots', 0, 'data']);
plot = plot ? plot.toJS() : {};
var ids = self.getPlotResourceIds(plot,modelId);
acc = acc.concat(ids)
}
}
return acc;
}, []);
// clean up results - no False/undefined and no duplicates
return codes.filter(Boolean).filter(function (code, i) {
return codes.indexOf(code) === i
});
},
easyAccessToken: function () {
return properties.modelContext().easyAccessToken ?
{ easyAccessToken: properties.modelContext().easyAccessToken } : {}
},
getAllTags: function () {
var $dfd = $.Deferred();
if (window.properties.isTemplateAuthoring()) $dfd.resolve({});
else {
var req = $.ajax({
data: {
model: properties.modelId(),
},
url: properties.host() + "/api/tags",
dataType: "json",
});
req.done(function (resp) {
if (resp.tags) {
$dfd.resolve(resp.tags);
} else {
$dfd.reject({});
}
}).fail(function (a, b, c) {
$dfd.reject(a, b, c)
});
}
return $dfd.promise()
},
getDummyPlot: function () {
return FpoDataUtils;
},
// expression === selector === ${TOTAL_REVENUE(-2)+TOTAL_REVENUE(-1)} etc
getExpressionPlot: function(component){
var $dfd = $.Deferred();
// no more double translating. Expressions are transalated once at the top of the page load
// if code make it to here, it doesn't need to be translated again.... and the component didn't get removed
// as part of the original translation (no data === no chart/component on FET)
// you cna largely do a find in the presentations directory for 'expressionPlot' to see how it ends up here
// used to pass the expression to here, but all has been updated to be the expression, and then just returned
// expression charts don't update with scenarios, and if thag becomes a requirement, this will all need to change
if (component.get('charts') && component.get('charts').size){
// THIS 'selector' is important, but likely shouldn't be. These operate in place of 'code' for model charts
// however, without it, scenarios vs series (in highcharts) get out of whack, and redraws of chart dupliacte series
// think we use this as the series ID maybe - but should probably just use the charts ID (as there could be tons of the same selector, but only 1 numeric id)
var chart = component.get('charts').first().set('selector', component.get('selector') || component.get('code'));
$dfd.resolve(chart.toJS());
} else {
var payload = {
model: properties.modelId(),
expression: component.get('expression'),
type: 'chart'
}
var urlParams = $.param($.extend(true,{
model:properties.modelId()
},this.easyAccessToken()))
// shouldn't ever hit this....
var req = $.ajax({
// data: $.extend({
// model: properties.modelId(),
// plot:code,
// addSumComponents:1
// }, this.easyAccessToken()),
type: "POST",
data: payload,
url: properties.host() + "/q/expression?"+urlParams,
dataType: "json",
});
req.done(function (resp) {
// if (resp.rows && resp.rows.length){
if (resp && PlotUtils.isSuccessfulPlotTranslation(resp)) {
var plot = JSON.parse((resp.result));
// HEADS UP: on update, we need to use this expression to find the correct component to update
// 1 expression could be used multiple times - our modelPlots - we get all the codes and remove duplicates
// if multiple components have the same code, then the data will be the same from the backend/
plot.selector = expression;
$dfd.resolve(plot);
} else {
$dfd.reject(resp)
}
}).fail(function (a, b, c) {
console.log('getPlot', a, b, c)
$dfd.reject(a, b, c)
});
}
// return !getResources ? $dfd.promise() : this.getPlotWithResources(code)
return $dfd.promise()
},
getPlotData: function(component){
//
return PlotUtils.isModelPlot(component) ?
this.getPlot(component.get('code'),PlotUtils.getComponentModelId(component),true) :
this.getExpressionPlot(component)
},
getPlot: function (code, modelId, getResources) {
var self = this;
if (getResources) {
return this.getPlotWithResources(code,modelId)
}
var $dfd = $.Deferred();
var req = $.ajax({
data: $.extend({
model: modelId,
plot: code,
addSumComponents: 1
}, this.easyAccessToken()),
url: properties.host() + "/api/plot/get",
dataType: "json",
});
req.done(function (resp) {
if (resp.rows && resp.rows.length) {
$dfd.resolve(resp.rows[0]);
} else {
//creating a dummy object for unavailable plots
var resp = { code: code, chartData: [{ array: [] }] };
$dfd.resolve(resp);
//console.log('\ngetPlot,',code,',req.done but something not right>>',resp)
//$dfd.reject({msg:code+'-no data found - solr should throw an error'})
}
}).fail(function (a, b, c) {
console.log('getPlot', a, b, c)
$dfd.reject(a, b, c)
});
// return !getResources ? $dfd.promise() : this.getPlotWithResources(code)
return $dfd.promise()
},
// just going to run a get request...
updateDifferentModelPlots: function(components){
// going to be similar to getPlots, except modelId's will ne different
// create an array of getPlot()
console.log('updateDifferentModelPlots',components)
if (!components || !components.length){
return $.Deferred().resolve({});
}
var self = this;
var deferredPromises = components.reduce(function(deferreds,component){
deferreds.push(self.getPlot(component.get('code'), PlotUtils.getComponentModelId(component), true))
return deferreds
},[])
return this.createPromiseAll(deferredPromises)
},
// only do these after an update... these will for now, be the same as their previous values
// however, an expression on the current model, *should* return different values if passed a different scenarioId
updateExpressionPlots: function(components){
if (!components || !components.length){
return $.Deferred().resolve({});
}
var self = this;
var deferredPromises = components.reduce(function(deferreds,component){
// TODO: we should make use of component.modelId, or a passed in one, in case a new scenarioId
// in this case, the one just created by an update, reutrns updated data...
// this may require tracking the modelId on an expression chart (may, or may not happen - they don't really work for me)
deferreds.push(self.getExpressionPlot(component))
return deferreds;
},[])
return this.createPromiseAll(deferredPromises)
},
getExpressionPlots: function(components){
// right now, these do the same - we have no 'scenario' on expression plots
return this.updateExpressionPlots(components)
},
getPlots: function (codes, modelId, scenarioId, getResources) {
var self = this;
var $dfd = $.Deferred();
var $dfds = codes.reduce(function (deferreds, code) {
deferreds.push(
!scenarioId ?
self.getPlot(code, modelId, getResources) :
self.getScenarioForPlot(code, scenarioId, getResources)
);
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd)
},
// TODO: refactor - getPlot (getPlot/getPlotWithResources) should take a scenarioId, and use the getScenarioForPlot
getPlotWithResources: function (code, modelId, scenarioId) {
var self = this;
var $dfd = $.Deferred();
this.getPlot(code,modelId).done(function (plot) {
if (plot && (self.hasResources(plot))) {
self.getResources(plot, modelId, scenarioId, true).done(function () {
$dfd.resolve(plot)
}).fail(function (a, b, c) {
console.log('getResourcesFail', a, b, c)
})
} else if (plot) {
$dfd.resolve(plot)
}
});
return $dfd.promise();
},
hasResources: function (plot) {
return plot.components || plot.sumMaxRowIds || plot.sumMinRowIds || plot.bullwhipCharts || plot.waveCharts;
},
// this is the request created by the chartDrawer/default charts
getDefaultPlots: function () {
var $dfd = $.Deferred();
$.ajax(
{
data: {
model: properties.modelId()
},
url: properties.host() + "/api/plots/default",
dataType: "json",
}
).done(function (resp) {
$dfd.resolve(resp)
}).fail(function (a, b, c) {
// dispatch(ActionCreators.searchError(true))
});
return $dfd.promise();
},
createScenario: function () {
var $dfd = $.Deferred();
$.ajax({
data: $.extend({
model: properties.modelId()
}, this.easyAccessToken()),
url: properties.host() + "/api/scenario/create",
dataType: "json"
}).done(function (resp) {
$dfd.resolve(resp)
}).fail(function (a, b, c) {
$dfd.reject()
});
return $dfd.promise();
},
copyScenario: function (scenarioId) {
var $dfd = $.Deferred();
$.ajax({
data: {
model: properties.modelId(),
scenario: scenarioId
},
url: properties.host() + "/api/scenario/copy",
dataType: "json"
}).done(function (resp) {
$dfd.resolve(resp)
}).fail(function (a, b, c) {
//console.log('Service.createScenario failed',a,b,c);
$dfd.reject()
});
return $dfd.promise();
},
deleteScenario: function (model) {
var $dfd = $.Deferred();
if (model.modelId) {
if (tf.editMode) {
gavpv('delete/Scenario/mode/edit', 'Delete scenario in edit mode');
} else {
gavpvl('delete/Scenario/mode/view', 'Delete scenario in view mode');
}
$.ajax({
data: {
scenario: model.modelId
},
url: properties.host() + "/api/scenario/delete",
dataType: "json"
}).done(function (resp) {
$dfd.resolve(resp)
}).fail(function (a, b, c) {
$dfd.reject()
});
}
return $dfd.promise();
},
getScenarios: function (modelId) {
var $dfd = $.Deferred();
if (window.properties.isTemplateAuthoring()){
$dfd.resolve({});
}
else {
$.ajax({
data: {
model: properties.modelId()
},
url: properties.host() + '/api/scenario/list',
dataType: 'json'
}).done(function (resp) {
$dfd.resolve(resp.scenarios);
}).fail(function (a, b, c) {
//console.log('Service.getScenarios failed',a,b,c);
});
}
return $dfd.promise()
},
updatePlot: function (model, toRecalc, isFinal, isNew, component) {
//console.log('service.updatePlot',plotCode)
// this one we can just sort by prroperties.modelId() - we can only update those this way
var self = this;
if (!isFinal || !model) return;
// this is the base modelId....
var plotCodes=this.getAllPlotCodes(false,properties.modelId());
var $dfd = $.Deferred();
var payload = {
recalc: toRecalc,
derivedModel: model.modelId,
plots: plotCodes
}
$.ajax({
type: "POST",
url: properties.host() + "/api/scenario/plot/update?new=" + isNew + "&easyAccessToken=" + properties.modelContext().easyAccessToken,
dataType: "json",
data: JSON.stringify(payload),
contentType: "application/json",
}).done(function (resultData) {
// TODO: use the mergeComponents...
resultData = self.addResourceData(resultData);
if (resultData && resultData.status == "success") {
$dfd.resolve(resultData)
// TODO: some more childScenarioStuff
// for (var childScenarioIndex = 0; childScenarioIndex < model.childScenarios.length; childScenarioIndex++) {
// var childScenario = model.childScenarios[childScenarioIndex];
// childScenario.updateWithParentDriverChanges(copyOfDirtyDrivers, model);
// }
} else if (resultData && resultData.status == "error") {
$dfd.reject();
}
}).fail(function (a, b, c) {
gaEvent(['modelwidget', 'cannotsave']);
$dfd.reject()
});
return $dfd.promise();
},
// TODO: going to need similar treatment to the update...
resetScenarioPlot: function (scenarioId, plotCode) {
var $dfd = $.Deferred(),
self = this;
var payload = {
code: plotCode,
derivedModel: scenarioId,
// modelID will filter out non modesl plots from request...
plots: this.getAllPlotCodes(false,properties.modelId())
}
$.ajax({
type: "POST",
data: JSON.stringify(payload),
url: properties.host() + "/api/scenario/plot/reset",
dataType: "json",
contentType: "application/json"
}).done(function (resp) {
resp = self.addResourceData(resp);
if (resp && resp.status == "success") {
$dfd.resolve(resp)
// TODO: some more childScenarioStuff
// for (var childScenarioIndex = 0; childScenarioIndex < model.childScenarios.length; childScenarioIndex++) {
// var childScenario = model.childScenarios[childScenarioIndex];
// childScenario.updateWithParentDriverChanges(copyOfDirtyDrivers, model);
// }
} else if (resp && resp.status == "error") {
$dfd.reject();
}
}).fail(function (a, b, c) {
$dfd.reject(arguments)
});
return $dfd.promise();
},
getScenarioForPlot: function (plotCode, scenarioId, getResources, newFlag) {
var $dfd = $.Deferred(),
self = this;
$.ajax({
data: {
scenario: scenarioId,
code: plotCode,
new: newFlag,
easyAccessToken: properties.modelContext().easyAccessToken,
addSumComponents: 1
},
url: properties.host() + "/api/scenario/plot/get",
dataType: "json"
}).done(function (resp) {
var isSuccessful = resp.success && (resp.status === 'success') && resp.rows[plotCode],
plot;
if (isSuccessful) {
plot = resp.rows[plotCode];
}
if (getResources) {
if (self.hasResources(plot)) {
self.getScenarioForPlotsResources(plot, scenarioId, newFlag).done(function (resp) {
$dfd.resolve(self.mergeResources(plot, resp));
})
} else {
$dfd.resolve(plot);
}
// go get components...
} else {
$dfd.resolve(plot);
}
}).fail(function (a, b, c) {
//$dfd.reject(arguments)
var plot = {
code: plotCode,
chartData: [{ array: [] }]
}
$dfd.resolve(plot);
});
return $dfd.promise();
},
// make sure to account for modleId if passed in
getPlotResourceIds: function (plot,modelId) {
var _plot = [plot];
var a = _plot.reduce(function (acc, plot) {
var componentIds = [];
// modelId ? plot.modelId === modelId : true
// only consider if its passed in///
if (modelId ? plot.modelId === modelId : true){
if (plot.components && plot.components.length) {
var components = plot.components.map(function (component) {
return component.code
})
componentIds.push(components);
}
if (plot.waveCharts && plot.waveCharts.length) {
var waveIds = plot.waveCharts.flat();
componentIds.push(waveIds.flat())
}
if (plot.bullwhipCharts && plot.bullwhipCharts.length) {
var bullwhipCharts = plot.bullwhipCharts.reduce(function (acc, bullwhipMap) {
var components = Object.keys(bullwhipMap);
components.forEach(function (component) {
// we do replacement of the code wth the actual plot for plotting
// so on page load, it will be {lo:'code'} and on update it will be {lo:{code:'code',chartData:[]}}
acc.push(typeof bullwhipMap[component] === 'string' ? bullwhipMap[component] : bullwhipMap[component].code)
});
return acc;
}, [])
componentIds.push(bullwhipCharts)
}
if (plot.sumMaxRowIds && plot.sumMaxRowIds.length) {
var sumMax = plot.sumMaxRowIds.map(function (code) {
return code
})
componentIds.push(sumMax);
}
if (plot.sumMinRowIds && plot.sumMinRowIds.length) {
var sumMin = plot.sumMinRowIds.map(function (code) {
return code
})
componentIds.push(sumMin);
}
}
return acc.concat.apply([], componentIds);
}, []);
return a;
},
// resources are components - but also the remote min/max...
getScenarioForPlotsResources: function (plot, scenario, newFlag) {
var self = this;
var $dfd = $.Deferred();
var componentIds = this.getPlotResourceIds(plot);
var $dfds = componentIds.reduce(function (deferreds, code) {
deferreds.push(self.getScenarioForPlot(code, scenario, false, newFlag));
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd)
},
// plot is the base - its 'components' starts out as an array of codes
// we fetch the codes, their scenarios, etc, and then merge it
getResources: function (plot, modelId, scenarioId, mergeResources) {
var self = this;
var $dfd = $.Deferred();
var componentCodes = this.getPlotResourceIds(plot)
// prob need a better way of handling...
if (scenarioId && scenarioId !== 'undefined.trefis') {
self.getScenarioForPlotsResources(plot, scenarioId).done(function (scenarioComponentsData) {
if (mergeResources) {
$dfd.resolve(self.mergeResources(plot, scenarioComponentsData));
} else {
$dfd.resolve(scenarioComponentsData)
}
}).fail(function (a, b, c) {
console.log('getResources FAIL', a, b, c)
})
} else {
self.getPlots(componentCodes,modelId).done(function (componentsData) {
if (mergeResources) {
$dfd.resolve(self.mergeResources(plot, componentsData));
} else {
$dfd.resolve(componentsData);
}
}).fail(function (a, b, c) {
console.log('getResourcesFail', a, b, c)
})
}
return $dfd.promise();
},
mergeResources: function (plot, resources) {
// from our update - all the plot adata comes back as rows:{{code:plotData}} - but from a get resources for a plt, it comes back as array
var map = Array.isArray(resources) ? resources.reduce(function (obj, component) {
obj[component.code] = component;
return obj;
}, {}) : resources;
// now syn them up to 1 or n places
if (plot.components && plot.components.length) {
var _components = plot.components.map(function (comp) {
return $.extend(true, {}, comp, map[comp.code])
});
plot.components = _components;
}
if (plot.sumMaxRowIds && plot.sumMaxRowIds.length) {
plot.sumMaxComponents = plot.sumMaxRowIds.reduce(function (acc, code) {
acc[code] = $.extend(true, {}, map[code])
return acc;
}, {})
}
if (plot.sumMinRowIds && plot.sumMinRowIds.length) {
plot.sumMinComponents = plot.sumMinRowIds.reduce(function (acc, code) {
acc[code] = $.extend(true, {}, map[code])
return acc;
}, {})
}
if (plot.bullwhipCharts && plot.bullwhipCharts.length) {
plot.bullwhipCharts = plot.bullwhipCharts.reduce(function (acc, bullwhipMap) {
var components = Object.keys(bullwhipMap).reduce(function (acc, componentName) {
acc[componentName] = $.extend(true, {}, map[bullwhipMap[componentName]])
return acc;
}, {})
acc.push(components)
return acc;
}, [])
}
if (plot.waveCharts && plot.waveCharts.length) {
// need to add the actual chart data to the pairs - currently just codes
plot.waveCharts = plot.waveCharts.reduce(function (acc, waveChartPairs) {
var pairWithData = waveChartPairs.map(function (pair) {
return pair.map(function (code) {
return map[code];
})
});
acc.push(pairWithData);
return acc;
}, [])
}
return $.extend(true, {}, plot)
},
getScenariosForDifferentModelPlot: function(code,modelId, scenarioList){
var self = this;
var $dfd = $.Deferred();
var $dfds = scenarioList.reduce(function (deferreds, scenarioId) {
deferreds.push(self.getPlot(code, modelId,true));
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd)
},
getScenariosForExpressionPlot: function(component, scenarioList,getResources){
var self = this;
var $dfd = $.Deferred();
// this.getExpressionPlots
//updateExpressionPlots: function(components){
// we don't have scenarios on expressions as of now. However, it may be likely that all the expressions in the
// dashboard are built from items in the model (row a + totalRevenue) - or whatever that will be actually updated
// on a new scenario - ie, cogs and
var $dfds = scenarioList.reduce(function (deferreds, scenarioId) {
deferreds.push(self.getExpressionPlot(component));
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd)
},
// Other modelPlots will use this method - their scenarioList will be used for length. Need to address
// scenario assignment
getScenariosForPlot: function (code, scenariosList, getResources) {
var self = this;
var $dfd = $.Deferred();
var $dfds = scenariosList.reduce(function (deferreds, scenarioId) {
deferreds.push(self.getScenarioForPlot(code, scenarioId, getResources));
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd)
},
// pass the original promise (deferred that we return, as well as the list of defferreds that are going to be resolved
/**
* returns a promsie ($dfd, the original deferred) comprised of the resolution of an array of promises/deferreds
* jquery equivalent of a Promise.all
* Promise.all is actually a promise that takes an array of promises as an input (an iterable). ... So you are passing all ten promises to Promise.all. Then, Promise.all itself as a promise will get resolved once all the ten promises get resolved or any of the ten promises get rejected with an error.
* @param $dfds the array of deferreds/promises that when all resolved, will resolve the original deferred
* @param $dfd the original deferred/promise to resolve - optional
* @returns {*}
*/
// https://stackoverflow.com/questions/5627284/pass-in-an-array-of-deferreds-to-when
// second top rated answer - need to make sure you get all the data back. resp would just be the first returned...
createPromiseAll: function($dfds,$dfd){
var _$dfd = $dfd ? $dfd : $.Deferred();
$.when.apply($, $dfds).then(function (resp) {
_$dfd.resolveWith(this, [Array.prototype.slice.call(arguments)]);
}, function (resp) {
// TODO: not sure what actually to throw/call here... what if 2 fail and one doesnt?
console.warn('fail',resp)
_$dfd.rejectWith(this, [Array.prototype.slice.call(arguments)]);
});
return _$dfd.promise()
},
// @deprecated?
getScenariosForPlots: function (plots, scenarios) {
var self = this;
var $dfd = $.Deferred();
var plotList = plots;
var $dfds = plotList.reduce(function (deferreds, plotCode) {
deferreds.push(self.getScenariosForPlot(plotCode, scenarios, false));
return deferreds;
}, []);
return this.createPromiseAll($dfds,$dfd);
},
// this code starts in modelManager.update()...
updateScenarioAttributes: function (scenario, attributes) {
var $dfd = $.Deferred(),
payload = $.extend({}, {
scenario: scenario.modelId,
}, attributes);
$.ajax({
type: "POST",
url: properties.host() + "/api/scenario/update",
dataType: "json",
data: JSON.stringify(payload),
contentType: "application/json",
}).done(function (resultData) {
if (resultData && resultData.status == "success") {
// we currently change everything by reference...
// resultData = scenarioPropteries (but may be slighlty different, we just merge the attributes
$dfd.resolve(resultData);
} else if (resultData && resultData.status == "error") {
$dfd.reject({ success: false, resp: resultData })
}
}).fail(function () {
console.log('update failed', arguments)
$dfd.reject(arguments)
})
return $dfd.promise();
},
// heads up - jquery usese promises, but we need this to block the load (or figure out how to keep
// all state objects from throwing errors (think the tour reducer is deprecated)
// so note the async:false below...
fetchDynamicContent: function(payload){
// need modelId & optional easy access token
var urlParams = $.param($.extend(true,{
model:properties.modelId()
},this.easyAccessToken()))
var $dfd = $.Deferred();
// if translatedContent is present then we are not going to call the api
if (window.translatedContent) {
$dfd.resolve(window.translatedContent)
} else {
$.ajax({
type: "POST",
url: properties.host() + '/api/template/content?' + urlParams,
async: false,
data: JSON.stringify(payload),
contentType: "application/json",
}).done(function(content){
$dfd.resolve(content)
}).fail(function () {
console.log('fail', arguments);
$dfd.reject(arguments)
})
}
return $dfd.promise();
},
// Not using this function. We should delete this.
// Not deleting it because it may get call from other places.
storeTitle: function(presentationId, dashboardTitle) {
var $dfd = $.Deferred();
if (presentationId < 0){
$dfd.resolve({});
}
var req = $.ajax({
type: "POST",
url: properties.host() + "/api/presentations/saveTitle",
data: {
presentationId: presentationId,
title: dashboardTitle
}
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(a,b,c){
$dfd.reject(a,b,c);
});
return $dfd.promise()
},
toggleDashboardVisibility: function(apiUrl){
var $dfd = $.Deferred();
var req = $.ajax({
type: "GET",
url: apiUrl,
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(a,b,c){
$dfd.reject(a,b,c);
});
return $dfd.promise()
},
hideAllCompanyDashboardsByTopic: function (ticker, topic) {
var $dfd = $.Deferred();
var req = $.ajax({
type: "GET",
url: properties.host() + "/api/data/hideAllTopicDash/" + ticker + (topic ? "?topic=" + topic : ""),
});
req.done(function (resultData) {
$dfd.resolve(resultData);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c);
});
return $dfd.promise()
},
hideAllCompanyDashboards: function (ticker) {
var $dfd = $.Deferred();
var req = $.ajax({
type: "GET",
url: properties.host() + "/api/data/hideAllDash/" + ticker,
});
req.done(function (resultData) {
$dfd.resolve(resultData);
}).fail(function (a, b, c) {
$dfd.reject(a, b, c);
});
return $dfd.promise()
},
deleteGlobalFunction: function(apiUrl){
var $dfd = $.Deferred();
var req = $.ajax({
type: "GET",
url: apiUrl,
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
saveGlobalFunction: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: properties.host() + '/api/template/funcs/global/save',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
saveTopicTag: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: properties.host() + '/topictags/api/save',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
deleteTopicTag: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url:properties.host() + '/topictags/api/delete',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
saveJob: function(params){
// no id, create...
var $dfd = $.Deferred();
var req = $.ajax({
type: 'POST',
url: properties.host() + '/api/dataworks/scheduled/job/save',
contentType: 'application/json',
data: JSON.stringify(params),
});
// // for local testing...
// // get scheduledJobs, push _params to it..
// var _params = params.id ? params : $.extend({},params,{id:uuidv4()});
// tf.clientStore.set(_params.id,_params,{isSession:false})
// $dfd.resolve(_params);
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
deleteJob: function(id){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'POST',
url: properties.host() + '/api/dataworks/scheduled/job/delete',
contentType: 'application/json',
data: JSON.stringify({id:id}),
});
// local testing...
// tf.clientStore.set(id);
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
runJob: function(id){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'POST',
url: properties.host() + '/api/dataworks/scheduled/job/run',
contentType: 'application/json',
data: JSON.stringify({id:id}),
});
req.done(function(resultData){
$dfd.resolve(resultData?JSON.parse(resultData):resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
// api url that the dataviewer payload would produce...
// on the create, maybe i want to load the setings, and save as someting new... gotta fetch the jobs
fetchScheduledJobs: function(){
},
// comes currently from tthe JSP - lets make an endpoint for it
fetchDataSources: function(){
},
// comes currently from the JSP - lets make an enpoint
fetchTickerLists: function(){
},
saveTickerCompany: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: properties.host() + '/dataworks/api/tickers/save',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
saveTickerList: function(params){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: properties.host() + '/api/namedtickers/save',
contentType: 'application/json',
data: JSON.stringify(params),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
// POST /api/namedtickers/save ==> save/update
},
deleteTickerList: function(id){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: properties.host() + '/api/namedtickers/delete',
contentType: 'application/json',
data: JSON.stringify({id:id}),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
// TODO: user should be able to download a specific ticker list
downloadTickerList: function(id){
},
downloadTickerLists: function(){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'get',
url:properties.host() + '/api/tickerListModify/download',
contentType: 'application/json',
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
},
uploadTickerList: function(){
// POST /api/tickerListModify/upload ==> upload the same downloaded json file to upload
},
// don't use this function. use getTemplateTagsById function.
getTemplateTags: function (templateName) {
var $dfd = $.Deferred();
var req = $.get(
properties.host() + '/api/topicTags/getTemplateTags/' +
templateName)
req.done(function (templateTags) {
$dfd.resolve(templateTags)
}).fail(function () {
$dfd.reject('could not get topic tags for model: '+properties.modelId())
})
return $dfd.promise()
},
getTemplateTagsById: function (templateId) {
var $dfd = $.Deferred();
var req = $.get(
properties.host() + '/api/topicTags/getTemplateTagsById/' +
templateId)
req.done(function (templateTags) {
$dfd.resolve(templateTags)
}).fail(function () {
$dfd.reject('could not get topic tags for the template : '+templateId)
})
return $dfd.promise()
},
setTemplateTags: function (templateId, tags) {
var $dfd = $.Deferred();
$.ajax({
type: 'POST',
url: properties.host() + '/api/topicTags/setTemplateTags/' + templateId,
data: JSON.stringify(tags),
contentType: 'application/json; charset=utf-8',
}).done(function () {
$dfd.resolve()
}).fail(function () {
$dfd.reject()
})
return $dfd.promise();
},
getAvailableTags: function(){
var $dfd = $.Deferred();
var req = $.get(properties.host() + '/api/topicTags/availableTags');
req.done(function (availableTags) {
$dfd.resolve(availableTags)
}).fail(function(){
$dfd.reject({})
})
return $dfd.promise()
},
savePeerGroupChanges: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: properties.host() + '/dataworks/api/peerGroupChanges/save',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
deletePeerGroup: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'POST',
url: properties.host() + '/dataworks/api/peerGroupChanges/delete',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
deletePresentation:function(modelId){
var self = this;
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/presentations/' + modelId,
type: 'DELETE'
});
req.done(function(){
self.getPresentations().done(function(presentations){
$dfd.resolve(presentations)
});
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
getPresentations: function(){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/presentations/',
type: 'GET'
})
req.done(function(presentations) {
for(var i in presentations){ //for efficiency returning state as string here
if(presentations[i].state) //so we parse it into json on the front end
presentations[i].state = JSON.parse(presentations[i].state)
}
$dfd.resolve(presentations)
}).fail(function(){
$dfd.reject(arguments)
});
return $dfd.promise()
},
searchTicker: function(ticker){
///
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/data/tickerSearch?ticker='+ticker,
type: 'GET'
})
req.done(function(tickers) {
$dfd.resolve(tickers)
}).fail(function(){
$dfd.reject(arguments)
});
return $dfd.promise()
},
getAllTickers: function(){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/data/allTickers',
type: 'GET'
})
req.done(function(tickers) {
$dfd.resolve(tickers)
}).fail(function(){
$dfd.reject(arguments)
});
return $dfd.promise()
},
getAllNotHiddenTicker: function () {
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/data/allNotHiddenTicker',
type: 'GET'
})
req.done(function (tickers) {
$dfd.resolve(tickers)
}).fail(function () {
$dfd.reject(arguments)
});
return $dfd.promise()
},
getDefaultDataSource: function(currentTemplateId){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/template/getDefaultDataSource?id='+currentTemplateId,
type: 'GET'
})
req.done(function(dataSource) {
$dfd.resolve(dataSource)
}).fail(function(error){
$dfd.reject(error)
});
return $dfd.promise()
},
setDefaultDataSource: function (currentTemplateId, dataSource) {
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/template/setDefaultDataSource?id=' + currentTemplateId + '&dataSource=' + dataSource,
type: 'GET'
})
req.done(function (dataSource) {
$dfd.resolve(dataSource)
}).fail(function (error) {
$dfd.reject(error)
});
return $dfd.promise()
},
saveTemplateStatus: function(currentTemplateId, status){
var $dfd = $.Deferred();
var req = $.ajax({
url: properties.host() + '/api/template/setTemplateStatus?id=' + currentTemplateId + '&status=' + status,
type: 'GET'
})
req.done(function (resultData) {
$dfd.resolve(resultData)
}).fail(function (error) {
$dfd.reject(error)
});
return $dfd.promise()
},
submitForIndexingApi: function(presentationId) {
var $dfd = $.Deferred();
var request= $.ajax({
url: properties.host() + '/indexing/submit/'+presentationId,
type: 'GET'
})
request.done(function (result) {
$dfd.resolve(result)
}).fail(function (error) {
$dfd.reject(error)
});
return $dfd.promise()
},
saveDynamicUrl: function(formData){
var $dfd = $.Deferred();
var req = $.ajax({
type: 'post',
url: properties.host() + '/dynamicUrlConfig/api/save',
contentType: 'application/json',
data: JSON.stringify(formData),
});
req.done(function(resultData){
$dfd.resolve(resultData);
}).fail(function(error){
$dfd.reject(error);
});
return $dfd.promise()
},
archiveAllUnlinkDashboards: function(apiUrl){
var $dfd = $.Deferred();
var req = $.ajax({
url: apiUrl+'/unlinkDashboards/archive',
type: "POST",
dataType: "json",
contentType: "application/json",
})
req.done(function(resp){
$dfd.resolve(resp);
}).fail(function(a,b,c){
$dfd.reject(a, b, c)
});
return $dfd.promise()
}
};
;;
// @include lib/immutable.js
// @include properties.js
// @include presentations/utils/services.js
// todo wrap this as a module
var ActionTypes = {
ADD_SECTION : "ADD_SECTION",
ADD_SECTION_MODAL_HIDE : "ADD_SECTION_MODAL_HIDE",// DO WE NEED THESE?
ADD_SECTION_MODAL_SHOW : "ADD_SECTION_MODAL_SHOW",// DO WE NEED THESE?
UPDATE_TITLE: 'UPDATE_TITLE',
UPDATE_TITLE_CONTENTS: 'UPDATE_TITLE_CONTENTS',
UPDATE_SECTION_TITLE: 'UPDATE_SECTION_TITLE',
UPDATE_SECTION_TITLE_CONTENTS: 'UPDATE_SECTION_TITLE_CONTENTS',
UPDATE_SECTION_SUBTITLE: 'UPDATE_SECTION_SUBTITLE',
UPDATE_SECTION_SUBTITLE_CONTENTS: 'UPDATE_SECTION_SUBTITLE_CONTENTS',
CHART_UPDATE_UI: 'CHART_UPDATE_UI',
SET_TEXT_BLOCK: 'SET_TEXT_BLOCK',
TEXT_BLOCK_MODAL: 'TEXT_BLOCK_MODAL',
CONSENSUS_EXPLAINED_MODAL: 'CONSENSUS_EXPLAINED_MODAL',
COPY_COMPONENT: 'COPY_COMPONENT',
FLASH_HELP_ICON: 'FLASH_HELP_ICON',
VIDEO_MODAL: 'VIDEO_MODAL',
IMAGE_MODAL: 'IMAGE_MODAL',
CONFIRM_DELETE_MODAL: 'CONFIRM_DELETE_MODAL',
SEND_INVITE_MODAL: 'SEND_INVITE_MODAL',
INFO_MODAL: 'INFO_MODAL',
SET_BASE_MODEL_TITLE: 'SET_BASE_MODEL_TITLE',
ORDER_SCENARIO_BEFORE: 'ORDER_SCENARIO_BEFORE',
ORDER_SCENARIO_AFTER: 'ORDER_SCENARIO_AFTER',
TOGGLE_REORDER_SCENARIOS: 'TOGGLE_REORDER_SCENARIOS',
UPDATE_FORECAST_MODELS_ORDER: 'UPDATE_FORECAST_MODELS_ORDER',
CREATE_CONSENSUS_SCENARIO: 'CREATE_CONSENSUS_SCENARIO',
TOGGLE_SHOW_SCENARIO_CREATOR: 'TOGGLE_SHOW_SCENARIO_CREATOR',
SET_HELP_URLS: 'SET_HELP_URLS',
SET_PRESENTATIONS: 'SET_PRESENTATIONS',
DRAG_DROP_COMPONENT: 'DRAG_DROP_COMPONENT',
ADD_CONTAINER: "ADD_CONTAINER",
CANCEL_REMOVE_COMPONENT : 'CANCEL_REMOVE_COMPONENT',
CHART_MODAL : 'CHART_MODAL',
CHART_POPUP_MODAL:'CHART_POPUP_MODAL',
CHART_MODAL_TAB_CHANGE:'CHART_MODAL_TAB_CHANGE',
USERLOGNESTEDMAP_MODAL : 'USERLOGNESTEDMAP_MODAL',
DRAFTDASHBOARDREADY_MODAL : 'DRAFTDASHBOARDREADY_MODAL',
COLLAPSE_SECTION : "COLLAPSE_SECTION",
EXPAND_SECTION : "EXPAND_SECTION",
EDIT_CONTAINER_ADVANCED: "EDIT_CONTAINER_ADVANCED",
DESTROY_MODAL : 'DESTROY_MODAL',
HIDE_MODAL : 'HIDE_MODAL',
SHOW_MODAL:'SHOW_MODAL',
REMOVE_COMPONENT : 'REMOVE_COMPONENT',
REMOVE_CONTAINER: "REMOVE_CONTAINER",
SCROLL : "SCROLL",
SET_SHARE_LINKS : "SET_SHARE_LINKS",
SHARE_LINKS_MODAL : 'SHARE_LINKS_MODAL',
SHOULD_CONFIRM_REMOVE_CHART : 'SHOULD_CONFIRM_REMOVE_CHART',
SHOULD_REMOVE_COMPONENT : 'SHOULD_REMOVE_COMPONENT',
TOGGLE_EXPAND_COLLAPSE_SECTION : "TOGGLE_EXPAND_COLLAPSE_SECTION",
VALUE_OUTPUT_MODAL : 'VALUE_OUTPUT_MODAL',
SET_CHART_TOOLTIP_POSITION : 'SET_CHART_TOOLTIP_POSITION',
SET_DEFAULT_TOOLS_VISIBILITY : 'SET_DEFAULT_TOOLS_VISIBILITY',
SET_DEFAULT_VIEW_MODE_TOOL : 'SET_DEFAULT_VIEW_MODE_TOOL',
SET_SECTION_NAVIGATION_ENABLED: 'SET_SECTION_NAVIGATION_ENABLED',
SET_SECTION_NAVIGATION_LOCATION: 'SET_SECTION_NAVIGATION_LOCATION',
SET_DISABLE_SHARED_SCENARIOS: 'SET_DISABLE_SHARED_SCENARIOS',
SET_ENABLE_DOWNLOAD_EXCEL: 'SET_ENABLE_DOWNLOAD_EXCEL',
SET_ENABLE_DOWNLOAD_SCENARIO_DATA: 'SET_ENABLE_DOWNLOAD_SCENARIO_DATA',
SET_CONTAINER_IDENTIFIER:'SET_CONTAINER_IDENTIFIER',
SET_BENCHMARKS_ENABLED: 'SET_BENCHMARKS_ENABLED',
OPEN_ADVANCED_CHART_SEARCH:'OPEN_ADVANCED_CHART_SEARCH',
CLOSE_ADVANCED_CHART_SEARCH:'CLOSE_ADVANCED_CHART_SEARCH',
OPEN_EXPORT_CHART:'OPEN_EXPORT_CHART',
GET_SEARCH_DATA:'GET_SEARCH_DATA',
GET_COMPONENT_FOR_CHART_SEARCH: 'GET_COMPONENT_FOR_CHART_SEARCH',
RELOAD_COMPONENT_FROM_ADVANCED: 'RELOAD_COMPONENT_FROM_ADVANCED',
COMPUTING: 'COMPUTING',
TOGGLE_SCENARIO_TAB_VISIBILITY: 'TOGGLE_SCENARIO_TAB_VISIBILITY',
INITIALIZE_SCENARIO_TAB_VISIBILITY: 'INITIALIZE_SCENARIO_TAB_VISIBILITY',
UPDATE_CONTAINER: "UPDATE_CONTAINER",
UPDATE_SECTION_DISPLAY: 'UPDATE_SECTION_DISPLAY',
PRESENTATION_TOUR_MODAL : 'PRESENTATION_TOUR_MODAL',
CLOSE_PRESENTATION_TOUR_MODAL : 'CLOSE_PRESENTATION_TOUR_MODAL',
EDITING_TOUR_MODAL: 'EDITING_TOUR_MODAL',
// TODO: sections and containers should use this as well
TOGGLE_COMPONENT_DROPDOWN: 'TOGGLE_COMPONENT_DROPDOWN',
SHOW_PREVIEW: 'SHOW_PREVIEW',
CLOSE_PREVIEW: 'CLOSE_PREVIEW',
UPDATE_DATA_AND_FILTERED_DATA : 'UPDATE_DATA_AND_FILTERED_DATA',
SHOW_PRESENTATION_TOUR_AGAIN : 'SHOW_PRESENTATION_TOUR_AGAIN',
REFRESH : "REFRESH",
INSTANCE_CHART_TYPE : 'INSTANCE_CHART_TYPE',
INSTANCE_CHART_ACTIVEPERIODTYPE: 'INSTANCE_CHART_ACTIVEPERIODTYPE',
INSTANCE_CHART_ACTIVELABEL_ID: 'INSTANCE_CHART_ACTIVELABEL_ID',
CHANGE_TOOL: 'CHANGE_TOOL',
UPDATE_SERVERSIDEPOSTPROGRESS : "UPDATE_SERVERSIDEPOSTPROGRESS",
SET_DEFAULT_CHART_DATA : "SET_DEFAULT_CHART_DATA",
ADD_XAXIS_HISTORY : 'ADD_XAXIS_HISTORY',
UNDO_XAXIS_HISTORY : 'UNDO_XAXIS_HISTORY',
UPDATE_XAXIS : "UPDATE_XAXIS",
UPDATE_XAXIS_DISPLAY: "UPDATE_XAXIS_DISPLAY",
BULK_UPDATE_XAXIS_COLUMNS: "BULK_UPDATE_XAXIS_COLUMNS",
BULK_UPDATE_XAXIS_COLUMNS_VARIADIC: "BULK_UPDATE_XAXIS_COLUMNS_VARIADIC",
BULK_GLOBAL_UPDATE_XAXIS_COLUMNS: "BULK_GLOBAL_UPDATE_XAXIS_COLUMNS",
UPDATE_CHART_DISPLAY:"UPDATE_CHART_DISPLAY",
UPDATE_YAXIS:"UPDATE_YAXIS",
SHOW_EDIT_SCENARIO_TITLE : 'SHOW_EDIT_SCENARIO_TITLE',
HIDE_EDIT_SCENARIO_TITLE : 'HIDE_EDIT_SCENARIO_TITLE',
UPDATE_CHART_INFO_OVERRIDE : 'UPDATE_CHART_INFO_OVERRIDE',
FIX_CHART_WRAPPER_TITLE : 'FIX_CHART_WRAPPER_TITLE',
PERSIST_READY: 'PERSIST_READY',
CHART_SEARCH_RESULT: 'CHART_SEARCH_RESULT',
CHART_SEARCH_STARTED: 'CHART_SEARCH_STARTED',
CHART_SEARCH_CLEARED: 'CHART_SEARCH_CLEARED',
CHART_DEFAULT_SEARCH: 'CHART_DEFAULT_SEARCH',
TIP_DISPLAYED: 'TIP_DISPLAYED',
REMOVE_FROM_TIP_DISPLAYED:'REMOVE_FROM_TIP_DISPLAYED',
TIP_REMOVE:'TIP_REMOVE',
TIPS_ON:'TIPS_ON',
TIPS_ADD:'TIP_ADDS',
SILENCE_ALL_TIPS:'SILENCE_ALL_TIPS',
VALIDATIONS_ADD:'VALIDATIONS_ADD',
SCENARIO_NOTIFICATIONS_ADD:'SCENARIO_NOTIFICATIONS_ADD',
SCENARIO_NOTIFICATIONS_CLEAR: 'SCENARIO_NOTIFICATIONS_CLEAR',
SCENARIO_NOTIFICATIONS_REMOVE:'SCENARIO_NOTIFICATIONS_REMOVE',
LAST_SECTION_SHOWN:'LAST_SECTION_SHOWN',
USER_RENAMED_SCENARIO:'USER_RENAMED_SCENARIO',
CHART_UPDATING:'CHART_UPDATING',
// TODO: not sure if this is in use...
CHART_LOADING:'CHART_LOADING',
CHART_DISPATCH_RESULT:'CHART_DISPATCH_RESULT',
CHART_RECALC:'CHART_RECALC',
CHART_RECALC_CREATEDSCENARIO:'CHART_RECALC_CREATEDSCENARIO',
CHART_RESET:'CHART_RESET',
USER_WANTS_EXIT:'USER_WANTS_EXIT',
USER_CAN_EXIT:'USER_CAN_EXIT',
UPDATE_AVAILABLE:'UPDATE_AVAILABLE',// we use this to notify the charts to show their loader
NOTIFY_GET_NEW_SCENARIO:'NOTIFY_GET_NEW_SCENARIO',// we need a different flag for whena new scneario is created from the button
UPDATE_SCENARIO_PLOT:'UPDATE_SCENARIO_PLOT',
REMOVE_SCENARIO_PLOT:'REMOVE_SCENARIO_PLOT',
UPDATE_ALL_PLOTS:'UPDATE_ALL_PLOTS',
DOWNLOAD_EXCEL: 'DOWNLOAD_EXCEL',
DOWNLOAD_DATA: 'DOWNLOAD_DATA',
HIDE_INTRO_VIDEO_MODAL: 'HIDE_INTRO_VIDEO_MODAL',
SHOW_BLANK_CHART_MESSAGE : 'SHOW_BLANK_CHART_MESSAGE',
DISMISS_BLANK_CHART_MESSAGE : 'DISMISS_BLANK_CHART_MESSAGE',
BULK_COPY_Y_AXIS_UNIT: 'BULK_COPY_Y_AXIS_UNIT',
BULK_COPY_Y_AXIS_LABEL: 'BULK_COPY_Y_AXIS_LABEL',
PLOT_ALL_SCENARIOS: 'PLOT_ALL_SCENARIOS',
UNPLOT_ALL_OTHER_SCENARIOS: 'UNPLOT_ALL_OTHER_SCENARIOS',
// websockets
UPDATE_PARSED_PROGRESS:'UPDATE_PARSED_PROGRESS',
UPDATE_DEFAULT_CHARTS:'UPDATE_DEFAULT_CHARTS',
SCREEN_SIZE_CHANGED:'SCREEN_SIZE_CHANGED',
UPDATE_PARSING_PROGRESS:'UPDATE_PARSING_PROGRESS',
UPDATE_SHEET_NAMES:'UPDATE_SHEET_NAMES',
SET_COMPONENT_PREVIEW:'SET_COMPONENT_PREVIEW',
SET_VALIDATION_CHECK:'SET_VALIDATION_CHECK',
UPDATE_TITLE_PREVIEW: 'UPDATE_TITLE_PREVIEW',
POPULATE_MODEL_DATA:'POPULATE_MODEL_DATA',
SET_META_TAG:'SET_META_TAG',
PROCESS_TAGS:'PROCESS_TAGS',
UPDATE_MODEL_INDEX:'UPDATE_MODEL_INDEX',
SET_SELECTOR_CODE: 'SET_SELECTOR_CODE',
SET_SELECTOR_ACTIVE_PERIOD:'SET_SELECTOR_ACTIVE_PERIOD',
// DYNAMIC_TEXT_MODAL:'DYNAMIC_TEXT_MODAL',
DYNAMIC_CHART_MODAL:'DYNAMIC_CHART_MODAL',
UPDATE_TEMPLATE_TOPIC_TAGS:'UPDATE_TEMPLATE_TOPIC_TAGS',
REPLACE_TEMPLATE_BLOCKS:'REPLACE_TEMPLATE_BLOCKS',
AUTO_POPULATE_TEMPLATE:'AUTO_POPULATE_TEMPLATE',
TRANSLATE_TEMPLATE: 'TRANSLATE_TEMPLATE',
SHOW_TEMPLATES : 'SHOW_TEMPLATES',
LOAD_TEMPLATE : 'LOAD_TEMPLATE',
SHOW_SAVE_TEMPLATE : 'SHOW_SAVE_TEMPLATE',
SAVE_TEMPLATE : 'SAVE_TEMPLATE',
SAVE_TEMPLATE_SLUG : 'SAVE_TEMPLATE_SLUG',
IS_LOADED:'IS_LOADED',// TEMPLATE OR NOT, ONCE THE LOADER DOES ITS THING, THIS GETS SET TO TRUE. A TEMPLATE WILL BERFRESHED ON ALL PAGE VIEWS, BUT WE MAY NEED TO KEY OFF THIS TO PERSIST USER CHANGES VS... ALSO WILL BE USED TO KEEP EDITMODE FROM BEING AUTO EXITED...
UNLINK_DASHBOARD:'UNLINK_DASHBOARD',
UNLINK_DASHBOARD_FAIL:'UNLINK_DASHBOARD_FAIL',
SET_CONTAINER_DATA_CONTROL:'SET_CONTAINER_DATA_CONTROL',
SET_LOCAL_VARIABLES: 'SET_LOCAL_VARIABLES',
SET_API_VARIABLES: 'SET_API_VARIABLES'
}
// action creators
// optional but highly recommended
var ActionCreators = {
processTags: function(tagsMap){
return {
type:ActionTypes.PROCESS_TAGS,
tags:tagsMap
}
},
screenSizeChanged: function(isSmallScreen){
return {
type:ActionTypes.SCREEN_SIZE_CHANGED,
isSmallScreen: isSmallScreen
}
},
collapseSection : function (id) {
return {
type : ActionTypes.COLLAPSE_SECTION,
id:id
}
},
expandSection : function (id) {
return {
type : ActionTypes.EXPAND_SECTION,
id:id
}
},
toggleExpandCollapseSection : function (id) {
return {
type : ActionTypes.TOGGLE_EXPAND_COLLAPSE_SECTION,
id:id
}
},
toggleExpandCollapseSections : function (ids) {
return {
type : ActionTypes.TOGGLE_EXPAND_COLLAPSE_SECTION,
ids : ids
}
},
editChartModalTabChange: function(activeTab){
return {
type: ActionTypes.CHART_MODAL_TAB_CHANGE,
activeTab: activeTab
}
},
addSectionModalShow : function() {
return {
type : ActionTypes.ADD_SECTION_MODAL_SHOW
}
},
addSectionModalHide : function() {
return {
type : ActionTypes.ADD_SECTION_MODAL_HIDE
}
},
addSection : function (openEditModal){
return {
type : ActionTypes.ADD_SECTION
}
},
setShareLinks: function(links) {
return {
type: ActionTypes.SET_SHARE_LINKS,
links: Immutable.fromJS(links)
}
},
updateSectionDisplay: function(id,display){
return {
type: ActionTypes.UPDATE_SECTION_DISPLAY,
id:id,
display:display
}
},
setContainerIdentifier: function(id,containerIdentifier){
return {
type: ActionTypes.SET_CONTAINER_IDENTIFIER,
identifier: containerIdentifier,
id:id
}
},
// used as part of the template translating process. It will look up if the value can return a chart or a translated string
// if either is true, the section will be shown. If both fail, section is hidden.
setContainerDataControl: function(id, control){
return {
type: ActionTypes.SET_CONTAINER_DATA_CONTROL,
dataControl:control,
id:id
}
},
// used as part of the template selector controls. could be in chart or section/container
// ${TOTAL_REVENUE}
setSelectorCode: function(id,selector){
return {
type: ActionTypes.SET_SELECTOR_CODE,
id:id,
selector:selector
}
},
updateTemplateTopicTags: function(tags){
return {
type: ActionTypes.UPDATE_TEMPLATE_TOPIC_TAGS,
tags:tags
}
},
updateContainer: function(id,display){
return {
type: ActionTypes.UPDATE_CONTAINER,
id: id,
display: display
}
},
addContainer: function(id){
return {
type: ActionTypes.ADD_CONTAINER,
id:id
}
},
removeContainer: function(id){
return {
type: ActionTypes.REMOVE_CONTAINER,
id:id
}
},
removeComponent: function(id){
return {
type: ActionTypes.REMOVE_COMPONENT,
id:id
}
},
editContainerAdvanced: function(id,isAdvancedEdit,updateChildren){
return {
type: ActionTypes.EDIT_CONTAINER_ADVANCED,
id:id,
isAdvancedEdit:isAdvancedEdit,
updateChildren:updateChildren
}
},
presentationTourModal: function() {
return {
type: ActionTypes.PRESENTATION_TOUR_MODAL
}
},
closePresentationTourModal: function(dontShowPresentationTourAgain) {
return {
type: ActionTypes.CLOSE_PRESENTATION_TOUR_MODAL,
dontShowPresentationTourAgain: dontShowPresentationTourAgain
}
},
editingTourModal: function(dontShowPresentationTourAgain) {
return {
type: ActionTypes.EDITING_TOUR_MODAL,
dontShowPresentationTourAgain: dontShowPresentationTourAgain
}
},
updateDataAndFilteredData : function(code, title, isKeyDriver, keyDrivers, inputOrOutput, unit, hasChartInfoOverride) {
return {
type : ActionTypes.UPDATE_DATA_AND_FILTERED_DATA,
code : code,
title : title,
isKeyDriver : isKeyDriver,
keyDrivers : keyDrivers,
inputOrOutput : inputOrOutput,
unit : unit,
hasChartInfoOverride : hasChartInfoOverride
};
},
showPresentationTourAgain: function(dontShowPresentationTourAgain) {
return {
type: ActionTypes.SHOW_PRESENTATION_TOUR_AGAIN,
dontShowPresentationTourAgain: dontShowPresentationTourAgain
}
},
refresh : function() {
return {
type : ActionTypes.REFRESH,
time : new Date().getTime()
}
},
chartTooltipPosition: function(position) {
return {
type : ActionTypes.SET_CHART_TOOLTIP_POSITION,
position : position
}
},
defaultToolsVisibility: function(visibility) {
return {
type : ActionTypes.SET_DEFAULT_TOOLS_VISIBILITY,
visibility : visibility
}
},
defaultViewModeTool: function(tool) {
return {
type : ActionTypes.SET_DEFAULT_VIEW_MODE_TOOL,
tool : tool
}
},
instanceChartType: function(id, chartType) {
return {
type: ActionTypes.INSTANCE_CHART_TYPE,
chartType: chartType,
id: id
}
},
instanceChartActivePeriodType: function(id, activePeriodType){
return {
type: ActionTypes.INSTANCE_CHART_ACTIVEPERIODTYPE,
activePeriodType: activePeriodType,
id: id
}
},
/**
* For Number only visualizations - this determines wich cell (label) in the category
* to return a value for. IE: which quarterly value the user wants to display.
* PeriodType: Quaterly (0) Label: 2018Q2
* @param id
* @param activeLabelId
* @returns {{type: *, activeLabelId: *, id: *}}
*/
instanceChartActiveLabelId: function(id,activeLabelId){
return {
type: ActionTypes.INSTANCE_CHART_ACTIVELABEL_ID,
activeLabelId: activeLabelId,
id: id
}
},
selectorSetActivePeriod: function(id,periodSelector){
return {
type: ActionTypes.SET_SELECTOR_ACTIVE_PERIOD,
periodSelector:periodSelector,
id:id
}
},
updatesServerSidePostProgress : function (serverSidePostProgress) {
return {
type : ActionTypes.UPDATE_SERVERSIDEPOSTPROGRESS,
serverSidePostProgress : serverSidePostProgress
}
},
updateXAxisColumnData : function (componentId,columnId, fieldName, fieldValue) {
return {
type : ActionTypes.UPDATE_XAXIS,
id: componentId,
columnId:columnId,
fieldName: fieldName,
fieldValue: fieldValue
}
},
updateXAxisDisplay: function(componentId,fieldName,fieldValue){
return {
type: ActionTypes.UPDATE_XAXIS_DISPLAY,
id: componentId,
fieldName:fieldName,
fieldValue:fieldValue
}
},
bulkUpdateXAxisColumnData: function(componentId,fieldName,fieldValue,columnIds){
return {
type : ActionTypes.BULK_UPDATE_XAXIS_COLUMNS,
id:componentId,
fieldName:fieldName,
fieldValue:fieldValue,
columnIds:columnIds
}
},
bulkUpdateVariadic: function(componentId, fieldName, columnsMap) {
return {
type: ActionTypes.BULK_UPDATE_XAXIS_COLUMNS_VARIADIC,
id: componentId,
fieldName: fieldName,
columnsMap: columnsMap
}
},
bulkUpdateYAxisUnit: function(sourceUnit, chartType, section, unit) {
return {
type: 'BULK_COPY_Y_AXIS_UNIT',
sourceUnit: sourceUnit,
chartType: chartType,
section: section,
unit: unit
};
},
bulkUpdateYAxisLabel: function(sourceLabel, chartType, section, unit) {
return {
type: 'BULK_COPY_Y_AXIS_LABEL',
sourceLabel: sourceLabel,
chartType: chartType,
section: section,
unit: unit
};
},
setDefaultChartData: function(columnInfoList,id){
return {
type: ActionTypes.SET_DEFAULT_CHART_DATA,
columnInfoList: columnInfoList,
id:id
}
},
// This is being used in the connect component in chart.js
// TODO: probably better as a util somewhere...
// @deprecated
packageUpdatedPlotData:function(plotData,scenarioId,componentId,userModified){
return {
plotData:plotData,
scenarioId:scenarioId,
componentId:componentId,
userModified:userModified
}
},
// could be a 1 as an object, or array of objects...
setUpdatedPlotData: function(plotData){
return {
type: ActionTypes.UPDATE_SCENARIO_PLOT,
plotData: plotData
}
},
updateChartDisplay : function(componentId,field,value){
return {
type:ActionTypes.UPDATE_CHART_DISPLAY,
fieldName: field,
fieldValue: value,
id: componentId
}
},
updateYAxis: function(componentId,field,value){
return {
type: ActionTypes.UPDATE_YAXIS,
fieldName:field,
fieldValue:value,
id:componentId
}
},
toggleScenarioTabVisibility: function(scenarioId) {
return {
type: ActionTypes.TOGGLE_SCENARIO_TAB_VISIBILITY,
id: scenarioId
}
},
createConsensusScenario: function(){
return {
type: ActionTypes.CREATE_CONSENSUS_SCENARIO,
id: scenarioId
}
},
showEditScenarioTitle: function(id) {
return {
type: ActionTypes.SHOW_EDIT_SCENARIO_TITLE,
id: id
}
},
hideEditScenarioTitle: function(id) {
return {
type: ActionTypes.HIDE_EDIT_SCENARIO_TITLE
}
},
openUserLogNestedMapModal : function() {
return {
type: ActionTypes.USERLOGNESTEDMAP_MODAL
};
},
openDraftDashboardReadyModal : function() {
return {
type: ActionTypes.DRAFTDASHBOARDREADY_MODAL
};
},
updateChartInfoOverride : function(code, chartInfo) {
return {
type : ActionTypes.UPDATE_CHART_INFO_OVERRIDE,
code : code,
chartInfo : chartInfo
}
},
fixChartWrapperTitle : function() {
return {
type : ActionTypes.FIX_CHART_WRAPPER_TITLE
}
},
flashHelpIcon: function(shouldFlash) {
return {
type: ActionTypes.FLASH_HELP_ICON,
shouldFlash: shouldFlash
}
},
showIntroVideoModal: function() {
var title = '2 Min Video to Get Started';
if(tf.editMode){
gavpv('open_video/' + VideoUtil.intro.title.replace(/\s/g, '') + '/mode/edit' , VideoUtil.intro.title + ' [edit]');
} else {
gavpv('open_video/' + VideoUtil.intro.title.replace(/\s/g, '') + '/mode/view' , VideoUtil.intro.title + ' [view]');
}
return {
type: ActionTypes.VIDEO_MODAL,
mimetype: 'video/mp4',
url: VideoUtil.intro.url,
title: VideoUtil.intro.title,
closeButtonTitle: 'Begin Editing Now',
hideAction: this.flashHelpIcon(true)
}
},
showVideoModal: function(options) {
if(tf.editMode){
gavpv('open_video/' + options.title.replace(/\s/g, '') + '/mode/edit' , options.title + ' [edit]');
} else {
gavpv('open_video/' + options.title.replace(/\s/g, '') + '/mode/view' , options.title + ' [view]');
}
return {
type: ActionTypes.VIDEO_MODAL,
mimetype: 'video/mp4',
url: options.url,
title: options.title || 'Video',
closeButtonTitle: options.closeButtonTitle,
hideAction: options.hideAction
}
},
showImageModal: function(options) {
if(tf.editMode){
gavpv('open_image/' + options.title.replace(/\s/g, '') + '/mode/edit' , options.title + ' [edit]');
} else {
gavpv('open_image/' + options.title.replace(/\s/g, '') + '/mode/view' , options.title + ' [view]');
}
return {
type: ActionTypes.IMAGE_MODAL,
url: options.url,
title: options.title || 'Image',
width: options.width,
closeButtonTitle: options.closeButtonTitle,
hideAction: options.hideAction
}
},
//mode = editTipsOn / viewTipsOn
tipsOn: function(mode,boolean){
return {
type: ActionTypes.TIPS_ON,
mode:mode,
display: boolean
}
},
silenceAllTips: function(){
return {
type:ActionTypes.SILENCE_ALL_TIPS
}
},
tipDisplayed: function(tipUID){
return {
type: ActionTypes.TIP_DISPLAYED,
id: tipUID
}
},
removeFromTipDisplayed: function(tip){
return {
type: ActionTypes.REMOVE_FROM_TIP_DISPLAYED,
tip: tip
}
},
tipsAdd: function(tips){
return {
type:ActionTypes.TIPS_ADD,
tips:tips
}
},
tipRemove: function(tip){
return {
type:ActionTypes.TIP_REMOVE,
tip:tip
}
},
validationsAdd: function(validations){
return {
type:ActionTypes.VALIDATIONS_ADD,
validations:validations
}
},
scenarioNotificationsAdd: function(notifications){
return {
type: ActionTypes.SCENARIO_NOTIFICATIONS_ADD,
notifications: notifications
}
},
scenarioNotificationsRemove: function(notification){
return {
type: ActionTypes.SCENARIO_NOTIFICATIONS_REMOVE,
notification:notification
}
},
scenarioNotificationsClear: function(){
return {
type: ActionTypes.SCENARIO_NOTIFICATIONS_CLEAR,
notifications: []
}
},
// set when the recalc starts, set at the end of the of the recalc/change in modelChart
computing: function(isComputing){
return {
type:ActionTypes.COMPUTING,
isComputing:isComputing
}
},
chartRecalc: function(code,scenarioId){
return {
type:ActionTypes.CHART_RECALC,
code:code,
scenarioId: scenarioId
}
},
chartReset: function(code,scenarioId){
return {
type:ActionTypes.CHART_RESET,
code:code,
scenarioId: scenarioId
}
},
newScenarioFromRecalc: function(scenarioId){
return {
type: ActionTypes.CHART_RECALC_CREATEDSCENARIO,
scenarioId:scenarioId,
}
},
userWantsExit: function(wants){
return {
type: ActionTypes.USER_WANTS_EXIT,
wants:wants //we toggle this if they cancel the exit via tip
}
},
userCanExit: function(boolean){
return {
type: ActionTypes.USER_CAN_EXIT,
canExit:boolean
}
},
showBlankChartMessage : function(chartCode) {
return {
type : ActionTypes.SHOW_BLANK_CHART_MESSAGE,
chartCode : chartCode
}
},
lastSectionShown: function(isShowing){
return {
type: ActionTypes.LAST_SECTION_SHOWN,
isShowing:isShowing
}
},
userRenamedScenario: function(modelId){
return {
type: ActionTypes.USER_RENAMED_SCENARIO,
modelId: modelId
}
},
searchStarted: function(){
return {
type: ActionTypes.CHART_SEARCH_STARTED
}
},
dispatchSearchResult: function(resp){
// this guy sets CHART_SEARCH_STARTED to false...
return {
type: ActionTypes.CHART_SEARCH_RESULT,
data: {
// searchTerm:... // the addChartTool doesn't use this yet
// TODO: allsearching should be through here
searchResults: resp
}
}
},
chartUpdating: function(id,isUpdating){
return {
type: ActionTypes.CHART_UPDATING,
id:id,
isUpdating: isUpdating
}
},
/**
* Adding the actions that are going to use thunk here. Not 100% sold on using thunk - seems a bit of overkill
* but its the redux way to do ajax (supposedly - go jquery!)
*
*/
getDefaultCharts: function(options){
return function(dispatch){
dispatch(ActionCreators.searchStarted());
Service.getDefaultPlots().done(function(resp){
dispatch(ActionCreators.dispatchSearchResult(resp))
})
}
},
/**
* Could be a plot frorm this model (properties.modelId() or another model)
* has a code and model property on it
* @param code - plotCode
* @param modelId - the base model to fetch it from
* @param getResources - we always get All resources..
* @returns {function(*): *}
*/
getModelPlot: function(code, modelId,getResources){
return Service.getPlot(code, modelId,true);
},
/**
* no plot code, no 'modelId' - it will only be translated once, and any other time,
* just echo back that first translation. Used to go to our expression service, but no longer
*
* NOTE: All live templates start out as selectors, but they get translated and their code/modelId used to fetch their data
* @param component
* @param getResources
* @returns {function(): *}
*/
getExpressionPlot: function(component,getResources){
return Service.getExpressionPlot(component)
},
getDummyPlot: function(dispatch){
return function(dispatch){
return Service.getDummyPlot();
}
},
getScenarioForPlot: function(plotCode,scenario,getComponents,newFlag){
return Service.getScenarioForPlot(plotCode, scenario,getComponents,newFlag)
},
getScenariosForPlot: function(plotCode,scenarioIds,getComponents){
return Service.getScenariosForPlot(plotCode,scenarioIds,getComponents)
},
getScenarioForPlotWithComponents: function(plotCode,scenario){
return function(dispatch){
return Service.getScenarioForPlotWithComponents(plotCode,scenario)
}
},
getScenariosForDifferentModelPlot: function(code, modelId, scenarios){
return Service.getScenariosForDifferentModelPlot(code, modelId, scenarios)
},
getScenariosForExpressionPlot: function(component,scenarios){
return Service.getScenariosForExpressionPlot(component,scenarios)
},
// doesn't get called
// @deprecated - can't find anythnig that uses this as an actioncreator.
getPlots: function(plotCodes,scenarioId){
return function(dispatch){
return Service.getPlots(plotCodes,scenarioId)
}
},
// update plot uses similar/same methods. could maybe combine...
// there are a series of actions that will happen when we make a scenario.
// we need to launch a loader icon
// create the scenario
// notify application that there is an update
// the second dispatch about the update is possibly unnecessary
// turn off the loader..
// thjis is all done in an effort to keep one dispatch to one update - ie, update doesnt change multip propteries at once
createScenario: function(model,fromScenarioButton){
store.dispatch(ActionCreators.computing(true));
var self = this;
return function(dispatch) {
var modelManager = window.store.getState().get('modelManager');
modelManager.createNewUserModel(!model,(model && model.published)).done(function(createdScenario){
self.getCopiedScenarioPlots(createdScenario).done(function(plots){
dispatch(ActionCreators.updateAllPlots(plots,createdScenario.modelId));
dispatch({ type: ActionTypes.UPDATE_FORECAST_MODELS_ORDER });
modelManager.updateStaleConsensusModels();
// maybe superfulous as the above calls calculate...
ConsensusScenarios.refreshConsensus(modelManager);
});
})
}
},
/**
* Jumbo method - gets all plots (model, different model, expression) then rerturns them
* @param scenario
* @param dispatch
* @returns {*}
*/
getCopiedScenarioPlots: function(scenario,dispatch){
var $dfd = $.Deferred();
var allPlots = PlotUtils.getPlotsByType()
// there could be problems if all itesm don't return successfully
// TODO: for example, expression plots are removed when they are fitst loaded.
// however, they may have not been loaded, but we are looking at the whole store here
// and not every component will have loaded (in hidden section, not on screen, etc..)
// forr expression charts, this means they will not have been removed, but .done won;te get fired
// because we failed them.
Service.createPromiseAll([
Service.getPlots(allPlots.currentModel.map(function(component){
return component.get('code')
}),null, scenario.modelId),
Service.updateDifferentModelPlots(allPlots.differentModel),
Service.getExpressionPlots(allPlots.expression)
]).done(function(resp){
var plotsMap = PlotUtils.mergeAllPlots(undefined,resp);
var plots = Service.addResourceData({rows:plotsMap});
$dfd.resolve(plots)
});
return $dfd.promise();
},
copyScenario: function(modelManager,scenario){
store.dispatch(ActionCreators.computing(true));
var self = this;
return function(dispatch){
var modelManager = window.store.getState().get('modelManager');
scenario.modelId === 'undefined.trefis' ?
dispatch(ActionCreators.createScenario(modelManager.get('trefis'),false))
:
modelManager.copyModel(scenario).done(function(createdScenario){
self.getCopiedScenarioPlots(createdScenario).done(function(copiedPlots){
dispatch(ActionCreators.updateAllPlots(copiedPlots,createdScenario.modelId));
dispatch({ type: ActionTypes.UPDATE_FORECAST_MODELS_ORDER });
});
})
}
},
updateAllPlots: function(responseData,scenarioId){
return {
type: ActionTypes.UPDATE_ALL_PLOTS,
plots: responseData,
scenarioId:scenarioId,
isComputing:false
}
},
// this code is just about the same as the updatePlot...
updateConsensusModel: function(model,toRecalc,isFinal){
store.dispatch(ActionCreators.computing(true));
var id = model.modelId;
return function(dispatch){
dispatch(ActionCreators.dispatchUpdateAvailable(true,id,true));
Service.updatePlot(model,toRecalc,isFinal,true,componentId).done(function(plots) {
dispatch(ActionCreators.updateAllPlots(plots, id));
}).fail(function(a,b,c){
console.log('action updatePlot/updateConsensus Failed',a,b,c)
})
}
},
/**
* Update plot actuall does more thn this - it updates All the plots. This can mean both
* plots from this model, plots from other models, or expression plots
* @param model
* @param [{
* collectionIdentifierCode;'
,
* derivedValues:[...ALL plot data, with modified values]
* }]]
* @param isFinal
* @param plotsByType - we have potentially several different types of plots now:
* modelPlots - plots from this mode; have a plot code and a modelId that matches properties.modelId()
* otherModelPlots = plots that come from 'A' model, have a plot code and a modelId but not the one we are currently on
* expressionPlots = have no code (and we don't track a modelId currently, are looked up using the expression service passing in the current modelid
* todo: define for expressionPlots if we can pass in a newly created modelId (or scenario id)
* TOdo: might be better to just grab each type explicity instead of packaged as an object
* @returns {Function}
*/
updatePlot: function(model,toRecalc,isFinal,plotsByType){
store.dispatch(ActionCreators.computing(true));
return function(dispatch){
var modelManager = window.store.getState().get('modelManager');
if (!model.yours){
modelManager.createNewUserModel(false,(model && model.published)).done(function(createdModel){
var id = createdModel.modelId;
dispatch(ActionCreators.dispatchUpdateAvailable(true,id,true));
Service.updatePlot(createdModel,toRecalc,isFinal,true).done(function(plots){
// remember the current model plots come back wiith the response.
// different model plots are treated the same as expresions now...
Service.createPromiseAll([
Service.updateExpressionPlots(plotsByType['expression']),
]).done(function(resp){
// resp = array of arrays. array for each promise above, then an array for each 'plot'
var plotMap = PlotUtils.mergeAllPlots(plots,resp)
dispatch(ActionCreators.updateAllPlots(plotMap,id))
dispatch(ActionCreators.newScenarioFromRecalc(id))
})
// if you have modified something, need to notifiy consensus to update...
if (!createdModel.isConsensusModel()){
ConsensusScenarios.refreshConsensus(modelManager);
}
// if (createdScenario.isConsensusModel()) ConsensusScenarios.calculateConsensus(modelManager,createdScenario)
}).fail(function(a,b,c){
console.log('action updatePlot Failed',a,b,c)
})
})
} else {
var id = model.modelId;
dispatch(ActionCreators.dispatchUpdateAvailable(true,id))
Service.updatePlot(model,toRecalc,isFinal).done(function(plots){
Service.createPromiseAll([
Service.updateExpressionPlots(plotsByType['expression']),
// Service.updateDifferentModelPlots(plotsByType['differentModel'])
]).done(function(resp){
// resp = array of arrays. array for each promise above, then an array for each 'plot'
var plotMap = PlotUtils.mergeAllPlots(plots,resp)
dispatch(ActionCreators.updateAllPlots(plotMap,id))
dispatch(ActionCreators.newScenarioFromRecalc(id))
})
if (!model.isConsensusModel()){
ConsensusScenarios.refreshConsensus(modelManager);
}
}).fail(function(a,b,c){
console.log('Service.updatePlot failed',a,b,c);
})
}
}
},
resetPlot: function(scenarioId,plotCode){
store.dispatch(ActionCreators.computing(true));
return function(dispatch){
// TODO: wire up to updatePlot (recalc) passing a reset:true
// this should return a set of plotCodes to be used for updating
Service.resetScenarioPlot(scenarioId,plotCode).done(function(plots){
// this should be fine as non model plots would not have been affected.
// ALSO, we only pass base scenario for expressionPlots. If that changes
// we will need to run something similar to updateAllModelPlots
dispatch(ActionCreators.updateAllPlots(plots,scenarioId));
ConsensusScenarios.refreshConsensus(store.getState().get('modelManager'));
})
}
},
loadTemplate: function (templateId, template) {
return function (dispatch) {
if (!template) {
// Service.getTemplates().done(function (resp) {
Service.getTemplateById(templateId).done(function (resp) {
// THIS POPULATES TEMPLATE...
Service.getTemplateState(templateId).done(function (templateState) {
dispatch({
type: ActionTypes.LOAD_TEMPLATE,
template: JSON.parse(templateState),
currentTemplate: resp
});
})
})
} else {
dispatch({
type: ActionTypes.LOAD_TEMPLATE,
template: template
});
}
}
},
populateTemplate: function (templateId, skipPreview) {
return function (dispatch) {
// a little messy - need to grab the tags first...
// TODO: we assume we are getting them successfully -
// if something goes wrong getting them, its not the end of the world, just wont populate template...
// TODO: put tags somewhere else - maybe [ui, tags]?
Service.getAllTags().done(function (tagsMap) {
if (tagsMap) {
// TODO: just access these through the processTags...
window.tagsMap = tagsMap;
}
store.dispatch(ActionCreators.processTags(tagsMap));
Service.getTemplateById(templateId).done(function (resp) {
var template = resp ? resp : null;
// THIS POPULATES TEMPLATE...
dispatch({
type: ActionTypes.AUTO_POPULATE_TEMPLATE,
template: template,
skipPreview: skipPreview
})
})
})
}
},
translateTemplate: function(){
return {
type: ActionTypes.TRANSLATE_TEMPLATE
}
},
/**
* This action is used if the user unlinks and edits the CURRENT url (does not choose to copy to a new url)
* The service will mark the presentatio as isLiveTEmplate:false. WE will also do this on the front end in the
* UI property, as well as add the presentation ID to the state, triggering a save...
*
* If the user chooses the copy path, the form submission we use for copying a dashboard will do the page navigation
*
* @param presentationId
* @returns {Function}
*/
unlinkDashboard: function(presentationId,copy){
return function(dispatch) {
Service.unlinkDashboard(presentationId,copy).done(function(resp){
dispatch({type: ActionTypes.UNLINK_DASHBOARD,persistedId:presentationId,isLiveTemplate:false})
}).fail(function(){
dispatch({type:ActionTypes.UNLINK_DASHBOARD_FAIL})
// this will flash a message to the user, and take them out of edit mode
})
}
},
deleteScenario: function(scenarioId){
return {
type: ActionTypes.REMOVE_SCENARIO_PLOT,
scenarioId: scenarioId
}
},
setSheetNames : function (sheetNames) {
return {
type : ActionTypes.UPDATE_SHEET_NAMES,
sheetNames:sheetNames
}
},
dispatchUpdateAvailable: function(isAvailable,scenarioId,hasNewFlag){
return {
type: ActionTypes.UPDATE_AVAILABLE,
updateAvailable:isAvailable,
scenarioId:scenarioId,
newFlag:hasNewFlag // what is this? in part of the sharepoint speedup, this is/was needed / requestsed. ONly to be sent for the get requests of of update available (all plots that are requesting new scenario plot data for the first time..
}
},
setBenchmarksEnabled: function(benchmarksEnabled){
return {
type: ActionTypes.SET_BENCHMARKS_ENABLED,
enabled: benchmarksEnabled
}
},
openAdvancedChartSearch: function(id){
return {
type:ActionTypes.OPEN_ADVANCED_CHART_SEARCH,
id:id
}
},
closeAdvancedChartSearch: function(){
return {
type:ActionTypes.CLOSE_ADVANCED_CHART_SEARCH
}
},
openExportChart: function(chart){
return {
type:ActionTypes.OPEN_EXPORT_CHART,
chart:chart
}
},
reloadBenchmarks: function(componentId,shouldUpdate){
return {
type: ActionTypes.RELOAD_COMPONENT_FROM_ADVANCED,
componentId: componentId,
shouldUpdate: shouldUpdate
}
},
getSearchData: function(){
return {
type: ActionTypes.GET_SEARCH_DATA
}
},
updateParsedProgress: function(websocketResp){
return {
type:ActionTypes.UPDATE_PARSED_PROGRESS,
data:websocketResp,
}
},
toggleIndexDashboard: function(toggleIndex){
return {
type:ActionTypes.UPDATE_MODEL_INDEX,
data:toggleIndex
}
},
setLocalVariables: function(localVariables){
return {
type:ActionTypes.SET_LOCAL_VARIABLES,
data:localVariables
}
},
setApiVariables: function(apiVariables){
return {
type:ActionTypes.SET_API_VARIABLES,
data:apiVariables
}
},
setComponentSize: function (id, name, value) {
return {
type: 'SET_COMPONENT_SIZE',
propName: name,
propValue: value,
componentId: id
}
}
};
;;
var ConsensusScenarios = {
create: function(modelManager, forecastModelIds, autoRefresh, withUnmodified, addNewScenarios,includeBaseModel) {
var self = this;
// store.dispatch(ActionCreators.createConsensusScenario(modelManager,newModelOptions,consensusOptions));
// looking at the code, we don't support creating with options set anymore...
var consensusModel = modelManager.createNewUserModel(true, null, null, null).done(function(consensus){
modelManager.updateModel(consensus,{consensusOptions:{
forecastModelIds: forecastModelIds,
autoRefresh: autoRefresh,
withUnmodified: withUnmodified,
addNewScenarios: addNewScenarios,
includeBaseModel:includeBaseModel
}},function(){
// scenario here woul donly be the scenario and its attributes, but wouldn't be a forecastModel class -
// missing the class prototype methods like isConsensusModel, etc...
var consensusScenario = modelManager.get('models').filter(function(model){
return model.modelId === consensus.modelId
})[0];
self.calculateConsensus(modelManager, consensusScenario)
})
})
},
createDefault: function(modelManager) {
var autoRefresh = true
, withUnmodified = false
, addNewScenarios = true
, includeBaseModel = true
var forecastModelIds = modelManager.get('models')
.filter(this.modelAvailableForConsensusModels, this)
.map(modelManager.modelId)
// first things first - do we have this scenarioData?\
// we should probably dispatch an action that does all this... then populates a plots
// this.checkStoreForModelData(forecastModelIds) ? this.create() : store.dispatch('Time to dispatch');
// return;
this.create(modelManager, forecastModelIds, autoRefresh, withUnmodified, addNewScenarios,includeBaseModel)
},
// Calculates the average and updates the consensus scenario given the valuesMap and driver from the modelChart update
/*
* TODO:
* if modelManager gets refactored so that doSaveNewModel returns the ajax promise, and is then returned in
* doSave, recalculate, and doComputation, then modelChart could just wait for that promise to resolve when calling
* doComputation and then call calculateConsensus instead. Then this update method and the valuesMapToDriversMap won't be needed
*/
update: function(modelManager, consensusModel, valuesMap, driver) {
// not sure why update woulndn't just use the calculate again?
this.calculateConsensus(modelManager,consensusModel);
},
isModelModified: function(model){
var modifiedScenarios = store.getState().getIn(['ui','modifiedPlots']);
modifiedScenarios = modifiedScenarios ? modifiedScenarios.toJS() : {};
// its a immutble store object, should probably use keySeq()
return model.modelId !== 'undefined.trefis' ? Object.keys(modifiedScenarios).indexOf(model.modelId.toString()) > -1 : true;
// return Object.keys(store.getState().getIn(['ui','modifiedPlots']).toJS()).indexOf(model.modelId) > -1;
},
// Calculates the average and updates the consensus scenarioi based on existing modified drivers in models
calculateConsensus: function(modelManager, consensusModel, forecastModelIds) {
if (forecastModelIds)
this.setForecastModelIds(consensusModel, forecastModelIds)
var modelsForConsensus = modelManager.getModels(this.consensusModelIds(consensusModel))
var noDerivedModelsInConsensus = !modelsForConsensus.some(function(model) { return model.isDerived })
var noModelsModified = modelsForConsensus.every(function(model) {
return !this.isModelModified(model);
},this);
if (noDerivedModelsInConsensus) {
consensusModel.consensusOptions.includeBaseModel = true // must always have at least one model, so make sure at least base is included
consensusModel.clear()
}
else if (noModelsModified) {
consensusModel.clear(true)
}
else {
var driversMap = this.driverValuesMap(
this.modelsToDriversMap(modelsForConsensus),
modelsForConsensus,
modelManager.get('trefis'),
this.arithmeticMean,
consensusModel.consensusOptions.withUnmodified
)
// for each Driver, we need to get the plots that are un changed - basically, mimicking what the chart update does
// we pass all the values changed and unchanged
// for now, just get the base data for the plot
var plotsToUpdate = [];
$.each(driversMap, function(driver, valuesMap) {
// on d_new we have to pass ALL th data, so we will grab it by default the consensus is made by copying the base model...
var plotData = PlotUtils.getPlotForScenario('undefined.trefis',driver);
// get a merged valuesMap - items that have 'had the consensus plotted against them as well the original values for the others
// console.log('PLOTDATA',plotData);
var allValuesMap = SeriesUtil.getChartData({data:plotData}).reduce(function(acc,datum){
acc[datum.Identifier] = valuesMap[datum.Identifier] || datum.Value;
return acc;
},{});
// we pass the data in a new way value=/stringValue=/ userModifieda
var values = PlotUtils.packagePlotDataForUpdate({data:plotData},allValuesMap,valuesMap)
// we should actually build this up, then pass at once. backend should be able to handle arrays...
plotsToUpdate.push(PlotUtils.packageForRecalc(driver,values))
// consensusModel.doComputation(valuesMap, null, driver, true)
});
// i think thi screates a circular recerence, since the update probably notifies others to update...
store.dispatch(ActionCreators.updateConsensusModel(consensusModel,plotsToUpdate,true));
}
},
setForecastModelIds: function(consensusModel, forecastModelIds) {
consensusModel.consensusOptions = $.extend({}, consensusModel.consensusOptions, this.partitionModelIds(forecastModelIds))
this.updateConsensusOptions(store.getState().get('modelManager'),consensusModel,{consensusOptions:consensusModel.consensusOptions});
},
// Helper function; the forecastModelIds array shouldn't include the base model,
// which is included by toggling the includeBaseModel flag
partitionModelIds: function(forecastModelIds) {
var modelIds = forecastModelIds.filter(function(id) { return id !== 'undefined.trefis' })
return { forecastModelIds: modelIds, includeBaseModel: modelIds.length < forecastModelIds.length }
},
// Similar to partitionModelIds, checks includeBaseModel and adds it to the returned array of modelIds
consensusModelIds: function(consensusModel) {
return (consensusModel.consensusOptions.includeBaseModel ? ['undefined.trefis'] : [])
.concat(consensusModel.consensusOptions.forecastModelIds)
},
isModelInConsensus: function(model) {
var self = this
return function(consensusModel) {
return self.consensusModelIds(consensusModel).indexOf(model.modelId) > -1
}
},
// could be newer cerated, or newer updated, as in, i publish a model, i need the consensus to update...
isModelNewerThanConsensus: function(model, consensusModel) {
return new Date(model.createdAt).getTime() >= new Date(consensusModel.updatedAt).getTime() ||
new Date(model.updatedAt).getTime() >= new Date(consensusModel.updatedAt).getTime()
},
// Checks if the model is available for consensus models in general
modelAvailableForConsensusModels: function(model) {
return model.isDerived && model.published
},
// Checks if the model is available for a specific consensus model
modelAvailableForConsensus: function(consensusModel, includeManuallyAddable) {
var self = this
return function(model) {
var isAvailable = consensusModel !== model
&& (self.modelAvailableForConsensusModels(model)
|| (consensusModel.consensusOptions.includeBaseModel && model.modelId === 'undefined.trefis'))
&& (consensusModel.consensusOptions.addNewScenarios && !includeManuallyAddable ?
self.isModelInConsensus(model)(consensusModel) || self.isModelNewerThanConsensus(model, consensusModel) : true);
return isAvailable
}
},
modelsAvailableForConsensus: function(consensusModel) {
return function(models) {
return models.filter(ConsensusScenarios.modelAvailableForConsensus(consensusModel))
}
},
modelIdsAvailableForConsensus: function(consensusModel) {
var modelsAvailableForConsensus = this.modelsAvailableForConsensus(consensusModel)
return function(models) {
return modelsAvailableForConsensus(models).map(function(model) { return model.modelId })
}
},
updateConsensusOptions: function(modelManager, consensusModel, consensusOptions, cb) {
var withUnmodifiedChanged = consensusModel.consensusOptions.withUnmodified
!== consensusOptions.withUnmodified
modelManager.updateModel(consensusModel, consensusOptions, function() {
if (withUnmodifiedChanged) this.calculateConsensus(modelManager, consensusModel)
}.bind(this))
},
// predicate function to disclude unmodified values from user scenarios
isModified: function(model, baseModel, identifier) {
var isBaseModel = model.modelId === baseModel.modelId
, valueWasModified = this.getValue(model,identifier) !== this.getValue(baseModel,identifier)
return isBaseModel || valueWasModified
},
arithmeticMean: function(values) {
return values.reduce(function(acc, value) { return acc + value }, 0) / values.length
},
geometricMean: function(values) {
return Math.pow(values.reduce(function(acc, value) { return acc * value }, 1), 1 / values.length)
},
mode: function(values) {
return Immutable.List(values).groupBy(function(v){return v}).map(function(v){return v.size}).sort().keySeq().last()
},
/**
* We used to have a bunhc of maps in companyData that were copied onto the models. This replicates that,
* just that we store these on the store, and they are populated from the data returned from the services...
*
* @param scenario
* @param identifier
* @returns An immutable Map of the cell data (period driver code, value, type, basically a chart datapoint)
*/
getCellFromStore: function(scenario,identifier){
return store.getState().getIn(['ui','modelData',scenario.modelId,'values',identifier])
},
isNumeric: function(cell){
// we now have strings (tag column etc in data...)
return cell.get('Type') === 'NUMBER'
},
getValue: function(scenario,code){
var cell = this.getCellFromStore(scenario,code);
// in theory, at leats for these purposes, you can't modify a string since it doesn't appear in the chart
// and the calling code for this works on modified drivers and their periodCodes...
return this.isNumeric(cell) ? cell.get('Value') : 0
},
// returns an averaged drivers map in the form { [driver]: { [period driver]: value } }
driverValuesMap: function(driversMap, modelsForConsensus, baseModel, averagingFn, withUnmodified) {
var self = this
var driverValuesMap = $.extend({}, driversMap)
$.each(driverValuesMap, function(driver, periodDrivers) {
$.each(periodDrivers, function(periodDriver) {
var values = modelsForConsensus
.filter(function(model) {
return withUnmodified ? true : self.isModified(model, baseModel, periodDriver)
})
.map(function(model) {
return self.getValue(model,periodDriver)
})
// toggleDriver????
console.warn('base.companyData.isToggle() consensusScenarios is doing a check, but just returning true')
driverValuesMap[driver][periodDriver] = false ? self.mode(values) : averagingFn(values)
})
})
// console.log('value',{
// driversMap:driversMap,
// driverValuesMap:driverValuesMap,
// })
return driverValuesMap
},
getModifiedDriversForScenario: function(scenario){
return store.getState().getIn(['ui','modifiedPlots',scenario.modelId]) ? store.getState().getIn(['ui','modifiedPlots',scenario.modelId]).toJS() : []
},
// returns a drivers map in the form { [plotCode]: { [Identifier (chartData[0].array- ).Identifier]: [] } }
modelsToDriversMap: function(scenariosForConsensus) {
var self = this;
return scenariosForConsensus.reduce(function(modifiedDrivers, scenario) {
var scenarioModifiedDrivers = self.getModifiedDriversForScenario(scenario);
$.each(scenarioModifiedDrivers, function(driver, periodDrivers) {
modifiedDrivers[driver] = modifiedDrivers[driver] || {}
if (periodDrivers.length){
periodDrivers.forEach(function(periodDriver) {
modifiedDrivers[driver][periodDriver] = []
})
}
})
return modifiedDrivers
}, {})
},
// returns a drivers map in the form { [driver]: { [period driver]: [] } }
valuesMapToDriversMap: function(valuesMap, driver) {
return Object.keys(valuesMap).reduce(function(modifiedDrivers, periodDriver) {
modifiedDrivers[driver] = modifiedDrivers[driver] || {}
modifiedDrivers[driver][periodDriver] = []
return modifiedDrivers
}, {})
},
// This is called from modelChart to update the autoRefresh consensus scenarios
updateAutoRefresh: function(modelManager, valuesMap, driver) {
return modelManager.getConsensusModels()
.filter(function(consensusModel) {
// console.log(ConsensusScenarios.isModelInConsensus(modelManager.get('active'))(consensusModel))
return ConsensusScenarios.isModelInConsensus(modelManager.get('active'))(consensusModel)
&& consensusModel.consensusOptions.autoRefresh
}).forEach(function(consensusModel,a,b) {
this.update(modelManager, consensusModel, valuesMap, driver)
}, this)
},
refreshConsensus: function(modelManager){
return modelManager.getConsensusModels().filter(function(consensusModel){
return consensusModel.consensusOptions.autoRefresh
}).forEach(function(consensusModel){
// this.update(modelManager,consensusModel)
},this)
}
};
;;
// lib/jquery.min.js
// @include infrastructure/polyfills.js
// @include lib/underscore.js
// @include lib/backbone.js
// @include common.js
// @include infrastructure/forecastModel.js
// @include properties.js
// @include lib/reselect.js
// @include presentations/actions/index.js
// @include presentations/store.js
// @include presentations/consensusScenarios.js
/**
* Stores the various ForecastModels that can be used in the client. Must be initialized
* by calling setCompany and passing the companyData.
*
* Built as a Backbone model.
*
* Exposes the following attributes on the model:
* models: Array of ForecastModel with all models currently loaded (generally 1 base model, and 0 or more derived models)
* trefis: ForecastModel of base model
* active: ForecastModel of the "active" model that should be shown in the sankey.
* Generally points to the same ForecastModel instance as either the user model,
* or one of the comparison models
* publishedModels: Array of ForecastModels, that is a subset of 'models'. Includes only the models that are published scenarios
*
* In addition, exposes the following events:
* userChange: fired when the user's model changes, either because the user dragged a line,
* or the user switched models
* titleChange: fired when the user edits the title of one of the models. Has one argument,
* a boolean which is true iff the title change is really coming from the
* user modifying a line thus creating a new user model
* modelDeleted: fired when a model is deleted from the filmstrip. Has one argument,
* the ForecastModel that was deleted
* model:added: fired when a new model is added to the models array. Has one argument,
* the ForecastModel that was added
*/
var ModelManager = Backbone.Model.extend({
/*
* This dispatches the key/value pair from Backbone.Model.set to redux
*/
dispatch: function(key, value) {
if (key === 'companyData')
store.dispatch({ type: 'SET_COMPANY_DATA', companyData: value });
else if (key === 'trefis')
store.dispatch({ type: 'SET_BASE_FORECAST_MODEL', baseForecastModel: value });
else if (key === 'models')
store.dispatch({ type: 'SET_FORECAST_MODELS', forecastModels: value });
else if (key === 'active')
store.dispatch({ type: 'SET_ACTIVE_FORECAST_MODEL', activeForecastModel: value });
else if (key === 'hiddenForecastModels')
store.dispatch({type:'INITIALIZE_SCENARIO_TAB_VISIBILITY',hiddenForecastModels:value})
else if (key === 'selectedPeriod')
store.dispatch({ type: 'SET_SELECTED_PERIOD', selectedPeriod: value });
else if (key === 'periodSelectionManager')
store.dispatch({ type: 'SET_PERIOD_SELECTION_MANAGER', periodSelectionManager: value });
else if (key === 'publishedModels')
store.dispatch({ type: 'SET_PUBLISHED_FORECAST_MODELS', publishedForecastModels: value });
else if (key === 'modelManager')
store.dispatch({type:'SET_MODEL_MANAGER', modelManager: value});
else if (key === 'parentChildrenManager')
store.dispatch({type:'SET_PARENT_CHILD_MANAGER', parentChildrenManager:value});
},
/* A map of modelManager attributes to it's redux counterpart */
modelManagerToReduxProps: {
companyData: 'companyData',
trefis: 'baseForecastModel',
models: 'forecastModels',
active: 'activeForecastModel',
periodSelectionManager: 'periodSelectionManager',
publishedModels: 'publishedForecastModels',
modelManager: 'modelManager',
parentChildrenManager: 'parentChildrenManager'
},
/*
* This overrides the backbone set and get methods so that we can start using the redux store to
* handle the model layer and still maintain backwards compatibility with older code
*/
set: function(key, value, options) {
if (!properties.presentationMode())
return Backbone.Model.prototype.set.call(this, key, value, options);
if (key == null) return this;
if (typeof key === 'object') {
for (var k in key)
this.dispatch(k, key[k])
}
else if (typeof key === 'string') {
this.dispatch(key, value)
}
return this
},
get: function(attr) {
if (!properties.presentationMode())
return Backbone.Model.prototype.get.call(this, attr);
var property = store.getState().get(this.modelManagerToReduxProps[attr]);
return Immutable.isImmutable(property) ? property.toJS() : property
},
nextTitleId: 1,
initialize: function() {
// If it's in presentation mode, pass the model manager up to the store...
if (properties.presentationMode()) {
var self = this;
// Listen to any events that get fired when a forecast model changes, and update the
// forecastModels in the store. This way react picks up all changes to forecast models
// since forecastModels isn't entirely an immutablejs collection (only shallowly an immutable list)
this.set({ modelManager: this });
// TODO: would really like to get this out of here... this leads to a lot of store updates
this.on('all', function() {
store.dispatch({ type: 'SET_FORECAST_MODELS', forecastModels: self.get('models') })
});
/* Subscribe to the redux store to trigger the model manager events */
store.subscribe(reduxWatch(function() { return store.getState().get('companyData') })(function() {
demoWidget.modelManager.trigger('change:companyData')
}));
store.subscribe(reduxWatch(function() { return store.getState().get('baseForecastModel') })(function() {
demoWidget.modelManager.trigger('change:trefis')
}));
store.subscribe(reduxWatch(function() { return store.getState().get('forecastModels') })(function(oldVal, newVal) {
// Only trigger the change:models event from the store if the length changes, which is currently
// the only change you could do from the store that can be tracked anyway
if (oldVal && newVal && oldVal.size !== newVal.size) {
new ShareLinksModel(self, store).shareLinks.done(function(links) {
store.dispatch(ActionCreators.setShareLinks(links))
});
}
}));
store.subscribe(reduxWatch(function() { return store.getState().get('activeForecastModel') })(function() {
demoWidget.modelManager.trigger('change:active selected:model')
}));
store.subscribe(reduxWatch(function() { return store.getState().get('periodSelectionManager') })(function() {
demoWidget.modelManager.trigger('change:periodSelectionManager')
}));
store.subscribe(reduxWatch(function() { return store.getState().get('publishedForecastModels') })(function() {
demoWidget.modelManager.trigger('change:publishedModels')
}))
store.subscribe(reduxWatch(function() { return store.getState().getIn(['ui', 'hiddenScenarioTabs']) })(function(a,b) {
demoWidget.modelManager.get('models').forEach(function(model) {
var isVisible = !store.getState().getIn(['ui', 'hiddenScenarioTabs']).includes(model.modelId)
if (isVisible !== model.displayProperties.visible) {
// toggle our modelInQuestion's display properties
model.displayProperties.visible = isVisible
// charts hear this, drop the series...
demoWidget.modelManager.trigger("displayPropertyChanged", model);
}
})
}))
}
// Otherwise, perform the old logic
else {
this.on('change:models', function(){
this.rebuildPublishedModels()
});
}
},
/**
* Sets the company data for this model. Should be called to initialize the model manager.
* After this is called, the trefis, user, active, and comparisons fields will be set.
*
* @param {CompanyData} companyData
* @param {boolean} useDerivedModels if true, we'll preload any derived models included in the initialLoad.
* If false, we'll ignore any derived models
* @param {function} callback -- optional parameter. If specified the function will be called after all models' promises have been calculated
*/
setCompany: function(companyData, useDerivedModels, callback) {
var $dfd = $.Deferred(),
self = this,
loggedInUser = properties.loggedInUser() || {},
publishedModels = [],
userModelData,
modelPromises = [],
useWebWorkers = !!callback; // Only if a callback function is provided we will attempt using webworkers. Otherwise we use calculate synchronously.
var baseModel = ForecastModel.createTrefisModel(useWebWorkers, companyData);
// TODO: does baseModel also need a listener? On load, its the only model,
// needs to trigger events from modelChart?
modelPromises.push(baseModel);
var urlVars = PresentUtil.getUrlVars();
if (urlVars && (urlVars['skipScenarioLoad']==='true')){
self.doModelManagerSetting($dfd,modelPromises,baseModel,companyData,callback);
return $dfd.promise()
} else {
if(useDerivedModels) {
Service.getScenarios().done(function (scenarios) {
console.log(scenarios)
$.each(scenarios, function(i, scenario) {
if(scenario.forecastType == ForecastModel.TYPE_YOURS) {
var model = ForecastModel.createUsersModel(
useWebWorkers,
baseModel,
companyData,
scenario,
loggedInUser.userId,
loggedInUser.firstName,
loggedInUser.lastName
);
modelPromises.push(model);
} else if(scenario.forecastType == ForecastModel.TYPE_OTHER) {
var model = ForecastModel.createComparisonModel(useWebWorkers, baseModel, companyData, scenario);
modelPromises.push(model);
}
});
self.doModelManagerSetting($dfd,modelPromises,baseModel,companyData,callback);
}).fail(function(a,b,c){
console.log('set company data - fail from service',a,b,c)
})
}
return $dfd.promise();
}
},
doModelManagerSetting: function($dfd,modelPromises,baseModel,companyData,callback){
var activeModel = baseModel;
var self = this;
// The backend controller sets showDerivedModel based on the url query parameter from a share link url
if(properties.showDerivedModel() != null){
//TREF-6713: set derived model as active model if derived was pass in as parameter
for (var i = 0; i < modelPromises.length; i++) {
var model = modelPromises[i];
if(model.modelId == properties.showDerivedModel()) {
activeModel = model;
}
}
}
self.set({
companyData: companyData, // store the companyData since we may need it later
trefis: baseModel,
models: modelPromises,
active: activeModel
});
// set child/parent relationships loaded from server
var models = self.get('models');
for (var i = 0; i < models.length; i++) {
var model = models[i];
if (model.parentModelId){
var parentChildrenManager = self.getParentChildrenManager();
var parentID = model.parentModelId;
var parentModels = $.grep(models, function(model) {
return model.modelId == parentID;
});
if (parentChildrenManager && (parentModels.length > 0)){
parentChildrenManager.addChildAndParentRelationship(model, parentModels[0]);
}
}
}
// why not just do all scenario visibility stuff here?
var selectedState = properties.modelContext().selectedState;
//var modelIds = Immutable.List( state.get('forecastModels').toJS().map(function(model) { return model.modelId }) );
var modelIds = self.get('models').map(function(model){
return model.modelId
})
var hiddenScenarioTabs;
var activeModelId = self.get('active').modelId;
if (selectedState[0]=='default'){
// if there is not selected state, then make sure base and active model are not hidden
hiddenScenarioTabs = modelIds.reduce(function(hiddenScenarios,modelId){
if (modelId != 'undefined.trefis' && modelId != activeModelId){
hiddenScenarios.push(modelId)
}
return hiddenScenarios;
},[]);
} else {
// selected for comparison
// ["undefined.trefis", 12072, 12073]
// modelIds....
// ['undeifined.trefis',12072,12073,12074]
// properties.showDerivedModel()
// 12072 === active...
hiddenScenarioTabs = modelIds.reduce(function (hiddenScenarios, modelId) {
if (!(selectedState.indexOf(modelId) > -1)) {
hiddenScenarios.push(modelId)
}
return hiddenScenarios;
}, []);
}
// probably should only be done in store - making it immutable ....
self.set('hiddenForecastModels',Immutable.fromJS(hiddenScenarioTabs))
self.set('publishedModels',Immutable.fromJS(self.get('models').filter(function(){
return model.published
})))
// these will fetch as regular when selected
// self.updateStaleConsensusModels();
self.publishModelOnParam();
if(callback)
callback();
// a one time event, to be used by widgets that previously relied on filmstrip $.deferred
self.trigger('loaded:models');
$dfd.resolve(true);
},
publishModelOnParam: function() {
var params = new URLSearchParams(location.search.slice(1));
if (params.has('publishModelId')) {
var model = this.get('models').find(function(model) {
return model.modelId == params.get('publishModelId')
});
if (model && model.yours && !model.published)
this.togglePublished(model, true)
}
},
updateConsensusModelsBasedOffModel: function(model) {
this.get('models')
.filter(this.isConsensusModel)
.filter(ConsensusScenarios.isModelInConsensus(model))
.forEach(function(consensusModel) {
return ConsensusScenarios.calculateConsensus(this, consensusModel)
}, this)
},
// TODO: might be better to have the backend ON DELETE CASCADE consensusOptions.forecastModelIds that
// won't exist anymore than to deal with this on the frontend
updateStaleConsensusModels: function() {
this.get('models')
.filter(this.isConsensusModel)
.filter(this.isConsensusStale, this)
.forEach(function(consensusModel) {
ConsensusScenarios.calculateConsensus(
this,
consensusModel,
ConsensusScenarios.modelIdsAvailableForConsensus(consensusModel)(this.get('models'))
)
}, this)
},
modelExists: function(modelId) {
return !!this.getModel(modelId)
},
getModel: function(modelId) {
return this.get('models').find(function(model) { return model.modelId == modelId })
},
getModels: function(modelIds) {
return this.get('models')
.filter(function(model) { return modelIds.indexOf(model.modelId) > -1 })
},
modelId: function(model) {
return model.modelId
},
// Checks if the consensus has any new or no longer existing modelIds if autoRefresh is enabled
isConsensusStale: function(consensusModel) {
var comparator = consensusModel.consensusOptions.addNewScenarios ? 'equals' : 'isSubset';
var isStale = consensusModel.consensusOptions.autoRefresh
&& !Immutable.Set(ConsensusScenarios.consensusModelIds(consensusModel))[comparator](
Immutable.Set(ConsensusScenarios.modelIdsAvailableForConsensus(consensusModel)(this.get('models')))
);
return isStale;
},
isConsensusModel: function(model) {
return model.isConsensusModel()
},
hasSubmitToView: function() {
return this.get('models').filter(function(model) {
return model.isSubmitToViewConsensus()
}).length > 0
},
yourModels: function() {
return this.get('models').filter(function(model) { return model.yours })
},
yourPublishedModels: function() {
return this.get('models').filter(function(model) {
return model.yours && model.published
})
},
isConsensusAvailable: function(model) {
return !model.yours && model.isSubmitToViewConsensus() ? this.yourPublishedModels().length > 0 : true
},
hasSomeUnavailableConsensus: function() {
return this.hasConsensusModels() && this.get('models').some(function(model) {
return !this.isConsensusAvailable(model)
}, this)
},
isModelAvailable: function(model) {
return model.isConsensusModel() ? this.isConsensusAvailable(model) : true
},
getUnavailableConsensus: function() {
return this.getConsensusModels().filter(function(consensusModel) {
return !this.isConsensusAvailable(consensusModel)
}, this)
},
modelInUnavailableConsensus: function(model) {
return this.getUnavailableConsensus().some(function(consensusModel) {
if (model.isDerived)
return consensusModel.consensusOptions.forecastModelIds.indexOf(model.modelId)>-1;
else
return false // base model should always be shown
})
},
modelNotInUnavailableConsensus: function(model) {
return !this.modelInUnavailableConsensus(model)
},
unavailableModelsInConsensus: function(consensusModel) {
return consensusModel.consensusOptions.forecastModelIds
.map(this.getModel, this)
.some(this.modelInUnavailableConsensus, this)
},
/**
* Creates a new user model whose values are identical to the base model,
* and sets this new model as the active model.
*
* @param {boolean} fromNewScenarioButton if true, the newly created model will have no grid view
* preferences (defaulting to whatever's in the JSON data). If false, the newly created model
* will have grid view preferences copied from the current preferences on the base model
*
* @param {boolean} fromModifyingPUblishedScenario if true, we need to show the warning for new
* @param {ForecastModel, optional} parentDerivedModel used to copy from
* model creation.
*
* @param {ForecastModel} derivedModel model to based it off of (optional)
*/
//createNewUserModel: function(fromNewScenarioButton, fromModifyingPublishedScenario, derivedModel) {
createNewUserModel: function(fromNewScenarioButton, fromModifyingPublishedScenario, derivedModel, parentDerivedModel, consensusOptions) {
var self = this;
var loggedInUser = properties.loggedInUser() || {};
var $dfd = $.Deferred();
//TREF-4589 get active model grid view preferences to copy to new user model (from modifying derived models)
// cannot always get the active model, since we could it could fromNewScenarioButton while a derived model is active
Service.createScenario().done(function(scenario){
var activeDerivedModelGridViewPreferences = (fromModifyingPublishedScenario ? self.get('active').gridViewPreferences : null);
var userModel = ForecastModel.createUsersModel(
false,
self.get('trefis'),
self.get('companyData'),
scenario || null, // passing no data here means it'll default its values to the base model
loggedInUser.userId,
loggedInUser.firstName,
loggedInUser.lastName,
activeDerivedModelGridViewPreferences,
parentDerivedModel,
consensusOptions
);
// TODO: shouldbe be necessary as we are passing in a derived model...
if(tf.editMode){
gavpv('create/Scenario/mode/edit','Create scenario in edit mode');
} else {
gavpvl('create/Scenario/mode/view','Create scenario in view mode');
}
var title = '' + (self.nextTitleId++);
// For modelChart.js, demoWidget.js
// Adding these so that anything listenting to change:models would have some insight into
// where the change came from.
// scenario modification behavior will use origin.publishedScenario to determin whether
// to show the new scenario message even if the user has seen it from modifying a point on base
var origin = {};
origin.publishedScenario = fromModifyingPublishedScenario;
origin.newScenarioButton = fromNewScenarioButton;
// set the parent child relationship
if (parentDerivedModel){
var parentChildrenManager = this.getParentChildrenManager();
parentChildrenManager.addChildAndParentRelationship(userModel, parentDerivedModel);
}
// TODO: errors if we don't add these here...
self.set({
models: self.get('models').concat([userModel]),
active: userModel
});
// may be an issue - we don't pass any options to the new scenario service (consensus... that was done in the doComputation/save)
$dfd.resolve(userModel);
});
return $dfd.promise();
},
copyModel: function(scenarioToCopy){
var self = this;
var $dfd = $.Deferred();
var loggedInUser = properties.loggedInUser() || {};
var activeDerivedModelGridViewPreferences = (scenarioToCopy.published ? this.get('active').gridViewPreferences : null);
Service.copyScenario(scenarioToCopy.modelId).done(function(newScenario){
// TODO: we can probably do away with a bunch of this - just adding the parent/child methods and consensus methods
var userModel = ForecastModel.createUsersModel(
false,
self.get('trefis'),
self.get('companyData'),
newScenario || null, // passing no data here means it'll default its values to the base model
loggedInUser.userId,
loggedInUser.firstName,
loggedInUser.lastName,
activeDerivedModelGridViewPreferences,
null,
scenarioToCopy.consensusOptions // i guess if the scenario already had consensus Options, they would be on the return scenario as well
);
self.set({
models: self.get('models').concat([userModel]),
active: userModel
});
$dfd.resolve(userModel);
})
return $dfd.promise();
},
addModel: function(model,setAsActive){
var self = this;
self.set({
models: self.get('models').concat([model]),
});
self.trigger('add:model',model);
if (setAsActive){
self.set({
active: model
});
self.trigger('change:models');
}
},
/**
* Internal helper method that adds various listeners to models.
* Should be called once on any new user models.
* @param {ForecastModel} userModel
*/
addListeners: function(model) {
var self = this;
model.addChangeListener(_.bind(function(isFinal, driverSetChanged) {
// this get set to start in a modelChart in the chartChangeListener
// probably can just be this...
this.trigger('modelRecalc', model, isFinal, driverSetChanged, false);
self.getStore().dispatch(ActionCreators.computing(false))
}, this));
model.addBeforeChangeListener(_.bind(function(isFinal, driverSetChanged) {
}, this));
},
/**
* Unloads the current comparison model, so user is viewing their own model again.
*/
selectBaseAsActive: function() {
this.set({
active: this.get('trefis')
});
},
/**
* Loads the given model and makes it the active model -- meant to be called e.g.
* when the user clicks on a tab.
* @param {ForecastModel} model
* @return {Deferred}
*/
loadAndMakeActive: function(model) {
// no need to load anything currently, just set it to active model.
// in the future, we may need to do some loading or initializing of the ForecastModel
// before it can be used as the active model.
//var deferred = $.Deferred();
this.set({
active: model
});
if(model.defaultPeriod && this.get('periodSelectionManager')) {
this.get('periodSelectionManager').setSelectedPeriod(model.defaultPeriod);
}
// internally, this will ALSO trigger change:active
// that event though gets triggered whether a user selected the model, or a new model was created
// insector listens to this
this.trigger('selected:model');
//deferred.resolve(model);
//return deferred;
},
/**
* toggles whether the given model is a published analyst model or not
* @param {ForecastModel} model
* @param {boolean} isPublishedModel
*/
togglePublished: function(model, isPublishedModel) {
var self = this;
this.updateModel(model, {published: !!isPublishedModel}, function(success) {
// we are still modifying by reference - this seems to trigger the accurate state/re-render
self.rebuildPublishedModels();
self.updateStaleConsensusModels()
// TODO: Logic was copied from scenariosTool.js -> toggleScenarioTabVisibility (TREF-7689)
// but shouldn't have to manually check and trigger this kind of thing anywhere
;(function toggleSeriesInConsensus() {
self.getConsensusModels().forEach(function(model) {
// Hides submit to view consensus if user no longer has any published models
if (!self.isConsensusAvailable(model)) {
store.dispatch({ type: 'HIDE_SCENARIO_TAB', id: model.modelId })
model.displayProperties.visible = false
// this is what actually controls the visible/private toggle. Thre is code for whenevery
// something is 'set' in modelManager to trigger the set_forecast_model
self.trigger("displayPropertyChanged", model);
}
})
})()
},
true);
},
rebuildPublishedModels: function() {
var publishedModels = $.grep(this.get('models'), function(model) {
return model.published;
});
this.set({publishedModels: publishedModels});
},
deleteModel: function(model) {
var self = this;
Service.deleteScenario(model).done(function(){
self.cleanUpDeletedModel(model);
self.updateStaleConsensusModels()
})
},
/**
* instead of storing model objects on the filmstrip (or any model display switcher),
* we can just store an id.
* @param id
*/
getModelById: function(id){
var model;
return model;
},
cleanUpDeletedModel: function(model) {
var models = this.get('models');
models = $.grep(models, function(m) {
return m !== model;
});
// this will trigger models:change
this.set({models: models});
// modelChart uses the model/id to remove from its series
// gridview uses this event to trigger its loadModels
this.trigger('model:deleted', model);
if(model === this.get('active')) {
this.set({active: this.get('trefis')});
}
this.getParentChildrenManager().removeChildScenario(model);
},
/**
* Should be called to indicate when the user clicks on the "request scenario" tab, to show the form
*/
openRequestScenario: function(){
$.ajax({
type: 'POST',
url: getHost()+'/emailfeedback',
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
modelId: properties.modelId(),
type: "REQUESTCLICK"
}),
success: $.noop,
error: $.noop
});
},
/**
* Called to indicate when the user has submitted the "request scenario" form
*
* @param {String} userName
* @param {String} userEmail
* @param {String} customField
* @return {boolean} success
*/
sendRequestScenario: function(userName, userEmail, customField) {
if(userName == "Your Name"){
alert("Please enter your name.");
return false;
}
if(!tf.mailRE.test(userEmail)){
alert("Email format is invalid.");
return false;
}
$.ajax({
type: 'POST',
url: getHost()+'/emailfeedback',
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
modelId: properties.modelId(),
type: "REQUESTINFO",
userName: userName,
userEmail: userEmail,
customField: customField
}),
success: $.noop,
error: $.noop
});
return true;
},
/**
* Updates our models to be persisted in session. User changes the order,
* we should have that order reflected in this.get('models')
* FOR NOW, getting models has [base,derived,derived...]
* we add 1 to our 'desired' index to accomodate for this, since a user can't re-arrange
* the base model
* @param {Object} desiredOrderMap
*/
updateModelOrder: function(desiredOrderMap){
var models = this.get('models');
var newModelsOrder = [];
_.forEach(models,function(model,i){
var id = model.modelId;
// ignore first, we don't want it moved
// it is our base model.
if (i > 0){
// this preserves our true get('models') order
// since desiredOrderMap will have something at "0", "1" but those
// will be actually be 1,2
// 0 is currently reserved for the base model.
var index = desiredOrderMap[id] + 1;
move(models,i,index);
}
});
// TODO: create as a mixin for underscore/lo-dash
function move(array, fromIndex, toIndex) {
array.splice(toIndex, 0, array.splice(fromIndex, 1)[0] );
return array;
}
},
/**
* Persists a new order of derived model tabs to the server. Should be called when the user drag-n-drops the derived model tabs.
* @param {Object} newOrder map from derived model id to desired order index. E.g. if you have three models with ids 5, 6, and 7,
* and you want to arrange them in the order: 7, 5, 6, then you would pass in {"7": 0, "5": 1, "6": 2}
*/
saveOrdering: function(newOrder) {
// update our model order..
this.updateModelOrder(newOrder);
this.trigger("reorder:models",this.get('models'));
var companyId = this.get('trefis').companyData.il.id;
if(companyId) {
$.ajax({
type: 'POST',
url: getHost()+'/api/derivedmodel/ordering/' + companyId + '?_method=PUT',
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(newOrder),
success: $.noop,
//error: tf.ajaxError
error: function(error){
// console.warn('save ordering you got an error',error)
}
});
}
},
/**
* Updates attributes on the given model and saves the result to the server. Also automatically
* handles events, and rollback of changes if the request to the server fails.
*
* Added as part of TREF-4392.
*
* @param {ForecastModel} model - the model that should be modified
* @param {Object} attributes - fields that should be updated on the model. Keys can
* include: scenarioExplanation, title, defaultPeriod, published
* @param {Function} callback - optional callback function that will be called
* once the request to server finishes. Will be passed a single boolean argument
* indicating whether the request was successful
* @param {boolean} forceAnalystSave - optional. published analyst models are usually not saved to the server when updated.
* If this param is true, we'll save anyway.
*/
updateModel: function(model, attributes, callback, forceAnalystSave) {
// TODO: we used to have a bunhc of listeners scattered through out the codebase
// now we mainly used random sets. IN the base of publishing, its the setting of the new publishedModels array
// that triggers the redraw. We should probably set the the new model on the forecastModel array to be more explicit
Service.updateScenarioAttributes(model,attributes).done(function(scenarioPropertiesFromServer){
$.extend(model,scenarioPropertiesFromServer);
if (callback){
callback()
}
})
},
/**
* Need a business logic class to loop through?
* Business logic for these?
* @returns {*} array of all notifications
*
* TODO: this is just for the charts to call? however its sprinkled all over modelChart
* // there is a watcher on store.js that i believe handles the actual scenario notification...
*/
getNotifications: function(){
var notifications = [],
scenario = this.get('active'),
exceededDriverLimit = PlotUtils.hasExceededDriverLimit();
// TODO: need to have something that loops through all the potential business rules
// and generates the notifications aray from it..
if (exceededDriverLimit){
notifications.push(SCENARIO_NOTIFICATIONS_MSGS['exceededDriverLimitCHARTMSG'](PlotUtils.getDriverLimit()))
}
return notifications;
},
getParentChildrenManager: function(){
return this.get('parentChildrenManager');
},
hasConsensusModels: function() {
return this.getConsensusModels().length > 0
},
getConsensusModels: function(models) {
return (models || this.get('models')).filter(function(model) { return model.isConsensusModel() })
},
getStore: function() {
return store
}
});
;;
// lib/jquery.min.js
// @include infrastructure/chartWrapper.js
// @include infrastructure/driverFilmstrip.js
// @include infrastructure/forecastModel.js
// @include infrastructure/modelManager.js
ChartUtil = {
/**
* @param {Object} comments an array of comments objects
* @param {Object} companyData hash from symbol to data for company on which to base charts
* @param {String} divIdPrefix divIdPrefix + postId should be the id of the div in which the chart should go
* @param {Object} tracker a tracker for filmstrip events.
*/
addCommentCharts: function(comments, companyDatas, divIdPrefix, tracker) {
$.each(comments, function(i,x) {
if (x.model && companyDatas[x.symbol] && $("#"+divIdPrefix + x.postId +" .cts_filmstrip").length == 0) {
var modelManager = new ModelManager(),
chartWrapper = new ChartWrapper(divIdPrefix + x.postId, modelManager, tracker),
companyData = companyDatas[x.symbol],
forecastModel = ForecastModel.createComparisonModel(false, null, companyData, x.model),
filmstrip = new DriverFilmstrip(chartWrapper, tracker, "cts_filmstrip")
;
chartWrapper.setEditable(false);
chartWrapper.mainChart.verboseMouseover = false;
modelManager.setCompany(companyData, 0);
filmstrip.addListener(function(driver) {
chartWrapper.setData(companyData, null, driver, null, [x.model.creatorFirstName], null,
filmstrip.getDrivers().length >1 ?
(filmstrip.getPosition()+1) + " of " + filmstrip.getDrivers().length + " - "
:undefined);
});
filmstrip.setDrivers(forecastModel.modifiedDriverList());
}
});
},
};
;;
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.DeepDiff = factory());
}(this, (function () { 'use strict';
var $scope;
var conflict;
var conflictResolution = [];
if (typeof global === 'object' && global) {
$scope = global;
} else if (typeof window !== 'undefined') {
$scope = window;
} else {
$scope = {};
}
conflict = $scope.DeepDiff;
if (conflict) {
conflictResolution.push(
function() {
if ('undefined' !== typeof conflict && $scope.DeepDiff === accumulateDiff) {
$scope.DeepDiff = conflict;
conflict = undefined;
}
});
}
// nodejs compatible on server side and in the browser.
function inherits(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
}
function Diff(kind, path) {
Object.defineProperty(this, 'kind', {
value: kind,
enumerable: true
});
if (path && path.length) {
Object.defineProperty(this, 'path', {
value: path,
enumerable: true
});
}
}
function DiffEdit(path, origin, value) {
DiffEdit.super_.call(this, 'E', path);
Object.defineProperty(this, 'lhs', {
value: origin,
enumerable: true
});
Object.defineProperty(this, 'rhs', {
value: value,
enumerable: true
});
}
inherits(DiffEdit, Diff);
function DiffNew(path, value) {
DiffNew.super_.call(this, 'N', path);
Object.defineProperty(this, 'rhs', {
value: value,
enumerable: true
});
}
inherits(DiffNew, Diff);
function DiffDeleted(path, value) {
DiffDeleted.super_.call(this, 'D', path);
Object.defineProperty(this, 'lhs', {
value: value,
enumerable: true
});
}
inherits(DiffDeleted, Diff);
function DiffArray(path, index, item) {
DiffArray.super_.call(this, 'A', path);
Object.defineProperty(this, 'index', {
value: index,
enumerable: true
});
Object.defineProperty(this, 'item', {
value: item,
enumerable: true
});
}
inherits(DiffArray, Diff);
function arrayRemove(arr, from, to) {
var rest = arr.slice((to || from) + 1 || arr.length);
arr.length = from < 0 ? arr.length + from : from;
arr.push.apply(arr, rest);
return arr;
}
function realTypeOf(subject) {
var type = typeof subject;
if (type !== 'object') {
return type;
}
if (subject === Math) {
return 'math';
} else if (subject === null) {
return 'null';
} else if (Array.isArray(subject)) {
return 'array';
} else if (Object.prototype.toString.call(subject) === '[object Date]') {
return 'date';
} else if (typeof subject.toString === 'function' && /^\/.*\//.test(subject.toString())) {
return 'regexp';
}
return 'object';
}
function deepDiff(lhs, rhs, changes, prefilter, path, key, stack) {
path = path || [];
var currentPath = path.slice(0);
if (typeof key !== 'undefined') {
if (prefilter) {
if (typeof(prefilter) === 'function' && prefilter(currentPath, key)) { return; }
else if (typeof(prefilter) === 'object') {
if (prefilter.prefilter && prefilter.prefilter(currentPath, key)) { return; }
if (prefilter.normalize) {
var alt = prefilter.normalize(currentPath, key, lhs, rhs);
if (alt) {
lhs = alt[0];
rhs = alt[1];
}
}
}
}
currentPath.push(key);
}
// Use string comparison for regexes
if (realTypeOf(lhs) === 'regexp' && realTypeOf(rhs) === 'regexp') {
lhs = lhs.toString();
rhs = rhs.toString();
}
var ltype = typeof lhs;
var rtype = typeof rhs;
if (ltype === 'undefined') {
if (rtype !== 'undefined') {
changes(new DiffNew(currentPath, rhs));
} else {
changes(new DiffDeleted(currentPath, lhs));
}
} else if (rtype === 'undefined') {
changes(new DiffDeleted(currentPath, lhs));
} else if (realTypeOf(lhs) !== realTypeOf(rhs)) {
changes(new DiffEdit(currentPath, lhs, rhs));
} else if (realTypeOf(lhs) === 'date' && (lhs - rhs) !== 0) {
changes(new DiffEdit(currentPath, lhs, rhs));
} else if (ltype === 'object' && lhs !== null && rhs !== null) {
stack = stack || [];
if (stack.indexOf(lhs) < 0) {
stack.push(lhs);
if (Array.isArray(lhs)) {
var i, len = lhs.length;
for (i = 0; i < lhs.length; i++) {
if (i >= rhs.length) {
changes(new DiffArray(currentPath, i, new DiffDeleted(undefined, lhs[i])));
} else {
deepDiff(lhs[i], rhs[i], changes, prefilter, currentPath, i, stack);
}
}
while (i < rhs.length) {
changes(new DiffArray(currentPath, i, new DiffNew(undefined, rhs[i++])));
}
} else {
var akeys = Object.keys(lhs);
var pkeys = Object.keys(rhs);
akeys.forEach(function(k, i) {
var other = pkeys.indexOf(k);
if (other >= 0) {
deepDiff(lhs[k], rhs[k], changes, prefilter, currentPath, k, stack);
pkeys = arrayRemove(pkeys, other);
} else {
deepDiff(lhs[k], undefined, changes, prefilter, currentPath, k, stack);
}
});
pkeys.forEach(function(k) {
deepDiff(undefined, rhs[k], changes, prefilter, currentPath, k, stack);
});
}
stack.length = stack.length - 1;
} else if (lhs !== rhs) {
// lhs is contains a cycle at this element and it differs from rhs
changes(new DiffEdit(currentPath, lhs, rhs));
}
} else if (lhs !== rhs) {
if (!(ltype === 'number' && isNaN(lhs) && isNaN(rhs))) {
changes(new DiffEdit(currentPath, lhs, rhs));
}
}
}
function accumulateDiff(lhs, rhs, prefilter, accum) {
accum = accum || [];
deepDiff(lhs, rhs,
function(diff) {
if (diff) {
accum.push(diff);
}
},
prefilter);
return (accum.length) ? accum : undefined;
}
function applyArrayChange(arr, index, change) {
if (change.path && change.path.length) {
var it = arr[index],
i, u = change.path.length - 1;
for (i = 0; i < u; i++) {
it = it[change.path[i]];
}
switch (change.kind) {
case 'A':
applyArrayChange(it[change.path[i]], change.index, change.item);
break;
case 'D':
delete it[change.path[i]];
break;
case 'E':
case 'N':
it[change.path[i]] = change.rhs;
break;
}
} else {
switch (change.kind) {
case 'A':
applyArrayChange(arr[index], change.index, change.item);
break;
case 'D':
arr = arrayRemove(arr, index);
break;
case 'E':
case 'N':
arr[index] = change.rhs;
break;
}
}
return arr;
}
function applyChange(target, source, change) {
if (target && source && change && change.kind) {
var it = target,
i = -1,
last = change.path ? change.path.length - 1 : 0;
while (++i < last) {
if (typeof it[change.path[i]] === 'undefined') {
it[change.path[i]] = (typeof change.path[i] === 'number') ? [] : {};
}
it = it[change.path[i]];
}
switch (change.kind) {
case 'A':
applyArrayChange(change.path ? it[change.path[i]] : it, change.index, change.item);
break;
case 'D':
delete it[change.path[i]];
break;
case 'E':
case 'N':
it[change.path[i]] = change.rhs;
break;
}
}
}
function revertArrayChange(arr, index, change) {
if (change.path && change.path.length) {
// the structure of the object at the index has changed...
var it = arr[index],
i, u = change.path.length - 1;
for (i = 0; i < u; i++) {
it = it[change.path[i]];
}
switch (change.kind) {
case 'A':
revertArrayChange(it[change.path[i]], change.index, change.item);
break;
case 'D':
it[change.path[i]] = change.lhs;
break;
case 'E':
it[change.path[i]] = change.lhs;
break;
case 'N':
delete it[change.path[i]];
break;
}
} else {
// the array item is different...
switch (change.kind) {
case 'A':
revertArrayChange(arr[index], change.index, change.item);
break;
case 'D':
arr[index] = change.lhs;
break;
case 'E':
arr[index] = change.lhs;
break;
case 'N':
arr = arrayRemove(arr, index);
break;
}
}
return arr;
}
function revertChange(target, source, change) {
if (target && source && change && change.kind) {
var it = target,
i, u;
u = change.path.length - 1;
for (i = 0; i < u; i++) {
if (typeof it[change.path[i]] === 'undefined') {
it[change.path[i]] = {};
}
it = it[change.path[i]];
}
switch (change.kind) {
case 'A':
// Array was modified...
// it will be an array...
revertArrayChange(it[change.path[i]], change.index, change.item);
break;
case 'D':
// Item was deleted...
it[change.path[i]] = change.lhs;
break;
case 'E':
// Item was edited...
it[change.path[i]] = change.lhs;
break;
case 'N':
// Item is new...
delete it[change.path[i]];
break;
}
}
}
function applyDiff(target, source, filter) {
if (target && source) {
var onChange = function(change) {
if (!filter || filter(target, source, change)) {
applyChange(target, source, change);
}
};
deepDiff(target, source, onChange);
}
}
Object.defineProperties(accumulateDiff, {
diff: {
value: accumulateDiff,
enumerable: true
},
observableDiff: {
value: deepDiff,
enumerable: true
},
applyDiff: {
value: applyDiff,
enumerable: true
},
applyChange: {
value: applyChange,
enumerable: true
},
revertChange: {
value: revertChange,
enumerable: true
},
isConflict: {
value: function() {
return 'undefined' !== typeof conflict;
},
enumerable: true
},
noConflict: {
value: function() {
if (conflictResolution) {
conflictResolution.forEach(function(it) {
it();
});
conflictResolution = null;
}
return accumulateDiff;
},
enumerable: true
}
});
return accumulateDiff;
})));
;;
// @include lib/redux.js
// @include lib/redux-watch.js
// @include lib/immutable.js
// @include presentations/stateSaver.js
// @include lib/underscore.js
// @include util/chartUtil.js
// @include lib/deep-diff.js
/**
* strip out ui property, from state and components, ie state.ui, state.components.*.ui,
* remove other properties that shoulnt be persited
*/
// Credit David Walsh (https://davidwalsh.name/javascript-debounce-function)
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debouncedStateSaver(persistState){
Service.saveState(persistState)
.done(function(result) {
if (result.presentationId)
store.dispatch({ type: 'SET_PERSISTED_ID', persistedId: result.presentationId })
})
}
var stateSaver = _.debounce(debouncedStateSaver, 250);
var toPersistState = function(state) {
if(!state)return state;//hacky workaround, the file is being included on non presentation pages and firing off with null state causing exceptions
var firstVisibleChart = getFirstChartVisibleChart(state);
return state
.delete('ui') //delete root ui property
.update('components', function(components){
return components.map(function(component) { //iterate over all components
return component.delete('ui') //and delete the ui property
})
})
/** delete preview for all chart except the first visible one */
.update('components', function(components){
return components.map(function(component) {
if(component.get('id') != firstVisibleChart && component.get('type') === 'chart')
return component.delete('preview');
return component;
})
})
.delete('periodSelectionManager') //and delete more state properties that shouldnt be persisted
.delete('forecastModels')
.delete('companyData')
.delete('activeForecastModel')
.delete('notifications')
.delete('publishedForecastModels')
.delete('baseForecastModel')
.delete('modelManager')
// we dont store the parent children stuff - its probably ot in use, but also derived from modelManager
.delete('parentChildrenManager')
// we don't use a selectedPeriod globally
.delete('selectedPeriod')
.delete('notificationDrawer')
.delete('chartDrawer')
.delete('localStorage')
.delete('shareLinks')
.delete('tips')
.delete('notifications')
.delete('tipsDisplayed')
.delete('validations')
.delete('defaultPresentationTemplate')
}
var addStateSaver = function(store) {
store.subscribe(reduxWatch(function() { return store.getState() })(function(state, prevState) {
if(!state)return state //hacky workaround, the file is being included on non presentation pages and firing off with null state causing exceptions
if (!state.getIn(['ui','editMode'])) return state; // if you aren't in edit mode, we aren't saving
if (!state.getIn(['persistReady'])) return state; // if root component hasnt finished loading, wait to let state settle down
if(state.get("isBlankTemplate"))return state; //not going to save blank template
// fb7344 if the nextId is close to the number of digits for MAX_SAFE_INTEGER then something is suspect
// likely what would cause it is coercion (ie: '14' + 1) of the nextId, leading to '141' instead of '15'
// this makes sure a bug like this fails before it starts corrupting the state
var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991
if (state.get('nextId').length >= MAX_SAFE_INTEGER.toString().length - 1) {
throw new Error(
'The state.nextId is getting very close to Number.MAX_SAFE_INTEGER. '+
'This is very likely a bug. Check the logic for nextId (See the stateSaver for more info)'
)
}
var persistState = toPersistState(state)
, prevPersistState = toPersistState(prevState)
// Save to the store only if properties that need to be persisted have changed
// console.log(persistState.get('persistedId'),persistState.equals(prevPersistState),{
// PREV:prevPersistState,
// CURR:persistState,
// pv:prevPersistState.toJS(),
// cr:persistState.toJS()
// })
if (!persistState.equals(prevPersistState)) {
// console.log(DeepDiff(persistState.toJS(), prevPersistState.toJS()));
stateSaver(persistState)
}
}))
}
;;
// @include lib/immutable.js
// @include lib/reselect.js
var createDeepEqualSelector = Reselect.createSelectorCreator(
Reselect.defaultMemoize,
Immutable.is
);
// Get the top level sections
var getRootSections = function(state) {
return state.get('contents').map(function(id) {
return state.getIn(['components', id])
})
};
var areAnySectionsInEditTitle = function(state) {
return getRootSections(state).filter(function(section){
return section.getIn(['ui','editingTitle'])
}).toJS().length
};
var getRootSectionIds = function(state) {
return state.get('contents')
};
var getContentsTypes = function(state, props) {
return state.getIn(['components', props.id, 'contents']) ? state.getIn(['components', props.id, 'contents']).map(function(id) {
return { id: id, type: state.getIn(['components', id, 'type']) }
}).toJS() : []
};
var getDisplayProps = function(state, props) {
return state.getIn(['components', props.id, 'display']) ? state.getIn(['components', props.id, 'display']).toJS() : {}
};
var makeGetComponent = function() {
return createDeepEqualSelector(function(state, props) {
return state.getIn(['components', props.id])
}, function(values) {
return values
})
};
var getComponent = function(state, props) {
return state.getIn(['components', props.id])
};
var getComponents = function(state) {
return state.get('components')
};
var getClassSet = Reselect.createSelector(getComponent, function(component) {
switch (component.get('type')) {
case 'container':
/*
* TODO add section now returns component's display.containterWidth, display.componentWidth, display.type, display.valuesOnly
* Alternative we can forget this getClassSet and have respective Components like HorizontalList, Carousel, List, Wrap
*/
return Immutable.Map({
'inner-container': true,
'section-container': true,
horizontal: component.getIn(['display', 'type']) === 'horizontalList',
}).toJS();
default:
return {}
}
});
var getModelManager = function(state) {
return state.get('modelManager')
};
var getShareLinks = function(state) {
var model = state.get('activeForecastModel');
if (model.isDerived && !model.modelId){
return null;
}
return model.isDerived ? state.getIn(['shareLinks', 'derivedModels', model.modelId.toString()])
: state.getIn(['shareLinks', 'baseModels', properties.modelId().toString()])
};
var getAllShareLinks = function(state) {
var allLinks = {};
state.get('forecastModels').toJS().filter(function(model) {
if (model.modelId){
var link = model.isDerived ? state.getIn(['shareLinks', 'derivedModels', model.modelId.toString()])
: state.getIn(['shareLinks', 'baseModels', properties.modelId().toString()])
allLinks[model.modelId] = link.map(function(e) { return e ? e.replace('http:', 'https:') : e })
}
});
return allLinks;
};
var getModelLinkTypes = function(state) {
var allLinkTypes = {};
state.get('forecastModels').toJS().filter(function(model) {
if (model.modelId){
allLinkTypes[model.modelId] = !model.isDerived ? 'base'
: model.published ? 'published' : 'private'
}
});
return allLinkTypes;
};
// This will determine if scenarios that are hidden (i.e. not compared) are able to be shared too
var getShareLinkState = function(state) {
var selectedState = "";
var anyHiddenScenarios = false;
var visibleModels = state.get('forecastModels').toJS().filter(function(model) {
if (!state.getIn(['ui','hiddenScenarioTabs']).has(model.modelId)){
return true
}
});
for (var i = 0; i < visibleModels.length; i++) {
var model = visibleModels[i];
selectedState = selectedState + "&selectedState=" + model.modelId
}
return selectedState;
};
// gives the number of charts contained in the component with the given id and all of its (deep) contents
var getChartCount = function(state, id) {
var component = getComponent(state, {id: id});
var result = 0;
if(component) {
var contents = component.get('contents');
if(contents) {
for(var i = 0; i < contents.size; i++) {
result += getChartCount(state, contents.get(i));
}
}
if(component.get('type') === 'chart') {
result++;
}
}
return result;
};
var getPlottableStructure = createDeepEqualSelector(function(code,state){
return Immutable.fromJS($.custom.modelChart.prototype.getPlottableStructure(code, state.get('baseForecastModel'), false))
},function(values){
return values;
});
var getChartOptions = createDeepEqualSelector(function(state) {
var color = '#7f7f7f'; //"#7f7f7f"
// could also pass id and set plot code with state.component.id.code
var gridViewChartOptions = {
xAxis: {
labels: {
style: {
color: "#7f7f7f",
fontSize: "10.5px",
fontFamily: 'Helvetica, Arial, sans-serif'
}
},
lineWidth:1,
lineColor: '#d8d8d8',
},
yAxis: {
title: {
style: {
color: "#7f7f7f",
fontSize: "10.5px",
fontFamily: 'Helvetica, Arial, sans-serif'
}
},
labels: {
style: {
color: "#7f7f7f",
fontSize: "10.5px",
fontFamily: 'Helvetica, Arial, sans-serif'
}
},
lineColor: '#d8d8d8',
lineWidth: 0
},
tf: {
tooltip: {
shared: true
}
}
};
var chartOptions = {
tf: {
periodSelectionManager: state.get('periodSelectionManager'),
label: {enabled:false },
plotLines: {
nowLine: {enabled:false },
}
},
yAxis: {
lineWidth:1,
},
xAxis: {
tickLength:3, // this is 5 for outputs...
tickWidth:1,
},
};
var options = {
gridviewChart: true,
modelManager: state.get('modelManager'),
useAutoRatioWidthHeight: true,
needsButtonBar: false,
dragPromptEnabled: false,
//editableSymbol: 'url(' + getHost() +'/images/chartHandleBlue.png)',
// editableSymbolActive: 'url(' + getHost() +'/images/chartHandleBlue.png)',
//TREF-8152: Needed to allow Highcharts to process image for export of chart
//https://github.com/highcharts/highcharts/issues/1765
//http://jsfiddle.net/gh/get/jquery/1.7.2/highslide-software/highcharts.com/tree/master/samples/highcharts/plotoptions/series-marker-symbol/
editableSymbol: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wYWCiQdSvfxEAAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAADgSURBVBjTjZAxjoJAAEX/jGSGbei0WCosKLZFEgrvYG0F3SZ7Ac/AEezcbbam2RNQmKAXoCEW00hoNtnITICxmOgmJhpf/fN+8ggA7MSJFVW7+Nz+rspaBQDgj9k+jpw09Oxs5r4oshMntvo+rnMhE6k0QGDQAGcEc5dv0uXk3SqqdpELmchOAxT/EEB2GrmQSVG1P6Pm7ePr0HSvV9MNfQc0f/2UlrUK7o0u5rJWAcWTUH/M9tAPFtoUoHHkpJzd/+aMII6clIaenc1dvuEWAQZjgAYwANwyeULPzsizwc8RJF+he5u+XAAAAABJRU5ErkJggg==)',
//editableSymbolUnaffectingOutputs: 'url(' + getHost() +'/images/chartHandleBlue.png)',
editableSymbolUnaffectingOutputs: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wYWCiQdSvfxEAAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAADgSURBVBjTjZAxjoJAAEX/jGSGbei0WCosKLZFEgrvYG0F3SZ7Ac/AEezcbbam2RNQmKAXoCEW00hoNtnITICxmOgmJhpf/fN+8ggA7MSJFVW7+Nz+rspaBQDgj9k+jpw09Oxs5r4oshMntvo+rnMhE6k0QGDQAGcEc5dv0uXk3SqqdpELmchOAxT/EEB2GrmQSVG1P6Pm7ePr0HSvV9MNfQc0f/2UlrUK7o0u5rJWAcWTUH/M9tAPFtoUoHHkpJzd/+aMII6clIaenc1dvuEWAQZjgAYwANwyeULPzsizwc8RJF+he5u+XAAAAABJRU5ErkJggg==)',
editableUserSeriesOptions: {
tf: {
series: {
// gridview uses different symbols that are smaller than the normal ones
},
},
},
chart: $.extend({}, chartOptions,gridViewChartOptions),
};
return Immutable.fromJS(options);
}, function(values) {
return values
});
var getRootSections = Reselect.createSelector([
getRootSectionIds,
getComponents
], function(ids, components) {
return ids.map(function(id) {
return components.get(id)
})
});
var getRootSectionDisplays = Reselect.createSelector(getRootSections, function(sections) {
return sections.map(function(section) {
return section.get('display')
})
});
var getRootSectionsMeta = Reselect.createSelector([
getRootSectionIds, getRootSectionDisplays
], function(ids, displays) {
return displays.zipWith(function(display, id) { return display.set('id', id) }, ids)
});
var getHiddenScenarioTabs = function(state) {
return state.getIn(['ui','hiddenScenarioTabs'])
};
var getScenarioOrder = function(state) {
return state.getIn(['display', 'forecastModels', 'order'])
};
var getForecastModels = function(state) {
return state.get('forecastModels')
};
var getVisibleForecastModels = Reselect.createSelector([
getHiddenScenarioTabs, getForecastModels
], function(hiddenScenarioTabs, forecastModels) {
return forecastModels.filter(function(model) {
return !(hiddenScenarioTabs.has(model.modelId) ||
hiddenScenarioTabs.has(parseInt(model.modelId)))
})
});
var getVisibleScenarioOrder = Reselect.createSelector([
getHiddenScenarioTabs, getScenarioOrder
], function(hiddenScenarioTabs, scenarioOrder) {
return scenarioOrder.filter(function(modelId) {
return !(hiddenScenarioTabs.has(modelId) ||
hiddenScenarioTabs.has(parseInt(modelId)))
})
});
var orderForecastModels = function(scenarioOrder, forecastModels) {
if (!scenarioOrder.isEmpty())
return scenarioOrder.map(function(modelId) {
return forecastModels.find(function(model) {
return model.modelId.toString() === modelId
})
});
else
return forecastModels
};
var getOrderedForecastModels = Reselect.createSelector([
getScenarioOrder, getForecastModels
], orderForecastModels);
var getVisibleOrderedForecastModels = Reselect.createSelector([
getVisibleScenarioOrder, getForecastModels
], orderForecastModels);
// Gets all components of the specified type in the order in which they
// appear in the presentation (reading from top-left to bottom-right)
var getComponentsOfTypeInOrder = function(state, type) {
var sections = state.get('contents')
return sections.map(function(sectionId) {
var containers = state.getIn(['components', sectionId, 'contents'])
return containers.map(function(containerId) {
return state.getIn(['components', containerId, 'contents']).filter(function(id) {
return state.getIn(['components', id, 'type']) === type
})
})
}).flatten()
}
// TODO: move all selectors into this namespace to avoid naming conflicts with smartconcat
var Selectors = {
getIsCollapsed: function(section) {
return section ?
section.hasIn(['ui', 'isCollapsed'])
? section.getIn(['ui', 'isCollapsed'])
: section.getIn(['display', 'isCollapsed']):
false
},
getSelectedOffer: function(state) {
return state.get('selectedOfferTier')
.map(function(offerTier, offerType) {
var offer = state.getIn(['offers', offerType])
return offer.getIn(['offerTier', offerTier])
.set('offerType', offerType)
.set('offerTier', offerTier)
.set('title', offer.get('title'))
})
.valueSeq()
}
};
;;
/* * * * * * * * * * * * * * * * * * *
* Utility functions for the reducers *
* * * * * * * * * * * * * * * * * * */
var incrementNextId = function(nextId, n) { return (parseInt(nextId) + (n !== undefined ? parseInt(n) : 1)).toString() }
var makeSection = function(sectionId, containerId, title, display) {
return Immutable.fromJS({
type: 'section',
contents: [containerId], //sections can only have containers
id: sectionId,
title: {
text: title ? title : "Untitled Section"
//collapsedIdentifiers:['aj45','ac34'] //TBD, values/charts that appear when collapsed
},
display: display ? display : {
collapsible: true,
isCollapsed: false,
sticky:false
}
})
}
// is this needed/still used? Can the stick be part of 'makeSection'?
var makeSectionDefault = function(sectionId, containerId) {
return Immutable.fromJS({
type: 'section',
contents: [containerId], //sections can only have containers
id: sectionId,
title: {
text: "Untitled Section"
//collapsedIdentifiers:['aj45','ac34'] //TBD, values/charts that appear when collapsed
},
display: {
collapsible:false,
isCollapsed: false,
sticky:false
}
})
}
var makeContainer = function(id, containerWidth, componentWidth, type, valuesOnly) {
return Immutable.fromJS({
type: 'container',
id: id,
contents:[], //charts, values, other low level components, or sections
title: {},
display: {
containerWidth: containerWidth ? containerWidth : '100',
componentWidth: componentWidth ? componentWidth : '25', // creates 25% width children...
type: type ? type : 'horizontalList', //carousel, //list, //wrap
valuesOnly:valuesOnly ? valuesOnly : false,
defaultEmptyText: 'Add Content From Drawer'
},
hint: {
types: 'output',
//display: {'bar', 'valueOnly'}
count: -1 //numeric, if <0 get all, maybe with a realistic upper limit
}
})
}
var getComponentParentId = function(state,id){
return state.getIn(['components']).filter(function(components){
return components.has('contents')
}).reduce(function(acc,component){
if (component.get('contents').indexOf(id) > -1){
acc = component.get('id')
}
return acc
},undefined)
};
// TODO: too many 'default' area duplicated: makeChartDefault in reducerUtil.js, doModelChart in chart.js, and getDefaultSmoothing and SET_DEFAULT_CHART_DATA in editChartReducer.js
var makeChartDefault = function(id,code,options){
return Immutable.fromJS($.extend({
type:'chart',
code: code,
id: id
}, options))
}
var createComponent = function(type,options){
switch(type){
case "chart":
return makeChartDefault(options.id,options.code,options)
case "container":
case "section":
case "outputVal":
case "htmlBlock":
case "dynamic-text-block": return createDynamicTextBlock(options.id)
case "dynamic-chart-block": return createDynamicChartBlock(options.id)
case "related-link-block": return createRelatedLinkBlock(options.id)
case "link-block": return createLinkBlock(options.id)
default:
return;
}
}
var removeItem = function(item) {
return function(list) {
return list.filter(function(listItem) {
return Immutable.isImmutable(listItem) ? !listItem.equals(item) : listItem !== item
})
}
}
var identityFn = function(a) { return a }
var identityFactory = function() { return identityFn }
var createNewSection = function(state, id, direction){
/**
* Adding a section consists of:
* creating a 2 new components
* - a section
* - a container
* both have unique ids and use the defaults.
*
* After creationg both, push the id of the new container to the 'contents'
* of the section id.
*
* Add this new section Id to the main contents array
*/
var sectionId = incrementNextId(state.get('nextId'), 0)
, containerId = incrementNextId(state.get('nextId'), 1)
, nextId = incrementNextId(state.get('nextId'), 2)
, idIndex = id ? state.get('contents').indexOf(id) : null
return state.setIn(['components', sectionId], makeSection(sectionId, containerId))
.setIn(['components', containerId], makeContainer(containerId))
.update('contents', function(contents) {
return idIndex !== null && idIndex !== -1 ?
contents.insert(direction === 'down' ? idIndex + 1 : idIndex, sectionId) :
contents.push(sectionId)
})
.set('nextId', nextId)
}
var updateComponentRefs = function(state, componentId, shallow, updateFn) {
var contents = state.getIn(['components', componentId, 'contents'])
return (contents || []).reduce(function(state, id) {
return !shallow ? updateComponentRefs(state, id, shallow, identityFactory) : state
}, state)
.update('components', updateFn(componentId, false))
// Also update the id in the root contents
.update('contents', updateFn(componentId, true))
}
var removeComponentRefs = function(state, componentId, shallow) {
return updateComponentRefs(state, componentId, shallow, function(id, isRootContents) {
return isRootContents ? removeItem(id) : function(components) {
return components.map(function(component) {
return !component.has('contents') ? component : component.update('contents', removeItem(id))
})
}
})
}
var removeComponent = function(state, componentId) {
return updateComponentRefs(state, componentId, false, function(id, isRootContents) {
return isRootContents ? removeItem(id) : function(components) {
return components.remove(id)
}
})
}
var updateContents = function(state, containerId, fn) {
return !containerId ? state.update('contents', fn) : state.updateIn(['components', containerId, 'contents'], fn)
}
var getParentComponent = function(state, componentId) {
return state.get('contents').contains(componentId) ? state : state.get('components').find(function(component) {
return component.has('contents') && component.get('contents').contains(componentId)
})
}
var getDropIndex = function(state, action) {
if (!action.dnd.drop.itemId) return 0 // Drop index is 0 for empty containers
var contents = !action.dnd.drop.containerId ? state.get('contents') : state.getIn(['components', action.dnd.drop.containerId, 'contents'])
if (action.dnd.drop.itemId === 'emptyComponent') return contents ? contents.size : 0
var dragBeforeDrop = contents.contains(action.dnd.drag.itemId) && contents.indexOf(action.dnd.drag.itemId) < contents.indexOf(action.dnd.drop.itemId)
var dropIndex = contents.indexOf(action.dnd.drop.itemId) + (action.dnd.drop.dropAfter ? 1 : 0) + (dragBeforeDrop ? -1 : 0)
return dropIndex
}
var isDroppable = function(state, action) {
var isTemp = action.dnd.drag.itemId.lastIndexOf('temp', 0) === 0
|| action.dnd.drag.itemId === 'dnd-widget';
if (isTemp){
isDroppable = true;
} else {
var dragParent = getParentComponent(state, action.dnd.drag.itemId)
var isSection = dragParent.has('components') || dragParent.get('type') === 'section'
var canDragParentBeEmpty = isSection ? dragParent.get('contents').count() > 1 : true
var isDroppable = action.dnd.drag.itemId !== action.dnd.drop.itemId && canDragParentBeEmpty
}
return isDroppable
}
var swapElem = function(collection, a, b) {
var indexOfA = collection.indexOf(a)
, indexOfB = collection.indexOf(b)
return indexOfA === -1 ? collection : collection.set(indexOfA, b).set(indexOfB, a)
}
var moveElem = function(collection, a, position) {
var indexOfA = collection.indexOf(a)
if (position === 'before') {
return indexOfA <= 0 ? collection
: collection.set(indexOfA, collection.get(indexOfA - 1)).set(indexOfA - 1, a)
}
else if (position === 'after') {
return indexOfA === collection.size - 1 ? collection
: collection.set(indexOfA, collection.get(indexOfA + 1)).set(indexOfA + 1, a)
}
else {
return collection
}
}
/**
* filter out a given componentId from a contents array, ex filter out '7' from ['1', '2', '3', '7']
*/
var removeFromContents = function(componentId) {
return function(contents) {
return contents.filter(function(id) { return id !== componentId })
}
}
/**
* recursively remove the component, all references to the component and all its children
*/
var deleteComponent = function(state, componentId) {
if(state.getIn(['components', componentId, 'type']) == 'chart'){
var code = state.getIn(['components', componentId, 'code']);
if(code && code.startsWith('s_')){
state = state.deleteIn(['staticChart', code]);
}
//get blank string for blank chart
var chartName = !!state.getIn(['components', componentId, 'display', 'chartDisplay','chartTitle']) ? state.getIn(['components', componentId, 'display', 'chartDisplay','chartTitle']).replace(/ /g,"_") : '';
chartName = chartName!='' ? '/chartName=' + chartName : '';
gavpv('delete/Chart' + chartName,'User deletes a chart');
}
if(state.getIn(['components', componentId, 'type']) == 'section'){
gavpv('delete/Section','User deletes a section');
}
return (state.getIn(['components', componentId, 'contents']) || []) //get all child contents for this components, or blank array if has no children
.reduce( function(state, id) {
return deleteComponent(state, id)}, state
) //recusrively delete each child, reduce back to single state
.update('contents',removeFromContents(componentId)) //remove component id from root state.contents
.deleteIn(['components',componentId]) // remove whole components from state.components
.update('components', function(components){
return components.map(function(component) { //iterate over all components
return !component.has('contents') ? component : //if it has contents field
component.update('contents', removeFromContents(componentId)) //filter the contents to remove the id being deleted
})
});
}
var createTextBlock = function(state) {
return Immutable.fromJS({
type: 'text',
editorContents: [],
id: state.get('nextId').toString()
})
}
var createDynamicTextBlock = function(id) {
return Immutable.fromJS({
type: 'dynamic-text-block',
editorContents: [],
id: id
})
}
var createRelatedLinkBlock = function(id) {
return Immutable.fromJS({
type: 'related-link-block',
editorContents: [],
id: id
})
}
var createLinkBlock = function(id) {
return Immutable.fromJS({
type: 'link-block',
editorContents: [],
id: id
})
}
var createDynamicChartBlock = function(id) {
return Immutable.fromJS({
type: 'dynamic-chart-block',
editorContents: [],
id: id
})
}
//TREF-7650- creates json specific state for Quill.js text editor to use
var createTextBlockWithInitialHTML = function(editorContents, id) {
return Immutable.fromJS({
type: 'text',
editorContents: editorContents,
id: id
})
}
var createTextBlockWithInitialHTML2 = function(editorContents, previewHTML, id) {
return Immutable.fromJS({
type: 'text',
editorContents: editorContents,
id: id,
preview: previewHTML
})
}
var moveListItem = function(list, item, direction) {
var itemIndex = list.indexOf(item)
, beforeIndex = itemIndex > 0 ? itemIndex - 1 : itemIndex
, afterIndex = itemIndex < list.size - 1 ? itemIndex + 1 : itemIndex
return direction === 'before' ?
list.set(itemIndex, list.get(beforeIndex)).set(beforeIndex, list.get(itemIndex)) :
list.set(itemIndex, list.get(afterIndex)) .set(afterIndex, list.get(itemIndex))
}
/* Does a deep copy of a component, updating the ids to unique ones
* Returns a list of copied components to be concatenated to state.get('components')
*/
var copyComponent = function(state, id, newId) {
//add this variable outside the recursion for tracking the last component for multiple container
//this will update the last component and doesn't depend on the recursion tree
var copyComponentLastId = (newId || state.get('nextId')).toString()
var _copyComponent = function (state, id, newId) {
var component = getComponent(state, {id: id})
var nextId = (newId || state.get('nextId')).toString()
// Get a list of new ids for the components
var newContents = (component.get('contents') || Immutable.List()).reduce(function (acc, componentId) {
copyComponentLastId = incrementNextId(acc.last() ? acc.last() : copyComponentLastId)
return acc.push(copyComponentLastId)
}, Immutable.List())
var initial = Immutable.List().push(component.set('id', nextId).set('contents', newContents))
var copiedComponents = (component.get('contents') || Immutable.List()).reduce(function (acc, componentId, index) {
var childComponents = _copyComponent(state, componentId, newContents.get(index))
return acc.concat(childComponents)
}, initial)
return copiedComponents
}
return _copyComponent(state, id, newId)
// var component = getComponent(state, {id: id})
// var nextId = (newId || state.get('nextId')).toString()
// copyComponentLastId = (newId && copyComponentLastId) ? copyComponentLastId : nextId
// // Get a list of new ids for the components
// var newContents = (component.get('contents') || Immutable.List()).reduce(function(acc, componentId) {
// copyComponentLastId = incrementNextId(acc.last() ? acc.last() : copyComponentLastId)
// return acc.push(copyComponentLastId)
// }, Immutable.List())
// var initial = Immutable.List().push(component.set('id', nextId).set('contents', newContents))
// var copiedComponents = (component.get('contents') || Immutable.List()).reduce(function(acc, componentId, index) {
// var childComponents = copyComponent(state, componentId, newContents.get(index))
// return acc.concat(childComponents)
// }, initial)
// return copiedComponents
}
var copyComponentToState = function(state, id) {
var parentComponent = getParentComponent(state, id)
, copiedComponents = copyComponent(state, id)
, isRootComponent = state.get('contents').contains(id)
return state
.set('nextId', incrementNextId(copiedComponents.last().get('id')))
.update('components', function(components) {
var newComponents = copiedComponents.reduce(function(acc, component) {
return acc.set(component.get('id'), component)
}, components)
// Add the id of the copied component to it's parent component if it's not a root component
return isRootComponent ? newComponents :
newComponents.updateIn([parentComponent.get('id'), 'contents'], function(contents) {
return contents.insert(contents.indexOf(id) + 1, copiedComponents.first().get('id'))
})
})
.update('contents', function(contents) {
// If it's a root component like a section, then add the id of copied section here
return !isRootComponent ? contents :
contents.insert(contents.indexOf(id) + 1, copiedComponents.first().get('id'))
})
}
/* traverse trough section->container->component to find the first visible chart.
returns true when a chart was found, which break the loop */
var getFirstChartVisibleChart = function (state){
var firstVisibleChart = '-1';
var foundFlag = false;
if(state && state.get('contents')){
state.get('contents').some(function(sectionId){
var section = state.get('components').get(sectionId);
if(section && section.get('contents')){
section.get('contents').some(function(containerId){
var container = state.get('components').get(containerId);
if(container && container.get('contents')){
container.get('contents').some(function(componentId){
var component = state.get('components').get(componentId);
if(component && component.get('type')==='chart'){
firstVisibleChart = componentId;
foundFlag = true;
}
return foundFlag;
})
}
return foundFlag;
})
}
return foundFlag;
})
}
return firstVisibleChart;
}
;;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Utility functions that manipulate generic data structures *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
var DataUtil = {
/* Gets object key/value pairs from a