Render HTML from Data Table

html
<span id='dropDownDocProp' style='display:none'>
<SpotfireControl id="calculatedValueGoesHere123" />
calculated value expression: first([myHTMLColumn])
</span>

<div id="here">html renders here</div>

js
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){
$("#here").html(newValue)
}


//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+' ').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);




Sample Data
copy the text below and add a data table from clipboard in spotfire

ID Detail
2 "<div style=""position:relative;""> <div style=""opacity:0.5;position:absolute;left:50px;top:-30px;width:300px;height:150px;background-color:#40B3DF""></div> <div style=""opacity:0.3;position:absolute;left:120px;top:20px;width:100px;height:170px;background-color:#73AD21""></div> <div style=""margin-top:30px;width:360px;height:130px;padding:20px;border-radius:10px;border:10px solid #EE872A;font-size:120%;""> <h1>CSS = Styles and Colors</h1> <div style=""letter-spacing:12px;font-size:15px;position:relative;left:25px;top:25px;"">Manipulate Text</div> <div style=""color:#40B3DF;letter-spacing:12px;font-size:15px;position:relative;left:25px;top:30px;"">Colors, <span style=""background-color:#B4009E;color:#ffffff;""> Boxes</span></div> </div> </div>"
3 "<style> table { width:100%; } table, th, td { border: 1px solid black; border-collapse: collapse; } th, td { padding: 5px; text-align: left; } table.names tr:nth-child(even) { background-color: #eee; } table.names tr:nth-child(odd) { background-color:#fff; } table.names th { background-color: black; color: white } </style> </head> <body><table> <tr> <th>Firstname</th> <th>Lastname</th> <th>Age</th> </tr> <tr> <td>Jill</td> <td>Smith</td> <td>50</td> </tr> <tr> <td>Eve</td> <td>Jackson</td> <td>94</td> </tr> <tr> <td>John</td> <td>Doe</td> <td>80</td> </tr> </table><br><table class=""names""> <tr> <th>Firstname</th> <th>Lastname</th> <th>Age</th> </tr> <tr> <td>Jill</td> <td>Smith</td> <td>50</td> </tr> <tr> <td>Eve</td> <td>Jackson</td> <td>94</td> </tr> <tr> <td>John</td> <td>Doe</td> <td>80</td> </tr> </table>"
7 "<style> .flex-container { display: -webkit-flex; display: flex; -webkit-flex-flow: row wrap; flex-flow: row wrap; text-align: center; } .flex-container > * { padding: 15px; -webkit-flex: 1 100%; flex: 1 100%; } .article { text-align: left; } header {background: black;color:white;} footer {background: #aaa;color:white;} .nav {background:#eee;}.nav ul { list-style-type: none; padding: 0; } .nav ul a { text-decoration: none; }@media all and (min-width: 768px) { .nav {text-align:left;-webkit-flex: 1 auto;flex:1 auto;-webkit-order:1;order:1;} .article {-webkit-flex:5 0px;flex:5 0px;-webkit-order:2;order:2;} footer {-webkit-order:3;order:3;} } </style><div class=""flex-container""> <header> <h1>City Gallery</h1> </header><nav class=""nav""> <ul> <li><a href=""#"">London</a></li> <li><a href=""#"">Paris</a></li> <li><a href=""#"">Tokyo</a></li> </ul> </nav><article class=""article""> <h1>London</h1> <p>London is the capital city of England. It is the most populous city in the United Kingdom, with a metropolitan area of over 13 million inhabitants.</p> <p>Standing on the River Thames, London has been a major settlement for two millennia, its history going back to its founding by the Romans, who named it Londinium.</p> <p><strong>Resize this page to see what happens!</strong></p> </article></div>"
10 "<style> body {background-color: powderblue;} h1 {color: red;} p {color: blue;} </style> <h1>This is a Heading</h1> <p>This is a paragraph.</p> <script> function myFunction() { document.getElementById(""demo"").innerHTML = ""Hello JavaScript!""; } </script> </head> <body> <h1>My Web Page</h1> <p id=""demo"">A Paragraph</p> <button type=""button"" onclick=""myFunction()"">Try it</button><img src=""https://www.w3schools.com/images/html5.gif""> "

Change sparkline color on Graphical Table

from Spotfire.Dxp.Application. Visuals import VisualTypeIdentifiers
from Spotfire.Dxp.Application. Visuals.Miniatures import GraphicalTable, SparklineMiniatureVisualization 
from System.Drawing import Color 

#graphPlot is a GraphicalTable visualization script parameter
#color is a string document property holding values like "red", "blue" or "papayawhip" 

for column in graphPlot.As[GraphicalTable]() .Columns: 
    if column.Visualization.TypeId == VisualTypeIdentifiers. SparklineMiniatureVisualization: 
print column.Title
if column.Title == "Trend": 
#column.Visualization.LineColor = Color.FromArgb(R,G,B)
column.Visualization.LineColor = Color.FromName(color)



Thanks to Mike Akister and Adam W!

nice little round button



html
<span id="btn"><SpotfireControl id="fe2511f37ad94401ae64dc4f59d1cf24" /></span>

js
$("#btn input").css({
    "color": "#26A2ED",
    "border-radius": "60%",
    "height": "30px",
    "width": "30px",
    "font-size": "8px",
    "padding": "0px",
    "position": "absolute"
})

slideshow

To loop through pages automatically we need a javascript timer on each page to trigger an iron python script that changes to the next page.




html
<FONT size=6><SPAN>the iron python script switches to the next page</SPAN></FONT> 

<DIV id=switchPageBtn><SpotfireControl id="ff8993c967e049f580fed8aa9b25ec45" /></DIV>


js
$(function(){
clearTimeout(document.body.timeout);
document.body.timeout=setTimeout(function(){$('#switchPageBtn input').click();}, 3000)
});

script 
#current page number
cp = Document.Pages.IndexOf(Document.ActivePageReference)

#total pages
tp = Document.Pages.Count

#calculate next page
np = cp+1 if cp+1<tp else 0

#go to next page
Document.ActivePageReference = Document.Pages[np]


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" )
})