Slide menu



html

<script src='//cdn.muicss.com/mui-0.4.6/js/mui.js'></script>

<style>

#sidedrawer {
  position: fixed;
  top: 0;
  bottom: 0;
  width: 300px;
  left: -300px;
  overflow: auto;
  z-index: 2;
  background-color: #fff;
  border:1px solid lightblue;
  top:27px;
  transition: transform 0.2s;
}

@media (min-width: 768px) {
  #sidedrawer {
    transform: translate(300px);
  }

  body.hide-sidedrawer #sidedrawer {
    transform: translate(0px);
  }
}

#sidedrawer ul {
  list-style: none;
}

#sidedrawer > ul {
  padding-left: 0px;
}

#sidedrawer > ul > li:first-child {
  padding-top: 15px;
}

#sidedrawer strong {
  display: block;
  padding: 15px 22px;
  cursor: pointer;
}

#sidedrawer strong:hover {
  background-color: #E0E0E0;
}

#sidedrawer strong + ul > li {
  padding: 6px 0px;
}

.closebtn{
  cursor: pointer;
}
</style>

<b  class="closebtn js-hide-sidedrawer"><font size=4>☰</font></b>  click the hamburger to to open the menu

<div id="sidedrawer" class="mui--no-user-select">
click the top right button to close the menu 
 <div style='float:right;cursor:hand;font-size:20px' class="closebtn js-hide-sidedrawer">☒</div>
<div class="mui-divider"></div> 
<ul>
  <li class="active">
    <strong>Options</strong> 
    <ul> 
      <li>This is a List Box filter </li>
      <li><SpotfireControl id="8e46dc29e17746e38b69ed088933e1fc" /></li>
    </ul>
  </li>
  <li>
    <strong>More Options</strong>
    <ul> 
      <li>This is a Check Box filter</li>
      <li><SpotfireControl id="a5d461b193084a338c6b4c4d0cfb0cea" /></li>
    </ul>
  </li>
</ul> 
</div>
</div>
</div>


js

jQuery(function($) {
  var $bodyEl = $('body'),
      $sidedrawerEl = $('#sidedrawer');
  
  
  function showSidedrawer() {
    // show overlay
    var options = {
      onclose: function() {
        $sidedrawerEl
          .removeClass('active')
          .appendTo(document.body);
      }
    };
    
    var $overlayEl = $(mui.overlay('on', options));
    
    // show element
    $sidedrawerEl.appendTo($overlayEl);
    setTimeout(function() {
      $sidedrawerEl.addClass('active');
    }, 20);
  }
  
  
  function hideSidedrawer() {
    $bodyEl.toggleClass('hide-sidedrawer');
  }
  
  
  $('.js-show-sidedrawer').on('click', showSidedrawer);
  $('.js-hide-sidedrawer').on('click', hideSidedrawer);
  
  
  var $titleEls = $('strong', $sidedrawerEl);
  
  $titleEls
    .next()
    .hide();
  
  $titleEls.on('click', function() {
    $(this).next().slideToggle(200);
  });
});


Change Scatterplot Shape

from Spotfire.Dxp.Application.Visuals  import ScatterPlot,MarkerShape,MarkerType,CategoryKey

vis = v.As[ScatterPlot]()

#params
shape = Document.Properties["SHAPE"]
category = Document.Properties["CATEGO"]

markerType = getattr(MarkerType,shape)
vis.ShapeAxis.ShapeMap[category] = MarkerShape(markerType)

#list available shapes to copy and add data table from clipboard to populate shape dropdown:
shapes = [a for a in dir(MarkerType) if  not callable(getattr(MarkerType,a)) and not '__' in a ]
for x in shapes:print x



Trigger javascript from Dropdown-list property control (7.0+)

Trigger a javascript function after a dropdown property control changes. To trigger an ironPython, just associate a script on the document property.


html
<span id='dropDownDocProp'>
<SpotfireControl id="6d69ba781efa4b93ab8aaa1857a45d2d" />
</span>

javascript
MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

//this is the target element to monitor changes
//just put the span id here. You can remove next line and add a script param called targetDomId
var targetDomId = "dropDownDocProp"

