From 7ef89dbf019eff2b7b2dc92466a8fcbc34a4b927 Mon Sep 17 00:00:00 2001 From: Abdallah Akrab Date: Fri, 8 Apr 2022 13:24:16 +0200 Subject: [PATCH] style(js): prettify js files --- .../templates/js/BarChartModule.js | 323 +++++----- .../templates/js/CanvasHexModule.js | 93 +-- .../templates/js/CanvasModule.js | 93 +-- .../visualization/templates/js/ChartModule.js | 166 ++--- mesa/visualization/templates/js/GridDraw.js | 581 ++++++++++-------- mesa/visualization/templates/js/HexDraw.js | 398 ++++++------ .../templates/js/InteractionHandler.js | 221 ++++--- .../templates/js/NetworkModule_sigma.js | 102 +-- .../templates/js/PieChartModule.js | 173 +++--- mesa/visualization/templates/js/TextModule.js | 24 +- mesa/visualization/templates/js/runcontrol.js | 549 +++++++++-------- 11 files changed, 1478 insertions(+), 1245 deletions(-) diff --git a/mesa/visualization/templates/js/BarChartModule.js b/mesa/visualization/templates/js/BarChartModule.js index 4d451974525..a133675b72e 100644 --- a/mesa/visualization/templates/js/BarChartModule.js +++ b/mesa/visualization/templates/js/BarChartModule.js @@ -1,153 +1,186 @@ -'use strict'; +"use strict"; // Note: This grouped bar chart is based off the example found here: // https://bl.ocks.org/mbostock/3887051 -const BarChartModule = function(fields, canvas_width, canvas_height, sorting, sortingKey) { - // Create the overall chart div - const chart_div_tag = "
"; - const chart_div = $(chart_div_tag)[0]; - $("#elements").append(chart_div); - - // Create the tag: - const svg_tag = ""; - // Append it to #elements - const svg_element = $(svg_tag)[0]; - chart_div.append(svg_element); - - //create the legend - const legend_tag = "
"; - const legend_element = $(legend_tag)[0]; - chart_div.append(legend_element); - - const legend = d3.select(legend_element) - .attr("style","display:block;width:" - + canvas_width + "px;text-align:center") - - legend.selectAll("span") - .data(fields) - .enter() - .append("span") - .html(function(d){ - return "" + " " + - d["Label"].replace(" ", " ") - }) - .attr("style", "padding-left:10px;padding-right:10px;") - - // setup the d3 svg selection - const svg = d3.select(svg_element) - const margin = {top: 20, right: 20, bottom: 30, left: 40} - const width = +svg.attr("width") - margin.left - margin.right - const height = +svg.attr("height") - margin.top - margin.bottom - const g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - - // Setup the bar chart - const x0 = d3.scaleBand() - .rangeRound([0, width]) - .paddingInner(0.1); - const x1 = d3.scaleBand() - .padding(0.05); - const y = d3.scaleLinear() - .rangeRound([height, 0]); - const colorScale = d3.scaleOrdinal(fields.map(field => field["Color"])); - const keys = fields.map(f => f['Label']) - const chart = g.append("g") - const axisBottom = g.append("g") - const axisLeft = g.append("g") - - axisBottom - .attr("class", "axis") - .attr("transform", "translate(0," + height + ")") +const BarChartModule = function ( + fields, + canvas_width, + canvas_height, + sorting, + sortingKey +) { + // Create the overall chart div + const chart_div_tag = + "
"; + const chart_div = $(chart_div_tag)[0]; + $("#elements").append(chart_div); + + // Create the tag: + const svg_tag = + ""; + // Append it to #elements + const svg_element = $(svg_tag)[0]; + chart_div.append(svg_element); + + //create the legend + const legend_tag = "
"; + const legend_element = $(legend_tag)[0]; + chart_div.append(legend_element); + + const legend = d3 + .select(legend_element) + .attr( + "style", + "display:block;width:" + canvas_width + "px;text-align:center" + ); + + legend + .selectAll("span") + .data(fields) + .enter() + .append("span") + .html(function (d) { + return ( + "" + + " " + + d["Label"].replace(" ", " ") + ); + }) + .attr("style", "padding-left:10px;padding-right:10px;"); + + // setup the d3 svg selection + const svg = d3.select(svg_element); + const margin = { top: 20, right: 20, bottom: 30, left: 40 }; + const width = +svg.attr("width") - margin.left - margin.right; + const height = +svg.attr("height") - margin.top - margin.bottom; + const g = svg + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + // Setup the bar chart + const x0 = d3.scaleBand().rangeRound([0, width]).paddingInner(0.1); + const x1 = d3.scaleBand().padding(0.05); + const y = d3.scaleLinear().rangeRound([height, 0]); + const colorScale = d3.scaleOrdinal(fields.map((field) => field["Color"])); + const keys = fields.map((f) => f["Label"]); + const chart = g.append("g"); + const axisBottom = g.append("g"); + const axisLeft = g.append("g"); + + axisBottom + .attr("class", "axis") + .attr("transform", "translate(0," + height + ")") + .call(d3.axisBottom(x0)); + + axisLeft.attr("class", "axis").call(d3.axisLeft(y).ticks(null, "s")); + + //Render step + this.render = function (data) { + //Axes + let minY = d3.min(data, function (d) { + return d3.min(keys, function (key) { + return d[key]; + }); + }); + if (minY > 0) { + minY = 0; + } + const maxY = d3.max(data, function (d) { + return d3.max(keys, function (key) { + return d[key]; + }); + }); + + x0.domain( + data.map(function (d, i) { + return i; + }) + ); + x1.domain(keys).rangeRound([0, x0.bandwidth()]); + y.domain([minY, maxY]).nice(); + + if (data.length > 1) { + axisBottom + .attr("transform", "translate(0," + y(0) + ")") .call(d3.axisBottom(x0)); - - axisLeft - .attr("class", "axis") - .call(d3.axisLeft(y).ticks(null, "s")) - - - //Render step - this.render = function(data){ - //Axes - let minY = d3.min(data, function(d){ - return d3.min(keys, function(key){ - return d[key]; - }) - }) - if(minY > 0){ - minY = 0; - } - const maxY = d3.max(data, function(d){ - return d3.max(keys, function(key){ - return d[key]; - }) - }) - - x0.domain(data.map(function(d, i) { return i })); - x1.domain(keys).rangeRound([0, x0.bandwidth()]); - y.domain([minY,maxY]).nice(); - - if(data.length > 1){ - axisBottom - .attr("transform", "translate(0," + y(0) + ")") - .call(d3.axisBottom(x0)) - } - - axisLeft.call(d3.axisLeft(y).ticks(null, "s")) - - //Sorting - if(sorting != "none"){ - if(sorting == "ascending"){ - data.sort((a, b) => b[sortingKey] - a[sortingKey]); - } else if (sorting == "descending") { - data.sort((a, b) => a[sortingKey] - b[sortingKey]); - } - } - - //Draw Chart - const rects = chart - .selectAll("g") - .data(data) - .enter().append("g") - .attr("transform", function(d, i) { return "translate(" + x0(i) + ",0)"; }) - .selectAll("rect") - - rects - .data(function(d) { - return keys.map(function(key) { - return {key: key, value: d[key]}; - }); - }) - .enter() - .append("rect") - .attr("x", function(d) { return x1(d.key); }) - .attr("width", x1.bandwidth()) - .attr("fill", function(d) { return colorScale(d.key); }) - .attr("y", function(d) { return Math.min(y(d.value),y(0)); }) - .attr("height", function(d) { return Math.abs(y(d.value) - y(0)); }) - .append("title") - .text(function (d) { return d.value; }) - - //Update chart - chart - .selectAll("g") - .data(data) - .selectAll("rect") - .data(function(d) { - return keys.map(function(key) { - return {key: key, value: d[key]}; - }); - }) - .attr("y", function(d) { return Math.min(y(d.value),y(0)); }) - .attr("height", function(d) { return Math.abs(y(d.value) - y(0)); }) - .select("title") - .text(function (d) { return d.value; }) - - } - this.reset = function(){ - chart.selectAll("g") - .data([]) - .exit().remove(); + axisLeft.call(d3.axisLeft(y).ticks(null, "s")); + //Sorting + if (sorting != "none") { + if (sorting == "ascending") { + data.sort((a, b) => b[sortingKey] - a[sortingKey]); + } else if (sorting == "descending") { + data.sort((a, b) => a[sortingKey] - b[sortingKey]); + } } -} + //Draw Chart + const rects = chart + .selectAll("g") + .data(data) + .enter() + .append("g") + .attr("transform", function (d, i) { + return "translate(" + x0(i) + ",0)"; + }) + .selectAll("rect"); + + rects + .data(function (d) { + return keys.map(function (key) { + return { key: key, value: d[key] }; + }); + }) + .enter() + .append("rect") + .attr("x", function (d) { + return x1(d.key); + }) + .attr("width", x1.bandwidth()) + .attr("fill", function (d) { + return colorScale(d.key); + }) + .attr("y", function (d) { + return Math.min(y(d.value), y(0)); + }) + .attr("height", function (d) { + return Math.abs(y(d.value) - y(0)); + }) + .append("title") + .text(function (d) { + return d.value; + }); + + //Update chart + chart + .selectAll("g") + .data(data) + .selectAll("rect") + .data(function (d) { + return keys.map(function (key) { + return { key: key, value: d[key] }; + }); + }) + .attr("y", function (d) { + return Math.min(y(d.value), y(0)); + }) + .attr("height", function (d) { + return Math.abs(y(d.value) - y(0)); + }) + .select("title") + .text(function (d) { + return d.value; + }); + }; + + this.reset = function () { + chart.selectAll("g").data([]).exit().remove(); + }; +}; diff --git a/mesa/visualization/templates/js/CanvasHexModule.js b/mesa/visualization/templates/js/CanvasHexModule.js index 7b7626d1cbf..da83057d7a7 100644 --- a/mesa/visualization/templates/js/CanvasHexModule.js +++ b/mesa/visualization/templates/js/CanvasHexModule.js @@ -1,38 +1,57 @@ -const CanvasHexModule = function(canvas_width, canvas_height, grid_width, grid_height) { - // Create the element - // ------------------ - - // Create the tag with absolute positioning : - const canvas_tag = `` - - const parent_div_tag = '
' - - // Append it to body: - const canvas = $(canvas_tag)[0]; - const interaction_canvas = $(canvas_tag)[0]; - const parent = $(parent_div_tag)[0]; - - //$("body").append(canvas); - $("#elements").append(parent); - parent.append(canvas); - parent.append(interaction_canvas); - - // Create the context and the drawing controller: - const context = canvas.getContext("2d"); - - const interactionHandler = new InteractionHandler(canvas_width, canvas_height, grid_width, grid_height, interaction_canvas.getContext("2d")); - - const canvasDraw = new HexVisualization(canvas_width, canvas_height, grid_width, grid_height, context, interactionHandler); - - this.render = function(data) { - canvasDraw.resetCanvas(); - for (const layer in data) - canvasDraw.drawLayer(data[layer]); - canvasDraw.drawGridLines("#eee"); - }; - - this.reset = function() { - canvasDraw.resetCanvas(); - }; - +const CanvasHexModule = function ( + canvas_width, + canvas_height, + grid_width, + grid_height +) { + // Create the element + // ------------------ + + // Create the tag with absolute positioning : + const canvas_tag = ``; + + const parent_div_tag = + '
'; + + // Append it to body: + const canvas = $(canvas_tag)[0]; + const interaction_canvas = $(canvas_tag)[0]; + const parent = $(parent_div_tag)[0]; + + //$("body").append(canvas); + $("#elements").append(parent); + parent.append(canvas); + parent.append(interaction_canvas); + + // Create the context and the drawing controller: + const context = canvas.getContext("2d"); + + const interactionHandler = new InteractionHandler( + canvas_width, + canvas_height, + grid_width, + grid_height, + interaction_canvas.getContext("2d") + ); + + const canvasDraw = new HexVisualization( + canvas_width, + canvas_height, + grid_width, + grid_height, + context, + interactionHandler + ); + + this.render = function (data) { + canvasDraw.resetCanvas(); + for (const layer in data) canvasDraw.drawLayer(data[layer]); + canvasDraw.drawGridLines("#eee"); + }; + + this.reset = function () { + canvasDraw.resetCanvas(); + }; }; diff --git a/mesa/visualization/templates/js/CanvasModule.js b/mesa/visualization/templates/js/CanvasModule.js index 43373cd8f55..0bde83f9aeb 100644 --- a/mesa/visualization/templates/js/CanvasModule.js +++ b/mesa/visualization/templates/js/CanvasModule.js @@ -1,38 +1,57 @@ -const CanvasModule = function(canvas_width, canvas_height, grid_width, grid_height) { - // Create the element - // ------------------ - - // Create the tag with absolute positioning : - const canvas_tag = `` - - const parent_div_tag = '
' - - // Append it to body: - const canvas = $(canvas_tag)[0]; - const interaction_canvas = $(canvas_tag)[0]; - const parent = $(parent_div_tag)[0]; - - //$("body").append(canvas); - $("#elements").append(parent); - parent.append(canvas); - parent.append(interaction_canvas); - - // Create the context for the agents and interactions and the drawing controller: - const context = canvas.getContext("2d"); - - // Create an interaction handler using the - const interactionHandler = new InteractionHandler(canvas_width, canvas_height, grid_width, grid_height, interaction_canvas.getContext("2d")); - const canvasDraw = new GridVisualization(canvas_width, canvas_height, grid_width, grid_height, context, interactionHandler); - - this.render = function(data) { - canvasDraw.resetCanvas(); - for (const layer in data) - canvasDraw.drawLayer(data[layer]); - canvasDraw.drawGridLines("#eee"); - }; - - this.reset = function() { - canvasDraw.resetCanvas(); - }; - +const CanvasModule = function ( + canvas_width, + canvas_height, + grid_width, + grid_height +) { + // Create the element + // ------------------ + + // Create the tag with absolute positioning : + const canvas_tag = ``; + + const parent_div_tag = + '
'; + + // Append it to body: + const canvas = $(canvas_tag)[0]; + const interaction_canvas = $(canvas_tag)[0]; + const parent = $(parent_div_tag)[0]; + + //$("body").append(canvas); + $("#elements").append(parent); + parent.append(canvas); + parent.append(interaction_canvas); + + // Create the context for the agents and interactions and the drawing controller: + const context = canvas.getContext("2d"); + + // Create an interaction handler using the + const interactionHandler = new InteractionHandler( + canvas_width, + canvas_height, + grid_width, + grid_height, + interaction_canvas.getContext("2d") + ); + const canvasDraw = new GridVisualization( + canvas_width, + canvas_height, + grid_width, + grid_height, + context, + interactionHandler + ); + + this.render = function (data) { + canvasDraw.resetCanvas(); + for (const layer in data) canvasDraw.drawLayer(data[layer]); + canvasDraw.drawGridLines("#eee"); + }; + + this.reset = function () { + canvasDraw.resetCanvas(); + }; }; diff --git a/mesa/visualization/templates/js/ChartModule.js b/mesa/visualization/templates/js/ChartModule.js index 6e75cbadea4..69150c650a0 100644 --- a/mesa/visualization/templates/js/ChartModule.js +++ b/mesa/visualization/templates/js/ChartModule.js @@ -1,91 +1,99 @@ -const ChartModule = function(series, canvas_width, canvas_height) { - // Create the tag: - const canvas_tag = ""; - // Append it to #elements - const canvas = $(canvas_tag)[0]; - $("#elements").append(canvas); - // Create the context and the drawing controller: - const context = canvas.getContext("2d"); +const ChartModule = function (series, canvas_width, canvas_height) { + // Create the tag: + const canvas_tag = + ""; + // Append it to #elements + const canvas = $(canvas_tag)[0]; + $("#elements").append(canvas); + // Create the context and the drawing controller: + const context = canvas.getContext("2d"); - const convertColorOpacity = function(hex) { + const convertColorOpacity = function (hex) { + if (hex.indexOf("#") != 0) { + return "rgba(0,0,0,0.1)"; + } - if (hex.indexOf('#') != 0) { - return 'rgba(0,0,0,0.1)'; - } + hex = hex.replace("#", ""); + r = parseInt(hex.substring(0, 2), 16); + g = parseInt(hex.substring(2, 4), 16); + b = parseInt(hex.substring(4, 6), 16); + return "rgba(" + r + "," + g + "," + b + ",0.1)"; + }; - hex = hex.replace('#', ''); - r = parseInt(hex.substring(0, 2), 16); - g = parseInt(hex.substring(2, 4), 16); - b = parseInt(hex.substring(4, 6), 16); - return 'rgba(' + r + ',' + g + ',' + b + ',0.1)'; + // Prep the chart properties and series: + const datasets = []; + for (const i in series) { + const s = series[i]; + const new_series = { + label: s.Label, + borderColor: s.Color, + backgroundColor: convertColorOpacity(s.Color), + data: [], }; + datasets.push(new_series); + } - // Prep the chart properties and series: - const datasets = [] - for (const i in series) { - const s = series[i]; - const new_series = { - label: s.Label, - borderColor: s.Color, - backgroundColor: convertColorOpacity(s.Color), - data: [] - }; - datasets.push(new_series); - } - - const chartData = { - labels: [], - datasets: datasets - }; + const chartData = { + labels: [], + datasets: datasets, + }; - const chartOptions = { - responsive: true, - tooltips: { - mode: 'index', - intersect: false + const chartOptions = { + responsive: true, + tooltips: { + mode: "index", + intersect: false, + }, + hover: { + mode: "nearest", + intersect: true, + }, + scales: { + x: { + display: true, + title: { + display: true, }, - hover: { - mode: 'nearest', - intersect: true + ticks: { + maxTicksLimit: 11, }, - scales: { - x: { - display: true, - title: { - display: true - }, - ticks: { - maxTicksLimit: 11 - } - }, - y: { - display: true, - title: { - display: true - } - } - } - }; + }, + y: { + display: true, + title: { + display: true, + }, + }, + }, + }; - const chart = new Chart(context, { - type: 'line', - data: chartData, - options: chartOptions - }); + const chart = new Chart(context, { + type: "line", + data: chartData, + options: chartOptions, + }); - this.render = function(data) { - chart.data.labels.push(control.tick); - for (let i = 0; i < data.length; i++) { - chart.data.datasets[i].data.push(data[i]); - } - chart.update(); - }; + this.render = function (data) { + chart.data.labels.push(control.tick); + for (let i = 0; i < data.length; i++) { + chart.data.datasets[i].data.push(data[i]); + } + chart.update(); + }; - this.reset = function() { - while (chart.data.labels.length) { chart.data.labels.pop(); } - chart.data.datasets.forEach(function(dataset) { - while (dataset.data.length) { dataset.data.pop(); } - }); - chart.update(); - }; + this.reset = function () { + while (chart.data.labels.length) { + chart.data.labels.pop(); + } + chart.data.datasets.forEach(function (dataset) { + while (dataset.data.length) { + dataset.data.pop(); + } + }); + chart.update(); + }; }; diff --git a/mesa/visualization/templates/js/GridDraw.js b/mesa/visualization/templates/js/GridDraw.js index b9c43fd6edd..eb5f2020955 100644 --- a/mesa/visualization/templates/js/GridDraw.js +++ b/mesa/visualization/templates/js/GridDraw.js @@ -38,58 +38,96 @@ other agent locations, represented by circles: */ -const GridVisualization = function(width, height, gridWidth, gridHeight, context, interactionHandler) { - - // Find cell size: - const cellWidth = Math.floor(width / gridWidth); - const cellHeight = Math.floor(height / gridHeight); - - // Find max radius of the circle that can be inscribed (fit) into the - // cell of the grid. - const maxR = Math.min(cellHeight, cellWidth)/2 - 1; - - // Calls the appropriate shape(agent) - this.drawLayer = function(portrayalLayer) { - // Re-initialize the lookup table - (interactionHandler) ? interactionHandler.mouseoverLookupTable.init() : null - - for (const i in portrayalLayer) { - const p = portrayalLayer[i]; - - // If p.Color is a string scalar, cast it to an array. - // This is done to maintain backwards compatibility - if (!Array.isArray(p.Color)) - p.Color = [p.Color]; - - // Does the inversion of y positioning because of html5 - // canvas y direction is from top to bottom. But we - // normally keep y-axis in plots from bottom to top. - p.y = gridHeight - p.y - 1; - - // if a handler exists, add coordinates for the portrayalLayer index - (interactionHandler) ? interactionHandler.mouseoverLookupTable.set(p.x, p.y, i) : null; - - // If the stroke color is not defined, then the first color in the colors array is the stroke color. - if (!p.stroke_color) - p.stroke_color = p.Color[0] - - if (p.Shape == "rect") - this.drawRectangle(p.x, p.y, p.w, p.h, p.Color, p.stroke_color, p.Filled, p.text, p.text_color); - else if (p.Shape == "circle") - this.drawCircle(p.x, p.y, p.r, p.Color, p.stroke_color, p.Filled, p.text, p.text_color); - else if (p.Shape == "arrowHead") - this.drawArrowHead(p.x, p.y, p.heading_x, p.heading_y, p.scale, p.Color, p.stroke_color, p.Filled, p.text, p.text_color); - else - this.drawCustomImage(p.Shape, p.x, p.y, p.scale, p.text, p.text_color) - } - // if a handler exists, update its mouse listeners with the new data - (interactionHandler) ? interactionHandler.updateMouseListeners(portrayalLayer): null; - }; - - // DRAWING METHODS - // ===================================================================== - - /** +const GridVisualization = function ( + width, + height, + gridWidth, + gridHeight, + context, + interactionHandler +) { + // Find cell size: + const cellWidth = Math.floor(width / gridWidth); + const cellHeight = Math.floor(height / gridHeight); + + // Find max radius of the circle that can be inscribed (fit) into the + // cell of the grid. + const maxR = Math.min(cellHeight, cellWidth) / 2 - 1; + + // Calls the appropriate shape(agent) + this.drawLayer = function (portrayalLayer) { + // Re-initialize the lookup table + interactionHandler ? interactionHandler.mouseoverLookupTable.init() : null; + + for (const i in portrayalLayer) { + const p = portrayalLayer[i]; + + // If p.Color is a string scalar, cast it to an array. + // This is done to maintain backwards compatibility + if (!Array.isArray(p.Color)) p.Color = [p.Color]; + + // Does the inversion of y positioning because of html5 + // canvas y direction is from top to bottom. But we + // normally keep y-axis in plots from bottom to top. + p.y = gridHeight - p.y - 1; + + // if a handler exists, add coordinates for the portrayalLayer index + interactionHandler + ? interactionHandler.mouseoverLookupTable.set(p.x, p.y, i) + : null; + + // If the stroke color is not defined, then the first color in the colors array is the stroke color. + if (!p.stroke_color) p.stroke_color = p.Color[0]; + + if (p.Shape == "rect") + this.drawRectangle( + p.x, + p.y, + p.w, + p.h, + p.Color, + p.stroke_color, + p.Filled, + p.text, + p.text_color + ); + else if (p.Shape == "circle") + this.drawCircle( + p.x, + p.y, + p.r, + p.Color, + p.stroke_color, + p.Filled, + p.text, + p.text_color + ); + else if (p.Shape == "arrowHead") + this.drawArrowHead( + p.x, + p.y, + p.heading_x, + p.heading_y, + p.scale, + p.Color, + p.stroke_color, + p.Filled, + p.text, + p.text_color + ); + else + this.drawCustomImage(p.Shape, p.x, p.y, p.scale, p.text, p.text_color); + } + // if a handler exists, update its mouse listeners with the new data + interactionHandler + ? interactionHandler.updateMouseListeners(portrayalLayer) + : null; + }; + + // DRAWING METHODS + // ===================================================================== + + /** Draw a circle in the specified grid cell. x, y: Grid coords r: Radius, as a multiple of cell size @@ -99,40 +137,48 @@ const GridVisualization = function(width, height, gridWidth, gridHeight, context text: Inscribed text in rectangle. text_color: Color of the inscribed text. */ - this.drawCircle = function(x, y, radius, colors, stroke_color, fill, text, text_color) { - const cx = (x + 0.5) * cellWidth; - const cy = (y + 0.5) * cellHeight; - const r = radius * maxR; - - context.beginPath(); - context.arc(cx, cy, r, 0, Math.PI * 2, false); - context.closePath(); - - context.strokeStyle = stroke_color; - context.stroke(); - - if (fill) { - const gradient = context.createRadialGradient(cx, cy, r, cx, cy, 0); - - for (let i = 0; i < colors.length; i++) { - gradient.addColorStop(i/colors.length, colors[i]); - } - - context.fillStyle = gradient; - context.fill(); - } - - // This part draws the text inside the Circle - if (text !== undefined) { - context.fillStyle = text_color; - context.textAlign = 'center'; - context.textBaseline= 'middle'; - context.fillText(text, cx, cy); - } - - }; - - /** + this.drawCircle = function ( + x, + y, + radius, + colors, + stroke_color, + fill, + text, + text_color + ) { + const cx = (x + 0.5) * cellWidth; + const cy = (y + 0.5) * cellHeight; + const r = radius * maxR; + + context.beginPath(); + context.arc(cx, cy, r, 0, Math.PI * 2, false); + context.closePath(); + + context.strokeStyle = stroke_color; + context.stroke(); + + if (fill) { + const gradient = context.createRadialGradient(cx, cy, r, cx, cy, 0); + + for (let i = 0; i < colors.length; i++) { + gradient.addColorStop(i / colors.length, colors[i]); + } + + context.fillStyle = gradient; + context.fill(); + } + + // This part draws the text inside the Circle + if (text !== undefined) { + context.fillStyle = text_color; + context.textAlign = "center"; + context.textBaseline = "middle"; + context.fillText(text, cx, cy); + } + }; + + /** Draw a rectangle in the specified grid cell. x, y: Grid coords w, h: Width and height, [0, 1] @@ -142,46 +188,59 @@ const GridVisualization = function(width, height, gridWidth, gridHeight, context text: Inscribed text in rectangle. text_color: Color of the inscribed text. */ - this.drawRectangle = function(x, y, w, h, colors, stroke_color, fill, text, text_color) { - - context.beginPath(); - const dx = w * cellWidth; - const dy = h * cellHeight; - - // Keep in the center of the cell: - const x0 = (x + 0.5) * cellWidth - dx/2; - const y0 = (y + 0.5) * cellHeight - dy/2; - - context.strokeStyle = stroke_color; - context.strokeRect(x0, y0, dx, dy); - - if (fill) { - const gradient = context.createLinearGradient(x0, y0, x0 + cellWidth, y0 + cellHeight); - - for (let i = 0; i < colors.length; i++) { - gradient.addColorStop(i/colors.length, colors[i]); - } - - // Fill with gradient - context.fillStyle = gradient; - context.fillRect(x0,y0,dx,dy); - } - else { - context.fillStyle = color; - context.strokeRect(x0, y0, dx, dy); - } - // This part draws the text inside the Rectangle - if (text !== undefined) { - const cx = (x + 0.5) * cellWidth; - const cy = (y + 0.5) * cellHeight; - context.fillStyle = text_color; - context.textAlign = 'center'; - context.textBaseline= 'middle'; - context.fillText(text, cx, cy); - } - }; - - /** + this.drawRectangle = function ( + x, + y, + w, + h, + colors, + stroke_color, + fill, + text, + text_color + ) { + context.beginPath(); + const dx = w * cellWidth; + const dy = h * cellHeight; + + // Keep in the center of the cell: + const x0 = (x + 0.5) * cellWidth - dx / 2; + const y0 = (y + 0.5) * cellHeight - dy / 2; + + context.strokeStyle = stroke_color; + context.strokeRect(x0, y0, dx, dy); + + if (fill) { + const gradient = context.createLinearGradient( + x0, + y0, + x0 + cellWidth, + y0 + cellHeight + ); + + for (let i = 0; i < colors.length; i++) { + gradient.addColorStop(i / colors.length, colors[i]); + } + + // Fill with gradient + context.fillStyle = gradient; + context.fillRect(x0, y0, dx, dy); + } else { + context.fillStyle = color; + context.strokeRect(x0, y0, dx, dy); + } + // This part draws the text inside the Rectangle + if (text !== undefined) { + const cx = (x + 0.5) * cellWidth; + const cy = (y + 0.5) * cellHeight; + context.fillStyle = text_color; + context.textAlign = "center"; + context.textBaseline = "middle"; + context.fillText(text, cx, cy); + } + }; + + /** Draw an arrow head in the specified grid cell. x, y: Grid coords s: Scaling of the arrowHead with respect to cell size[0, 1] @@ -191,139 +250,145 @@ const GridVisualization = function(width, height, gridWidth, gridHeight, context text: Inscribed text in shape. text_color: Color of the inscribed text. */ - this.drawArrowHead = function(x, y, heading_x, heading_y, scale, colors, stroke_color, fill, text, text_color) { - const arrowR = maxR * scale; - const cx = (x + 0.5) * cellWidth; - const cy = (y + 0.5) * cellHeight; - if (heading_x === 0 && heading_y === 1) { - p1_x = cx; - p1_y = cy - arrowR; - p2_x = cx - arrowR; - p2_y = cy + arrowR; - p3_x = cx; - p3_y = cy + 0.8*arrowR; - p4_x = cx + arrowR; - p4_y = cy + arrowR; - } - else if (heading_x === 1 && heading_y === 0) { - p1_x = cx + arrowR; - p1_y = cy; - p2_x = cx - arrowR; - p2_y = cy - arrowR; - p3_x = cx - 0.8*arrowR; - p3_y = cy; - p4_x = cx - arrowR; - p4_y = cy + arrowR; - } - else if (heading_x === 0 && heading_y === (-1)) { - p1_x = cx; - p1_y = cy + arrowR; - p2_x = cx - arrowR; - p2_y = cy - arrowR; - p3_x = cx; - p3_y = cy - 0.8*arrowR; - p4_x = cx + arrowR; - p4_y = cy - arrowR; - } - else if (heading_x === (-1) && heading_y === 0) { - p1_x = cx - arrowR; - p1_y = cy; - p2_x = cx + arrowR; - p2_y = cy - arrowR; - p3_x = cx + 0.8*arrowR; - p3_y = cy; - p4_x = cx + arrowR; - p4_y = cy + arrowR; - } - - context.beginPath(); - context.moveTo(p1_x, p1_y); - context.lineTo(p2_x, p2_y); - context.lineTo(p3_x, p3_y); - context.lineTo(p4_x, p4_y); - context.closePath(); - - context.strokeStyle = stroke_color; - context.stroke(); - - if (fill) { - const gradient = context.createLinearGradient(p1_x, p1_y, p3_x, p3_y); - - for (let i = 0; i < colors.length; i++) { - gradient.addColorStop(i/colors.length, colors[i]); - } - - // Fill with gradient - context.fillStyle = gradient; - context.fill(); - } - - // This part draws the text inside the ArrowHead - if (text !== undefined) { - context.fillStyle = text_color - context.textAlign = 'center'; - context.textBaseline= 'middle'; - context.fillText(text, cx, cy); - } - }; - - this.drawCustomImage = function (shape, x, y, scale, text, text_color_) { - const img = new Image(); - img.src = "local/".concat(shape); - if (scale === undefined) { - scale = 1 - } - // Calculate coordinates so the image is always centered - const dWidth = cellWidth * scale; - const dHeight = cellHeight * scale; - const cx = x * cellWidth + cellWidth / 2 - dWidth / 2; - const cy = y * cellHeight + cellHeight / 2 - dHeight / 2; - - // Coordinates for the text - const tx = (x + 0.5) * cellWidth; - const ty = (y + 0.5) * cellHeight; - - - img.onload = function() { - context.drawImage(img, cx, cy, dWidth, dHeight); - // This part draws the text on the image - if (text !== undefined) { - // ToDo: Fix fillStyle - // context.fillStyle = text_color; - context.textAlign = 'center'; - context.textBaseline= 'middle'; - context.fillText(text, tx, ty); - } - } - } - - /** + this.drawArrowHead = function ( + x, + y, + heading_x, + heading_y, + scale, + colors, + stroke_color, + fill, + text, + text_color + ) { + const arrowR = maxR * scale; + const cx = (x + 0.5) * cellWidth; + const cy = (y + 0.5) * cellHeight; + if (heading_x === 0 && heading_y === 1) { + p1_x = cx; + p1_y = cy - arrowR; + p2_x = cx - arrowR; + p2_y = cy + arrowR; + p3_x = cx; + p3_y = cy + 0.8 * arrowR; + p4_x = cx + arrowR; + p4_y = cy + arrowR; + } else if (heading_x === 1 && heading_y === 0) { + p1_x = cx + arrowR; + p1_y = cy; + p2_x = cx - arrowR; + p2_y = cy - arrowR; + p3_x = cx - 0.8 * arrowR; + p3_y = cy; + p4_x = cx - arrowR; + p4_y = cy + arrowR; + } else if (heading_x === 0 && heading_y === -1) { + p1_x = cx; + p1_y = cy + arrowR; + p2_x = cx - arrowR; + p2_y = cy - arrowR; + p3_x = cx; + p3_y = cy - 0.8 * arrowR; + p4_x = cx + arrowR; + p4_y = cy - arrowR; + } else if (heading_x === -1 && heading_y === 0) { + p1_x = cx - arrowR; + p1_y = cy; + p2_x = cx + arrowR; + p2_y = cy - arrowR; + p3_x = cx + 0.8 * arrowR; + p3_y = cy; + p4_x = cx + arrowR; + p4_y = cy + arrowR; + } + + context.beginPath(); + context.moveTo(p1_x, p1_y); + context.lineTo(p2_x, p2_y); + context.lineTo(p3_x, p3_y); + context.lineTo(p4_x, p4_y); + context.closePath(); + + context.strokeStyle = stroke_color; + context.stroke(); + + if (fill) { + const gradient = context.createLinearGradient(p1_x, p1_y, p3_x, p3_y); + + for (let i = 0; i < colors.length; i++) { + gradient.addColorStop(i / colors.length, colors[i]); + } + + // Fill with gradient + context.fillStyle = gradient; + context.fill(); + } + + // This part draws the text inside the ArrowHead + if (text !== undefined) { + context.fillStyle = text_color; + context.textAlign = "center"; + context.textBaseline = "middle"; + context.fillText(text, cx, cy); + } + }; + + this.drawCustomImage = function (shape, x, y, scale, text, text_color_) { + const img = new Image(); + img.src = "local/".concat(shape); + if (scale === undefined) { + scale = 1; + } + // Calculate coordinates so the image is always centered + const dWidth = cellWidth * scale; + const dHeight = cellHeight * scale; + const cx = x * cellWidth + cellWidth / 2 - dWidth / 2; + const cy = y * cellHeight + cellHeight / 2 - dHeight / 2; + + // Coordinates for the text + const tx = (x + 0.5) * cellWidth; + const ty = (y + 0.5) * cellHeight; + + img.onload = function () { + context.drawImage(img, cx, cy, dWidth, dHeight); + // This part draws the text on the image + if (text !== undefined) { + // ToDo: Fix fillStyle + // context.fillStyle = text_color; + context.textAlign = "center"; + context.textBaseline = "middle"; + context.fillText(text, tx, ty); + } + }; + }; + + /** Draw Grid lines in the full gird */ - this.drawGridLines = function() { - context.beginPath(); - context.strokeStyle = "#eee"; - maxX = cellWidth * gridWidth; - maxY = cellHeight * gridHeight; - - // Draw horizontal grid lines: - for(let y=0; y<=maxY; y+=cellHeight) { - context.moveTo(0, y+0.5); - context.lineTo(maxX, y+0.5); - } - - for(let x=0; x<=maxX; x+= cellWidth) { - context.moveTo(x+0.5, 0); - context.lineTo(x+0.5, maxY); - } - - context.stroke(); - }; - - this.resetCanvas = function() { - context.clearRect(0, 0, width, height); - context.beginPath(); - }; - + this.drawGridLines = function () { + context.beginPath(); + context.strokeStyle = "#eee"; + maxX = cellWidth * gridWidth; + maxY = cellHeight * gridHeight; + + // Draw horizontal grid lines: + for (let y = 0; y <= maxY; y += cellHeight) { + context.moveTo(0, y + 0.5); + context.lineTo(maxX, y + 0.5); + } + + for (let x = 0; x <= maxX; x += cellWidth) { + context.moveTo(x + 0.5, 0); + context.lineTo(x + 0.5, maxY); + } + + context.stroke(); + }; + + this.resetCanvas = function () { + context.clearRect(0, 0, width, height); + context.beginPath(); + }; }; diff --git a/mesa/visualization/templates/js/HexDraw.js b/mesa/visualization/templates/js/HexDraw.js index d49de065211..a09bbbff9bc 100644 --- a/mesa/visualization/templates/js/HexDraw.js +++ b/mesa/visualization/templates/js/HexDraw.js @@ -38,50 +38,70 @@ other agent locations, represented by circles: */ -const HexVisualization = function(width, height, gridWidth, gridHeight, context, interactionHandler) { - - // Find cell size: - const cellWidth = Math.floor(width / gridWidth); - const cellHeight = Math.floor(height / gridHeight); - - // Find max radius of the circle that can be inscribed (fit) into the - // cell of the grid. - const maxR = Math.min(cellHeight, cellWidth)/2 - 1; - - // Configure the interaction handler to use a hex coordinate mapper - (interactionHandler) ? interactionHandler.setCoordinateMapper("hex") : null; - - // Calls the appropriate shape(agent) - this.drawLayer = function(portrayalLayer) { - // Re-initialize the lookup table - (interactionHandler) ? interactionHandler.mouseoverLookupTable.init() : null - for (const i in portrayalLayer) { - const p = portrayalLayer[i]; - // Does the inversion of y positioning because of html5 - // canvas y direction is from top to bottom. But we - // normally keep y-axis in plots from bottom to top. - p.y = gridHeight - p.y - 1; - - // if a handler exists, add coordinates for the portrayalLayer index - (interactionHandler) ? interactionHandler.mouseoverLookupTable.set(p.x, p.y, i) : null; - - if (p.Shape == "hex") - this.drawHex(p.x, p.y, p.r, p.Color, p.Filled, p.text, p.text_color); - else if (p.Shape == "circle") - this.drawCircle(p.x, p.y, p.r, p.Color, p.Filled, p.text, p.text_color); - else if (p.Shape == "arrowHead") - this.drawArrowHead(p.x, p.y, p.heading_x, p.heading_y, p.scale, p.Color, p.Filled, p.text, p.text_color); - else - this.drawCustomImage(p.Shape, p.x, p.y, p.scale, p.text, p.text_color) - } - // if a handler exists, update its mouse listeners with the new data - (interactionHandler) ? interactionHandler.updateMouseListeners(portrayalLayer): null; - }; - - // DRAWING METHODS - // ===================================================================== - - /** +const HexVisualization = function ( + width, + height, + gridWidth, + gridHeight, + context, + interactionHandler +) { + // Find cell size: + const cellWidth = Math.floor(width / gridWidth); + const cellHeight = Math.floor(height / gridHeight); + + // Find max radius of the circle that can be inscribed (fit) into the + // cell of the grid. + const maxR = Math.min(cellHeight, cellWidth) / 2 - 1; + + // Configure the interaction handler to use a hex coordinate mapper + interactionHandler ? interactionHandler.setCoordinateMapper("hex") : null; + + // Calls the appropriate shape(agent) + this.drawLayer = function (portrayalLayer) { + // Re-initialize the lookup table + interactionHandler ? interactionHandler.mouseoverLookupTable.init() : null; + for (const i in portrayalLayer) { + const p = portrayalLayer[i]; + // Does the inversion of y positioning because of html5 + // canvas y direction is from top to bottom. But we + // normally keep y-axis in plots from bottom to top. + p.y = gridHeight - p.y - 1; + + // if a handler exists, add coordinates for the portrayalLayer index + interactionHandler + ? interactionHandler.mouseoverLookupTable.set(p.x, p.y, i) + : null; + + if (p.Shape == "hex") + this.drawHex(p.x, p.y, p.r, p.Color, p.Filled, p.text, p.text_color); + else if (p.Shape == "circle") + this.drawCircle(p.x, p.y, p.r, p.Color, p.Filled, p.text, p.text_color); + else if (p.Shape == "arrowHead") + this.drawArrowHead( + p.x, + p.y, + p.heading_x, + p.heading_y, + p.scale, + p.Color, + p.Filled, + p.text, + p.text_color + ); + else + this.drawCustomImage(p.Shape, p.x, p.y, p.scale, p.text, p.text_color); + } + // if a handler exists, update its mouse listeners with the new data + interactionHandler + ? interactionHandler.updateMouseListeners(portrayalLayer) + : null; + }; + + // DRAWING METHODS + // ===================================================================== + + /** Draw a circle in the specified grid cell. x, y: Grid coords r: Radius, as a multiple of cell size @@ -90,39 +110,38 @@ const HexVisualization = function(width, height, gridWidth, gridHeight, context, text: Inscribed text in rectangle. text_color: Color of the inscribed text. */ - this.drawCircle = function(x, y, radius, color, fill, text, text_color) { - const cx = (x + 0.5) * cellWidth; - let cy; - if(x % 2 == 0){ - cy = (y + 0.5) * cellHeight; - } else { - cy = ((y + 0.5) * cellHeight) + cellHeight/2; - } - const r = radius * maxR; - - context.beginPath(); - context.arc(cx, cy, r, 0, Math.PI * 2, false); - context.closePath(); - - context.strokeStyle = color; - context.stroke(); - - if (fill) { - context.fillStyle = color; - context.fill(); - } - - // This part draws the text inside the Circle - if (text !== undefined) { - context.fillStyle = text_color; - context.textAlign = 'center'; - context.textBaseline= 'middle'; - context.fillText(text, cx, cy); - } - - }; - - /** + this.drawCircle = function (x, y, radius, color, fill, text, text_color) { + const cx = (x + 0.5) * cellWidth; + let cy; + if (x % 2 == 0) { + cy = (y + 0.5) * cellHeight; + } else { + cy = (y + 0.5) * cellHeight + cellHeight / 2; + } + const r = radius * maxR; + + context.beginPath(); + context.arc(cx, cy, r, 0, Math.PI * 2, false); + context.closePath(); + + context.strokeStyle = color; + context.stroke(); + + if (fill) { + context.fillStyle = color; + context.fill(); + } + + // This part draws the text inside the Circle + if (text !== undefined) { + context.fillStyle = text_color; + context.textAlign = "center"; + context.textBaseline = "middle"; + context.fillText(text, cx, cy); + } + }; + + /** Draw a hexagon in the specified grid cell. x, y: Grid coords r: Radius, as a multiple of cell size @@ -131,122 +150,117 @@ const HexVisualization = function(width, height, gridWidth, gridHeight, context, text: Inscribed text in rectangle. text_color: Color of the inscribed text. */ - this.drawHex = function(x, y, radius, color, fill, text, text_color) { - const cx = (x + 0.5) * cellWidth; - let cy; - if(x % 2 == 0){ - cy = (y + 0.5) * cellHeight; - } else { - cy = ((y + 0.5) * cellHeight) + cellHeight/2; - } - maxHexRadius = cellHeight/Math.sqrt(3) - const r = radius * maxHexRadius; - - function hex_corner(x,y, size, i){ - const angle_deg = 60 * i - const angle_rad = Math.PI / 180 * angle_deg - return [(x + size * Math.cos(angle_rad) * 1.2), - (y + size * Math.sin(angle_rad))] - } - - - context.beginPath(); - let [px, py] = hex_corner(cx,cy,r,1) - // console.log(px,py) - context.moveTo(px,py) - //for i in range(5): - Array.from(new Array(5), (n,i) => { - [px, py] = hex_corner(cx,cy,r,i + 2) - // console.log(px,py) - context.lineTo(px,py) - }) - context.closePath() - - context.strokeStyle = color; - context.stroke(); - - if (fill) { - context.fillStyle = color; - context.fill(); - } - // This part draws the text inside the Circle - if (text !== undefined) { - context.fillStyle = text_color; - context.textAlign = 'center'; - context.textBaseline= 'middle'; - context.fillText(text, cx, cy); - } - - }; - - - this.drawCustomImage = function (shape, x, y, scale, text, text_color_) { - const img = new Image(); - img.src = "local/".concat(shape); - if (scale === undefined) { - scale = 1 - } - // Calculate coordinates so the image is always centered - const dWidth = cellWidth * scale; - const dHeight = cellHeight * scale; - const cx = x * cellWidth + cellWidth / 2 - dWidth / 2; - const cy = y * cellHeight + cellHeight / 2 - dHeight / 2; - - // Coordinates for the text - const tx = (x + 0.5) * cellWidth; - const ty = (y + 0.5) * cellHeight; - - - img.onload = function() { - context.drawImage(img, cx, cy, dWidth, dHeight); - // This part draws the text on the image - if (text !== undefined) { - // ToDo: Fix fillStyle - // context.fillStyle = text_color; - context.textAlign = 'center'; - context.textBaseline= 'middle'; - context.fillText(text, tx, ty); - } - } - } - - /** + this.drawHex = function (x, y, radius, color, fill, text, text_color) { + const cx = (x + 0.5) * cellWidth; + let cy; + if (x % 2 == 0) { + cy = (y + 0.5) * cellHeight; + } else { + cy = (y + 0.5) * cellHeight + cellHeight / 2; + } + maxHexRadius = cellHeight / Math.sqrt(3); + const r = radius * maxHexRadius; + + function hex_corner(x, y, size, i) { + const angle_deg = 60 * i; + const angle_rad = (Math.PI / 180) * angle_deg; + return [ + x + size * Math.cos(angle_rad) * 1.2, + y + size * Math.sin(angle_rad), + ]; + } + + context.beginPath(); + let [px, py] = hex_corner(cx, cy, r, 1); + // console.log(px,py) + context.moveTo(px, py); + //for i in range(5): + Array.from(new Array(5), (n, i) => { + [px, py] = hex_corner(cx, cy, r, i + 2); + // console.log(px,py) + context.lineTo(px, py); + }); + context.closePath(); + + context.strokeStyle = color; + context.stroke(); + + if (fill) { + context.fillStyle = color; + context.fill(); + } + // This part draws the text inside the Circle + if (text !== undefined) { + context.fillStyle = text_color; + context.textAlign = "center"; + context.textBaseline = "middle"; + context.fillText(text, cx, cy); + } + }; + + this.drawCustomImage = function (shape, x, y, scale, text, text_color_) { + const img = new Image(); + img.src = "local/".concat(shape); + if (scale === undefined) { + scale = 1; + } + // Calculate coordinates so the image is always centered + const dWidth = cellWidth * scale; + const dHeight = cellHeight * scale; + const cx = x * cellWidth + cellWidth / 2 - dWidth / 2; + const cy = y * cellHeight + cellHeight / 2 - dHeight / 2; + + // Coordinates for the text + const tx = (x + 0.5) * cellWidth; + const ty = (y + 0.5) * cellHeight; + + img.onload = function () { + context.drawImage(img, cx, cy, dWidth, dHeight); + // This part draws the text on the image + if (text !== undefined) { + // ToDo: Fix fillStyle + // context.fillStyle = text_color; + context.textAlign = "center"; + context.textBaseline = "middle"; + context.fillText(text, tx, ty); + } + }; + }; + + /** Draw Grid lines in the full gird */ - this.drawGridLines = function(strokeColor) { - context.beginPath(); - context.strokeStyle = strokeColor || "#eee"; - const maxX = cellWidth * gridWidth; - const maxY = cellHeight * gridHeight; - - const xStep = cellWidth * 0.33; - const yStep = cellHeight * 0.5; - - let yStart = yStep; - for(let x=cellWidth/2; x<=maxX; x+= cellWidth) { - for(let y=yStart; y<=maxY; y+=cellHeight) { - - context.moveTo(x - 2 * xStep, y); - - context.lineTo(x - xStep, y - yStep) - context.lineTo(x + xStep, y - yStep) - context.lineTo(x + 2 * xStep, y ) - - context.lineTo(x + xStep, y + yStep ) - context.lineTo(x - xStep, y + yStep ) - context.lineTo(x - 2 * xStep, y) - - } - yStart = (yStart === 0) ? yStep: 0; - } - - context.stroke(); - }; - - this.resetCanvas = function() { - context.clearRect(0, 0, width, height); - context.beginPath(); - }; - + this.drawGridLines = function (strokeColor) { + context.beginPath(); + context.strokeStyle = strokeColor || "#eee"; + const maxX = cellWidth * gridWidth; + const maxY = cellHeight * gridHeight; + + const xStep = cellWidth * 0.33; + const yStep = cellHeight * 0.5; + + let yStart = yStep; + for (let x = cellWidth / 2; x <= maxX; x += cellWidth) { + for (let y = yStart; y <= maxY; y += cellHeight) { + context.moveTo(x - 2 * xStep, y); + + context.lineTo(x - xStep, y - yStep); + context.lineTo(x + xStep, y - yStep); + context.lineTo(x + 2 * xStep, y); + + context.lineTo(x + xStep, y + yStep); + context.lineTo(x - xStep, y + yStep); + context.lineTo(x - 2 * xStep, y); + } + yStart = yStart === 0 ? yStep : 0; + } + + context.stroke(); + }; + + this.resetCanvas = function () { + context.clearRect(0, 0, width, height); + context.beginPath(); + }; }; diff --git a/mesa/visualization/templates/js/InteractionHandler.js b/mesa/visualization/templates/js/InteractionHandler.js index 0adb99c75d2..a605b9a7cc2 100644 --- a/mesa/visualization/templates/js/InteractionHandler.js +++ b/mesa/visualization/templates/js/InteractionHandler.js @@ -1,4 +1,3 @@ - /** Mesa Visualization InteractionHandler ==================================================================== @@ -22,83 +21,82 @@ portrayal = { **/ -var InteractionHandler = function(width, height, gridWidth, gridHeight, ctx){ - +var InteractionHandler = function (width, height, gridWidth, gridHeight, ctx) { // Find cell size: const cellWidth = Math.floor(width / gridWidth); const cellHeight = Math.floor(height / gridHeight); const lineHeight = 10; - // list of standard rendering features to ignore (and key-values in the portrayal will be added ) - const ignoredFeatures = [ - 'Shape', - 'Filled', - 'Color', - 'r', - 'x', - 'y', - 'w', - 'h', - 'width', - 'height', - 'heading_x', - 'heading_y', - 'stroke_color', - 'text_color' - ]; + // list of standard rendering features to ignore (and key-values in the portrayal will be added ) + const ignoredFeatures = [ + "Shape", + "Filled", + "Color", + "r", + "x", + "y", + "w", + "h", + "width", + "height", + "heading_x", + "heading_y", + "stroke_color", + "text_color", + ]; // Set a variable to hold the lookup table and make it accessible to draw scripts - var mouseoverLookupTable = this.mouseoverLookupTable = buildLookupTable(gridWidth, gridHeight); - function buildLookupTable(gridWidth, gridHeight){ + var mouseoverLookupTable = (this.mouseoverLookupTable = buildLookupTable( + gridWidth, + gridHeight + )); + function buildLookupTable(gridWidth, gridHeight) { var lookupTable; - this.init = function(){ - lookupTable = [...Array(gridHeight).keys()].map(i => Array(gridWidth)); - } + this.init = function () { + lookupTable = [...Array(gridHeight).keys()].map((i) => Array(gridWidth)); + }; - this.set = function(x, y, value){ - if(lookupTable[y][x]) - lookupTable[y][x].push(value); - else - lookupTable[y][x] = [value]; - } + this.set = function (x, y, value) { + if (lookupTable[y][x]) lookupTable[y][x].push(value); + else lookupTable[y][x] = [value]; + }; - this.get = function(x, y){ - if(lookupTable[y]) - return lookupTable[y][x] || [] + this.get = function (x, y) { + if (lookupTable[y]) return lookupTable[y][x] || []; return []; - } + }; return this; } var coordinateMapper; - this.setCoordinateMapper = function(mapperName){ - if(mapperName === "hex"){ - coordinateMapper = function(event){ - const x = Math.floor(event.offsetX/cellWidth); - const y = (x % 2 === 0) - ? Math.floor(event.offsetY/cellHeight) - : Math.floor((event.offsetY - cellHeight/2 )/cellHeight) - return {x: x, y: y}; - } - return; + this.setCoordinateMapper = function (mapperName) { + if (mapperName === "hex") { + coordinateMapper = function (event) { + const x = Math.floor(event.offsetX / cellWidth); + const y = + x % 2 === 0 + ? Math.floor(event.offsetY / cellHeight) + : Math.floor((event.offsetY - cellHeight / 2) / cellHeight); + return { x: x, y: y }; + }; + return; } // default coordinate mapper for grids - coordinateMapper = function(event){ - return { - x: Math.floor(event.offsetX/cellWidth), - y: Math.floor(event.offsetY/cellHeight) - }; + coordinateMapper = function (event) { + return { + x: Math.floor(event.offsetX / cellWidth), + y: Math.floor(event.offsetY / cellHeight), + }; }; }; - this.setCoordinateMapper('grid'); - + this.setCoordinateMapper("grid"); // wrap the rect styling in a function - function drawTooltipBox(ctx, x, y, width, height){ + function drawTooltipBox(ctx, x, y, width, height) { ctx.fillStyle = "#F0F0F0"; ctx.beginPath(); ctx.shadowOffsetX = -3; @@ -110,56 +108,77 @@ var InteractionHandler = function(width, height, gridWidth, gridHeight, ctx){ ctx.shadowColor = "transparent"; } - var listener; var tmp - this.updateMouseListeners = function(portrayalLayer){tmp = portrayalLayer - - // Remove the prior event listener to avoid creating a new one every step - ctx.canvas.removeEventListener("mousemove", listener); - - // define the event litser for this step - listener = function(event){ - // clear the previous interaction - ctx.clearRect(0, 0, width, height); - - // map the event to x,y coordinates - const position = coordinateMapper(event); - const yPosition = Math.floor(event.offsetY/cellHeight); - const xPosition = Math.floor(event.offsetX/cellWidth); - - // look up the portrayal items the coordinates refer to and draw a tooltip - mouseoverLookupTable.get(position.x, position.y).forEach((portrayalIndex, nthAgent) => { - const agent = portrayalLayer[portrayalIndex]; - const features = Object.keys(agent).filter(k => ignoredFeatures.indexOf(k) < 0); - const textWidth = Math.max.apply(null, features.map(k => ctx.measureText(`${k}: ${agent[k]}`).width)); - const textHeight = features.length * lineHeight; - const y = Math.max(lineHeight * 2, Math.min(height - textHeight, event.offsetY - textHeight/2)); - const rectMargin = 2 * lineHeight; - var x = 0; - var rectX = 0; - - if(event.offsetX < width/2){ - x = event.offsetX + rectMargin + nthAgent * (textWidth + rectMargin); - ctx.textAlign = "left"; - rectX = x - rectMargin/2; - } else { - x = event.offsetX - rectMargin - nthAgent * (textWidth + rectMargin + lineHeight ); - ctx.textAlign = "right"; - rectX = x - textWidth - rectMargin/2; - } - - // draw a background box - drawTooltipBox(ctx, rectX, y - rectMargin, textWidth + rectMargin, textHeight + rectMargin); - - // set the color and draw the text - ctx.fillStyle = "black"; - features.forEach((k,i) => { - ctx.fillText(`${k}: ${agent[k]}`, x, y + i * lineHeight) - }) - }) - + var listener; + var tmp; + this.updateMouseListeners = function (portrayalLayer) { + tmp = portrayalLayer; + + // Remove the prior event listener to avoid creating a new one every step + ctx.canvas.removeEventListener("mousemove", listener); + + // define the event litser for this step + listener = function (event) { + // clear the previous interaction + ctx.clearRect(0, 0, width, height); + + // map the event to x,y coordinates + const position = coordinateMapper(event); + const yPosition = Math.floor(event.offsetY / cellHeight); + const xPosition = Math.floor(event.offsetX / cellWidth); + + // look up the portrayal items the coordinates refer to and draw a tooltip + mouseoverLookupTable + .get(position.x, position.y) + .forEach((portrayalIndex, nthAgent) => { + const agent = portrayalLayer[portrayalIndex]; + const features = Object.keys(agent).filter( + (k) => ignoredFeatures.indexOf(k) < 0 + ); + const textWidth = Math.max.apply( + null, + features.map((k) => ctx.measureText(`${k}: ${agent[k]}`).width) + ); + const textHeight = features.length * lineHeight; + const y = Math.max( + lineHeight * 2, + Math.min(height - textHeight, event.offsetY - textHeight / 2) + ); + const rectMargin = 2 * lineHeight; + var x = 0; + var rectX = 0; + + if (event.offsetX < width / 2) { + x = + event.offsetX + rectMargin + nthAgent * (textWidth + rectMargin); + ctx.textAlign = "left"; + rectX = x - rectMargin / 2; + } else { + x = + event.offsetX - + rectMargin - + nthAgent * (textWidth + rectMargin + lineHeight); + ctx.textAlign = "right"; + rectX = x - textWidth - rectMargin / 2; + } + + // draw a background box + drawTooltipBox( + ctx, + rectX, + y - rectMargin, + textWidth + rectMargin, + textHeight + rectMargin + ); + + // set the color and draw the text + ctx.fillStyle = "black"; + features.forEach((k, i) => { + ctx.fillText(`${k}: ${agent[k]}`, x, y + i * lineHeight); + }); + }); }; ctx.canvas.addEventListener("mousemove", listener); }; return this; -} +}; diff --git a/mesa/visualization/templates/js/NetworkModule_sigma.js b/mesa/visualization/templates/js/NetworkModule_sigma.js index d1147b5253b..342825353b4 100644 --- a/mesa/visualization/templates/js/NetworkModule_sigma.js +++ b/mesa/visualization/templates/js/NetworkModule_sigma.js @@ -1,50 +1,52 @@ -const NetworkModule = function(canvas_width, canvas_height) { - - const div_tag = "
"; - - // Append it to #elements: - const div = $(div_tag)[0]; - $("#elements").append(div); - - let s = { - container: 'graph-container', - settings: { - defaultNodeColor: 'black', - minEdgeSize: 1, - maxEdgeSize: 5 - } - }; - - this.render = function(data) { - const graph = JSON.parse(JSON.stringify(data)); - - // Update the instance's graph: - if (s instanceof sigma) { - s.graph.clear(); - s.graph.read(graph); - } - // ...or instantiate sigma if needed: - else if (typeof s === 'object') { - s.graph = graph; - s = new sigma(s); - } - - //Initialize nodes as a circle - s.graph.nodes().forEach(function(node, i, a) { - node.x = Math.cos(Math.PI * 2 * i / a.length); - node.y = Math.sin(Math.PI * 2 * i / a.length); - }); - - //Call refresh to render the new graph - s.refresh(); - }; - - this.reset = function() { - if (s instanceof sigma) { - s.graph.clear(); - s.refresh(); - } - }; - -}; \ No newline at end of file +const NetworkModule = function (canvas_width, canvas_height) { + const div_tag = + "
"; + + // Append it to #elements: + const div = $(div_tag)[0]; + $("#elements").append(div); + + let s = { + container: "graph-container", + settings: { + defaultNodeColor: "black", + minEdgeSize: 1, + maxEdgeSize: 5, + }, + }; + + this.render = function (data) { + const graph = JSON.parse(JSON.stringify(data)); + + // Update the instance's graph: + if (s instanceof sigma) { + s.graph.clear(); + s.graph.read(graph); + } + // ...or instantiate sigma if needed: + else if (typeof s === "object") { + s.graph = graph; + s = new sigma(s); + } + + //Initialize nodes as a circle + s.graph.nodes().forEach(function (node, i, a) { + node.x = Math.cos((Math.PI * 2 * i) / a.length); + node.y = Math.sin((Math.PI * 2 * i) / a.length); + }); + + //Call refresh to render the new graph + s.refresh(); + }; + + this.reset = function () { + if (s instanceof sigma) { + s.graph.clear(); + s.refresh(); + } + }; +}; diff --git a/mesa/visualization/templates/js/PieChartModule.js b/mesa/visualization/templates/js/PieChartModule.js index a7677d1a295..2e568b46546 100644 --- a/mesa/visualization/templates/js/PieChartModule.js +++ b/mesa/visualization/templates/js/PieChartModule.js @@ -1,86 +1,113 @@ -'use strict'; +"use strict"; //Note: This pie chart is based off the example found here: //https://bl.ocks.org/mbostock/3887235 -const PieChartModule = function(fields, canvas_width, canvas_height) { - // Create the overall chart div - const chart_div_tag = "
"; - const chart_div = $(chart_div_tag)[0]; - $("#elements").append(chart_div); +const PieChartModule = function (fields, canvas_width, canvas_height) { + // Create the overall chart div + const chart_div_tag = + "
"; + const chart_div = $(chart_div_tag)[0]; + $("#elements").append(chart_div); - // Create the tag: - const svg_tag = ""; - // Append it to #elements - const svg_element = $(svg_tag)[0]; - chart_div.append(svg_element); + // Create the tag: + const svg_tag = + ""; + // Append it to #elements + const svg_element = $(svg_tag)[0]; + chart_div.append(svg_element); - //create the legend - const legend_tag = "
"; - const legend_element = $(legend_tag)[0]; - chart_div.append(legend_element); + //create the legend + const legend_tag = "
"; + const legend_element = $(legend_tag)[0]; + chart_div.append(legend_element); - const legend = d3.select(legend_element) - .attr("style","display:block;width:" - + canvas_width + "px;text-align:center") + const legend = d3 + .select(legend_element) + .attr( + "style", + "display:block;width:" + canvas_width + "px;text-align:center" + ); - legend.selectAll("span") - .data(fields) - .enter() - .append("span") - .html(function(d){ - return "" + " " + - d["Label"].replace(" ", " ") - }) - .attr("style", "padding-left:10px;padding-right:10px;") + legend + .selectAll("span") + .data(fields) + .enter() + .append("span") + .html(function (d) { + return ( + "" + + " " + + d["Label"].replace(" ", " ") + ); + }) + .attr("style", "padding-left:10px;padding-right:10px;"); - // setup the d3 svg selection - const svg = d3.select(svg_element) - const width = +svg.attr("width") - const height = +svg.attr("height") - const maxRadius = Math.min(width, height) / 2 - const g = svg.append("g").attr("transform", - "translate(" + width / 2 + "," + height / 2 + ")"); + // setup the d3 svg selection + const svg = d3.select(svg_element); + const width = +svg.attr("width"); + const height = +svg.attr("height"); + const maxRadius = Math.min(width, height) / 2; + const g = svg + .append("g") + .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); - // Create the base chart and helper methods - const color = d3.scaleOrdinal(fields.map(field => field["Color"])); - const pie = d3.pie() - .sort(null) - .value(function(d) { return d; }); - const path = d3.arc() - .outerRadius(maxRadius) - .innerRadius(0); - const arc = g.selectAll(".arc") - .data(pie(fields.map(field => 0)))//Initialize the pie chart with dummy data - .enter().append("g") - .attr("class", "arc") + // Create the base chart and helper methods + const color = d3.scaleOrdinal(fields.map((field) => field["Color"])); + const pie = d3 + .pie() + .sort(null) + .value(function (d) { + return d; + }); + const path = d3.arc().outerRadius(maxRadius).innerRadius(0); + const arc = g + .selectAll(".arc") + .data(pie(fields.map((field) => 0))) //Initialize the pie chart with dummy data + .enter() + .append("g") + .attr("class", "arc"); - arc.append("path") - .attr("d", path) - .style("fill", function(d, i) { return color(i); }) - .append("title") - .text(function(d){return d.value}) + arc + .append("path") + .attr("d", path) + .style("fill", function (d, i) { + return color(i); + }) + .append("title") + .text(function (d) { + return d.value; + }); - this.render = function(data) { - //Update the pie chart each time new data comes in - arc.data(pie(data)) - .select("path") - .attr("d", path) - .select("title") - .text(function(d){ - return d.value + " : " - + (((d.endAngle - d.startAngle)*100.0) - / (Math.PI * 2)).toFixed(2) - + "%" - }) - } + this.render = function (data) { + //Update the pie chart each time new data comes in + arc + .data(pie(data)) + .select("path") + .attr("d", path) + .select("title") + .text(function (d) { + return ( + d.value + + " : " + + (((d.endAngle - d.startAngle) * 100.0) / (Math.PI * 2)).toFixed(2) + + "%" + ); + }); + }; - this.reset = function() { - //Reset the chart by setting each field to 0 - arc.data(pie(fields.map(field => 0))) - .enter().select("g") + this.reset = function () { + //Reset the chart by setting each field to 0 + arc + .data(pie(fields.map((field) => 0))) + .enter() + .select("g"); - arc.select("path") - .attr("d", path) - } - -} + arc.select("path").attr("d", path); + }; +}; diff --git a/mesa/visualization/templates/js/TextModule.js b/mesa/visualization/templates/js/TextModule.js index 2806bcd85e6..2f3e8d62bd2 100644 --- a/mesa/visualization/templates/js/TextModule.js +++ b/mesa/visualization/templates/js/TextModule.js @@ -1,15 +1,15 @@ -const TextModule = function() { - const tag = "

