Created
October 30, 2022 12:47
-
-
Save ThomasRohde/6382de4e0eda1a96ee229f90a871afc4 to your computer and use it in GitHub Desktop.
Charting framework for Archi #Archi #JArchi #ArchiMateTool
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
/* | |
Author: Thomas Klok Rohde | |
Description: | |
Helper function to create HTML producing a Plotly chart. | |
This chart shows the distribution of ArchiMate objects used in the current model. | |
Place in __DIR__/lib directory | |
History: | |
October 29, 2022 : Created | |
*/ | |
function createChart(pred, opts) { | |
let buckets = new Map(); | |
let labels = []; | |
let dataset = []; | |
let title = opts.title ? opts.title : "No Title"; | |
$("*").not("folder").each(o => { | |
if (pred(o.type, o.name, o)) { | |
let count = parseInt(buckets.get(o.type)); | |
if (!count) count = 0; | |
buckets.set(o.type, count + 1); | |
} | |
}); | |
for (let [key, value] of buckets) { | |
labels.push(key); | |
dataset.push(value); | |
} | |
let chartContent = `var chart = { | |
"data": [ | |
{ | |
"y": ${JSON.stringify(dataset)}, | |
"x": ${JSON.stringify(labels)}, | |
"type": "${opts.type}" | |
}], | |
"layout": { | |
"title": { | |
"text": "${title}", | |
"font": { | |
"size": 24 | |
} | |
}, | |
"layout.title.font.size" : 22, | |
"xaxis" : { | |
automargin: true | |
} | |
} | |
} | |
plot = Plotly.newPlot( | |
'plotly_div', | |
chart.data, | |
chart.layout, {displayModeBar: false}) | |
`; | |
return chartContent; | |
} |
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
/* | |
Author: Thomas Klok Rohde | |
Description: | |
Helper function to create HTML producing a Plotly chart. | |
This chart shows the distribution of ArchiMate objects used in the current model. | |
Place in __DIR__/lib directory | |
History: | |
October 29, 2022 : Created | |
*/ | |
function createChart(pred, opts) { | |
let title = opts.title ? opts.title : "No Title"; | |
let data = []; | |
let cont = true; | |
let ranges = [[0.1, "red"],[0.4, "yellow"],[1.0,"green"]]; | |
$("*").not("folder").each(o => { | |
if (cont && pred(o.type, o.name, o)) { | |
const prop = o.prop(opts.property); | |
const value = parseInt(prop); | |
if (prop && value != NaN) { | |
let indicator = { | |
value: value, | |
mode: "number+gauge", | |
gauge: { shape: `${opts.type}`, bar: { color: "black" }, axis: { range: [opts.min, opts.max] } }, | |
type: "indicator", | |
title: { text: `${opts.property}`} | |
} | |
steps = []; | |
current = 0; | |
ranges.forEach(r => { | |
let range = { | |
range: [current, Math.floor(opts.max * r[0])], | |
color: r[1] | |
} | |
steps.push(range); | |
current = Math.floor(opts.max * r[0]); | |
}); | |
indicator.gauge.steps = steps; | |
data.push(indicator); | |
cont = false; | |
} | |
} | |
}); | |
let chartContent = ` | |
const labelLength = ${title.length * 14}; | |
var chart = { | |
"data": ${JSON.stringify(data)}, | |
"layout": { | |
"title": { | |
"text": "${title}", | |
"font": { | |
"size": 24 | |
} | |
} | |
} | |
} | |
if (chart.data[0].gauge.shape == "bullet") { | |
chart.layout.margin = {"l": labelLength } | |
} | |
plot = Plotly.newPlot( | |
'plotly_div', | |
chart.data, | |
chart.layout, {displayModeBar: false}) | |
`; | |
return chartContent; | |
} |
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
/* | |
Author: Thomas Klok Rohde | |
Description: | |
Helper function to create HTML producing a Plotly chart. | |
This chart shows a table with all the properties and their values. | |
You might have to resize the containing note to fit all the data, or filter the source size using a predicate function | |
Place in __DIR__/lib directory | |
History: | |
October 29, 2022 : Created | |
*/ | |
function createChart(pred, opts) { | |
let title = opts.title ? opts.title : "No Title"; | |
let headers = [["<b>Name</b>"]]; | |
let props = new Set(); | |
let targets = []; | |
$("*").not("folder").each(o => { | |
if (pred(o.type, o.name, o)) { | |
targets.push(o); | |
o.prop().forEach(p => { if (p.length > 0) props.add(p.split(":")[0]) }) | |
} | |
}); | |
props.forEach(p => { | |
headers.push([`<b>${p}</b>`]) | |
}) | |
let values = []; | |
let names = []; | |
targets.forEach(o => { | |
names.push(($(o).is("relationship") ? `${o.type} (${o.source.name} - ${o.target.name})` : o.name)) | |
}); | |
values.push(names); | |
props.forEach(p => { | |
let column = []; | |
targets.forEach(o => { | |
let value = o.prop(p); | |
if (!value) value = "-"; else value = value.toString(); | |
column.push(value); | |
}) | |
values.push(column); | |
}); | |
chartContent = `var chart = { | |
"data": [{ | |
type: 'table', | |
header: { | |
values: ${JSON.stringify(headers)}, | |
align: "center", | |
line: { width: 1, color: 'black' }, | |
fill: { color: "grey" }, | |
font: { family: "Arial", size: 12, color: "white" } | |
}, | |
cells: { | |
values: ${JSON.stringify(values)}, | |
align: "center", | |
line: { color: "black", width: 1 }, | |
font: { family: "Arial", size: 11, color: ["black"] } | |
} | |
}], | |
"layout": { | |
"title": { | |
"text": "${title}", | |
"font": { | |
"size": 24 | |
} | |
}, | |
"height": ${opts.height}, | |
"layout.title.font.size" : 22 | |
} | |
} | |
plot = Plotly.newPlot( | |
'plotly_div', | |
chart.data, | |
chart.layout, {displayModeBar: false}) | |
`; | |
return chartContent; | |
} |
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
/* | |
Author: Thomas Klok Rohde | |
Description: Insert chart to view | |
History: | |
October 11, 2022 : Created with base set of scripts | |
*/ | |
console.show(); | |
console.clear(); | |
let note = $(selection).filter("diagram-model-note").first(); | |
// Get a list of all defined properties in the model | |
let props = new Set(); | |
$("*").not("folder").each(o => { | |
o.prop().forEach(p => { | |
if (p.length) props.add(p); | |
}) | |
}) | |
let properties = ""; | |
props.forEach(p => { | |
properties += `<option value="${p}">${p}</option> | |
`; | |
}); | |
if (note == undefined) | |
throw new Error("No note selected"); | |
let chartTemplate = {}; | |
let chartFilter = ""; | |
if (note.prop("Chart")) { | |
chartTemplate = JSON.parse(note.prop("Chart")); | |
chartFilter = chartTemplate.filter ? chartTemplate.filter : ""; | |
console.log(JSON.stringify(chartTemplate,null,3)) | |
} | |
const SWT = Java.type('org.eclipse.swt.SWT'); | |
const FillLayout = Java.type('org.eclipse.swt.layout.FillLayout'); | |
const Shell = Java.type('org.eclipse.swt.widgets.Shell'); | |
const Browser = Java.type('org.eclipse.swt.browser.Browser'); | |
const ProgressAdapter = Java.extend(Java.type('org.eclipse.swt.browser.ProgressAdapter')); | |
const LocationAdapter = Java.extend(Java.type('org.eclipse.swt.browser.LocationAdapter')); | |
const CustomFunction = Java.extend(Java.type('org.eclipse.swt.browser.BrowserFunction')); | |
const IArchiImages = Java.type('com.archimatetool.editor.ui.IArchiImages'); | |
const ImageFactory = Java.type('com.archimatetool.editor.ui.ImageFactory'); | |
const Rectangle = Java.type('org.eclipse.swt.graphics.Rectangle'); | |
const Monitor = Java.type('org.eclipse.swt.widgets.Monitor'); | |
let display = shell.getDisplay(); | |
let newShell = new Shell(display, SWT.MODAL | SWT.TITLE | SWT.ON_TOP); | |
newShell.setText("Insert chart"); | |
newShell.setLayout(new FillLayout()); | |
html = `<html> | |
<title>Insert chart</title> | |
<style> | |
*, | |
*:before, | |
*:after { | |
box-sizing: border-box; | |
font-family: sans-serif; | |
font-size: 12px; | |
} | |
input[type=text], | |
input[type=number], | |
input[type=email], | |
select { | |
padding: 5px; | |
margin: 5px 0; | |
border-radius: 5px; | |
border-width: 2px; | |
width: 100%; | |
} | |
.option { | |
display: none; | |
margin: 10px; | |
border-width: 2px; | |
border-radius: 5px; | |
} | |
button { | |
margin: 10px; | |
padding: 5px; | |
width: 100px; | |
} | |
</style> | |
<body> | |
<input id="title1" type="text" value="${chartTemplate.title}" placeholder="Insert chart title"> | |
<input id="filter1" type="text" value="" placeholder="Insert javascript expression - name, type and obj are available" onchange="validate('#filter1')"> | |
<input name="chart" type="radio" value="chartArchiDistribution" onchange="toggleOptions('#options1')"> | |
Distribution of ArchiMate types | |
<fieldset id="options1" disabled="true" class="option"> | |
<legend>Select chart type</legend> | |
<select id="charttype1"> | |
<option value="bar">Bar</option> | |
<option value="lines">Lines</option> | |
<select> | |
</fieldset> | |
</input> | |
<br> | |
<input name="chart" type="radio" value="chartGauges" onchange="toggleOptions('#options2')"> | |
Show gauge for a numerical property | |
<fieldset id="options2" disabled="true" class="option"> | |
<legend>Select chart options</legend> | |
Chart type | |
<select id="charttype2"> | |
<option value="bullet">Bullet</option> | |
<option value="gauge">Gauge</option> | |
<select> | |
Property | |
<select id="properties"> | |
${properties} | |
<select> | |
Minimum value<input type="number" id="minGauge" value=0 name="minGauge"></input>Maximum value<input type="number" id="maxGauge" value=100 name="maxGauge"></input> | |
</fieldset> | |
</input> | |
<br> | |
<input name="chart" type="radio" value="chartPropertyTable" onchange="hideOptions(['#options1','#options2'])"> | |
Table of defined attributes | |
</input> | |
<br> | |
<button id="insertbutton" disabled="true" onclick="insertPressed()">Insert</button><button onclick="cancelPressed()">Cancel</button> | |
<script type="text/javascript"> | |
function insertPressed() { | |
let chartButtons = document.getElementsByName('chart'); | |
let chartName = null; | |
let chartType = null; | |
let chartProperty = null; | |
let chart = null; | |
let chartMin = 0; | |
let chartMax = 100; | |
for (let i = 0; i < chartButtons.length; i++) { | |
if (chartButtons[i].checked) { | |
chartName = chartButtons[i].value; | |
if (chartName == "chartArchiDistribution") chartType = document.querySelector('#charttype1').value; | |
if (chartName == "chartPropertyTable") chartType = "table"; | |
if (chartName == "chartGauges") { | |
chartType = document.querySelector('#charttype2').value; | |
chartProperty = document.querySelector('#properties').value; | |
} | |
} | |
} | |
if (!chartName) chart = null; | |
else | |
chart = { | |
chart: chartName, | |
type: chartType | |
} | |
if (chartProperty) { | |
chart.property = chartProperty; | |
chart.min = chartMin; | |
chart.max = chartMax; | |
} | |
let filter = document.querySelector('#filter1').value; | |
if (filter) chart.filter = filter; | |
let title = document.querySelector('#title1').value; | |
if (title) chart.title = title; | |
insertPressedEvent(JSON.stringify(chart)); | |
} | |
function cancelPressed() { | |
cancelPressedEvent(); | |
} | |
function toggleOptions(id) { | |
let options = ['#options1','#options2']; | |
let element = document.querySelector(id); | |
element.disabled = !element.disabled; | |
if (element.disabled) element.style.display = 'none'; | |
else { | |
element.style.display = 'block'; | |
options.forEach(o => { | |
if (o != id) hideOptions([o]); | |
}) | |
} | |
document.querySelector('#insertbutton').disabled = false; | |
} | |
function hideOptions(ids) { | |
ids.forEach(id => { | |
let element = document.querySelector(id); | |
element.disabled = true; | |
element.style.display = 'none'; | |
}); | |
document.querySelector('#insertbutton').disabled = false; | |
} | |
let chartButtons = document.getElementsByName('chart'); | |
let current = "${chartTemplate.chart}"; | |
for (let i = 0; i < chartButtons.length; i++) { | |
if (chartButtons[i].value == current) chartButtons[i].checked = true; | |
} | |
if (current == "chartArchiDistribution") { | |
toggleOptions('#options1'); | |
document.getElementById('charttype1').value = '${chartTemplate.type}'; | |
} | |
if (current == "chartGauges") { | |
toggleOptions('#options2'); | |
document.getElementById('charttype2').value = '${chartTemplate.type}'; | |
document.getElementById('properties').value = '${chartTemplate.property}'; | |
} | |
document.getElementById('filter1').value = '${chartFilter}'; | |
</script> | |
</body>`; | |
var insertPressed = false; | |
var cancelPressed = false; | |
let browser = new Browser(newShell, SWT.NONE); | |
var chart; | |
browser.addProgressListener(new ProgressAdapter({ | |
completed: function (event) { | |
let fncOk = new CustomFunction(browser, "insertPressedEvent", { | |
function: function (args) { | |
chart = args[0]; | |
insertPressed = true; | |
} | |
}); | |
let fncCancel = new CustomFunction(browser, "cancelPressedEvent", { | |
function: function (args) { | |
cancelPressed = true; | |
} | |
}); | |
browser.addLocationListener(new LocationAdapter({ | |
changed: function (e) { | |
browser.removeLocationListener(this); | |
fncOk.dispose(); | |
fncCancel.dispose(); | |
} | |
})); | |
} | |
})); | |
// Write the HTML to a temporary file, so we are allowed to execute a local script | |
let System = Java.type('java.lang.System'); | |
let tmpfile = System.getProperty("java.io.tmpdir") + "layout.html"; | |
$.fs.writeFile(tmpfile, html); | |
browser.setUrl("file:///" + tmpfile); | |
// Set icon to Archi icon, in case shell has a style which displays icons | |
newShell.setImage(IArchiImages.ImageFactory.getImage(IArchiImages.ICON_APP)); | |
newShell.setSize(800, 775); | |
// Center dialog on screen | |
const primary = display.getPrimaryMonitor(); | |
const bounds = primary.getBounds(); | |
const rect = newShell.getBounds(); | |
const x = bounds.x + (bounds.width - rect.width) / 2; | |
const y = bounds.y + (bounds.height - rect.height) / 2; | |
newShell.setLocation(x, y); | |
newShell.open(); | |
while (!newShell.isDisposed() && !insertPressed && !cancelPressed) { | |
if (!display.readAndDispatch()) display.sleep(); | |
} | |
if (insertPressed && chart != undefined) { | |
note.prop("Chart", chart); | |
newShell.dispose(); | |
load(__DIR__ + "Refresh chart.ajs"); | |
} | |
else if (cancelPressed) { | |
console.log('Dialog cancelled.'); | |
newShell.dispose(); | |
} |
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
/* | |
Author: Thomas Klok Rohde | |
Description: Refresh selected chart | |
History: | |
October 26, 2022 : Created | |
*/ | |
console.show(); | |
console.clear(); | |
// Avoid name clashes by encapsulation. | |
(function () { | |
let note = $(selection).filter("diagram-model-note").first(); | |
if (note == undefined) | |
throw new Error("No note selected."); | |
const SWT = Java.type('org.eclipse.swt.SWT'); | |
const FillLayout = Java.type('org.eclipse.swt.layout.FillLayout'); | |
const Shell = Java.type('org.eclipse.swt.widgets.Shell'); | |
const Browser = Java.type('org.eclipse.swt.browser.Browser'); | |
const ProgressAdapter = Java.extend(Java.type('org.eclipse.swt.browser.ProgressAdapter')); | |
const LocationAdapter = Java.extend(Java.type('org.eclipse.swt.browser.LocationAdapter')); | |
const CustomFunction = Java.extend(Java.type('org.eclipse.swt.browser.BrowserFunction')); | |
const IArchiImages = Java.type('com.archimatetool.editor.ui.IArchiImages'); | |
const ImageFactory = Java.type('com.archimatetool.editor.ui.ImageFactory'); | |
let display = shell.getDisplay(); | |
let newShell = new Shell(display, SWT.SHELL_TRIM | SWT.ON_TOP | SWT.APPLICATION_MODAL); | |
newShell.setText("Refresh chart"); | |
newShell.setLayout(new FillLayout()); | |
let width = note.bounds.width; | |
let height = note.bounds.height; | |
if (!note.prop("Chart")) { | |
throw new Error("Selected note does not contain chart property."); | |
} | |
const chartOptions = JSON.parse(note.prop("Chart")); | |
chartOptions.width = width; | |
chartOptions.height = height; | |
let predicate = function (type, name, obj) { return true }; | |
let chartContent; | |
if (chartOptions.filter) { | |
let newPredicate = new Function("tp", "nm", "obj", "return " + chartOptions.filter); | |
try { | |
e = $("*").first(); | |
$(e).each(o => { | |
let p = newPredicate(o.type, o.name, o); | |
}) | |
predicate = newPredicate; | |
} | |
catch (error) { | |
console.log("Error in predicate. Skipping") | |
} | |
} | |
//try { | |
load(__DIR__ + "/lib/" + chartOptions.chart + ".js"); | |
chartContent = createChart(predicate, chartOptions); | |
//} | |
//catch (error) { | |
// throw new Error("Wrong chart type: " + chartOptions.chart) | |
//} | |
let html = `<html> | |
<script src="${__DIR__ + "/lib/plotly.js"}"></script> | |
<style> | |
:root { | |
font-family: Sans-serif; | |
} | |
button { | |
margin-right: 10px; | |
padding: 5px; | |
width: 100px; | |
} | |
table | |
{ | |
font-size:0.8em; | |
} | |
</style> | |
<div id="buttons"> | |
<button onclick="insertPressed()">Insert</button><button onclick="cancelPressed()">Cancel</button> | |
</div> | |
<div id="plotly_div" ></div> | |
<script> | |
function insertPressed() { | |
plot | |
.then(function (gd) { | |
Plotly.toImage(gd, { format: 'png', height: ${height}, width: ${width} }) | |
.then(function (url) { | |
insertPressedEvent(url); | |
}) | |
}); | |
} | |
function cancelPressed() { | |
cancelPressedEvent(); | |
} | |
${chartContent} | |
</script> | |
</html>`; | |
var insertPressed = false; | |
var cancelPressed = false; | |
let browser = new Browser(newShell, SWT.NONE); | |
var image; | |
browser.addProgressListener(new ProgressAdapter({ | |
completed: function (event) { | |
let fncOk = new CustomFunction(browser, "insertPressedEvent", { | |
function: (args) => { | |
image = args[0]; | |
insertPressed = true; | |
} | |
}); | |
let fncCancel = new CustomFunction(browser, "cancelPressedEvent", { | |
function: (args) => { | |
cancelPressed = true; | |
} | |
}); | |
browser.addLocationListener(new LocationAdapter({ | |
changed: (e) => { | |
browser.removeLocationListener(this); | |
fncOk.dispose(); | |
fncCancel.dispose(); | |
} | |
})); | |
} | |
})); | |
// Write the HTML to a temporary file, so we are allowed to execute a local script | |
let System = Java.type('java.lang.System'); | |
let tmpfile = System.getProperty("java.io.tmpdir") + "creategraph.html"; | |
$.fs.writeFile(tmpfile, html); | |
browser.setUrl("file:///" + tmpfile); | |
// Set icon to Archi icon | |
newShell.setImage(IArchiImages.ImageFactory.getImage(IArchiImages.ICON_APP)); | |
newShell.setSize(Math.floor(width * 2.4) + 200, Math.floor(height * 2.4)); | |
newShell.open(); | |
while (!newShell.isDisposed() && !insertPressed && !cancelPressed) { | |
if (!display.readAndDispatch()) display.sleep(); | |
} | |
if (insertPressed) { | |
let data = image.replace(/^data:image\/\w+;base64,/, ""); | |
let tmpimagefile = System.getProperty("java.io.tmpdir") + "chartimage.png"; | |
$.fs.writeFile(tmpimagefile, data, "BASE64"); | |
note.imageSource = IMAGE_SOURCE.CUSTOM; | |
note.image = model.createImage(tmpimagefile); | |
note.imagePosition = IMAGE_POSITION.MIDDLE_CENTRE; | |
note.borderType = BORDER.RECTANGLE; | |
} | |
else if (cancelPressed) | |
console.log('Cancelled.') | |
newShell.dispose(); | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment