This script is useful for announcements. A floating button is visible to bring the popup again if needed. Includes a checkbox to turn it off next time the script is triggered.
Warning / Notice Popup or Banner
Incrementally add new rows without reloading the entire dataset
Suppose that you have a main table with all your data and you want to bring new records. You will need to setup a temporary table pointing to the same data source but just brings new records that are not yet on your main table. The temporary table has to be setup on-demand to bring new records that do not exists on the main table. Something like: >= Max([MainTable].[id]) + 1 id is a sequence.
step by step setup procedure:
- Add two data tables in memory on your analysis
- destination Table contains all the data. Could be millions of records
- source Table comes from the same source. Set on-demand parameters:
- Set a min range expression input to be : Max([destinationTable].[id]) + 1
- That will take new records
- At this point nothing is shown, unless you refresh sourceTable with new records
- The iron python script programmatically reloads only the sourceTable and appends those rows to the main table.
- To keep a fair amount of records and prevent the data canvas to have unlimited number of transformations, you can delete old records.
Tooltips for column headers
Enable html tooltips with this simple script and power up your cross table, graphical table or data tables
(() => { let i = 0; let tooltipTimeout = null; // Tooltips dictionary with lowercase keys const tooltips = { 'city': 'Another big city!', 'state': 'The inner text or textContent for this item is "state"', 'population': 'Population in millions<br>Look, we can use images!<br><img style="background:white" src="https://img.icons8.com/ios-filled/100/000000/crowd.png">', 'country': 'Tooltip for country', 'latitude': 'Geographical latitude.', 'longitude': 'Geographical longitude.', 'average temperature (f)': 'Average temperature data.', 'air quality index': 'Current air quality index.', 'observation date': 'Date of observation.', 'bullet graph':'This bullet graph represnts the air quality index min and max' }; const tooltip = document.createElement('div'); tooltip.id = 'tooltip'; tooltip.style.position = 'absolute'; tooltip.style.backgroundColor = 'black'; tooltip.style.color = 'white'; tooltip.style.padding = '5px'; tooltip.style.borderRadius = '5px'; tooltip.style.fontSize = '12px'; tooltip.style.visibility = 'hidden'; tooltip.style.opacity = '0'; tooltip.style.transition = 'opacity 0.2s'; tooltip.style.pointerEvents = 'none'; // Ensures the tooltip doesn't trigger any events document.body.appendChild(tooltip); const hasColumnHeaderClass = (element) => { while (element) { if ([...element.classList].some(cls => cls.includes('column-header'))) { return true; } element = element.parentElement; } return false; }; // Function to handle mouseover event const handleMouseOver = (event) => { const target = event.target; const textContent = target.textContent.trim().toLowerCase(); // Clear any existing timeout to avoid overlapping timeouts if (tooltipTimeout) { clearTimeout(tooltipTimeout); } tooltipTimeout = setTimeout(() => { if (tooltips[textContent] && hasColumnHeaderClass(target)) { tooltip.innerHTML = tooltips[textContent]; tooltip.style.visibility = 'visible'; tooltip.style.opacity = '1'; i++; console.log(i); } else { tooltip.style.visibility = 'hidden'; tooltip.style.opacity = '0'; } }, 800); // 0.8 seconds delay }; // Function to handle mousemove event const handleMouseMove = (event) => { tooltip.style.left = `${event.pageX + 10}px`; tooltip.style.top = `${event.pageY + 10}px`; }; // Function to handle mouseout event const handleMouseOut = () => { // Clear any existing timeout when mouse leaves if (tooltipTimeout) { clearTimeout(tooltipTimeout); } tooltip.style.visibility = 'hidden'; tooltip.style.opacity = '0'; }; // Attach event handlers using 'on' properties document.body.onmouseover = handleMouseOver; document.body.onmousemove = handleMouseMove; document.body.onmouseout = handleMouseOut; })();
Range Slider
Convert a regular Spotfire Input field Property Control as a Range Slider in Spotfire text area
html
<div id='slider'>
<SpotfireControl id="Input Field Property Control" /> <SpotfireControl id="label Property Control" />
</div>
js
(()=>{
const slider=document.querySelector('#slider input');
slider.type="range";
slider.min = 10;
slider.max = 50;
slider.step = 0.5;
slider.oninput = () => {
slider.blur();
slider.focus();
}
})()
Confirmation Dialog
html
<div id="confirmExecute">
<SpotfireControl id="2fe15f7fd4174c08b40c0ee27c5591c1" />
</div>
<div id="actualExecute" style="position:fixed;top:-100px">
<SpotfireControl id="7475a74b91ae474c80bf4f979bd1f56a" />
</div>
JavaScript
//setup the first spotfire sleep button (that does nothing) show a confirm dialog
document.querySelector("#confirmExecute input").onclick=function(){
Spotfire.ConfirmDialog.showYesNoCancelDialog("Confirm Execution","Do you really want to execute?",okClick,noClick,null)
}
//programatically click on spotfire control when confirming
okClick = function(x){
document.querySelector("#actualExecute input").click();
}
//display a friendly message if
noClick = function(x){
//alert("OK, no worries")
Spotfire.ConfirmDialog.showDialog("OK","No worries!",[])
}
Get Image Layer dimensions on Map Visualization
Just uncheck the marker layer or remove the Positioning markers (geocoding or coordinate columns) from the marker layer, go back to the image layer and hit the reset button
Autocomplete
Add autocomplete to an existing Spotfire input control (webplayer and cliente )
html
<div id="autocomplete">
<SpotfireControl id="spotfire_Input" />
</div>
<div id="autocomplete-data" hidden>
John Doe,Jane Smith,Robert Johnson,Michael Brown,Emily Davis,Sarah Miller,James Wilson,Patricia Moore,Richard Taylor,Linda Anderson
</div>
Preventing zoom when scrolling with the mouse wheel on visuals with zoom sliders
Here is how to prevent zoom on visualization that have zoom sliders on to zoom when scrolling the page down using the mouse wheel
preventMouseScrollFromZoom.js
divsWithZoomSliders = [...document.querySelectorAll(".sf-element.sf-element-visual")].filter(div => div.querySelector(".sf-element-zoom-slider"));
divsToDisable = divsWithZoomSliders.map(div => div.querySelector(".sf-element.sf-element-canvas-visualization"));
divsToDisable.forEach(div => {div.firstChild.addEventListener('wheel', function(event) {event.preventDefault()}, { passive: true })});
Highlight Visual based on Document Property
When a document property changes, it highlights a visual. This can be useful for data analysis to pay close attention to visuals that require focus
html
<pre id=docPropValues xhidden>
{
"Indexed price charter":"<SpotfireControl id="5858b9bd6d344a98ba87c742af3d9f05" />",
"Top holders by percent held":"<SpotfireControl id="96f46c37e0ab4731a43124c827f3956f" />",
"Historical data":"<SpotfireControl id="5302059ba4724d1f8e45c6a1b95bcfe6" />",
"Calendar quarter estimates":"<SpotfireControl id="21331969168d4e2fb600d4ed1e0004be" />"
}
</pre>
JavaScript
//finds visuals in which title contains visualTitle (use *= for contains, ^= starts with, $= ends with or = exact match)
elements = Array.from(document.querySelectorAll(".sf-element.sf-element-visual"));
function highlighVisual(visualTitle){
//set background for those visuals found
elementWithChild = elements.filter(element => element.querySelector(
[title*='"+visualTitle+"']") !== null); //<-- change here for search operator
elementWithChild.forEach(x=>x.style.background="red")
}
element = document.querySelector('#docPropValues');
observer = new MutationObserver(_ => {
json = JSON.parse(element.innerText);
//reset visual backgrounds
elements.forEach(x=>{x.style.background=""})
Object.entries(json)
.filter(([key, value]) => value === "True")
.map(([key, value]) => key)
.forEach(visualTitle => {highlighVisual(visualTitle)});
});
observer.observe(element, {
childList: true,
characterData: true,
subtree: true
});
IronPython Show/Hide Items
from Spotfire.Dxp.Application.Visuals import BarChart
barChart = vis.As[BarChart]()
#get filter rules
gotFilerRules,filterRuleCollection = barChart.TryGetFilterRules()
#delete all filter rules
if gotFilerRules:
for filterRule in filterRuleCollection:
filterRuleCollection.Remove(filterRule)
#print filterRule.DisplayName, filterRule.Expression
#add a filter rule collection to show top 5 axis values
#filterRuleCollection.AddTopNRule("THEN [Y.FinalValue]",5,True)
Create trellised visualizations based on marking
from Spotfire.Dxp.Application.Visuals import BarChart, VisualContent, VisualTypeIdentifiers, LabelOrientation, BarChartOrientation
from Spotfire.Dxp.Application.Layout import LayoutDefinition
#script params
dataTable = Document.Data.Tables["Best predictors"]
#delete all barchart visuals
page = Document.ActivePageReference
for vis in page.Visuals:
if vis.TypeId == VisualTypeIdentifiers.BarChart:
page.Visuals.Remove(vis)
#The last visual left should be the treemap
tm = next(iter(page.Visuals))
#create a barchart template
bc = Application.Document.ActivePageReference.Visuals.AddNew[BarChart]()
bc.Data.DataTableReference = dataTable
bc.Title = "${DataTable.DisplayName}"
bc.Legend.Visible= False
bc.YAxis.Expression = "Sum([p-value])"
bc.XAxis.Expression = "<[Case Name]>"
bc.SortedBars=True
bc.Orientation = BarChartOrientation.Horizontal
#duplicate as many barcharts as selected sites from marking
siteNames = Document.Properties["markedSites"]
sites = [s.strip() for s in siteNames.split(',')]
#setup first barchart
firstSite = sites.pop()
bc.Title = firstSite
siteVisuals = [bc]
bc.Data.WhereClauseExpression = "[Site_No] = '"+firstSite+"'"
#create visuals
for site in sites:
vis = page.Visuals.AddDuplicate(bc.Visual)
vis.Title =site
bc = vis.As[BarChart]()
bc.Data.WhereClauseExpression = "[Site_No] = '"+site+"'"
siteVisuals.append(vis.As[BarChart]())
#arrange visuals
#tm is the existing treemap and will take 10% of the screen
ld = LayoutDefinition()
ld.BeginSideBySideSection()
ld.Add(tm, 10)
# Begin a stacked section for the second column at 70% of the screen
ld.BeginStackedSection(70)
i = 0
for bc in siteVisuals:
if i % 3 == 0:
if i > 0: ld.EndSection()
ld.BeginSideBySideSection()
ld.Add(bc.Visual)
i += 1
ld.EndSection()
ld.EndSection()
ld.EndSection()
page.ApplyLayout(ld)
To trigger this script when marking changes, create a bypass data function. The script definition is simply an 'x' and so is the input and output. Make sure it runs automatically. The script parameters for the 'x' input is "UniqueConcatenate([Explore_YieldData - Explore_YieldData].[Site])" limited by the blue Marking. The output is a document property called "markedSites" that must be setup to trigger the above script when its value changes.