"; - const text = $(tag)[0]; +const TextModule = function () { + const tag = "

"; + const text = $(tag)[0]; - // Append text tag to #elements: - $("#elements").append(text); + // Append text tag to #elements: + $("#elements").append(text); - this.render = function(data) { - $(text).html(data); - }; + this.render = function (data) { + $(text).html(data); + }; - this.reset = function() { - $(text).html(""); - }; -}; \ No newline at end of file + this.reset = function () { + $(text).html(""); + }; +}; diff --git a/mesa/visualization/templates/js/runcontrol.js b/mesa/visualization/templates/js/runcontrol.js index fcb0016a58d..e7768aad37e 100644 --- a/mesa/visualization/templates/js/runcontrol.js +++ b/mesa/visualization/templates/js/runcontrol.js @@ -16,7 +16,6 @@ const stepModelButton = document.getElementById("step"); const resetModelButton = document.getElementById("reset"); const stepDisplay = document.getElementById("currentStep"); - /** * A ModelController that defines the model state. * @param {number} tick=0 - Initial step of the model @@ -25,104 +24,103 @@ const stepDisplay = document.getElementById("currentStep"); * @param {boolean} finished=false - Initialize the model in a finished state? */ function ModelController(tick = 0, fps = 3, running = false, finished = false) { - this.tick = tick; - this.fps = fps; - this.running = running; - this.finished = finished; - - /** Start the model and keep it running until stopped */ - this.start = function start() { - this.running = true; - this.step(); - startModelButton.firstElementChild.innerText = "Stop"; - } - - /** Stop the model */ - this.stop = function stop() { - this.running = false; - startModelButton.firstElementChild.innerText = "Start"; - } - - /** - * Step the model one step ahead. - * - * If the model is in a running state this function will be called repeatedly - * after the visualization elements are rendered. */ - this.step = function step() { - this.tick += 1; - stepDisplay.innerText = this.tick; - send({ type: "get_step", step: this.tick }); - } - - /** Reset the model and visualization state but keep its running state */ - this.reset = function reset() { - this.tick = 0; - stepDisplay.innerText = this.tick; - // Reset all the visualizations - vizElements.forEach(element => element.reset()); - if (this.finished) { - this.finished = false; - startModelButton.firstElementChild.innerText = "Start"; - } - clearTimeout(this.timeout) - send({ type: "reset" }); - } - - /** Stops the model and put it into a finished state */ - this.done = function done() { - this.stop(); - this.finished = true; - startModelButton.firstElementChild.innerText = "Done"; + this.tick = tick; + this.fps = fps; + this.running = running; + this.finished = finished; + + /** Start the model and keep it running until stopped */ + this.start = function start() { + this.running = true; + this.step(); + startModelButton.firstElementChild.innerText = "Stop"; + }; + + /** Stop the model */ + this.stop = function stop() { + this.running = false; + startModelButton.firstElementChild.innerText = "Start"; + }; + + /** + * Step the model one step ahead. + * + * If the model is in a running state this function will be called repeatedly + * after the visualization elements are rendered. */ + this.step = function step() { + this.tick += 1; + stepDisplay.innerText = this.tick; + send({ type: "get_step", step: this.tick }); + }; + + /** Reset the model and visualization state but keep its running state */ + this.reset = function reset() { + this.tick = 0; + stepDisplay.innerText = this.tick; + // Reset all the visualizations + vizElements.forEach((element) => element.reset()); + if (this.finished) { + this.finished = false; + startModelButton.firstElementChild.innerText = "Start"; } - - /** - * Render visualisation elements with new data. - * @param {any[]} data Model state data passed to the visualization elements - */ - this.render = function render(data) { - vizElements.forEach((element, index) => element.render(data[index])) - if (this.running) { - this.timeout = setTimeout(() => this.step(), 1000 / this.fps); - } - } - - /** - * Update the frames per second - * @param {number} val - The new value of frames per second - */ - this.updateFPS = function (val) { - this.fps = Number(val); + clearTimeout(this.timeout); + send({ type: "reset" }); + }; + + /** Stops the model and put it into a finished state */ + this.done = function done() { + this.stop(); + this.finished = true; + startModelButton.firstElementChild.innerText = "Done"; + }; + + /** + * Render visualisation elements with new data. + * @param {any[]} data Model state data passed to the visualization elements + */ + this.render = function render(data) { + vizElements.forEach((element, index) => element.render(data[index])); + if (this.running) { + this.timeout = setTimeout(() => this.step(), 1000 / this.fps); } + }; + + /** + * Update the frames per second + * @param {number} val - The new value of frames per second + */ + this.updateFPS = function (val) { + this.fps = Number(val); + }; } /* * Set up the the FPS control */ const fpsControl = $("#fps").slider({ - max: 20, - min: 0, - value: controller.fps, - ticks: [0, 20], - ticks_labels: [0, 20], - ticks_position: [0, 100] + max: 20, + min: 0, + value: controller.fps, + ticks: [0, 20], + ticks_labels: [0, 20], + ticks_position: [0, 100], }); fpsControl.on("change", () => controller.updateFPS(fpsControl.val())); - /* * Button logic for start, stop and reset buttons */ startModelButton.onclick = () => { - if (controller.running) { - controller.stop(); - } else if (!controller.finished) { - controller.start(); - } + if (controller.running) { + controller.stop(); + } else if (!controller.finished) { + controller.start(); + } }; stepModelButton.onclick = () => { - if (!controller.running & !controller.finished) { - controller.step(); - } + if (!controller.running & !controller.finished) { + controller.step(); + } }; resetModelButton.onclick = () => controller.reset(); @@ -132,7 +130,7 @@ resetModelButton.onclick = () => controller.reset(); /** Open the websocket connection; support TLS-specific URLs when appropriate */ const ws = new WebSocket( - (window.location.protocol === "https:" ? "wss://" : "ws://") + + (window.location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/ws" ); @@ -142,26 +140,26 @@ const ws = new WebSocket( * @param {string} message - the message received from the WebSocket */ ws.onmessage = function (message) { - const msg = JSON.parse(message.data); - switch (msg["type"]) { - case "viz_state": - // Update visualization state - controller.render(msg["data"]) - break; - case "end": - // We have reached the end of the model - controller.done(); - break; - case "model_params": - // Create GUI elements for each model parameter and reset everything - initGUI(msg["params"]); - controller.reset(); - break; - default: - // There shouldn't be any other message - console.log("Unexpected message."); - console.log(msg); - } + const msg = JSON.parse(message.data); + switch (msg["type"]) { + case "viz_state": + // Update visualization state + controller.render(msg["data"]); + break; + case "end": + // We have reached the end of the model + controller.done(); + break; + case "model_params": + // Create GUI elements for each model parameter and reset everything + initGUI(msg["params"]); + controller.reset(); + break; + default: + // There shouldn't be any other message + console.log("Unexpected message."); + console.log(msg); + } }; /** @@ -169,11 +167,10 @@ ws.onmessage = function (message) { * @param {string} message - The message to send to the Python server */ const send = function (message) { - const msg = JSON.stringify(message); - ws.send(msg); + const msg = JSON.stringify(message); + ws.send(msg); }; - /* * GUI initialization (for input parameters) */ @@ -183,165 +180,195 @@ const send = function (message) { * @param {object} model_params - Create the GUI from these model parameters */ const initGUI = function (model_params) { + const sidebar = $("#sidebar"); + + const onSubmitCallback = function (param_name, value) { + send({ type: "submit_params", param: param_name, value: value }); + }; + + const addBooleanInput = function (param, obj) { + const domID = param + "_id"; + sidebar.append( + [ + "
", + "

