mirror of
https://git.gfz-potsdam.de/naaice/tug.git
synced 2025-12-13 09:28:23 +01:00
543 lines
17 KiB
HTML
543 lines
17 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en-US">
|
||
<head>
|
||
<!-- <link rel="stylesheet" href="style.css"> -->
|
||
<style>
|
||
body {
|
||
background-color: white;
|
||
}
|
||
|
||
:root {
|
||
--max-width: 1440px;
|
||
}
|
||
|
||
.main-wrapper {
|
||
display: grid;
|
||
grid-template-areas: "sidebar main";
|
||
grid-template-columns: minmax(0,1fr) minmax(0,2.5fr);
|
||
grid-column-gap: 50px;
|
||
|
||
margin: 0 auto;
|
||
max-width: var(--max-width);
|
||
}
|
||
|
||
#state-info {
|
||
background-color: lightgray;
|
||
width: 120px;
|
||
text-align: center;
|
||
border: 2px solid black;
|
||
border-radius: 5px;
|
||
font-family: Arial, Helvetica, sans-serif;
|
||
font-size: 25px;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="root">
|
||
<div class="main-wrapper">
|
||
|
||
<div class="sidebar-container">
|
||
<aside id="sidebar">
|
||
<div class="menu">
|
||
<form>
|
||
<label for="c_file">File (XTREME-format): </label>
|
||
<input type="file" id="c_file" name="c_file" accept="text/csv" />
|
||
</form>
|
||
</div>
|
||
<div class="info">
|
||
<p>
|
||
Use the keys <i>q</i> and <i>e</i> for increasing and decreasing the state by <b>1</b>.
|
||
The keys <i>a</i> and <i>d</i> for increasing and decresing the state by <b>10</b>.
|
||
</p>
|
||
<div id="state-info">
|
||
State: -
|
||
</div>
|
||
<div id="legend-info">
|
||
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
|
||
<div class="content-container">
|
||
<main id="content" class="main-content">
|
||
|
||
</main>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
|
||
<script type="module">
|
||
|
||
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
|
||
|
||
// layout information; constants
|
||
const width = document.getElementById('content').offsetWidth;
|
||
const height = document.getElementById('content').offsetWidth;
|
||
const svgMargin = { top: 10, right: 10, bottom: 10, left: 10 };
|
||
const boundary_gap = 1;
|
||
const gridWidth = width - svgMargin.left - svgMargin.right - 2*boundary_gap;
|
||
const gridHeight = height - svgMargin.top - svgMargin.bottom - 2*boundary_gap;
|
||
|
||
// simulation information
|
||
var state = 0; // iteration state
|
||
var iteration_count = 0;
|
||
var number_rows = 0;
|
||
var number_cols = 0;
|
||
var max_concentration = 0;
|
||
var data = [];
|
||
var leftb, rightb, topb, bottomb;
|
||
|
||
// test data; to be deleted later on
|
||
var test_data = [{"row": 0, "col": 0, "value": 1},
|
||
{"row": 0, "col": 1, "value": 1},
|
||
{"row": 0, "col": 2, "value": 1},
|
||
{"row": 0, "col": 3, "value": 1},
|
||
{"row": 1, "col": 0, "value": 1},
|
||
{"row": 1, "col": 1, "value": 1},
|
||
{"row": 1, "col": 2, "value": 1},
|
||
{"row": 1, "col": 3, "value": 1},
|
||
{"row": 2, "col": 0, "value": 1},
|
||
{"row": 2, "col": 1, "value": 1},
|
||
{"row": 2, "col": 2, "value": 1},
|
||
{"row": 2, "col": 3, "value": 1},
|
||
{"row": 3, "col": 0, "value": 1},
|
||
{"row": 3, "col": 1, "value": 1},
|
||
{"row": 3, "col": 2, "value": 1},
|
||
{"row": 3, "col": 3, "value": 1}];
|
||
|
||
|
||
//////////////////////////////////////////
|
||
////////
|
||
//////////////////////////////////////////
|
||
// Copyright 2021, Observable Inc.
|
||
// Released under the ISC license.
|
||
// https://observablehq.com/@d3/color-legend
|
||
function Legend(color, {
|
||
title,
|
||
tickSize = 6,
|
||
width = 320,
|
||
height = 44 + tickSize,
|
||
marginTop = 18,
|
||
marginRight = 0,
|
||
marginBottom = 16 + tickSize,
|
||
marginLeft = 0,
|
||
ticks = width / 64,
|
||
tickFormat,
|
||
tickValues
|
||
} = {}) {
|
||
|
||
function ramp(color, n = 256) {
|
||
const canvas = document.createElement("canvas");
|
||
canvas.width = n;
|
||
canvas.height = 1;
|
||
const context = canvas.getContext("2d");
|
||
for (let i = 0; i < n; ++i) {
|
||
context.fillStyle = color(i / (n - 1));
|
||
context.fillRect(i, 0, 1, 1);
|
||
}
|
||
return canvas;
|
||
}
|
||
|
||
const svg = d3.create("svg")
|
||
.attr("width", width)
|
||
.attr("height", height)
|
||
.attr("viewBox", [0, 0, width, height])
|
||
.style("overflow", "visible")
|
||
.style("display", "block");
|
||
|
||
let tickAdjust = g => g.selectAll(".tick line").attr("y1", marginTop + marginBottom - height);
|
||
let x;
|
||
|
||
// Continuous
|
||
if (color.interpolate) {
|
||
const n = Math.min(color.domain().length, color.range().length);
|
||
|
||
x = color.copy().rangeRound(d3.quantize(d3.interpolate(marginLeft, width - marginRight), n));
|
||
|
||
svg.append("image")
|
||
.attr("x", marginLeft)
|
||
.attr("y", marginTop)
|
||
.attr("width", width - marginLeft - marginRight)
|
||
.attr("height", height - marginTop - marginBottom)
|
||
.attr("preserveAspectRatio", "none")
|
||
.attr("xlink:href", ramp(color.copy().domain(d3.quantize(d3.interpolate(0, 1), n))).toDataURL());
|
||
}
|
||
|
||
// Sequential
|
||
else if (color.interpolator) {
|
||
x = Object.assign(color.copy()
|
||
.interpolator(d3.interpolateRound(marginLeft, width - marginRight)),
|
||
{range() { return [marginLeft, width - marginRight]; }});
|
||
|
||
svg.append("image")
|
||
.attr("x", marginLeft)
|
||
.attr("y", marginTop)
|
||
.attr("width", width - marginLeft - marginRight)
|
||
.attr("height", height - marginTop - marginBottom)
|
||
.attr("preserveAspectRatio", "none")
|
||
.attr("xlink:href", ramp(color.interpolator()).toDataURL());
|
||
|
||
// scaleSequentialQuantile doesn’t implement ticks or tickFormat.
|
||
if (!x.ticks) {
|
||
if (tickValues === undefined) {
|
||
const n = Math.round(ticks + 1);
|
||
tickValues = d3.range(n).map(i => d3.quantile(color.domain(), i / (n - 1)));
|
||
}
|
||
if (typeof tickFormat !== "function") {
|
||
tickFormat = d3.format(tickFormat === undefined ? ",f" : tickFormat);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Threshold
|
||
else if (color.invertExtent) {
|
||
const thresholds
|
||
= color.thresholds ? color.thresholds() // scaleQuantize
|
||
: color.quantiles ? color.quantiles() // scaleQuantile
|
||
: color.domain(); // scaleThreshold
|
||
|
||
const thresholdFormat
|
||
= tickFormat === undefined ? d => d
|
||
: typeof tickFormat === "string" ? d3.format(tickFormat)
|
||
: tickFormat;
|
||
|
||
x = d3.scaleLinear()
|
||
.domain([-1, color.range().length - 1])
|
||
.rangeRound([marginLeft, width - marginRight]);
|
||
|
||
svg.append("g")
|
||
.selectAll("rect")
|
||
.data(color.range())
|
||
.join("rect")
|
||
.attr("x", (d, i) => x(i - 1))
|
||
.attr("y", marginTop)
|
||
.attr("width", (d, i) => x(i) - x(i - 1))
|
||
.attr("height", height - marginTop - marginBottom)
|
||
.attr("fill", d => d);
|
||
|
||
tickValues = d3.range(thresholds.length);
|
||
tickFormat = i => thresholdFormat(thresholds[i], i);
|
||
}
|
||
|
||
// Ordinal
|
||
else {
|
||
x = d3.scaleBand()
|
||
.domain(color.domain())
|
||
.rangeRound([marginLeft, width - marginRight]);
|
||
|
||
svg.append("g")
|
||
.selectAll("rect")
|
||
.data(color.domain())
|
||
.join("rect")
|
||
.attr("x", x)
|
||
.attr("y", marginTop)
|
||
.attr("width", Math.max(0, x.bandwidth() - 1))
|
||
.attr("height", height - marginTop - marginBottom)
|
||
.attr("fill", color);
|
||
|
||
tickAdjust = () => {};
|
||
}
|
||
|
||
svg.append("g")
|
||
.attr("transform", `translate(0,${height - marginBottom})`)
|
||
.call(d3.axisBottom(x)
|
||
.ticks(ticks, typeof tickFormat === "string" ? tickFormat : undefined)
|
||
.tickFormat(typeof tickFormat === "function" ? tickFormat : undefined)
|
||
.tickSize(tickSize)
|
||
.tickValues(tickValues))
|
||
.call(tickAdjust)
|
||
.call(g => g.select(".domain").remove())
|
||
.call(g => g.append("text")
|
||
.attr("x", marginLeft)
|
||
.attr("y", marginTop + marginBottom - height - 6)
|
||
.attr("fill", "currentColor")
|
||
.attr("text-anchor", "start")
|
||
.attr("font-weight", "bold")
|
||
.attr("class", "title")
|
||
.text(title));
|
||
|
||
return svg.node();
|
||
}
|
||
//////////////////////////////////////////
|
||
////////
|
||
//////////////////////////////////////////
|
||
|
||
|
||
|
||
// max concentration
|
||
function calc_max_concentration(state) {
|
||
// var maxRow = data[state].map(function(row){ return Math.max.apply(Math, row); });
|
||
// var maxC = Math.max.apply(null, maxRow);
|
||
var maxC = Math.max(...data[state].map(d => d.value))
|
||
return maxC;
|
||
}
|
||
|
||
// scales
|
||
var col, row, color;
|
||
function create_n_update_scales() {
|
||
col = d3.scaleBand()
|
||
.range([0, gridWidth])
|
||
.domain([...Array(number_cols).keys()])
|
||
.padding(0.05);
|
||
row = d3.scaleBand()
|
||
.range([0, gridHeight])
|
||
.domain([...Array(number_rows).keys()])
|
||
.padding(0.05);
|
||
color = d3.scaleLinear()
|
||
.range(["#d0d0ec", "#7f0000"])
|
||
.domain([0, max_concentration]);
|
||
}
|
||
|
||
// grid
|
||
function draw_n_update_grid(grid_data) {
|
||
document.getElementById('state-info').innerHTML = `State: ${state}`;
|
||
// Grid rects
|
||
grid.selectAll()
|
||
.data(grid_data, (d,i) => d.row+':'+d.col)
|
||
.enter()
|
||
.append("rect")
|
||
.attr("x", (d,i) => col(d.col))
|
||
.attr("y", (d,i) => row(d.row))
|
||
.attr("width", col.bandwidth())
|
||
.attr("height", row.bandwidth())
|
||
.attr("fill", (d,i) => color(d.value))
|
||
.on("mouseover", mouseover)
|
||
.on("mousemove", mousemove)
|
||
.on("mouseleave", mouseleave);
|
||
}
|
||
|
||
// boundaries
|
||
function draw_boundaries() {
|
||
left_side.selectAll("rect")
|
||
.data(leftb)
|
||
.join("rect")
|
||
.attr("x", (d,i) => 0)
|
||
.attr("y", (d,i) => row(i))
|
||
.attr("width", svgMargin.left)
|
||
.attr("height", row.bandwidth())
|
||
.attr("fill", function(d,i) {
|
||
if (d == -1) {
|
||
return "#000000";
|
||
} else {
|
||
return color(d);
|
||
}
|
||
});
|
||
right_side.selectAll("rect")
|
||
.data(rightb)
|
||
.join("rect")
|
||
.attr("x", (d,i) => 0)
|
||
.attr("y", (d,i) => row(i))
|
||
.attr("width", svgMargin.right)
|
||
.attr("height", row.bandwidth())
|
||
.attr("fill", function(d,i) {
|
||
if (d == -1) {
|
||
return "#000000";
|
||
} else {
|
||
return color(d);
|
||
}
|
||
});
|
||
top_side.selectAll("rect")
|
||
.data(topb)
|
||
.join("rect")
|
||
.attr("x", (d,i) => col(i))
|
||
.attr("y", (d,i) => 0)
|
||
.attr("width", col.bandwidth())
|
||
.attr("height", svgMargin.top)
|
||
.attr("fill", function(d,i) {
|
||
if (d == -1) {
|
||
return "#000000";
|
||
} else {
|
||
return color(d);
|
||
}
|
||
});
|
||
bottom_side.selectAll("rect")
|
||
.data(bottomb)
|
||
.join("rect")
|
||
.attr("x", (d,i) => col(i))
|
||
.attr("y", (d,i) => 0)
|
||
.attr("width", col.bandwidth())
|
||
.attr("height", svgMargin.bottom)
|
||
.attr("fill", function(d,i) {
|
||
if (d == -1) {
|
||
return "#000000";
|
||
} else {
|
||
return color(d);
|
||
}
|
||
});
|
||
}
|
||
|
||
function draw_legend() {
|
||
d3.select("#legend-info").selectAll("svg").remove();
|
||
|
||
var legend = Legend(d3.scaleLinear([0, max_concentration], ["#d0d0ec", "#7f0000"]), {
|
||
title: "Concentration",
|
||
tickFormat: ".2f",
|
||
tickValues: [0, max_concentration/4, max_concentration/2, max_concentration/4*3, max_concentration]
|
||
});
|
||
|
||
d3.select("#legend-info")
|
||
.node()
|
||
.appendChild(legend);
|
||
}
|
||
|
||
// tooltip events
|
||
var mouseover = function(event, d) {
|
||
tooltip.style("opacity", 1);
|
||
}
|
||
var mousemove = function(event, d) {
|
||
const tooltip_text = d3.select('.tooltip-text');
|
||
const tooltip_box = d3.select('.tooltip-box');
|
||
|
||
tooltip_text.text(`Value: ${d.value}`);
|
||
var box_width = tooltip_text.node().getComputedTextLength();
|
||
tooltip_box.attr("width", box_width+10);
|
||
|
||
const [x, y] = d3.pointer(event);
|
||
|
||
tooltip
|
||
.attr('transform', `translate(${x+svgMargin.left+50}, ${y+svgMargin.top})`);
|
||
}
|
||
var mouseleave = function(event, d) {
|
||
tooltip.style("opacity", 0)
|
||
}
|
||
|
||
|
||
// key events for changing grid iteration state
|
||
addEventListener("keydown", (event) => {
|
||
if (event.isComposing || event.keyCode === 81) {
|
||
if (state > 0) {
|
||
state -= 1;
|
||
max_concentration = calc_max_concentration(state);
|
||
create_n_update_scales();
|
||
draw_legend();
|
||
draw_n_update_grid(data[state]);
|
||
}
|
||
}
|
||
if (event.isComposing || event.keyCode === 69) {
|
||
if (state < iteration_count-1) {
|
||
state += 1;
|
||
max_concentration = calc_max_concentration(state);
|
||
create_n_update_scales();
|
||
draw_legend();
|
||
draw_n_update_grid(data[state]);
|
||
}
|
||
}
|
||
if (event.isComposing || event.keyCode === 65) {
|
||
if (state > 9) {
|
||
state -= 10;
|
||
} else {
|
||
state = 0;
|
||
}
|
||
max_concentration = calc_max_concentration(state);
|
||
create_n_update_scales();
|
||
draw_legend();
|
||
draw_n_update_grid(data[state]);
|
||
}
|
||
if (event.isComposing || event.keyCode === 68) {
|
||
if (state < iteration_count-10) {
|
||
state += 10;
|
||
} else {
|
||
state = iteration_count-1;
|
||
}
|
||
max_concentration = calc_max_concentration(state);
|
||
create_n_update_scales();
|
||
draw_legend();
|
||
draw_n_update_grid(data[state]);
|
||
}
|
||
});
|
||
|
||
|
||
// read data from file when selecting new file
|
||
async function create_data_struct(csv_text) {
|
||
var row_data = d3.dsvFormat(" ").parseRows(csv_text);
|
||
|
||
leftb = row_data[0];
|
||
rightb = row_data[1];
|
||
topb = row_data[2];
|
||
bottomb = row_data[3];
|
||
|
||
number_rows = row_data[0].length;
|
||
number_cols = row_data[2].length;
|
||
iteration_count = (row_data.length - 6) / (number_rows + 2);
|
||
console.log(iteration_count);
|
||
var iteration = []; // loop temp variable
|
||
var entry = {}; // loop temp variable
|
||
for (let i = 0; i < iteration_count; i++) {
|
||
iteration = []; // reset temp variable
|
||
for (let j = 0; j < number_rows; j++) {
|
||
for (let k = 0; k < number_cols; k++) {
|
||
entry = {}; // reset temp variable
|
||
entry.row = j;
|
||
entry.col = k;
|
||
entry.value = row_data[6 + i*(number_rows+2) + j][k];
|
||
iteration.push(entry);
|
||
}
|
||
}
|
||
data.push(iteration);
|
||
}
|
||
}
|
||
function reader(file, callback) {
|
||
const fr = new FileReader();
|
||
fr.onload = () => callback(null, fr.result);
|
||
fr.onerror = (err) => callback(err);
|
||
fr.readAsText(file)
|
||
}
|
||
document.getElementById("c_file").addEventListener("change", (evt) => {
|
||
if (!evt.target.files) { // no file, do nothing
|
||
return;
|
||
}
|
||
reader(evt.target.files[0], (err, res) => {
|
||
create_data_struct(res);
|
||
state = 0;
|
||
max_concentration = calc_max_concentration(state);
|
||
create_n_update_scales();
|
||
draw_legend();
|
||
draw_boundaries();
|
||
draw_n_update_grid(data[0]);
|
||
});
|
||
});
|
||
|
||
|
||
const svg = d3.create("svg")
|
||
.attr("width", width)
|
||
.attr("height", height);
|
||
|
||
var left_side = svg.append("g")
|
||
.attr("class", "boundary")
|
||
.attr("transform", `translate(0, ${svgMargin.top+boundary_gap})`);
|
||
var right_side = svg.append("g")
|
||
.attr("class", "boundary")
|
||
.attr("transform", `translate(${width-svgMargin.right}, ${svgMargin.top+boundary_gap})`);
|
||
var top_side = svg.append("g")
|
||
.attr("class", "boundary")
|
||
.attr("transform", `translate(${svgMargin.left+boundary_gap}, 0)`);
|
||
var bottom_side = svg.append("g")
|
||
.attr("class", "boundary")
|
||
.attr("transform", `translate(${svgMargin.left+boundary_gap}, ${height-svgMargin.bottom})`);
|
||
|
||
var grid = svg.append("g")
|
||
.attr("class", "grid")
|
||
.attr("transform", `translate(${svgMargin.left + boundary_gap}, ${svgMargin.top + boundary_gap})`);
|
||
|
||
// tooltip
|
||
var tooltip = svg.append("g")
|
||
.attr("class", "tooltip-area")
|
||
.style("opacity", 0);
|
||
var tooltip_box = tooltip.append("rect")
|
||
.attr("class", "tooltip-box")
|
||
.attr("height", 25)
|
||
.attr("transform", `translate(-5,-18)`)
|
||
.attr("fill", "white")
|
||
.attr("stroke", "black");
|
||
tooltip.append("text")
|
||
.attr("class", "tooltip-text")
|
||
.text("Value: ");
|
||
|
||
content.append(svg.node());
|
||
|
||
</script> |