docs/dynamic/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic landscape map</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Display:ital,wght@0,200;0,300;0,400;0,500;1,200;1,300;1,400;1,500&family=Noto+Sans+Mono:wght@200;300;400;500&family=Noto+Serif+Display:ital,wght@0,200;0,300;0,400;0,500;1,200;1,300;1,400;1,500&display=swap');
/* Reset browser */
* {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
}
html {
background-color: #CCCCCC;
height: 100vh;
}
body {
background-color: #F9F9F9;
min-height: 100vh;
font-family: 'Noto Sans Display', sans-serif;
font-weight: 300;
color: rgba(0, 0, 0, 0.75);
display: flex;
flex-direction: column;
}
/* Consistent spacing of layout */
header,
main,
footer,
article {
padding: 0.75em;
}
header {
padding-bottom: 0;
}
main {
padding-top: 0;
}
header h1 {
color: rgb(86, 89, 92);
font-family: 'Noto Serif Display', serif;
font-weight: 500;
font-size: 2em;
}
header h2 {
color: rgb(48, 117, 172);
font-family: 'Noto Serif Display', serif;
font-weight: 500;
font-size: 2em;
}
h3 {
color: rgb(48, 117, 172);
font-family: 'Noto Serif Display', serif;
font-weight: 500;
}
h4 {
font-weight: 500;
}
main {
flex-grow: 1;
}
footer {
font-size: 0.85em;
background-color: rgb(86, 89, 92);
color: rgba(255, 255, 255, 0.75);
}
footer section {
display: flex;
flex-wrap: wrap;
}
footer article {
flex: 1;
}
footer li {
white-space: nowrap;
/* TODO: use a media query to remove for small widths */
}
h1 {
font-family: 'Noto Serif Display', serif;
font-weight: 500;
}
h2 {
font-family: 'Noto Serif Display', serif;
font-weight: 500;
}
p+p,
p+h3 {
margin-top: 0.75em;
}
ul {
list-style-type: none;
}
main {
display: flex;
flex-direction: row-reverse;
}
#title::first-letter {
text-transform: capitalize;
}
@media only screen and (max-width: 750px) {
main {
flex-direction: column;
}
}
aside {
white-space: nowrap;
}
article {
overflow-y: auto;
}
label {
font-size: 0.8em;
color: rgb(48, 117, 172);
display: block;
margin-top: 0.75em;
}
label p {
color: rgba(0, 0, 0, 0.75);
}
#fileName {
cursor: pointer;
font-size: 1.25em;
}
input[type="file"] {
display: none;
}
select {
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
cursor: pointer;
border: none;
background: none;
color: rgba(0, 0, 0, 0.75);
font-family: 'Open Sans', sans-serif;
font-weight: 300;
font-size: 1em;
}
table {
width: 100%;
}
th {
background-color: rgba(48, 117, 172, 0.125);
font-weight: 500;
font-size: 0.8em;
}
th.xy {
background-color: rgba(48, 117, 172, 0);
}
td {
background-color: rgba(0, 0, 0, 0.125);
font-size: 0.8em;
text-align: center;
}
td,
th {
border: #F9F9F9 1.5px solid;
padding: 0.5em;
}
.axis.y,
.axis.x {
width: 20%;
}
#title {
display: inline-block;
}
.empty {
background-color: transparent;
}
#title::first-letter {
text-transform: capitalize;
}
</style>
</head>
<body>
<header>
<article>
<h1>Steelbreeze</h1>
<h2>Dynamic landscape map</h2>
</article>
</header>
<main>
<aside>
<article>
<h3>
Settings
</h3>
<form>
<label for="filePicker">Data source
<p id="fileName">None selected</p>
</label>
<input id="filePicker" type="file" accept=".csv">
<label for="xAxisPicker">x-axis</label>
<select id="xAxisPicker" name="xAxisPicker">
<option value=""></option>
</select>
<label for="xAxisPicker">y-axis</label>
<select id="yAxisPicker" name="yAxisPicker">
<option value=""></option>
</select>
<label for="factPicker">Fact</label>
<select id="factPicker" name="factPicker">
<option value=""></option>
</select>
</form>
</article>
</aside>
<section>
<article>
<h3>
<span id="title">Select a source file...</span>
</h3>
<table id="landscapeTable">
<tr>
<th class="axis xy"> </th>
<th class="axis x">Select x-axis</th>
</tr>
<tr></tr>
<th class="axis y">Select y-axis</th>
<td>Select fact</td>
</tr>
</table>
</article>
<article>
<h3>
Instructions
</h3>
<p>
This landscape map viewpoint will be genenrated from a .csv file of your choosing with axes based on
the columns found
within the data and an associated fact.
</p>
<p>Firstly, select a .csv file as a data source under thet Settings header; once selected, the x-axis,
y-axis and Fact will be populates with the column headings from the data source and can be used to
control the landscape map.</p>
<p>
Note: once you select the .csv file, the data is only loaded into your web browser; it goes no
further than this.
</p>
<h3>
Background
</h3>
<p>
Landscape maps are a viewpoint in ArchiMate. They represent relationships between
concepts projected on to a two-dimensional grid. They are a little like a pivot table, but in the
place of numerical aggregates, other facts can be displayed and adjacent cells with the same value
can be merged.
</p>
</article>
</section>
</main>
<html-include href="https://steelbreeze.net/components/footer.html"></html-include>
<script src = "https://steelbreeze.net/components/html-include.js"></script>
<script src="../dist/landscape.min.js"></script>
<script type="module">
import * as csv from 'https://cdn.skypack.dev/pin/@steelbreeze/csv@v1.0.0-rc.1-4nHggLhodIp0gDFtdQi7/mode=imports,min/optimized/@steelbreeze/csv.js';
import * as pivot from 'https://cdn.skypack.dev/pin/@steelbreeze/pivot@v4.3.0-JN707WjcEaD97F4i6JD7/mode=imports,min/optimized/@steelbreeze/pivot.js';
const context = {
file: undefined,
json: undefined,
dimensions: undefined,
axes: undefined,
cube: undefined,
getKey: undefined
};
const title = document.getElementById('title');
const filePicker = document.getElementById('filePicker');
const fileName = document.getElementById('fileName');
const factPicker = document.getElementById('factPicker');
const xAxisPicker = document.getElementById('xAxisPicker');
const yAxisPicker = document.getElementById('yAxisPicker');
filePicker.addEventListener('change', fileSelected);
factPicker.addEventListener('change', pivotAndRender);
xAxisPicker.addEventListener('change', pivotAndRender);
yAxisPicker.addEventListener('change', pivotAndRender);
function fileSelected(event) {
context.file = event.target.files[0];
title.innerText = context.file.name.slice(0, -4);
fileName.innerText = context.file.name;
fileOpen(context.file);
// reset the file picker now that we've processed it
filePicker.value = null;
}
function fileOpen(file) {
const reader = new FileReader();
reader.onloadend = fileRead;
reader.readAsText(file);
}
function fileRead(event) {
context.json = csv.parse(event.target.result);
const columnList = Object.getOwnPropertyNames(context.json[0]);
const optionList = `<option value="">None selected</option>${columnList.map(column => `<option value="${column}">${column}</option>`).join('')}`;
context.dimensions = {};
for (const column of columnList) {
context.dimensions[column] = context.json.map(row => row[column]).filter((value, index, source) => source.indexOf(value) === index);
}
factPicker.innerHTML = optionList;
xAxisPicker.innerHTML = optionList;
yAxisPicker.innerHTML = optionList;
pivotAndRender();
}
function pivotAndRender() {
context.axes = {
x: xAxisPicker.selectedIndex ? context.json.map(row => row[xAxisPicker.value]).filter(distinct).map(landscape.criteria(xAxisPicker.value)) : ['Select x-axis'].map(value => Object.assign(() => true, {metadata:[{ key: "noX", value: "Select x-axis"}]})),
y: yAxisPicker.selectedIndex ? context.json.map(row => row[yAxisPicker.value]).filter(distinct).map(landscape.criteria(yAxisPicker.value)) : ['Select y-axis'].map(value => Object.assign(() => true, {metadata:[{ key: "noY", value: "Select y-axis"}]}))
}
context.getKey = factPicker.selectedIndex ? (record) => { return { key: factPicker.value, value: record[factPicker.value], style: 'none' }; } : () => { return { key: '', value: 'Select fact', style: 'none' }; };
context.cube = pivot.pivot(context.json, context.axes.y, context.axes.x);
const tab = landscape.table(context.cube, context.axes.y, context.axes.x, context.getKey, false);
landscape.merge(tab, true, true);
renderTable(tab);
}
function renderTable(tableData, elementId, className) {
const tableElement = document.createElement('table');
tableElement.id = 'landscapeTable';
for (const rowData of tableData) {
tableElement.appendChild(renderRow(rowData));
}
document.getElementById('landscapeTable').replaceWith(tableElement);
}
function renderRow(rowData) {
const rowElement = document.createElement('tr');
for (const cellData of rowData) {
rowElement.appendChild(renderCell(cellData));
}
return rowElement;
}
function renderCell(cellData) {
const cellElement = document.createElement(cellData.style.includes('axis') ? 'th' : 'td');
cellElement.colSpan = cellData.cols;
cellElement.rowSpan = cellData.rows;
cellElement.className = `cell ${cellData.style}`;
const divElement = document.createElement('div');
divElement.appendChild(document.createTextNode(cellData.value));
cellElement.appendChild(divElement);
return cellElement;
}
function distinct(value, index, source) {
return source.indexOf(value) === index;
}
</script>
</body>
</html>