", + "", + "
", + ].join("") + ); + $("#" + domID).bootstrapSwitch({ + state: obj.value, + size: "small", + onSwitchChange: function (e, state) { + onSubmitCallback(param, state); + }, + }); + }; + + const addNumberInput = function (param, obj) { + const domID = param + "_id"; + sidebar.append( + [ + "
", + "

", + "", + "
", + ].join("") + ); + const numberInput = $("#" + domID); + numberInput.val(obj.value); + numberInput.on("change", function () { + onSubmitCallback(param, Number($(this).val())); + }); + }; + + const addSliderInput = function (param, obj) { + const domID = param + "_id"; + const tooltipID = domID + "_tooltip"; + sidebar.append( + [ + "
", + "

", + "", + obj.name, + "", + "

", + "", + "
", + ].join("") + ); + + // Enable tooltip label + if (obj.description !== null) { + $(tooltipID).tooltip({ + title: obj.description, + placement: "right", + }); + } - const sidebar = $("#sidebar"); - - const onSubmitCallback = function (param_name, value) { - send({ "type": "submit_params", "param": param_name, "value": value }); - }; - - const addBooleanInput = function (param, obj) { - const domID = param + '_id'; - sidebar.append([ - "
", - "

", - "", - "
" - ].join('')); - $('#' + domID).bootstrapSwitch({ - 'state': obj.value, - 'size': 'small', - 'onSwitchChange': function (e, state) { - onSubmitCallback(param, state); - } - }); - }; - - const addNumberInput = function (param, obj) { - const domID = param + '_id'; - sidebar.append([ - "
", - "