//function when dropdown value changes
var myFunction = function(oldValue,newValue){
alert("old value:["+oldValue+"]\nnew value:["+newValue+"]")
                //click on a actioncontrol with python script
}


//no need to change after this line.
var target = document.getElementById(targetDomId)

//callback is the function to trigger when target changes
var oldVal = target.innerText.trim()
var callback = function(mutations) {
newVal=$('#'+targetDomId+' .ComboBoxTextDivContainer').text()
if(newVal!=oldVal) myFunction(oldVal,newVal)
oldVal = newVal;
}

//this is to glue these two together
var observer = new MutationObserver(callback);

var opts = {
    childList: true, 
    attributes: true, 
    characterData: true, 
    subtree: true
}

observer.observe(target,opts);


Note: To trigger ironPython script, just associate the script with the document property

Checkbox property control


With this approach you can have a checkbox that can trigger javascript and python scripts:



The calculated value click action toggles a Boolean document property. When this Boolean doc property changes it triggers the iron python script.

The javascript is triggered by adding a click event handler


Explanation


  1. Calculated value has a custom expression to show a checkbox character marked when document property is on or off:

      If(${booleanDocProp},"☒","☐")

  2. Calculated value has a script associated to run a python script when clicked. The script changes the state of the document property:

    Document.Properties['booleanDocProp'] = not Document.Properties['booleanDocProp']

  3. Another python script changes the visualization title when document property changes:

    //viz is a Visualization type script parameter
    viz.Title = "Checkbox is ON" if Document.Properties['booleanDocProp'] else "Checkbox is OFF"

  4. Optionally trigger a javascript when the calculated value changes.

    cb=document.getElementById('boxyCheckbox')
    cb.addEventListener('click',function(){
      status = cb.innerText=="☒"?"OFF":"ON"
      alert("Checkbox is " + status)
    })

Reverse sort order on filters

Unfortunately there is no way to reverse the sort order on non categorical data such as dates and numbers on columns or filters. A work around is to create a calculated column to rank and concatenate.

Repeat("0",Max(Len(String(DenseRank([Date],"desc")))) - Len(String(DenseRank([Date],"desc")))) & DenseRank([Date],"desc") & " - " & [Date] as [DDate]






Highlight selected spotfire action control buttons






html
<style>

.on{
  background-color: #ABFF00;
  box-shadow:   rgba(45, 163, 215, 0.5) 0 0px 0px 0px, 
           inset rgb(45,163, 215) 0 -2px 0px, 
                 rgb(45,163, 215) 0 0px 7px;
}

.container {
    display: flex;
    flex-flow: row wrap;
    justify-content: space-around;
    backgrond:yellow;
}
.container > div {
    margin: 0px;
    padding: 0px;
    xwidth:220px;
    xheight:50px;
    xbackground-color: rgb(14,145,172);
    xborder-radius:5px;
    text-align:center;
    xcolor:white;
}

.r3 {
 font-weight:bold;
 width:100%;
 border-bottom:1px solid black;
 margin-right:20px; 
 text-align:center 
}

</style>


<div class="container">

   <div>
 <div class='r3'> Last <SpotfireControl id="action_button_control_here" /> days</div>
<span id='dayBtns'>
 <SpotfireControl id="action_button_control_here" /> <SpotfireControl id="action_button_control_here" />  <SpotfireControl id="action_button_control_here" /></span>
   </div>
   <div>
   <div class='r3'> Options</div>
    <TABLE border=0  width="1px" >
      <TBODY>
 <TR id='helpBtns'>
  <TD title="Shows subtitle (help) for each visualization">Help</TD>
  <TD ><SpotfireControl id="action_button_control_here" /></TD>
  <td ><SpotfireControl id="action_button_control_here" /></TD>
 </TR>
 <TR id='filterBtns'>
  <TD title="Shows or hides the Details on Demand Panel">Filters</TD>
  <TD ><SpotfireControl id="action_button_control_here" /></TD>
  <td ><SpotfireControl id="action_button_control_here" /></TD>
 </TR>
 <TR id='detailsBtns'>
  <TD title="Shows or hides the Details on Demand Panel">Details</TD>
  <TD ><SpotfireControl id="action_button_control_here" /></TD>
  <td ><SpotfireControl id="88ed6f5873ad4fb0b4cb6542696f4e3e" /></TD>
 </TR>
      </tbody>
    </table>
   </div>
</div>



JS
function lightsOff(cssSel){$(cssSel).removeClass("on")}


//make day selection buttons highlight when clicked
$("#dayBtns input").click(function(){
    $("#dayBtns input").removeClass("on")
 $(this).addClass("on")
})

//make on/off help buttons highlightable
$("#helpBtns input").click(function(){
    $("#helpBtns input").removeClass("on")
 $(this).addClass("on")
})

//add magic to details on|off buttons 
$("#detailsBtns input").click(function(){
    $("#detailsBtns input").removeClass("on")
 $(this).addClass("on")
})

//make spotfire action control buttons to turn on|off filter panel to highlight
$("#filterBtns input").click(function(){
    $("#filterBtns input").removeClass("on")
 $(this).addClass("on")
})


//highlight default buttons 
$("#dayBtns input:first").addClass("on")
$("#helpBtns input:last").addClass("on")
$("#detailsBtns input:last").addClass("on")

$("#filterBtns input:last").addClass("on")


Delete first 10 rows

#dt is a data table script parameter
from Spotfire.Dxp.Data import RowSelection, IndexSet

#select all rows
selection   = IndexSet(dt.RowCount,True)

#mark first 10 rows row as true and the rest false
for r in selection:
   selection[r] = (r<=10)

#delete rows based on selection (rows from indexset marked as true)
dt.RemoveRows(RowSelection(selection))

Gauge.js in Spotfire

Gauge.js implementation in Spotfire



Ingredients:
  • 1 text area
  • calculated value control or label property control
  • two javascripts: gauge.js and yourGauge.js in textarea

Example 1





html
<canvas id="gauge1"></canvas>
<span id="calcValue1" style='display:none'>
   <SpotfireControl id="SpotfireCalculatedValueControlHere" />
</span>   

note: the spotire calculated value must not contain any formatting

gauge.js
copy and paste the javascript contents of gauge.js from this link:
http://bernii.github.io/gauge.js/dist/gauge.min.js 


Note: If using more than one gauge in a tab, only one gauge.js is needed in one text area.

yourGauge.js
var opts = {
  lines: 12,
  angle: 0.054,
  lineWidth: 0.54,
  pointer: {
    length: 0.8,
    strokeWidth: 0.035,
    color: '#000000'
  },
  staticZones: [
   {strokeStyle: "#F03E3E", min: 0, max: 130}, // Red from 0 to 130
   {strokeStyle: "#FFDD00", min: 130, max: 250}, // Yellow
   {strokeStyle: "#30B32D", min: 250, max: 320}, // Green
   {strokeStyle: "#FFDD00", min: 320, max: 460}, // Yellow
   {strokeStyle: "#F03E3E", min: 460, max: 1000}  // Red
],
staticLabels: {
  font: "10px sans-serif",  
  labels: [000, 250, 350, 450, 550, 1000],  
  color: "#000000",  
  fractionDigits: 0  
},
  limitMax: 'false', 
  percentColors: [[0.0, "#a9d70b" ], [0.50, "#f9c802"], [1.0, "#ff0000"]], 
  strokeColor: '#E0E0E0',
  generateGradient: true
};

var target = document.getElementById('gauge1');
var gauge = new Gauge(target).setOptions(opts);
gauge.maxValue = 1000;
gauge.animationSpeed = 12;
//gauge.set(250);

function refreshGauge1(){
  val = parseInt($("#calcValue1").text())
  if(isNaN(val)) return

  gauge.set(val)
}

$("#calcValue1").one('DOMSubtreeModified',refreshGauge1)

refreshGauge1();

To show the actual calculated value with specific formatting, add another calculated value wrapped in a div with proper positioning:


191 lb

