Update file adding_basic_HTML_elements.md

This commit is contained in:
Max Ramgraber 2024-03-15 16:58:40 +01:00
parent 84f5e85e7c
commit bb05390600

View file

@ -3,23 +3,23 @@
## Embedding HTML files
A straightforward way to create interactive web-based elements is to code them directly using HTML with JavaScript. This allows us to build elements such as the one below, which shows the connection between a probability density function (pdf) and the associated cumulative density function (cdf):
<iframe src="../_static/element_pdf_and_cdf.html" width="600" height="400" frameborder="0"></iframe>
<iframe src="../_static/element_pdf_and_cdf.html" width="600" height="300" frameborder="0"></iframe>
If we want to embed such figures in the teachbook, you can do so via an iframe which reference a html file in the _static folder:
If we want to embed such figures in the teachbook, you can do so via an iframe which references a html file in the _static folder:
```html
<iframe src="../_static/element_pdf_and_cdf.html" width="600" height="400" frameborder="0"></iframe>
<iframe src="../_static/element_pdf_and_cdf.html" width="600" height="300" frameborder="0"></iframe>
```
Here, the src path moves up one subdirectory, then enters the _static folder, and loads the element_pdf_and_cdf.html file. So how do we create this figure?
Here, the src path moves up one subdirectory, then enters the `_static` folder, and loads the `element_pdf_and_cdf.html` file. So how do we create this figure?
## Creating a basic interactive HTML/JavaScript element
At the bottom of this page, I will provide the full code for the interactive element above. In this section, let us go through the code piece by piece. Let us begin by looking at the start of the code.
At the bottom of this page, I will provide the full code for the interactive element above. In this section, let us go through the code one piece at a time. Let us begin from the top.
We begin by specifying that this file is a HTML file. In the header, we can load packages we need later on. Within the `<script>` tags, we are loading three different libraries from web servers: `math.js`, which provides functions for basic mathematical operations, as well as two versions of the library `d3.js`, which contains many many useful functions for web interactivity.
In the first line, we specify that this file is a HTML file. In the header (`<head>`), we can load packages we need later on. Within the `<script>` tags, we are loading three different libraries from web servers: `math.js`, which provides functions for basic mathematical operations (similar to Python's `numpy` library), as well as two versions of the library `d3.js`, which contains many extremely useful functions for web interactivity.
The `<style>` tag below defines [CSS](https://en.wikipedia.org/wiki/CSS) properties that style the element. Two classic settings for the `body` are `margin: 0`, which makes sure that the useable space extends all the way to the edges, and `overflow: hidden`, which prevents scrolling if the element extends beyond its intended borders. We will scale out content to the available space later on.
The `<style>` tag below defines [CSS](https://en.wikipedia.org/wiki/CSS) properties that, unsurprisingly, style the element. Two common settings for the `body` are `margin: 0`, which makes sure that the useable space extends all the way to the edges, and `overflow: hidden`, which effectively clips content that extends outside the element's borders by removing the scrollbars.
```html
<!DOCTYPE html>
@ -37,7 +37,7 @@ The `<style>` tag below defines [CSS](https://en.wikipedia.org/wiki/CSS) propert
</head>
```
Next, we create a `<div>`, an HTML element that can group other HTML elements, that contians a [SVG](https://en.wikipedia.org/wiki/SVG) named `"click"`. This SVG will serve as the canvas on which we will place the interactive content.
Next, we create a `<div>`. This is an HTML element that groups other HTML elements. In our case, it only contains a [SVG](https://en.wikipedia.org/wiki/SVG) named `"click"`. This SVG will serve as the canvas on which we will later place the interactive content.
```html
<!-- Create a div where the graph will take place -->
@ -47,9 +47,9 @@ Next, we create a `<div>`, an HTML element that can group other HTML elements, t
</div>
```
So far, all of this has just been setup. The real magic begins in the `<script>` environment, which we place inside the HTML's `<body>`. This is where we code the actual content of the interactive element in JavaScript.
So far, all of this has just been setup. The real magic begins in the HTML's `<body>`, which contains the `<script>` environment. This is where we code the actual content of the interactive element in JavaScript.
The first few lines of our JavaScript code are simple. To scale out element with the size of its container (the iframe we created above), the code first reads out the width `vw` and height `vh` of its container. Here, we want to scale my interactive element to be twice as wide as tall, so we define the `height` of the canvas as half of its `width`. We then slect the `"click"` canvas we created before, and set its height and width to the new dimensions we just computed.
The first few lines of our JavaScript code are simple. To scale our element with the size of its container (the iframe we created above), the code first reads the width `vw` and height `vh` of its container. For this element, we define the interactive element to be twice as wide as tall, so we define the `height` of the canvas as half its `width`. We then select the `"click"` canvas we created before, create a variable for quick access called `svg`, and set its height and width to the new dimensions we just computed.
```html
<body>
@ -68,7 +68,7 @@ The first few lines of our JavaScript code are simple. To scale out element with
.attr("height", height) // This defines the canvas' height
```
Next, we may want to define some functions for the functionality of our element. The first function `linspace` is a simple helper function that creates `resolution` points between a `start` and `end` value, similar to `numpy.linspace` in Python. The second and third functions simply evaluate the pdf and cdf of a standard Gaussian distribution for a given `x` value.
Next, we may want to define some functions for our element. The first function `linspace` is simply creates `resolution` points between a `start` and `end` value, similar to `numpy.linspace` in Python. The second and third functions evaluate the pdf and cdf of a standard Gaussian distribution for a given `x` value.
```html
function linspace(start, end, resolution) {
@ -132,9 +132,12 @@ Next, we may want to define some functions for the functionality of our element.
}
```
Now, let us prepare the two subplots for the pdf and cdf. First, we define what values we want to plot on the x-axis and y-axis. We focus here on x-values between -3 and +3, and for the pdf, we want to see the maximum of the distribution and add a small padding factor of `1.2`. In JavaScript, we have a lot of control on how we want to create the interactive plot. This means that we must define where exactly we want to place our subplots on the canvas. Let us start by defining the horizontal extent of the subplot for the pdf from 10% of the width to 50% of the width, and its height from 10% of the canvas's height to 89% of the canvas's height (recall that our canvas is twice as wide as it is tall). Mind that in Javascript, 0% width and 0% height is defined in the top-left corner.
Now, let us prepare the two subplots for the pdf (left side) and cdf (right side). First, we define what range of values we want to plot on the x-axis and y-axis. On the x-axis, we want to plot values between -3 and +3. For the pdf subplot's yaxis, we want to see everything from `0` to the pdf*s maximum at `x=0`, plus 20% padding. In JavaScript, we have significant control on how we want to create our plot. This means that we must define where exactly we want to place our subplots on the canvas. Let us start by defining the horizontal width of the left subplot from 10% of the canvas's width to 50% of the canvas's width, and its height from 10% of the canvas's height to 89% of the canvas's height (recall that our canvas is twice as wide as it is tall). Mind that in Javascript, the point at 0% width and 0% height is located in the top-left corner.
Next, we create three helper functions. If we want to add an element to the canvas, we must define its size and position in pixels. For plotting, this is inconvenient, so here we define three functions that convert x-values to horizontal pixels (`xScale_pdf`), horizontal pixels to x-values (`xScale_pdf_inverse`), and pdf-values to vertical pixels (`yScale_pdf`).
Next, we create three helper functions. If we want to add an element to the canvas, we must define its position and size in pixel values. For plotting, this is inconvenient, so here we define three functions that
- convert x-values to horizontal pixels (`xScale_pdf`),
- convert horizontal pixels to x-values (`xScale_pdf_inverse`), and
- convert pdf-values to vertical pixels (`yScale_pdf`).
```html
// Define a subplot for the standard normal
@ -156,7 +159,7 @@ Next, we create three helper functions. If we want to add an element to the canv
.range([window_y_pdf[1], window_y_pdf[0]])
```
With these functions in hand, let us draw the first subplot. We start by drawing the x-axis and its label first, where we define the font-sizes relative to the canvas's width to ensure it scales with its containers size. Next, we do the same for the y-axis. Finally, we add a label on top. Note that we define all placement positions in terms of pixels (`width` and related variables).
With these functions in hand, let us draw the first subplot. We start by drawing the x-axis and its label first, where we define the font-sizes relative to the canvas's width to ensure the font scales with its container's size. Next, we do the same for the y-axis. Finally, we add a label on top.
```html
// Draw the x axis
@ -195,9 +198,9 @@ With these functions in hand, let us draw the first subplot. We start by drawing
.style("font-size", (16 * width / 600).toString() + "px")
```
Now, let us add the actual content. We start by creating 201 equally-spaced points on the x-axis between -3 and +3 using the `linspace` function we created above, then evaluate the corresponding densities using the `standard_Gaussian_pdf` function we also created. The first real element we add to the subplot is a simple round marker that indicates where we are on the pdf. We can add geometric shapes like circles via a syntax like `svg.append("circle)`, then define its attributes or style properties via the `.attr()` syntax. A circle requires a radius attribute `r`, a horizontal position `cx`, and a vertical position `cy`. We initially place our marker at x=0. Note how we use the Scale functions we created earlier to convert subplot dimensions into pixels. The `fill` and `stroke-width` properties define the style of the marker. An important property is the `id`, which we can use later to select and update this marker.
Now, let us add actual content. We start by creating 201 equally-spaced points on the x-axis between -3 and +3 using the `linspace` function we created above, then evaluate the corresponding densities using our `standard_Gaussian_pdf`. The first real feature we add to the subplot is a simple round marker that indicates where we currently are on the pdf. We can add geometric shapes via a syntax like `svg.append("circle)`, then define its attributes via `.attr()`. Defining a circle requires a radius attribute `r`, a horizontal position `cx`, and a vertical position `cy`. We initially place our marker at `x=0`. Note the use of the Scale functions we created earlier to convert subplot coordinates into canvas pixel coordinates. The `fill` and `stroke-width` properties define the style of the marker. An important property is the `id`, which we can use later to select and update this marker.
Next, we concatenate our `x` and `pdf` vectors into a list of dictionaries, which we then convert into pixel coordinates. Finally, we append it as a `path` via the `svg.append("path")` function. In addition, we want to represent that the cdf (which we will later plot on the right) represents the integral of all pdf values to the left of the current x-value. To this end, we want to create a filled shape that highlights this area. We can do so by creating another path that follows the pdf from the left border to the currently selected value (initially: `x=0`), then moves back to the subplot origin along the x-axis. The path-information `data_fill` is created the same way as before, but now we add it as a `svg.append("path")` that has the `fill` attribute, which - as the name implies - fills the region inside it with a solid color. We also assign a `class` to this line, which makes selecting all elements of this class later easy.
Next, we concatenate our `x` and `pdf` vectors into a list of dictionaries, which we then convert into pixel coordinates with a `valueline_pdf` function we create. Finally, we add it to the subplot as a `path` via the `svg.append("path")` function. In addition, we want to represent the fact that the cdf (which we will later plot on the right) represents the integral of all pdf values to the left of the current x-value. To this end, we want to create a filled shape that highlights this area. We can do so by creating another path that follows the pdf from the left border to the currently selected value (initially: `x=0`), then moves back to the subplot origin along the x-axis. The path-information `data_fill` is created the same way as before, but now we add it as a `svg.append("path")` that has the `fill` attribute, which - as the name implies - fills the region inside it with a solid color.
```html
// Evaluate the normal pdf
@ -231,11 +234,10 @@ Next, we concatenate our `x` and `pdf` vectors into a list of dictionaries, whic
});
svg.append("path")
.attr("class", "line")
.attr("d", valueline_pdf(data_pdf))
.attr("fill", "none")
.attr("stroke-width", 3 * height / 600)
.attr("stroke", "#666666") //"#4794c1")
.attr("stroke", "#666666")
.attr("id", "thatline")
var fill_x, fill_pdf
@ -254,15 +256,14 @@ Next, we concatenate our `x` and `pdf` vectors into a list of dictionaries, whic
})
}
svg.append("path")
.attr("class", "line")
.attr("d", valueline_pdf(data_fill))
.attr("fill", "#999999")
.attr("stroke", "None") //"#4794c1")
.attr("stroke", "None")
.attr("id", "fill")
.lower()
```
The next block essentially repeats the same procedure for the right subplot. Go through the code and see if you recognize parallels to the code above.
The next block essentially repeats the same procedure for the right subplot, but replaces the pdf evaluation with a cdf evaluation. We also add a dashed horizontal line that marks the integral value on the y-axis. Go through the code and see if you recognize parallels to the code above.
```html
// Define a subplot for the standard normal
@ -347,7 +348,6 @@ The next block essentially repeats the same procedure for the right subplot. Go
});
svg.append("path")
.attr("class", "line")
.attr("d", valueline_cdf(data_cdf))
.attr("fill", "none")
.attr("stroke-width", 3 * height / 600)
@ -366,13 +366,19 @@ The next block essentially repeats the same procedure for the right subplot. Go
.lower();
```
So far, this element is static. It's time to introduce some interactivity! This is something we add in the last block of this code. We start by defining three new variables: `movex` (x-position of the mouse), `movey` (y-position of the mouse), and `xpos` (what x-value we currently are looking at).
So far, this element is purely static. It's time to introduce some interactivity! We start by defining three new variables: `movex` (x-position of the mouse), `movey` (y-position of the mouse), and `xpos` (what x-value we currently are looking at).
Make interactive elements with d3 is extremely easy. Here, for instance, we simply use the syntax `svg.on("mousemove")`, which calls a function whenever the mouse moves over the canvas. We then define an unnamed function that reads out the current coordinates of the cursor (`d3.event.x` and `d3.event.y`). For this element, we choose to only update the figure if the cursor's x-position is in one of the subplots. We can test this with a simple if-clause.
Make interactive elements with d3 is extremely easy. Here, for instance, we simply use the syntax `svg.on("mousemove")`, which calls a function whenever the mouse moves over the canvas. We then define an unnamed function that reads out the current coordinates of the cursor (`d3.event.x` and `d3.event.y`), then does whatever we want with it. For this element, we choose to only update the figure if the cursor's x-position is within one of the subplots. We can test this with a simple if-clause:
If the cursor's x-position is within the first subplot (i.e., `(movex >= window_x_pdf[0] && movex <= window_x_pdf[1]) == True`), then we convert the x-position to an x-value using the `xScale_pdf_inverse` function we created before. We then select the circular position markers for the pdf (`d3.select("#marker_pdf")`) and cdf (`d3.select("#marker_cdf")`) we created before via their IDs, and update their positions in both subplots. Likewise, we update the fill path in the pdf plot (`d3.select("#fill")`) with the new `xpos`, and update the horizontal bar in the cdf subplot (`d3.select("#line_cdf")`).
If the cursor's x-position is within the first subplot (i.e., `(movex >= window_x_pdf[0] && movex <= window_x_pdf[1]) == True`), then we convert the x-position to an x-value using the `xScale_pdf_inverse` function we created before. We then
- select the circular position markers for the pdf (`d3.select("#marker_pdf")`) and cdf (`d3.select("#marker_cdf")`) we created before via their IDs, and update their positions in both subplots.
- Likewise, we update the fill path in the pdf plot (`d3.select("#fill")`) with the new `xpos`, and
- update the horizontal bar in the cdf subplot (`d3.select("#line_cdf")`).
If our cursor is within the second subplot, the second else-if clause gets activated. This clause basically does the same thing as the one for the first subplot, but uses the helper function `xScale_cdf_inverse` instead, thereby ensuring that we update both subplots by hovering over either one.
One of the nice things about these interactive JavaScript elements is that you can make the updates to your elements as complex as you want, as long as you find a way to code it. Here, we really just update the properties of pre-existing elements, but in principle you can also dynamically create new elements, destroy old elements, and so on. Finally, we close all open environments, and are done with this simple interactive element!
One of the nice things about these interactive JavaScript elements is that you can make the updates to your elements as complex as you want, as long as you find a way to code it. The sky is the limit! In this simple example, we really just update the properties of pre-existing elements, but in principle you can also dynamically create new elements, destroy old elements, and so on.
Finally, we close all open environments, and are done with this simple interactive element!
```html
// Shift the marker around on mouseover; restrict it to the contour
@ -463,7 +469,7 @@ One of the nice things about these interactive JavaScript elements is that you c
## HTML code for the pdf/cdf element
This is the code for the element above:
Here is the code for the element above in full:
```html
<!DOCTYPE html>
@ -643,7 +649,6 @@ This is the code for the element above:
});
svg.append("path")
.attr("class", "line")
.attr("d", valueline_pdf(data_pdf))
.attr("fill", "none")
.attr("stroke-width", 3 * height / 600)
@ -666,7 +671,6 @@ This is the code for the element above:
})
}
svg.append("path")
.attr("class", "line")
.attr("d", valueline_pdf(data_fill))
.attr("fill", "#999999")
.attr("stroke", "None") //"#4794c1")
@ -755,7 +759,6 @@ This is the code for the element above:
});
svg.append("path")
.attr("class", "line")
.attr("d", valueline_cdf(data_cdf))
.attr("fill", "none")
.attr("stroke-width", 3 * height / 600)