", - "", - "
" - ].join('')); - const numberInput = $('#' + domID); - numberInput.val(obj.value); - numberInput.on('change', function () { - onSubmitCallback(param, Number($(this).val())); - }) - }; - - const addSliderInput = function (param, obj) { - const domID = param + '_id'; - const tooltipID = domID + "_tooltip"; - sidebar.append([ - "
", - "

", - "", - obj.name, - "", - "

", - "", - "
" - ].join('')); - - // Enable tooltip label - if (obj.description !== null) { - $(tooltipID).tooltip({ - title: obj.description, - placement: 'right' - }); - } - - // Setup slider - const sliderInput = $("#" + domID); - sliderInput.slider({ - min: obj.min_value, - max: obj.max_value, - value: obj.value, - step: obj.step, - ticks: [obj.min_value, obj.max_value], - ticks_labels: [obj.min_value, obj.max_value], - ticks_positions: [0, 100] + // Setup slider + const sliderInput = $("#" + domID); + sliderInput.slider({ + min: obj.min_value, + max: obj.max_value, + value: obj.value, + step: obj.step, + ticks: [obj.min_value, obj.max_value], + ticks_labels: [obj.min_value, obj.max_value], + ticks_positions: [0, 100], + }); + sliderInput.on("change", function () { + onSubmitCallback(param, Number($(this).val())); + }); + }; + + const addChoiceInput = function (param, obj) { + const domID = param + "_id"; + const span = ""; + const template = [ + "

", + ""); + + // Finally render the dropdown and activate choice listeners + sidebar.append(template.join("")); + choiceIdentifiers.forEach(function (id, idx) { + $("#" + id).on("click", function () { + const value = obj.choices[idx]; + $("#" + domID).html(value + " " + span); + onSubmitCallback(param, value); + }); + }); + }; + + const addTextBox = function (param, obj) { + const well = $('
' + obj.value + "
")[0]; + sidebar.append(well); + }; + + const addParamInput = function (param, option) { + switch (option["param_type"]) { + case "checkbox": + addBooleanInput(param, option); + break; + + case "slider": + addSliderInput(param, option); + break; + + case "choice": + addChoiceInput(param, option); + break; + + case "number": + addNumberInput(param, option); // Behaves the same as just a simple number + break; + + case "static_text": + addTextBox(param, option); + break; + } + }; + + for (const option in model_params) { + const type = typeof model_params[option]; + const param_str = String(option); + + switch (type) { + case "boolean": + addBooleanInput(param_str, { + value: model_params[option], + name: param_str, }); - sliderInput.on('change', function () { - onSubmitCallback(param, Number($(this).val())); - }) - }; - - const addChoiceInput = function (param, obj) { - const domID = param + '_id'; - const span = ""; - const template = [ - "

