Created
September 7, 2012 08:21
-
-
Save brucemcpherson/3664315 to your computer and use it in GitHub Desktop.
roadmapper specific stuff
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//create a chart to append to roadmap | |
var cChartContainer = function (rt) { | |
var pScRoot = rt; | |
this.xChart = null; | |
this.root = function () { | |
return pScRoot; | |
}; | |
this.chart = function () { | |
return this.xChart; | |
}; | |
return this; | |
}; | |
cChartContainer.prototype.makeChart = function () { | |
var rt = this.root(); | |
var shp = rt.shape(); | |
if (rt.chartStyle() != SCHARTTYPES.ctNone) { | |
// the size of the chart will be a proportion of the roadmap size in parameter sheet | |
var chtHeight = shp.height() * rt.chartProportion(); | |
// add a panel under the roadamap to contain the chart | |
this.xChart = rt.addShape(SHAPETYPES.stPanel, shp.left(), shp.top() + shp.height(), | |
shp.width(), chtHeight ); | |
// create the google table | |
this.xBuilder = | |
DebugAssert( Charts.newDataTable() , 'failed to create a data table builder'); | |
// add the roadmap data | |
this.chartArray = []; | |
this.makeAxes() | |
.makeSeries(); | |
// transpose if necesay | |
var chartArray = arrayTranspose(this.chartArray); | |
// build a table | |
this.xBuilder.addColumn(Charts.ColumnType.STRING,chartArray[0][0]) ; | |
for (var i=0 ; i < chartArray[0].length ;i++) | |
this.xBuilder.addColumn(Charts.ColumnType.NUMBER, chartArray[0][i+1]) ; | |
// do the rows | |
for (var i=1 ; i < chartArray.length ;i++) { | |
this.xBuilder.addRow( chartArray[i]) ; | |
} | |
// now build it | |
this.xDataTable = DebugAssert( this.xBuilder.build() , 'failed to build a data table'); | |
// create the appropriate type of chart | |
this.xChartBuilder = rt.chartStyle() == SCHARTTYPES.ctShale ? | |
DebugAssert( Charts.newAreaChart(), 'failed to build area chart') : | |
rt.chartStyle() == SCHARTTYPES.ctColumnStacked ? | |
DebugAssert( Charts.newColumnChart(), 'failed to build column chart') : | |
rt.chartStyle() == SCHARTTYPES.ctLine ? | |
DebugAssert( Charts.newLineChart(), 'failed to build Line chart') : | |
DebugAssert (false,'unknown chart type ' + rt.chartStyle()); | |
// tweak it | |
this.xChartObject = this.xChartBuilder | |
.setDataTable(this.xDataTable) | |
.setStacked() | |
.setTitle(rt.text()) | |
.setDimensions(shp.width(), chtHeight) | |
.setLegendPosition(Charts.Position.BOTTOM) | |
.build(); | |
// add it to the panel and commit to the api | |
this.xChart.box().add(this.xChartObject); | |
this.xChart.commit(); | |
} | |
return this; | |
}; | |
cChartContainer.prototype.makeAxes = function () { | |
var oArray = this.root().scaleDates(); | |
//var builder = this.xBuilder; | |
var v = new Array(oArray.length+1); | |
v[0] = "Periods"; | |
for (var i=0; i < oArray.length ; i++){ | |
v[i+1] = oArray[i].finishText; | |
} | |
this.chartArray.push(v); | |
return this; | |
}; | |
cChartContainer.prototype.makeSeries = function ( scParent ) { | |
// its recursive, do the children first | |
var sc = fixOptional (scParent, this.root()); | |
var self = this; | |
sc.children().forEach(function(child){ | |
self.makeSeries( child); | |
} | |
); | |
if (!IsEmpty(sc.cost())) { | |
if ( sc.isData() && sc.cost() ){ | |
// add data for this series | |
self.makeValues(sc); | |
} | |
} | |
}; | |
cChartContainer.prototype.makeValues = function ( sc){ | |
//this calcultes the values based on the rules for treatment of the cost | |
var dsd = this.dLater(sc.activate(), sc.activate(), sc.root().activate()); | |
var dfd = this.dEarlier(sc.deActivate(), sc.deActivate(), sc.root().deActivate()); | |
var dur = dfd - dsd; | |
var bDone = false; | |
var oArray = this.root().scaleDates(); | |
var v = new Array(oArray.length+1); | |
for (i=0; i < oArray.length ; i++){ | |
var sd = oArray[i].start; | |
var fd = oArray[i].finish; | |
var efd = this.dEarlier(fd, sc.deActivate(), fd); | |
var esd = this.dLater(sd, sc.activate(), sd); | |
var p=0; | |
//proportion of annual cost occurring in this period | |
if ( esd > fd || efd < sd || bDone ) { | |
p =0; | |
} | |
else { | |
switch (sc.chartCostTreatment()) { | |
case STREATS.stcAnnual: | |
p = (efd - esd) / (DateSerial(Year(fd), 12, 31) - DateSerial(Year(fd), 1, 1) ); | |
break; | |
case STREATS.stcDuration: | |
p = (efd - esd + 1) / dur; | |
break; | |
case STREATS.stcOneOffStart: | |
p = 1; | |
bDone = True; | |
break; | |
case STREATS.stcOneOffFinish: | |
if (efd < fd ) { | |
p=1; | |
bDone = true; | |
} | |
else { | |
p=0; | |
} | |
break; | |
default: | |
DebugAssert ( false,'unknown chart cost treatment' ); | |
break; | |
} | |
DebugAssert ( p>=0,'some deactivate/activate dates must be reversed' ); | |
} | |
v[i+1] =p* sc.cost(); | |
} | |
v[0] = sc.text(); | |
this.chartArray.push(v); | |
return this; | |
}; | |
cChartContainer.prototype.dEarlier = function (a,b,missing){ | |
var ea = a ? a : missing; | |
var eb = b ? b : missing; | |
return ea > eb ? eb : ea; | |
}; | |
cChartContainer.prototype.dLater = function (a,b,missing){ | |
var ea = a ? a : missing; | |
var eb = b ? b : missing; | |
return ea < eb ? eb : ea; | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// emulate excel shapes by using the UI | |
// public variable holding the panel to contain the shapes | |
var shapePanel; | |
function usePanel() { | |
return shapePanel ? shapePanel : shapePanel = new cUiPanel(); | |
} | |
function showPanel() { | |
usePanel().app().setHeight(usePanel().xMaxHeight).setWidth(usePanel().xMaxWidth); | |
SpreadsheetApp.getActiveSpreadsheet().show(usePanel().app()); | |
return shapePanel; | |
} | |
var cUiPanel = function(){ | |
this.xApp = UiApp.createApplication(); | |
this.app = function(){ | |
return this.xApp; | |
}; | |
this.xAbsolutePanel = this.app().createAbsolutePanel(); | |
this.absolutePanel = function(){ | |
return this.xAbsolutePanel; | |
}; | |
this.app().add(this.absolutePanel()); | |
// keep track of the biggest assigned and tweak the panel later | |
this.xMaxHeight = 0; | |
this.xMaxWidth = 0; | |
}; | |
// a shape will be placed on the absolute panel defined by usePanel().xAbsolutePanel | |
var cShape = function (isaPanel) { | |
this.xIsaPanel = fixOptional(isaPanel,false); | |
this.xShapePanel = usePanel(); | |
this.xBox = this.xIsaPanel ? this.app().createHorizontalPanel() : this.app().createLabel(); | |
this.panel().add(this.box(),0,0); | |
this.xLeft = this.xTop = this.xHeight = this.xWidth =0; | |
this.xVisible = true; | |
return this; | |
}; | |
cShape.prototype.app = function() { | |
return this.shapePanel().app(); | |
}; | |
cShape.prototype.panel = function() { | |
return this.shapePanel().absolutePanel(); | |
}; | |
cShape.prototype.shapePanel = function() { | |
return this.xShapePanel; | |
}; | |
cShape.prototype.box = function() { | |
return this.xBox; | |
}; | |
cShape.prototype.borderCss = function() { | |
return '1px solid gray'; | |
}; | |
cShape.prototype.borderRadiusCss = function() { | |
return this.xRounded ? '5px' : '0px'; | |
}; | |
cShape.prototype.commit = function() { | |
// position and size the box | |
this.box() | |
.setHeight (this.height()) | |
.setWidth(this.width()) | |
.setStyleAttribute('backgroundColor',this.xBackgroundColor) | |
.setStyleAttribute('color',this.xColor) | |
.setStyleAttribute('textAlign',this.xTextAlign) | |
.setStyleAttribute('verticalAlign',this.xVerticalAlign) | |
.setStyleAttribute('fontSize',this.xFontSize) | |
.setStyleAttribute('border',this.borderCss()) | |
.setStyleAttribute('borderRadius',this.borderRadiusCss()); | |
if(!this.xIsaPanel)this.box().setText(this.text()); | |
this.panel().setWidgetPosition(this.box(), this.left() , this.top() ); | |
this.box().setVisible(this.visible()); | |
// adjust the absolute panel so its always big enough with a small % border | |
var smallPercent = 1.05; | |
var x= (this.height() + this.top() ) * smallPercent ; | |
if ( x > this.shapePanel().xMaxHeight) { | |
this.panel().setHeight(this.shapePanel().xMaxHeight = x); | |
} | |
x= (this.width() + this.left())* smallPercent; | |
if ( x > this.shapePanel().xMaxWidth) { | |
this.panel().setWidth(this.shapePanel().xMaxWidth = x); | |
} | |
return this; | |
}; | |
cShape.prototype.left = function() { | |
return this.xLeft; | |
}; | |
cShape.prototype.visible = function() { | |
return this.xVisible; | |
}; | |
cShape.prototype.top = function() { | |
return this.xTop; | |
}; | |
cShape.prototype.height = function() { | |
return this.xHeight; | |
}; | |
cShape.prototype.width = function() { | |
return this.xWidth; | |
}; | |
cShape.prototype.text = function() { | |
return this.xText; | |
}; | |
cShape.prototype.box = function() { | |
return this.xBox; | |
}; | |
cShape.prototype.setHeight = function(height) { | |
this.xHeight = height; | |
return this; | |
}; | |
cShape.prototype.setWidth = function(width) { | |
this.xWidth = width; | |
return this; | |
}; | |
cShape.prototype.setLeft = function(left) { | |
this.xLeft = left; | |
return this; | |
}; | |
cShape.prototype.setTop = function(top) { | |
this.xTop = top; | |
return this; | |
}; | |
cShape.prototype.setText = function(text) { | |
this.xText = text; | |
return this; | |
}; | |
cShape.prototype.setRounded = function(b) { | |
this.xRounded = fixOptional(b,true); | |
return this; | |
}; | |
cShape.prototype.setVisible = function(b) { | |
this.xVisible = fixOptional(b,true); | |
return this; | |
}; | |
cShape.prototype.setBackgroundColor = function(c) { | |
this.xBackgroundColor = c; | |
return this; | |
}; | |
cShape.prototype.setColor = function(c) { | |
this.xColor = c; | |
return this; | |
}; | |
cShape.prototype.setVerticalAlign = function(c) { | |
this.xVerticalAlign = c; | |
return this; | |
}; | |
cShape.prototype.setTextAlign = function(c) { | |
this.xTextAlign = c; | |
return this; | |
}; | |
cShape.prototype.setFontSize = function(c) { | |
this.xFontSize = c; | |
return this; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var cShapeContainer = function() { | |
var pChildren = new collection(); | |
this.children = function() { | |
return pChildren; | |
}; | |
this.xSerial = 0; | |
this.serial = function() { | |
return this.xSerial; | |
}; | |
this.xParent = null; | |
this.parent = function(){ | |
return this.xParent; | |
} | |
}; | |
cShapeContainer.prototype.debug = function (s){ | |
this.children().forEach( | |
function (sc) { | |
DebugPrint(fixOptional(s,''),'child',sc.id(),'target',sc.target(),'myspace',sc.mySpace(),sc.text()); | |
sc.debug('parent:'+sc.id()); | |
} | |
); | |
} | |
// create-- | |
cShapeContainer.prototype.create = function(rt,pr,rplot,dss){ | |
this.xScType = isUndefined(this.xDataRow=pr) ? SCTYPES.sctframe : SCTYPES.sctdata; | |
this.scType = function() { | |
return this.xScType; | |
}; | |
this.dataRow = function() { | |
return this.xDataRow; | |
}; | |
DebugAssert ( (this.xDataRow && this.xScType == SCTYPES.sctdata) || | |
(!this.xDataRow && this.xScType == SCTYPES.sctframe),'mismatchdata/type'); | |
this.xRoot = rt; | |
this.root = function() { | |
return this.xRoot; | |
}; | |
this.xWhere = rplot; | |
this.where = function() { | |
return this.xWhere; | |
}; | |
this.xDsets = dss; | |
this.xSerial = (this.root().xSerial ++); | |
return this; | |
}; | |
cShapeContainer.prototype.valid = function() { | |
return this.parent(); | |
}; | |
cShapeContainer.prototype.dSets = function() { | |
return this.root().xDsets; | |
}; | |
cShapeContainer.prototype.plot = function() { | |
return this.where(); | |
}; | |
// target..if parent is confirmed use the parents ID, otherwise go to the data. If blank, use the frame as the target | |
cShapeContainer.prototype.target = function() { | |
var s; | |
return this.valid() ? | |
this.parent().id() : | |
(s=this.fetchKey("Target")) ? | |
s : | |
this.root().id(); | |
}; | |
cShapeContainer.prototype.isFrame =function () { | |
return this.scType()==SCTYPES.sctframe; | |
}; | |
cShapeContainer.prototype.isData =function() { | |
return this.scType()==SCTYPES.sctdata; | |
}; | |
cShapeContainer.prototype.assertData = function(sExtra) { | |
if (this.dataRow()) { | |
DebugAssert( this.isData() ,'expected a data item ' +fixOptional(sExtra,'')); | |
} | |
else { | |
DebugAssert ( this.isFrame(),'expected a frame ' +fixOptional(sExtra,'')); | |
} | |
return this.dataRow(); | |
}; | |
cShapeContainer.prototype.isRounded = function(s) { | |
return s == SHAPETYPES.stRoundedRectangularCallout || s == SHAPETYPES.stRoundedRectangle; | |
} | |
cShapeContainer.prototype.whichShape = function(s) { | |
switch(LCase(s)) { | |
case "pentagon" : | |
return SHAPETYPES.stPentagon; | |
case "rectangle": | |
return SHAPETYPES.stRectangle; | |
case "rounded rectangle": | |
return SHAPETYPES.stRoundedRectangle; | |
case "chevron": | |
return SHAPETYPES.stChevron; | |
case "notched right arrow": | |
return SHAPETYPES.stNotchedRightArrow; | |
case "right arrow": | |
return SHAPETYPES.stRightArrow; | |
case "right arrow callout": | |
return SHAPETYPES.stRightArrowCallout; | |
case "rectangular callout": | |
return SHAPETYPES.stRectangularCallout; | |
case "rounded rectangular callout": | |
return SHAPETYPES.stRoundedRectangularCallout; | |
case "none": | |
return SHAPETYPES.stNone; | |
case "line callout accent bar": | |
return SHAPETYPES.stLineCallout2AccentBar; | |
default: | |
MsgBox ("Used default - cant find shape " + s); | |
return SHAPETYPES.stDefault; | |
} | |
}; | |
cShapeContainer.prototype.startScale = function() { | |
return this.xStartScale; | |
}; | |
cShapeContainer.prototype.finishScale = function() { | |
return this.xFinishScale; | |
}; | |
cShapeContainer.prototype.paramStartDate = function() { | |
var s = makeKey(this.paramCell("containers", "start date", "value").toString()); | |
var dsmallest=0; | |
if (s == "automatic" ) { | |
this.dSets().dataSet("data").rows().forEach( | |
function(dr,drIndex){ | |
var d = dr.value("activate"); | |
if((d && d < dsmallest) || !dsmallest) { | |
dsmallest=d; | |
} | |
} | |
); | |
return dsmallest; | |
} | |
else { | |
return this.param("containers", "start date", "value"); | |
} | |
}; | |
cShapeContainer.prototype.paramFinishDate = function() { | |
var s = makeKey(this.paramCell("containers", "finish date", "value").toString()); | |
var dbiggest=0; | |
if (s == "automatic" ) { | |
this.dSets().dataSet("data").rows().forEach( | |
function(dr,drIndex){ | |
var d = dr.value("deactivate"); | |
if((d && d > dbiggest) || !dbiggest) { | |
dbiggest=d; | |
} | |
} | |
); | |
return dbiggest; | |
} | |
else { | |
return this.param("containers", "finish date", "value"); | |
} | |
}; | |
cShapeContainer.prototype.activate = function() { | |
if (this.isFrame()) { | |
return this.startScale() ? this.startScale() : this.paramStartDate(); | |
} | |
else { | |
var mind = this.root().activate(); | |
return this.dateGiven('activate') ? | |
(this.fieldData("activate") < mind ? | |
mind : | |
this.fieldData("activate") ) : | |
mind; | |
} | |
}; | |
cShapeContainer.prototype.deActivate = function() { | |
if (this.isFrame()) { | |
return this.finishScale() ? this.finishScale() : this.paramFinishDate(); | |
} | |
else { | |
var maxd = this.root().deActivate(); | |
return this.dateGiven('deActivate') ? | |
(this.fieldData("deActivate") > maxd ? | |
maxd : this.fieldData("deActivate") ) : | |
maxd; | |
} | |
}; | |
cShapeContainer.prototype.text = function() { | |
return this.assertData('text') ? this.dataRow().toString("Description") : this.paramTitle(); | |
}; | |
cShapeContainer.prototype.calloutText = function() { | |
return this.assertData('callOutText') ? this.dataRow().toString("Callout") : ''; | |
}; | |
cShapeContainer.prototype.sequence = function() { | |
return this.fieldData("sequence"); | |
}; | |
cShapeContainer.prototype.cost = function() { | |
var x = this.fieldData("cost"); | |
return x ? x : 0 ; | |
}; | |
cShapeContainer.prototype.custom = function() { | |
return this.fieldData("custom"); | |
}; | |
cShapeContainer.prototype.fieldData = function(s) { | |
return this.assertData() ? this.dataRow().value(s) : null ; | |
}; | |
cShapeContainer.prototype.dateGiven = function(s) { | |
return IsDate( this.fieldData(s)); | |
}; | |
cShapeContainer.prototype.fetchKey = function(s) { | |
return this.isData() ? | |
makeKey(this.dataRow().value(s)) : | |
ECONSTANTS.frameID ; | |
}; | |
cShapeContainer.prototype.paramShapeCalloutTemplate = function(s) { | |
return this.assertData('paramShapeCalloutTemplate') ? this.fixupCustomCell("callout format").where() : null ; | |
}; | |
cShapeContainer.prototype.fixupCustomCell = function(sid) { | |
var cc= this.paramCustomCell(sid); | |
return cc ? cc : this.timeBasedRow().cell(sid) ; | |
}; | |
cShapeContainer.prototype.id = function() { | |
return this.fetchKey("id"); | |
}; | |
cShapeContainer.prototype.myWidth = function() { | |
return this.isData() ? | |
this.root().shape().width() * this.duration() / this.root().duration() : | |
this.paramFrameWidth(); | |
}; | |
cShapeContainer.prototype.myLeft = function() { | |
return this.root().shape() ? | |
this.root().shape().left() + | |
(this.activate()- this.root().activate() ) / | |
this.root().duration() * this.root().shape().width() : | |
this.paramFrameLeft(); | |
}; | |
cShapeContainer.prototype.duration = function() { | |
return this.deActivate() - this.activate(); | |
}; | |
cShapeContainer.prototype.paramGap = function() { | |
return this.fixupCustomCell("gap").value(); | |
}; | |
cShapeContainer.prototype.paramCalloutHeightAbove = function() { | |
return this.fixupCustomCell("callout % height").value(); | |
}; | |
cShapeContainer.prototype.paramCalloutMaxWidth = function() { | |
return this.fixupCustomCell("callout % width").value(); | |
}; | |
cShapeContainer.prototype.paramCalloutPosition = function() { | |
return this.fixupCustomCell("callout position").value(); | |
}; | |
cShapeContainer.prototype.paramExpansion = function() { | |
return isStringTrue(this.fixupCustomCell("allow expansion").value()); | |
}; | |
cShapeContainer.prototype.timeBasedRow = function() { | |
// try to figure out characteristics for a shape based on dates | |
var self = this; | |
var rdr = null; | |
this.dSets().dataSet("roadmap colors").rows().forEach( | |
function (dr) { | |
var sd = dr.value("decommission from"); | |
var fd = dr.value("decommission to"); | |
var datafd = self.deActivate(); | |
if ( (!self.dateGiven("deactivate") && (!sd || !fd)) || | |
(datafd >= sd && datafd <= fd) ){ | |
return(rdr = dr); | |
} | |
} | |
); | |
if(!rdr) | |
MsgBox ("Could not find time based parameter for deactivate date " + | |
CStr(self.deActivate()) + " ID " + self.id()); | |
return rdr; | |
}; | |
cShapeContainer.prototype.paramHeight = function() { | |
return this.fixupCustomCell("height").value(); | |
}; | |
cShapeContainer.prototype.paramFrameWidth = function() { | |
return this.param("containers", "width", "value"); | |
}; | |
cShapeContainer.prototype.paramFrameLeft = function() { | |
return this.param("containers", "left", "value"); | |
}; | |
cShapeContainer.prototype.paramFrameTop = function() { | |
return this.param("containers", "top", "value"); | |
}; | |
cShapeContainer.prototype.paramTitle = function() { | |
return this.param("options", "title", "value"); | |
}; | |
cShapeContainer.prototype.paramExpansion = function() { | |
return isStringTrue(this.fixupCustomCell("allow expansion").value()); | |
}; | |
cShapeContainer.prototype.paramShapeTemplate = function(s) { | |
return this.assertData('paramShapeTemplate') ? | |
this.fixupCustomCell("format").where() : | |
this.paramRange("containers", "frame", "format"); | |
}; | |
cShapeContainer.prototype.paramShapeType = function() { | |
return this.isFrame() ? | |
this.whichShape(this.param("containers", "frame", "value")): | |
this.whichShape(this.fixupCustomCell("shape").toString()); | |
}; | |
cShapeContainer.prototype.paramShapeCalloutType = function() { | |
var cc; | |
return this.isData() ? | |
(cc = this.fixupCustomCell("callout") ? whichShape(cc.toString()) : SHAPETYPES.stNone) : | |
SHAPETYPES.stNone | |
}; | |
cShapeContainer.prototype.chartProportion = function() { | |
return this.param("options", "chart proportion", "value"); | |
}; | |
cShapeContainer.prototype.paramYesNo = function(dsn,rid,sid) { | |
return isStringTrue(this.param(dsn, rid, sid)); | |
}; | |
cShapeContainer.prototype.param = function(dsn,rid,sid) { | |
return this.paramCell(dsn, rid, sid).value(); | |
}; | |
cShapeContainer.prototype.paramCell = function(dsn,rid,sid) { | |
return this.dSets().dataSet(makeKey(dsn)).cell(rid,sid); | |
}; | |
cShapeContainer.prototype.paramRange = function(dsn,rid,sid) { | |
return this.paramCell(dsn,rid,sid).where(); | |
}; | |
cShapeContainer.prototype.chartProportion = function() { | |
return (this.param("options", "chart proportion", "value")); | |
}; | |
// how many branches in my tree | |
cShapeContainer.prototype.treeLength = function() { | |
var ht = 1; | |
this.children().forEach( | |
function (sc) { | |
ht += sc.treeLength(); | |
} | |
); | |
return ht; | |
} | |
cShapeContainer.prototype.myGapAfterMe = function() { | |
return this.paramGap(); | |
}; | |
cShapeContainer.prototype.myGapBeforeChildren = function() { | |
return this.children().count() ? this.paramGap() : 0; | |
}; | |
cShapeContainer.prototype.myExpansion = function() { | |
return this.paramExpansion() ? true : this.biggestBranch() > 1 ; | |
}; | |
cShapeContainer.prototype.mySpace = function() { | |
var ht = 0; | |
if (!this.children().count()){ | |
ht= this.paramHeight() + this.myGapAfterMe(); | |
} | |
else { | |
if (this.myExpansion()){ | |
ht += this.myGapBeforeChildren(); | |
this.children().forEach( | |
function (sc,scIndex) { | |
ht += sc.mySpace(); | |
} | |
); | |
ht += this.myGapAfterMe(); | |
} | |
else | |
ht = this.paramHeight() + this.myGapAfterMe(); | |
} | |
return ht; | |
}; | |
cShapeContainer.prototype.myShapeHeight = function() { | |
return this.mySpace() - this.myGapAfterMe(); | |
}; | |
cShapeContainer.prototype.biggestBranch = function() { | |
var ht = this.children().count(); | |
this.children().forEach( | |
function (sc,scIndex) { | |
if (( t = sc.biggestBranch()) > ht) ht = t; | |
} | |
); | |
return ht; | |
}; | |
cShapeContainer.prototype.find = function(vId) { | |
var scFound = this.childExists(vId); | |
if (!scFound) { | |
this.children().forEach( | |
function (sc,scIndex) { | |
if (!scFound) scFound = sc.find(vId); | |
} | |
); | |
} | |
return scFound; | |
}; | |
cShapeContainer.prototype.childExists = function(vId,complain) { | |
return this.children().item(vId,fixOptional(complain,false)); | |
} | |
cShapeContainer.prototype.paramCustomCell = function(sValue,complain) { | |
var sCustom = this.fieldData("Custom"); | |
if (sCustom) { | |
var p = this.paramCell("Custom Bars", sCustom, sValue); | |
if (!p && fixOptional (complain,true)) { | |
MsgBox ("could not find custom format definition |" + | |
sCustom + "|" + sValue + "| in parameter sheet"); | |
} | |
return p; | |
} | |
}; | |
cShapeContainer.prototype.shape = function() { | |
return this.xShape; | |
}; | |
cShapeContainer.prototype.shapeType = function() { | |
return this.xShapeType; | |
}; | |
cShapeContainer.prototype.shapeTemplate = function(shp,ptWhere) { | |
var where = !isUndefined(ptWhere) ? ptWhere : this.paramShapeTemplate(); | |
var sh = fixOptional(shp,this.shape()); | |
// minimize api calls again | |
var wn = WorkSheetName(WorkSheet(where)); | |
var rn = where.getRow(); | |
var cn = where.getColumn(); | |
sh.setBackgroundColor(sheetCache(wn,'getBackgroundColors').getValue(rn,cn)); | |
sh.setColor(sheetCache(wn,'getFontColors').getValue(rn,cn)); | |
sh.setTextAlign(sheetCache(wn,'getHorizontalAlignments').getValue(rn,cn)); | |
sh.setVerticalAlign(sheetCache(wn,'getVerticalAlignments').getValue(rn,cn)); | |
sh.setFontSize(sheetCache(wn,'getFontSizes').getValue(rn,cn)) ; | |
return this; | |
} | |
cShapeContainer.prototype.makeShape = function (xTop) { | |
var self = this; | |
// this would be a frame | |
if (isUndefined(xTop)) xTop = self.paramFrameTop() + this.paramHeight() / 2;; | |
// none shapes are made invisible but take space | |
if ((this.xShapeType = self.paramShapeType()) == SHAPETYPES.stNone) | |
this.xShapeType = SHAPETYPES.stDefault; | |
//this is the most complex part - creating the shapes of the correct size and placing them in the right spot | |
self.xShape = self.addShape(this.xShapeType,self.paramFrameLeft(),xTop, self.paramFrameWidth(), this.myShapeHeight()); | |
self.shapeTemplate(); | |
self.shape() | |
.setText(self.text()) | |
.setVisible (self.paramShapeType() != SHAPETYPES.stNone); | |
if (self.isData()){ | |
self.shape() | |
.setLeft(self.myLeft()) | |
.setWidth(self.myWidth()); | |
} | |
self.shape().commit(); | |
//this is where it gets tricky | |
var xNextTop = self.shape().top(); | |
if(this.myExpansion()) { | |
//if we are allowing expansion of targets then need to make a gap to accommodate my children | |
xNextTop += self.myGapBeforeChildren(); | |
} | |
self.children().forEach( | |
function (sc) { | |
sc.makeShape(xNextTop); | |
xNextTop += sc.mySpace(); | |
} | |
); | |
}; | |
cShapeContainer.prototype.springClean=function(){ | |
DebugAssert (this.scType() == SCTYPES.sctframe, 'spring clean should start at root'); | |
this.associate(); | |
// probably dont need this in gapps | |
//deleteAllShapes Plot, nameStub | |
}; | |
cShapeContainer.prototype.associate=function(){ | |
//one off reassociation of items from the root to be children of their target | |
//no need for recursion since to start with all are associated with the top level frame | |
var self = this; | |
self.children().forEach( | |
function (scParent) { | |
self.children().forEach( | |
function (scChild,i) { | |
if(scChild.target() == scParent.id()){ | |
scParent.children().add (scChild, scChild.id()); | |
//confirm the parent as found | |
scChild.xParent = scParent; | |
} | |
} | |
) | |
} | |
); | |
//now all we need to do is clean up the children of the frame | |
for (var n = this.children().count(); n > 0 ; n--) { | |
var scChild = this.children().item(n); | |
if (!scChild.valid()) { | |
//we get here because we didnt find a target yet | |
if(scChild.target() != this.id()) { | |
//and it wasnt the frame.. so | |
MsgBox("Did not find target " + scChild.target() + " for ID " + scChild.id()); | |
} | |
// confirm the frame as the parent | |
scChild.xParent = this; | |
} | |
else { | |
//remove from the frames children as already now child of someone else | |
this.children().remove (n); | |
} | |
//belt and braces | |
DebugAssert (scChild.valid(),'logic failure in associate'); | |
} | |
return this; | |
}; | |
cShapeContainer.prototype.createScale = function(){ | |
var tickType = makeKey(this.param("containers", "ticks", "value")); | |
if (tickType == "automatic") tickType = this.autoScale(tickType); | |
return ( this.xScaleDates = this.createTicks(tickType)); | |
}; | |
cShapeContainer.prototype.scaleDates = function(){ | |
return this.xScaleDates; | |
} | |
cShapeContainer.prototype.createTicks = function(s){ | |
var oTicks = this.limitofScale(s, this.activate(), this.deActivate()); | |
//patch in new scale | |
this.xStartScale = oTicks.start; | |
this.xFinishScale = oTicks.finish; | |
var p = this.paramFrameLeft(); | |
var dheight = this.paramHeight(); | |
var ft = this.paramRange("containers", "ticks", "format"); | |
var ftop = this.paramFrameTop() ; | |
if (ftop < 0) { | |
MsgBox ("not enough room for scale - try changing the top parameter"); | |
ftop = 0; | |
} | |
var tScaleDates = []; | |
var xcds = oTicks.start; | |
var ticks=0; | |
var p =this.paramFrameLeft(); | |
while (xcds < oTicks.finish) { | |
ticks++; | |
DebugAssert( ticks <= ECONSTANTS.maxTicks, | |
"no room to show scale " + s + " :choose another scale"); | |
// get end date of the tick for this start date | |
var oSub = this.limitofScale(s, xcds, xcds); | |
DebugAssert (oSub.finish > xcds, ' got confused calculating scale'); | |
var w = this.myWidth() * (oSub.finish-oSub.start) / (oTicks.finish-oTicks.start+.0001); | |
var shp = this.addShape(SHAPETYPES.stRectangle, p, ftop, w, dheight) | |
.setText(oSub.finishText); | |
this.shapeTemplate(shp,ft); | |
shp.commit(); | |
p += shp.width(); | |
tScaleDates.push( oSub); | |
xcds = addDays(oSub.finish, 1); | |
} | |
if (ticks){ | |
return tScaleDates; | |
} | |
} | |
cShapeContainer.prototype.autoScale = function(){ | |
DebugAssert( this.scType() == SCTYPES.sctframe,'unexpected type wants dates'); | |
var idealTicks = ECONSTANTS.maxTicks * 0.5; | |
var tickDiff = ECONSTANTS.maxTicks + 1; | |
var s; | |
var sBest ='unknown'; | |
for ( s in {"weeks":0, "months":0, "quarters":0, "halfyears":0,"years":0} ) { | |
var ob = this.limitofScale(s, this.activate(), this.deActivate()); | |
if( Abs(idealTicks - ob.ticks) < tickDiff ){ | |
sBest = s; | |
tickDiff = Abs(idealTicks - ob.ticks); | |
} | |
} | |
DebugAssert( tickDiff <= ECONSTANTS.maxTicks,"Couldnt find a feasible automatic scale to use for roadmap"); | |
return sBest; | |
}; | |
cShapeContainer.prototype.limitofScale = function(s, sd, fd) { | |
var obj = {}; | |
obj['start'] = null; | |
obj['finish'] = null; | |
obj['ticks'] = 0; | |
obj['finishText'] = ''; | |
obj['startText'] = ''; | |
switch (s) { | |
case "weeks": | |
obj.start = addDays(sd, -(sd.getDay() % 7), -1); | |
obj.finish = addDays(fd, 6 - (fd.getDay() % 7), 1); | |
obj.ticks = Math | |
.floor((1 + daysDiff(obj.start, obj.finish)) / 7); | |
obj.startText = niceDate(obj.start); | |
obj.finishText = niceDate(obj.finish); | |
return obj; | |
case "months": | |
obj.start = new Date(sd.getFullYear(), sd.getMonth(), 1); | |
obj.finish = addDays(new Date(fd.getFullYear(), | |
fd.getMonth() + 1, 1), -1, 1); | |
obj.ticks = Math | |
.floor((1 + daysDiff(obj.start, obj.finish)) / 30); | |
obj.startText = ECONSTANTS.mths[obj.start.getMonth()] + '-' | |
+ padYear(obj.start); | |
obj.finishText = ECONSTANTS.mths[obj.finish.getMonth()] + '-' | |
+ padYear(obj.finish); | |
return obj; | |
case "quarters": | |
obj.start = new Date(sd.getFullYear(), sd.getMonth() | |
- (sd.getMonth() % 3), 1); | |
obj.finish = addDays(new Date(fd.getFullYear(), fd.getMonth() | |
+ 3 - (fd.getMonth() % 3), 1), -1, 1); | |
obj.ticks = Math | |
.floor((1 + daysDiff(obj.start, obj.finish)) / 90); | |
obj.startText = 'Q' | |
+ (1 + Math.floor(obj.start.getMonth() / 3)).toString() | |
+ padYear(obj.start); | |
obj.finishText = obj.startText; | |
return obj; | |
case "halfyears": | |
obj.start = new Date(sd.getFullYear(), sd.getMonth() | |
- (sd.getMonth() % 6), 1); | |
obj.finish = addDays(new Date(fd.getFullYear(), fd.getMonth() | |
+ 6 - (fd.getMonth() % 6), 1), -1, 1); | |
obj.ticks = Math | |
.floor((1 + daysDiff(obj.start, obj.finish)) / 183); | |
obj.startText = 'H' | |
+ (1 + Math.floor(obj.start.getMonth() / 6)).toString() | |
+ padYear(obj.start); | |
obj.finishText = obj.startText; | |
return obj; | |
case "years": | |
obj.start = new Date(sd.getFullYear(), 0, 1); | |
obj.finish = addDays( | |
new Date(fd.getFullYear() + 1, 0, 1), -1, 1); | |
obj.ticks = Math | |
.floor((1 + daysDiff(obj.start, obj.finish)) / 365); | |
obj.startText = obj.start.getFullYear().toString(); | |
obj.finishText = obj.startText; | |
return obj; | |
default: | |
DebugAssert(false, 'unknown scale ' + s); | |
return null; | |
} | |
}; | |
cShapeContainer.prototype.needSwapBar = function(y) { | |
var sorder = LCase(this.param("options", "sort bar order", "value")); | |
var self = this; | |
var bAscending = ( sorder == "ascending"); | |
if (sorder != "none") { | |
var mOrder = LCase(self.param("options", "sort bars by", "value")); | |
switch(mOrder){ | |
case "original": | |
return (self.serial() > y.serial() && bAscending) || | |
(self.serial() < y.serial() && !bAscending); | |
case "sequence": | |
return (self.sequence() > y.sequence() && bAscending) || | |
(self.sequence() < y.sequence() && !bAscending); | |
case "activate": | |
return (self.activate() > y.activate() && bAscending) || | |
(self.activate() < y.activate() && !bAscending); | |
case "deActivate": | |
return (self.deActivate() > y.deActivate() && bAscending) || | |
(self.deActivate() < y.deActivate() && !bAscending); | |
case "duration": | |
return (self.duration() > y.duration() && bAscending) || | |
(self.duration() < y.duration() && !bAscending); | |
case "id": | |
return (self.id() > y.id() && bAscending) || | |
(self.id() < y.id() && !bAscending); | |
case "description": | |
return (self.description() > y.id() && bAscending) || | |
(self.description() < y.id() && !bAscending); | |
default: | |
DebugAssert(false,'unknown sort order ' +mOrder); | |
} | |
} | |
return false; | |
}; | |
function needSwap (x,y) { | |
var xLen = x.treeLength(); | |
var yLen = y.treeLength(); | |
var sorder = x.param("options", "sort target popularity", "value"); | |
var bSwapBar = x.needSwapBar(y); | |
switch(LCase(sorder)) { | |
case "ascending popularity": | |
return (xLen < yLen) || (xLen == yLen && bSwapBar); | |
case "ascending popularity": | |
return (xLen > yLen) || (xLen == yLen && bSwapBar); | |
case "no popularity sort": | |
return bSwapBar; | |
default: | |
DebugAssert(false,'unknown popularity sort ' + sorder); | |
} | |
return false; | |
}; | |
cShapeContainer.prototype.sortChildren = function(){ | |
if (this.children().count()) { | |
this.children().forEach( | |
function (sc) { | |
sc.sortChildren (needSwap); | |
} | |
); | |
this.children().sort(needSwap); | |
} | |
}; | |
cShapeContainer.prototype.addShape = | |
function (shapeType,shapeLeft,shapeTop,shapeWidth,shapeHeight){ | |
return (new cShape(shapeType == SHAPETYPES.stPanel)) | |
.setWidth(shapeWidth) | |
.setHeight(shapeHeight) | |
.setTop(shapeTop) | |
.setLeft(shapeLeft) | |
.setRounded(this.isRounded(shapeType)); | |
}; | |
cShapeContainer.prototype.makeChart = function () { | |
this.xChartContainer = new cChartContainer(this.root()); | |
this.xChartContainer.makeChart(); | |
return this; | |
}; | |
cShapeContainer.prototype.chartStyle = function () { | |
switch(LCase(this.param("options", "chart style", "value"))) { | |
case "shale": | |
return SCHARTTYPES.ctShale | |
case "column stacked": | |
return SCHARTTYPES.ctColumnStacked | |
case "line": | |
return SCHARTTYPES.ctLine | |
default: | |
return SCHARTTYPES.ctDefault | |
} | |
}; | |
cShapeContainer.prototype.chartCostTreatment = function () { | |
switch(LCase(this.fixupCustomCell("chart cost treatment").toString())) { | |
case "annual": | |
return STREATS.stcAnnual | |
case "duration": | |
return STREATS.stcDuration | |
case "one off at start": | |
return STREATS.stcOneOffStart | |
case "one off at finish": | |
return STREATS.stcOneOffFinish | |
default: | |
return STREATS.stcDefault | |
} | |
}; | |
cShapeContainer.prototype.groupContainers = function () { | |
showPanel(); | |
//todo | |
}; | |
cShapeContainer.prototype.traceability = function () { | |
//todo | |
}; | |
cShapeContainer.prototype.doShapeCallouts = function () { | |
//TODO | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function actRoadMapper(wp,wd){ | |
//where the parameters are | |
var rParam = wholeSheet (fixOptional (wp, "Parameters")); | |
// automatically find where the data is | |
var rData = getLikelyColumnRange(Sheets(wd)); | |
// get the data and the parameters | |
var dSets = new cDataSets(); | |
dSets.create(); | |
dSets.init(rData,undefined , "data"); | |
dSets.init(rParam,undefined ,undefined , true, "roadmap colors"); | |
dSets.init(rParam,undefined ,undefined , true, "containers"); | |
dSets.init(rParam,undefined ,undefined , true, "options"); | |
dSets.init(rParam,undefined ,undefined , true, "custom bars"); | |
var ds = dSets.dataSet("data"); | |
if (!ds.where()) | |
MsgBox ("No data to process"); | |
else { | |
//check we have fields we need | |
if(ds.headingRow().validate(true, "Activate", "Deactivate", "ID", "Target", "Description")){ | |
var rplot = rangeExists(dSets.dataSet("options").cell("frameplot", "value").toString()); | |
if (rplot) doTheMap(dSets, rplot); | |
} | |
} | |
} | |
function doTheMap(dSets, rplot) { | |
var scRoot = new cShapeContainer(); | |
scRoot.create (scRoot,undefined,rplot,dSets); | |
// create s node for each data row | |
dSets.dataSet("data").rows().forEach( | |
function (dr,drItem) { | |
var sc = scRoot.find(dr.toString("ID")); | |
if (!sc) { | |
sc = new cShapeContainer(); | |
sc.create (scRoot, dr); | |
scRoot.children().add (sc, sc.id()); | |
} | |
else { | |
MsgBox (sc.id() + " is a duplicate - skipping"); | |
} | |
} | |
); | |
// clean up the structure and associate ids to targets | |
scRoot.springClean(); | |
scRoot.createScale(); | |
scRoot.sortChildren(); | |
// make the shapes | |
scRoot.makeShape(); | |
// do the chart if there is one | |
scRoot.doShapeCallouts(); | |
scRoot.makeChart(); | |
// add traceability data to each shape | |
scRoot.traceability(); | |
// group everything | |
scRoot.groupContainers(); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment