- /book/features/adding_basic_HTML_elements.md - /book/_static/element_pdf_and_cdf.html - /book/_toc.yml
39 KiB
Adding basic interactive HTML/JavaScript elements
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):
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:
<iframe src="../_static/element_pdf_and_cdf.html" width="600" height="400" 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?
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.
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.
The <style> tag below defines 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.
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/mathjs/lib/browser/math.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body{
margin: 0; overflow: hidden;
font-family: Helvetica, sans-serif;
}
</style>
</head>
Next, we create a <div>, an HTML element that can group other HTML elements, that contians a SVG named "click". This SVG will serve as the canvas on which we will place the interactive content.
<!-- Create a div where the graph will take place -->
<div id="my_datavisualization">
<svg id="click" xmlns="http://www.w3.org/2000/svg">
</svg>
</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.
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.
<body>
<script>
// Get the viewport height and width
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
// Fit to viewport
var height = vw * 0.5;
var width = vw;
// Create the canvas. We will use only part of it for the main plot
var svg = d3.select("#click") // This selects the div
.attr("width", width) // This defines the canvas' width
.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.
function linspace(start, end, resolution) {
var spacing = [];
// Go through a for-loop
var i;
for (i = 0; i < resolution; i++) {
spacing.push(start + (end - start) * i / (resolution - 1))
}
return spacing; // The function returns the linspace between p1 and p2
}
function standard_Gaussian_pdf(x) {
mean = 0;
std = 1;
// Create a dummy variable
var result = [];
normalize = math.dotDivide(
1,
math.sqrt(
math.dotMultiply(
math.dotMultiply(
2,
math.PI),
math.pow(
std,
2))));
var i;
for (i = 0; i < x.length; i++) {
// Evaluate the first element of the Gaussian mixture
expon = math.exp(
-math.dotDivide(
math.pow(
x[i] - mean,
2),
math.dotMultiply(
2,
math.pow(
std,
2))))
temp = math.dotMultiply(
normalize,
expon)
result.push(temp)
}
return result
}
function standard_Gaussian_cdf(x) {
mean = 0;
std = 1;
// Create a dummy variable
var result = [];
for (i = 0; i < x.length; i++) {
// Evaluate the first element of the Gaussian mixture
temp = 0.5 * (1 + math.erf(x[i] /math.sqrt(2)))
result.push(temp)
}
return result
}
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.
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).
// Define a subplot for the standard normal
const x_limits = [-3, 3];
const y_limits_pdf = [0, 1 / math.sqrt(2 * math.pi) * 1.2];
const window_x_pdf = [width * 0.1, width * 0.5];
const window_y_pdf = [height * 0.1, height * 0.89];
// Get scaling functions for the x scale and the y_scale
const xScale_pdf = d3.scaleLinear()
.domain([x_limits[0], x_limits[1]])
.range(window_x_pdf)
const xScale_pdf_inverse = d3.scaleLinear()
.domain(window_x_pdf)
.range([x_limits[0], x_limits[1]])
const yScale_pdf = d3.scaleLinear()
.domain([y_limits_pdf[0], y_limits_pdf[1]])
.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).
// Draw the x axis
svg
.append("g")
.attr("transform", "translate(0," + window_y_pdf[1].toString() + ")")
.call(d3.axisBottom(xScale_pdf).ticks(5))
.style("font-size", (12 * width / 600).toString() + "px")
svg.append("text")
.attr("transform",
"translate(" + (math.mean(window_x_pdf)).toString() + "," + (height * 0.995).toString() + ")")
.style("text-anchor", "middle")
.text("x")
.style("font-size", (12 * width / 600).toString() + "px")
// Draw the y axis
svg
.append("g")
.attr("transform", "translate(" + (window_x_pdf[0]).toString() + ",0)")
.attr("id", "mainxaxis")
.call(d3.axisLeft(yScale_pdf))
.style("font-size", (12 * width / 600).toString() + "px");
svg.append("text")
.attr("transform",
"translate(" + (width * 0.03).toString() + "," + (math.mean(window_y_pdf)).toString() + ") rotate(270)")
.style("text-anchor", "middle")
.text("probability density")
.style("font-size", (12 * width / 600).toString() + "px")
// Draw the subplot label
svg.append("text")
.attr("transform",
"translate(" + ((window_x_pdf[1] - window_x_pdf[0])/2 + window_x_pdf[0]).toString() + "," + (window_y_pdf[0]).toString() + ")")
.style("text-anchor", "middle")
.text("pdf")
.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.
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.
// Evaluate the normal pdf
var x = linspace(-3, 3, 201);
var pdf = standard_Gaussian_pdf(x);
svg.append("circle")
.attr("r", 10 * height / 600)
.attr("cx", xScale_pdf(0))
.attr("cy", yScale_pdf(standard_Gaussian_pdf([0])))
.attr("fill", "#666666") // "#c3e7f9"
.attr("stroke-width", 5 * height / 600)
.attr("id", "marker_pdf");
// Get the data for the path
var data_pdf = [];
for (i = 0; i < x.length; i++) {
data_pdf.push({
x: x[i],
y: pdf[i]
})
}
var valueline_pdf = d3.svg.line()
.x(function(d) {
return xScale_pdf(d.x);
})
.y(function(d) {
return yScale_pdf(d.y);
});
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("id", "thatline")
var fill_x, fill_pdf
var xpos = 0;
fill_x = linspace(x_limits[0], xpos, 201);
fill_pdf = standard_Gaussian_pdf(fill_x);
fill_x.push(xpos)
fill_x.push(x_limits[0])
fill_pdf.push(0)
fill_pdf.push(0)
var data_fill = [];
for (i = 0; i < fill_x.length; i++) {
data_fill.push({
x: fill_x[i],
y: fill_pdf[i]
})
}
svg.append("path")
.attr("class", "line")
.attr("d", valueline_pdf(data_fill))
.attr("fill", "#999999")
.attr("stroke", "None") //"#4794c1")
.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.
// Define a subplot for the standard normal
const y_limits_cdf = [0, 1];
const window_x_cdf = [width * 0.585, width * 0.985];
const window_y_cdf = [height * 0.1, height * 0.89];
// Get scaling functions for the x scale and the y_scale
const xScale_cdf = d3.scaleLinear()
.domain([x_limits[0], x_limits[1]])
.range(window_x_cdf)
const xScale_cdf_inverse = d3.scaleLinear()
.domain(window_x_cdf)
.range([x_limits[0], x_limits[1]])
const yScale_cdf = d3.scaleLinear()
.domain([y_limits_cdf[0], y_limits_cdf[1]])
.range([window_y_cdf[1], window_y_cdf[0]])
// Draw the x axis
svg
.append("g")
.attr("transform", "translate(0," + window_y_cdf[1].toString() + ")")
.call(d3.axisBottom(xScale_cdf).ticks(5))
.style("font-size", (12 * width / 600).toString() + "px")
svg.append("text")
.attr("transform",
"translate(" + (math.mean(window_x_cdf)).toString() + "," + (height * 0.995).toString() + ")")
.style("text-anchor", "middle")
.text("x")
.style("font-family", "arial")
.style("font-size", (12 * width / 600).toString() + "px")
// Draw the y axis
svg
.append("g")
.attr("transform", "translate(" + (window_x_cdf[0]).toString() + ",0)")
.attr("id", "mainxaxis")
.call(d3.axisLeft(yScale_cdf))
.style("font-size", (12 * width / 600).toString() + "px");
//.call(d3.axisLeft(yScale).tickFormat(""));
svg.append("text")
.attr("transform",
"translate(" + (width * 0.525).toString() + "," + (math.mean(window_y_cdf)).toString() + ") rotate(270)")
.style("text-anchor", "middle")
.text("cumulative probability")
.style("font-size", (12 * width / 600).toString() + "px")
// Draw the subplot label
svg.append("text")
.attr("transform",
"translate(" + ((window_x_cdf[1] - window_x_cdf[0])/2 + window_x_cdf[0]).toString() + "," + (window_y_cdf[0]).toString() + ")")
.style("text-anchor", "middle")
.text("cdf")
.style("font-size", (16 * width / 600).toString() + "px")
// Evaluate the normal pdf
var cdf = standard_Gaussian_cdf(x);
svg.append("circle")
.attr("r", 10 * height / 600)
.attr("cx", xScale_cdf(0))
.attr("cy", yScale_cdf(standard_Gaussian_cdf([0])))
.attr("fill", "#666666") // "#c3e7f9"
.attr("stroke-width", 5 * height / 600)
.attr("id", "marker_cdf");
// Get the data for the path
var data_cdf = [];
for (i = 0; i < x.length; i++) {
data_cdf.push({
x: x[i],
y: cdf[i]
})
}
var valueline_cdf = d3.svg.line()
.x(function(d) {
return xScale_cdf(d.x);
})
.y(function(d) {
return yScale_cdf(d.y);
});
svg.append("path")
.attr("class", "line")
.attr("d", valueline_cdf(data_cdf))
.attr("fill", "none")
.attr("stroke-width", 3 * height / 600)
.attr("stroke", "#666666") //"#4794c1")
.attr("id", "thatline")
svg.append('line')
.attr('x1', window_x_cdf[0])
.attr('x2', window_x_cdf[1])
.attr('y1', yScale_cdf(standard_Gaussian_cdf([0])))
.attr('y2', yScale_cdf(standard_Gaussian_cdf([0])))
.attr('stroke', '#000000')
.attr("stroke-width", 3 * height / 600)
.style("stroke-dasharray", ((10 * height / 600).toString()+","+(5 * height / 600).toString()))
.attr("id","line_cdf")
.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).
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.
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")).
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!
// Shift the marker around on mouseover; restrict it to the contour
var movex, movey, xpos
svg
.on("mousemove", function() {
// Get the current mouseover coordinates
movex = d3.event.x;
movey = d3.event.y;
if (movex >= window_x_pdf[0] && movex <= window_x_pdf[1]) {
xpos = xScale_pdf_inverse(movex);
d3.select("#marker_pdf")
.attr("cx", xScale_pdf(xpos))
.attr("cy", yScale_pdf(standard_Gaussian_pdf([xpos])));
d3.select("#marker_cdf")
.attr("cx", xScale_cdf(xpos))
.attr("cy", yScale_cdf(standard_Gaussian_cdf([xpos])));
fill_x = linspace(x_limits[0], xpos, 201);
fill_pdf = standard_Gaussian_pdf(fill_x);
fill_x.push(xpos)
fill_x.push(x_limits[0])
fill_pdf.push(0)
fill_pdf.push(0)
var data_fill = [];
for (i = 0; i < fill_x.length; i++) {
data_fill.push({
x: fill_x[i],
y: fill_pdf[i]
})
}
d3.select("#fill")
.attr("d", valueline_pdf(data_fill))
.lower();
d3.select("#line_cdf")
.attr('y1', yScale_cdf(standard_Gaussian_cdf([xpos])))
.attr('y2', yScale_cdf(standard_Gaussian_cdf([xpos])))
.lower();
} else if (movex >= window_x_cdf[0] && movex <= window_x_cdf[1]) {
xpos = xScale_cdf_inverse(movex);
d3.select("#marker_pdf")
.attr("cx", xScale_pdf(xpos))
.attr("cy", yScale_pdf(standard_Gaussian_pdf([xpos])));
d3.select("#marker_cdf")
.attr("cx", xScale_cdf(xpos))
.attr("cy", yScale_cdf(standard_Gaussian_cdf([xpos])));
fill_x = linspace(x_limits[0], xpos, 201);
fill_pdf = standard_Gaussian_pdf(fill_x);
fill_x.push(xpos)
fill_x.push(x_limits[0])
fill_pdf.push(0)
fill_pdf.push(0)
var data_fill = [];
for (i = 0; i < fill_x.length; i++) {
data_fill.push({
x: fill_x[i],
y: fill_pdf[i]
})
}
d3.select("#fill")
.attr("d", valueline_pdf(data_fill))
d3.select("#fill_cdf")
.attr('y', yScale_cdf(standard_Gaussian_cdf([xpos])))
.attr('height', window_y_cdf[1] - yScale_cdf(standard_Gaussian_cdf([xpos])))
.lower();
d3.select("#line_cdf")
.attr('y1', yScale_cdf(standard_Gaussian_cdf([xpos])))
.attr('y2', yScale_cdf(standard_Gaussian_cdf([xpos])))
.lower();
}
});
</script>
</body>
</html>
HTML code for the pdf/cdf element
This is the code for the element above:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/mathjs/lib/browser/math.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body{
margin: 0; overflow: hidden;
font-family: Helvetica, sans-serif;
}
</style>
</head>
<!-- Create a div where the graph will take place -->
<div id="my_datavisualization">
<svg id="click" xmlns="http://www.w3.org/2000/svg">
</svg>
</div>
<body>
<script>
// Get the viewport height and width
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
// Fit to viewport
var height = vw * 0.5;
var width = vw;
// Create the canvas. We will use only part of it for the main plot
var svg = d3.select("#click") // This selects the div
.attr("width", width) // This defines the canvas' width
.attr("height", height) // This defines the canvas' height
function linspace(start, end, resolution) {
var spacing = [];
// Go through a for-loop
var i;
for (i = 0; i < resolution; i++) {
spacing.push(start + (end - start) * i / (resolution - 1))
}
return spacing; // The function returns the linspace between p1 and p2
}
function standard_Gaussian_pdf(x) {
mean = 0;
std = 1;
// Create a dummy variable
var result = [];
normalize = math.dotDivide(
1,
math.sqrt(
math.dotMultiply(
math.dotMultiply(
2,
math.PI),
math.pow(
std,
2))));
var i;
for (i = 0; i < x.length; i++) {
// Evaluate the first element of the Gaussian mixture
expon = math.exp(
-math.dotDivide(
math.pow(
x[i] - mean,
2),
math.dotMultiply(
2,
math.pow(
std,
2))))
temp = math.dotMultiply(
normalize,
expon)
result.push(temp)
}
return result
}
function standard_Gaussian_cdf(x) {
mean = 0;
std = 1;
// Create a dummy variable
var result = [];
for (i = 0; i < x.length; i++) {
// Evaluate the first element of the Gaussian mixture
temp = 0.5 * (1 + math.erf(x[i] /math.sqrt(2)))
result.push(temp)
}
return result
}
// Define a subplot for the standard normal
const x_limits = [-3, 3];
const y_limits_pdf = [0, 1 / math.sqrt(2 * math.pi) * 1.2];
const window_x_pdf = [width * 0.1, width * 0.5];
const window_y_pdf = [height * 0.1, height * 0.89];
// Get scaling functions for the x scale and the y_scale
const xScale_pdf = d3.scaleLinear()
.domain([x_limits[0], x_limits[1]])
.range(window_x_pdf)
const xScale_pdf_inverse = d3.scaleLinear()
.domain(window_x_pdf)
.range([x_limits[0], x_limits[1]])
const yScale_pdf = d3.scaleLinear()
.domain([y_limits_pdf[0], y_limits_pdf[1]])
.range([window_y_pdf[1], window_y_pdf[0]])
// Draw the x axis
svg
.append("g")
.attr("transform", "translate(0," + window_y_pdf[1].toString() + ")")
.call(d3.axisBottom(xScale_pdf).ticks(5))
.style("font-size", (12 * width / 600).toString() + "px")
svg.append("text")
.attr("transform",
"translate(" + (math.mean(window_x_pdf)).toString() + "," + (height * 0.995).toString() + ")")
.style("text-anchor", "middle")
.text("x")
.style("font-size", (12 * width / 600).toString() + "px")
// Draw the y axis
svg
.append("g")
.attr("transform", "translate(" + (window_x_pdf[0]).toString() + ",0)")
.attr("id", "mainxaxis")
.call(d3.axisLeft(yScale_pdf))
.style("font-size", (12 * width / 600).toString() + "px");
svg.append("text")
.attr("transform",
"translate(" + (width * 0.03).toString() + "," + (math.mean(window_y_pdf)).toString() + ") rotate(270)")
.style("text-anchor", "middle")
.text("probability density")
.style("font-size", (12 * width / 600).toString() + "px")
// Draw the subplot label
svg.append("text")
.attr("transform",
"translate(" + ((window_x_pdf[1] - window_x_pdf[0])/2 + window_x_pdf[0]).toString() + "," + (window_y_pdf[0]).toString() + ")")
.style("text-anchor", "middle")
.text("pdf")
.style("font-size", (16 * width / 600).toString() + "px")
// Evaluate the normal pdf
var x = linspace(-3, 3, 201);
var pdf = standard_Gaussian_pdf(x);
svg.append("circle")
.attr("r", 10 * height / 600)
.attr("cx", xScale_pdf(0))
.attr("cy", yScale_pdf(standard_Gaussian_pdf([0])))
.attr("fill", "#666666") // "#c3e7f9"
.attr("stroke-width", 5 * height / 600)
.attr("id", "marker_pdf");
// Get the data for the path
var data_pdf = [];
for (i = 0; i < x.length; i++) {
data_pdf.push({
x: x[i],
y: pdf[i]
})
}
var valueline_pdf = d3.svg.line()
.x(function(d) {
return xScale_pdf(d.x);
})
.y(function(d) {
return yScale_pdf(d.y);
});
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("id", "thatline")
var fill_x, fill_pdf
var xpos = 0;
fill_x = linspace(x_limits[0], xpos, 201);
fill_pdf = standard_Gaussian_pdf(fill_x);
fill_x.push(xpos)
fill_x.push(x_limits[0])
fill_pdf.push(0)
fill_pdf.push(0)
var data_fill = [];
for (i = 0; i < fill_x.length; i++) {
data_fill.push({
x: fill_x[i],
y: fill_pdf[i]
})
}
svg.append("path")
.attr("class", "line")
.attr("d", valueline_pdf(data_fill))
.attr("fill", "#999999")
.attr("stroke", "None") //"#4794c1")
.attr("id", "fill")
.lower()
// Define a subplot for the standard normal
const y_limits_cdf = [0, 1];
const window_x_cdf = [width * 0.585, width * 0.985];
const window_y_cdf = [height * 0.1, height * 0.89];
// Get scaling functions for the x scale and the y_scale
const xScale_cdf = d3.scaleLinear()
.domain([x_limits[0], x_limits[1]])
.range(window_x_cdf)
const xScale_cdf_inverse = d3.scaleLinear()
.domain(window_x_cdf)
.range([x_limits[0], x_limits[1]])
const yScale_cdf = d3.scaleLinear()
.domain([y_limits_cdf[0], y_limits_cdf[1]])
.range([window_y_cdf[1], window_y_cdf[0]])
// Draw the x axis
svg
.append("g")
.attr("transform", "translate(0," + window_y_cdf[1].toString() + ")")
.call(d3.axisBottom(xScale_cdf).ticks(5))
.style("font-size", (12 * width / 600).toString() + "px")
svg.append("text")
.attr("transform",
"translate(" + (math.mean(window_x_cdf)).toString() + "," + (height * 0.995).toString() + ")")
.style("text-anchor", "middle")
.text("x")
.style("font-family", "arial")
.style("font-size", (12 * width / 600).toString() + "px")
// Draw the y axis
svg
.append("g")
.attr("transform", "translate(" + (window_x_cdf[0]).toString() + ",0)")
.attr("id", "mainxaxis")
.call(d3.axisLeft(yScale_cdf))
.style("font-size", (12 * width / 600).toString() + "px");
//.call(d3.axisLeft(yScale).tickFormat(""));
svg.append("text")
.attr("transform",
"translate(" + (width * 0.525).toString() + "," + (math.mean(window_y_cdf)).toString() + ") rotate(270)")
.style("text-anchor", "middle")
.text("cumulative probability")
.style("font-size", (12 * width / 600).toString() + "px")
// Draw the subplot label
svg.append("text")
.attr("transform",
"translate(" + ((window_x_cdf[1] - window_x_cdf[0])/2 + window_x_cdf[0]).toString() + "," + (window_y_cdf[0]).toString() + ")")
.style("text-anchor", "middle")
.text("cdf")
.style("font-size", (16 * width / 600).toString() + "px")
// Evaluate the normal pdf
var cdf = standard_Gaussian_cdf(x);
svg.append("circle")
.attr("r", 10 * height / 600)
.attr("cx", xScale_cdf(0))
.attr("cy", yScale_cdf(standard_Gaussian_cdf([0])))
.attr("fill", "#666666") // "#c3e7f9"
.attr("stroke-width", 5 * height / 600)
.attr("id", "marker_cdf");
// Get the data for the path
var data_cdf = [];
for (i = 0; i < x.length; i++) {
data_cdf.push({
x: x[i],
y: cdf[i]
})
}
var valueline_cdf = d3.svg.line()
.x(function(d) {
return xScale_cdf(d.x);
})
.y(function(d) {
return yScale_cdf(d.y);
});
svg.append("path")
.attr("class", "line")
.attr("d", valueline_cdf(data_cdf))
.attr("fill", "none")
.attr("stroke-width", 3 * height / 600)
.attr("stroke", "#666666") //"#4794c1")
.attr("id", "thatline")
svg.append('line')
.attr('x1', window_x_cdf[0])
.attr('x2', window_x_cdf[1])
.attr('y1', yScale_cdf(standard_Gaussian_cdf([0])))
.attr('y2', yScale_cdf(standard_Gaussian_cdf([0])))
.attr('stroke', '#000000')
.attr("stroke-width", 3 * height / 600)
.style("stroke-dasharray", ((10 * height / 600).toString()+","+(5 * height / 600).toString()))
.attr("id","line_cdf")
.lower();
// Shift the marker around on mouseover; restrict it to the contour
var movex, movey, xpos
svg
.on("mousemove", function() {
// Get the current mouseover coordinates
movex = d3.event.x;
movey = d3.event.y;
if (movex >= window_x_pdf[0] && movex <= window_x_pdf[1]) {
xpos = xScale_pdf_inverse(movex);
d3.select("#marker_pdf")
.attr("cx", xScale_pdf(xpos))
.attr("cy", yScale_pdf(standard_Gaussian_pdf([xpos])));
d3.select("#marker_cdf")
.attr("cx", xScale_cdf(xpos))
.attr("cy", yScale_cdf(standard_Gaussian_cdf([xpos])));
fill_x = linspace(x_limits[0], xpos, 201);
fill_pdf = standard_Gaussian_pdf(fill_x);
fill_x.push(xpos)
fill_x.push(x_limits[0])
fill_pdf.push(0)
fill_pdf.push(0)
var data_fill = [];
for (i = 0; i < fill_x.length; i++) {
data_fill.push({
x: fill_x[i],
y: fill_pdf[i]
})
}
d3.select("#fill")
.attr("d", valueline_pdf(data_fill))
.lower();
d3.select("#line_cdf")
.attr('y1', yScale_cdf(standard_Gaussian_cdf([xpos])))
.attr('y2', yScale_cdf(standard_Gaussian_cdf([xpos])))
.lower();
} else if (movex >= window_x_cdf[0] && movex <= window_x_cdf[1]) {
xpos = xScale_cdf_inverse(movex);
d3.select("#marker_pdf")
.attr("cx", xScale_pdf(xpos))
.attr("cy", yScale_pdf(standard_Gaussian_pdf([xpos])));
d3.select("#marker_cdf")
.attr("cx", xScale_cdf(xpos))
.attr("cy", yScale_cdf(standard_Gaussian_cdf([xpos])));
fill_x = linspace(x_limits[0], xpos, 201);
fill_pdf = standard_Gaussian_pdf(fill_x);
fill_x.push(xpos)
fill_x.push(x_limits[0])
fill_pdf.push(0)
fill_pdf.push(0)
var data_fill = [];
for (i = 0; i < fill_x.length; i++) {
data_fill.push({
x: fill_x[i],
y: fill_pdf[i]
})
}
d3.select("#fill")
.attr("d", valueline_pdf(data_fill))
d3.select("#fill_cdf")
.attr('y', yScale_cdf(standard_Gaussian_cdf([xpos])))
.attr('height', window_y_cdf[1] - yScale_cdf(standard_Gaussian_cdf([xpos])))
.lower();
d3.select("#line_cdf")
.attr('y1', yScale_cdf(standard_Gaussian_cdf([xpos])))
.attr('y2', yScale_cdf(standard_Gaussian_cdf([xpos])))
.lower();
}
});
</script>
</body>
</html>