", - ""); - - // Finally render the dropdown and activate choice listeners - sidebar.append(template.join('')); - choiceIdentifiers.forEach(function (id, idx) { - $('#' + id).on('click', function () { - const value = obj.choices[idx]; - $('#' + domID).html(value + ' ' + span); - onSubmitCallback(param, value); - }); + break; + case "number": + addNumberInput(param_str, { + value: model_params[option], + name: param_str, }); - }; - - const addTextBox = function (param, obj) { - const well = $('
' + obj.value + '
')[0]; - sidebar.append(well); - }; - - const addParamInput = function (param, option) { - switch (option['param_type']) { - case 'checkbox': - addBooleanInput(param, option); - break; - - case 'slider': - addSliderInput(param, option); - break; - - case 'choice': - addChoiceInput(param, option); - break; - - case 'number': - addNumberInput(param, option); // Behaves the same as just a simple number - break; - - case 'static_text': - addTextBox(param, option); - break; - } - }; - - for (const option in model_params) { - - const type = typeof (model_params[option]); - const param_str = String(option); - - switch (type) { - case "boolean": - addBooleanInput(param_str, { 'value': model_params[option], 'name': param_str }); - break; - case "number": - addNumberInput(param_str, { 'value': model_params[option], 'name': param_str }); - break; - case "object": - addParamInput(param_str, model_params[option]); // catch-all for params that use Option class - break; - } + break; + case "object": + addParamInput(param_str, model_params[option]); // catch-all for params that use Option class + break; } + } }; // Backward-Compatibility aliases