html
<canvas id="gauge1"></canvas>
<span id="calcValue1" style='display:none'>
   <SpotfireControl id="SpotfireCalculatedValueControlHere" />
</span>   

<div style='position:absolute;top:150px;left:125px;font-size:30px'>
   <SpotfireControl id="VisibleSpotfireCalculatedValueControlHere" /></div>


note: The visible Spotfire calculated value can contain any formatting, but it will not animate the same was as the next example
Example 2



html
<div style="text-align:center">
   <canvas width="200" height="170" id="gauge2"></canvas>
<div id="measure2
   style="font-size:45px;
        position:absolute;
        top:55px;left:0;right:0;
        text-align:center;">
   </div>
</div>

<span id="calcValue2" style='display:none'>
   <SpotfireControl id="SpotfireCalculatedValueControlHere" 
</span>    

js
var opts = {
  lines: 12, 
  angle: 0.22,
  lineWidth: 0.1, 
  pointer: {
    length: 0.5, 
    strokeWidth: 0.035, 
    color: '#afafaf' 
  },
  limitMax: 'false', 
  colorStart: '#2DA3DC', 
  colorStop: '#C0C0DB',
  strokeColor: '#EEEEEE', 
  generateGradient: true
};
var target = document.getElementById('gauge2'); 
var gauge = new Donut(target).setOptions(opts); 

gauge.maxValue = 100; 
gauge.animationSpeed = 32; 
gauge.setTextField(document.getElementById("measure2"));

function refreshGauge2(){
  val = parseInt($("#calcValue2").text())
  if(isNaN(val)) return

  gauge.set(val)
}

$("#calcValue2").one('DOMSubtreeModified',refreshGauge2)

refreshGauge2();




Note: When editing the html or javascript from the text area, the gauge might seem to disappear. Just click another tab and come back to re-render the gauge. It also works by making the calc value change.



Current user in webplayer

The System.Environment shows the actual logged in user in client but not in webplayer.  A work around is to use bookmarks

from Spotfire.Dxp.Application import BookmarkComponentFlags  
from Spotfire.Dxp.Application.AnalyticItems import BookmarkManager

#define name of temp bookmark (make sure is unique)
bookmarkName = "$_tmp_bookmark_$"

#create the bookmark. Second argument specifies what to capture
myBookmark = Document.Bookmarks.AddNew(bookmarkName ,BookmarkComponentFlags.FilterSettings)

#extract data from myBookmark 
print myBookmark.CreatedBy

#delete temp bookmark
Document.Bookmarks.Remove(myBookmark)


GetDistinctRows (7.0+)

#get unique values from column (7.0)
from Spotfire.Dxp.Data import DataValueCursor
from Spotfire.Dxp.Data import IndexSet

tableName = 'MyDataTable'
columnName = 'MycolumnName'

dt = Document.Data.Tables[tableName]
cursor = DataValueCursor.Create[str](dt.Columns[columnName])
distinctRows = dt.GetDistinctRows(None,cursor) #none param to grab them by the all
#distinctRows = dt.GetDistinctRows(IndexSet(dt.RowCount,True),cursor) #...or an index set with all true to get them all.

#get uniques
vals = []
distinctRows.Reset()
while distinctRows.MoveNext():vals.append(cursor.CurrentValue)

#show results
print vals

Trigger script when clicking on a tab

1. Page is opened
2. Text Area renders and executes Javascript
3. Javascript finds hidden action control link and clicks it

html
<div id='hiddenActionControl' style='display:none'>
<SpotfireControl id="link4ct10nc0n7r01" />
</div>

js
//executes every time the text area renders
$('#hiddenActionControl a').click()

script
#this script is triggered by the javascript associated in the text area through a hidden action control link.
Document.Properties["tabCount"] = Document.Properties["tabCount"] + 1

popup form

If you want to hack yourself and make the analysis more complicated that what it needs to, then you can create a windows form using clr but this form will only be available on the thick client (see here)

For webplayer AND client to work, you can use javascript and jquery dialogs. Unfortunateley jquery dialogs can't hold spotfire controls so far.

html
<button id='btnForm'>Submit feedback</button>
<pre id="myForm" >

Name        <input/>

Experience  <select>
     <option>Great</option>
     <option>Average</option>
     <option>Poor</option>
   </select>

Comments    <textarea/>

</pre>

js
dlg = $( "#myForm" ).dialog({
      autoOpen: false,
      height: 400,
      width: 350,
      modal: true,
      buttons: {
        "Submit comments": function() {
alert("Thanks for your feedback!")
dlg.dialog( "close" );
        },
        Cancel: function() {
          dlg.dialog( "close" );
        }
      }
});

$("#btnForm").button().on('click',function(){
dlg.dialog( "open" )
})




Selecting a column in Custom Expressions

Double click on a column name square bracket to select the entire column when editing Custom Expressions


Change bar segment labels from percentage to value

# barChart is a BarChart Visualizaiton script parameter
# labelInformationType is an integer document property script parameter with values 0|1 for pct or value
from Spotfire.Dxp.Application.Visuals import BarChart
barChart.As[BarChart]().SegmentLabelInformationType = labelInformationType 


# if you need to toggle from percantage to value, you can do this too:
from Spotfire.Dxp.Application.Visuals import BarChart, BarChart,LabelInformationType
barChart = barChart.As[BarChart]()
barChart.SegmentLabelInformationType = LabelInformationType.Value if barChart.SegmentLabelInformationType==LabelInformationType.Percentage else LabelInformationType.Percentage

Web Service to Data Table

#This script calls a webservice, parses JSON and dumps data to a datatable.

#GETS JSON DATA FROM URL
def getData(url):
import clr
clr.AddReference('System.Web.Extensions')
from System.Net import WebClient
from System.Web.Script.Serialization import JavaScriptSerializer
from System.IO import StreamReader

#Get the json data from web service
wc = WebClient()
wc.Headers.Add('Content-Type', 'application/x-www-form-urlencoded')
requestData= wc.OpenRead(url)
reader = StreamReader (requestData);
response = reader.ReadToEnd()

#Parse the results
js = JavaScriptSerializer()
responseDict = js.Deserialize(response,object)
return responseDict

#Convert JSON to tab-delimited text format
def Dict2TXT(dict, delimiter="\t"):
# Get first row to get headers
data=dict
tmp=[]
for x in data[0]: tmp.append(x.Key)
textData = "\t".join(tmp) + "\r\n"

#Get the rest of the data
for x in data:
tmp=[]
for y in x: tmp.append(y.Value)
textData += "\t".join(tmp) + "\r\n"

return textData

#Read tab-delimited text format in Spotfire
def txt2table(textData, dataTableName="Web Service Data",delimiter="\t"):
from System.IO import StreamWriter, MemoryStream, SeekOrigin
from Spotfire.Dxp.Data.Import import TextFileDataSource, TextDataReaderSettings
from Spotfire.Dxp.Data import DataType, DataTableSaveSettings

#Make a stream from the string
stream = MemoryStream()
writer = StreamWriter(stream)
writer.Write(textData)
writer.Flush()
stream.Seek(0, SeekOrigin.Begin)

#Set up the text data reader
readerSettings = TextDataReaderSettings()
readerSettings.Separator = delimiter
readerSettings.AddColumnNameRow(0)

#Create a data source to read in the stream
textDataSource = TextFileDataSource(stream, readerSettings)

#Add the data into a Data Table in Spotfire
if Document.Data.Tables.Contains(dataTableName):
Document.Data.Tables[dataTableName].ReplaceData(textDataSource)
else:
newTable = Document.Data.Tables.Add(dataTableName, textDataSource)
tableSettings = DataTableSaveSettings (dataTableName, False, False)
Document.Data.SaveSettings.DataTableSettings.Add(tableSettings)


#GETS JSON DATA FROM URL
data = getData("http://services.groupkt.com/country/get/all")
dict = data["RestResponse"]["result"]

#Convert JSON to tab-delimited text format
txt = Dict2TXT(dict)

#Read tab-delimited text format in Spotfire
txt2table(txt,"Countries")