In my D3 line chart, i´m trying to create a mouseover effect like in this example: http://bl.ocks.org/mbostock/3902569
In this example the author uses the bisector function, which is, as far as i understand, only supported for linear scales.
The problem is, that in my chart i have an ordinal x axis with different, discrete rangePoint tuples. So if it is like in the situation below (m = mouse position), i want to get the pixel position of the closest x value which would be x2 in this example.
m
|
x1----------x2----------x3
Is there any way to do that?
Using your linked example, here's a quick implementation of the mousemove function for an ordinal scale:
var tickPos = x.range();
function mousemove(d){
var m = d3.mouse(this),
lowDiff = 1e99,
xI = null;
// if you have a large number of ticks
// this search could be optimized
for (var i = 0; i < tickPos.length; i++){
var diff = Math.abs(m[0] - tickPos[i]);
if (diff < lowDiff){
lowDiff = diff;
xI = i;
}
}
focus
.select('text')
.text(ticks[xI]);
focus
.attr("transform","translate(" + tickPos[xI] + "," + y(data[xI].y) + ")");
}
Full code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundPoints([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = [{
x: 'A',
y: Math.random() * 10
}, {
x: 'B',
y: Math.random() * 10
}, {
x: 'C',
y: Math.random() * 10
}, {
x: 'D',
y: Math.random() * 10
}, {
x: 'E',
y: Math.random() * 10
}, {
x: 'F',
y: Math.random() * 10
}, {
x: 'G',
y: Math.random() * 10
}, {
x: 'H',
y: Math.random() * 10
}, {
x: 'I',
y: Math.random() * 10
}, {
x: 'J',
y: Math.random() * 10
}];
var ticks = data.map(function(d) {
return d.x
});
x.domain(ticks);
y.domain(d3.extent(data, function(d) {
return d.y;
}));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em")
.style('')
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", mousemove);
var tickPos = x.range();
function mousemove(d){
var m = d3.mouse(this),
lowDiff = 1e99,
xI = null;
for (var i = 0; i < tickPos.length; i++){
var diff = Math.abs(m[0] - tickPos[i]);
if (diff < lowDiff){
lowDiff = diff;
xI = i;
}
}
focus
.select('text')
.text(ticks[xI]);
focus
.attr("transform","translate(" + tickPos[xI] + "," + y(data[xI].y) + ")");
}
</script>
Simple Solution:
.on("mousemove", function () {
const x0 = x.invert(d3.mouse(d3.event.currentTarget)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i];
const rangeValueOfFirst = x(d0.date),
rangeValueOfSecond = x(d1.date),
rangeValueOfMousePos = d3.mouse(d3.event.currentTarget)[0],
closestD = Math.abs(rangeValueOfMousePos - rangeValueOfFirst) > Math.abs(rangeValueOfMousePos - rangeValueOfSecond) ? d1 : d0;
const focus = d3.select(".focus");
focus.attr("transform", () => "translate(" + x(closestD.date) + "," + y(closestD.close) + ")");
focus.select("text").text(closestD.close);
});
Related
I am trying to do a zoomable heatmap and the community here on SO have helped massively, however I am now stuck the whole day today trying to fix a glitch and I am hitting the wall every single time.
The issue is that the zoom looks jumpy, ie the plot is rendered fine, however when the zoom event is triggered some kind of transformation happens that changes the axes and the scaling in an abrupt way. The code below demonstrates this issue. The problem does not always happen, it depends on the heatmap dimension and/or the number of the dots.
Some similar cases from people with the same problem here on SO turned out to be that the zoom was not applied to the correct object but I think I am not doing that mistake. Many thanks in advance
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000000;
}
.x.axis path {
//display: none;
}
.chart rect {
fill: steelblue;
}
.chart text {
fill: white;
font: 10px sans-serif;
text-anchor: end;
}
#tooltip {
position: absolute;
background-color: #2B292E;
color: white;
font-family: sans-serif;
font-size: 15px;
pointer-events: none;
/*dont trigger events on the tooltip*/
padding: 15px 20px 10px 20px;
text-align: center;
opacity: 0;
border-radius: 4px;
}
</style>
<title>Heatmap Chart</title>
<!-- Reference style.css -->
<!-- <link rel="stylesheet" type="text/css" href="style.css">-->
<!-- Reference minified version of D3 -->
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
<script src='heatmap.js' type='text/javascript'></script>
</head>
<body>
<div id="chart">
<svg width="550" height="1000"></svg>
</div>
<script>
var dataset = [];
for (let i = 1; i < 60; i++) { //360
for (j = 1; j < 70; j++) { //75
dataset.push({
xKey: i,
xLabel: "xMark " + i,
yKey: j,
yLabel: "yMark " + j,
val: Math.random() * 25,
})
}
};
var svg = d3.select("#chart")
.select("svg")
var xLabels = [],
yLabels = [];
for (i = 0; i < dataset.length; i++) {
if (i == 0) {
xLabels.push(dataset[i].xLabel);
var j = 0;
while (dataset[j + 1].xLabel == dataset[j].xLabel) {
yLabels.push(dataset[j].yLabel);
j++;
}
yLabels.push(dataset[j].yLabel);
} else {
if (dataset[i - 1].xLabel == dataset[i].xLabel) {
//do nothing
} else {
xLabels.push(dataset[i].xLabel);
}
}
};
var margin = {
top: 0,
right: 25,
bottom: 40,
left: 75
};
var width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var dotSpacing = 0,
dotWidth = width / (2 * (xLabels.length + 1)),
dotHeight = height / (2 * yLabels.length);
// var dotWidth = 1,
// dotHeight = 3,
// dotSpacing = 0.5;
var daysRange = d3.extent(dataset, function(d) {
return d.xKey
}),
days = daysRange[1] - daysRange[0];
var hoursRange = d3.extent(dataset, function(d) {
return d.yKey
}),
hours = hoursRange[1] - hoursRange[0];
var tRange = d3.extent(dataset, function(d) {
return d.val
}),
tMin = tRange[0],
tMax = tRange[1];
// var width = (dotWidth * 2 + dotSpacing) * days,
// height = (dotHeight * 2 + dotSpacing) * hours;
// var width = +svg.attr("width") - margin.left - margin.right,
// height = +svg.attr("height") - margin.top - margin.bottom;
var colors = ['#2C7BB6', '#00A6CA', '#00CCBC', '#90EB9D', '#FFFF8C', '#F9D057', '#F29E2E', '#E76818', '#D7191C'];
// the scale
var scale = {
x: d3.scaleLinear()
.domain([-1, d3.max(dataset, d => d.xKey)])
.range([-1, width]),
y: d3.scaleLinear()
.domain([0, d3.max(dataset, d => d.yKey)])
.range([height, 0]),
//.range([(dotHeight * 2 + dotSpacing) * hours, dotHeight * 2 + dotSpacing]),
};
var xBand = d3.scaleBand().domain(xLabels).range([0, width]),
yBand = d3.scaleBand().domain(yLabels).range([height, 0]);
var axis = {
x: d3.axisBottom(scale.x).tickFormat((d, e) => xLabels[d]),
y: d3.axisLeft(scale.y).tickFormat((d, e) => yLabels[d]),
};
function updateScales(data) {
scale.x.domain([-1, d3.max(data, d => d.xKey)]),
scale.y.domain([0, d3.max(data, d => d.yKey)])
}
var colorScale = d3.scaleQuantile()
.domain([0, colors.length - 1, d3.max(dataset, function(d) {
return d.val;
})])
.range(colors);
var zoom = d3.zoom()
.scaleExtent([dotWidth, dotHeight])
.on("zoom", zoomed);
var tooltip = d3.select("body").append("div")
.attr("id", "tooltip")
.style("opacity", 0);
// SVG canvas
svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
//.call(zoom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Clip path
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height + dotHeight);
// Heatmap dots
var heatDotsGroup = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("g");
heatDotsGroup.call(zoom);
//Create X axis
var renderXAxis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + scale.y(-1) + ")")
.call(axis.x)
//Create Y axis
var renderYAxis = svg.append("g")
.attr("class", "y axis")
.call(axis.y);
function zoomed() {
d3.event.transform.y = 0;
d3.event.transform.x = Math.min(d3.event.transform.x, 5);
d3.event.transform.x = Math.max(d3.event.transform.x, (1 - d3.event.transform.k) * width);
d3.event.transform.k = Math.max(d3.event.transform.k, 1);
//console.log(d3.event.transform)
// update: rescale x axis
renderXAxis.call(axis.x.scale(d3.event.transform.rescaleX(scale.x)));
heatDotsGroup.attr("transform", d3.event.transform.toString().replace(/scale\((.*?)\)/, "scale($1, 1)"));
}
svg.call(renderPlot, dataset)
function renderPlot(selection, dataset) {
//updateScales(dataset);
heatDotsGroup.selectAll("ellipse")
.data(dataset)
.enter()
.append("ellipse")
.attr("cx", function(d) {
return scale.x(d.xKey) - xBand.bandwidth();
})
.attr("cy", function(d) {
return scale.y(d.yKey) + yBand.bandwidth();
})
.attr("rx", dotWidth)
.attr("ry", dotHeight)
.attr("fill", function(d) {
return colorScale(d.val);
})
.on("mouseover", function(d) {
$("#tooltip").html("X: " + d.xKey + "<br/>Y: " + d.yKey + "<br/>Value: " + Math.round(d.val * 100) / 100);
var xpos = d3.event.pageX + 10;
var ypos = d3.event.pageY + 20;
$("#tooltip").css("left", xpos + "px").css("top", ypos + "px").animate().css("opacity", 1);
}).on("mouseout", function() {
$("#tooltip").animate({
duration: 500
}).css("opacity", 0);
});
}
</script>
</body>
</html>
Change the zoom scaleExtend
var zoom = d3.zoom()
.scaleExtent([1, dotHeight])
.on("zoom", zoomed);
Call the zoom on the whole svg not on the heatDotsGroup because this node receives the tranformation, and also not on the g node that has the graph transformation here variable svg (to keep things a bit obscure)
svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(zoom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// heatDotsGroup.call(zoom);
Don't limit the zoom scale k in the tick. Already taken care of by the scaleExtent()
// d3.event.transform.k = Math.max(d3.event.transform.k, 1);
Why calculate all the d3.max() when you already have calculated the d3.extent() of these values?
I am having following code for line charts.
Here I'm showing two line charts in different <div> ids, I have added hover option for the line chart.
<html>
<head>
<script src="http://d3js.org/d3.v3.js"></script>
<style>
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 2;
shape-rendering: crispEdges;
}
.grid .tick {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
</style>
</head>
<body>
<div id="chart1"></div>
<div id="chart2"></div>
<script>
var data = [{
x: 'w1',
y: 5
}, {
x: 'w2',
y: 28
}, {
x: 'w3',
y: 58
}, {
x: 'w4',
y: 88
}, {
x: 'w5',
y: 8
}, {
x: 'w6',
y: 48
}, {
x: 'w7',
y: 28
}, {
x: 'w8',
y: 68
}, {
x: 'w9',
y: 8
}];
div_id ="#chart1";
div_id1="#chart2";
draw_line(data,div_id);
draw_line(data,div_id1);
function draw_line(data,div_id)
{
width = 600,
height = 260,
margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
width -= margin.left - margin.right;
height -= margin.top - margin.bottom;
// var parseDate = d3.time.format("%d-%b-%y").parse;
// var x = d3.time.scale().range([0, width]);
var x = d3.scale.ordinal().rangePoints([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom")
// .ticks(0).tickSize(0)
// .tickFormat("").outerTickSize(0);
var yAxis = d3.svg.axis().scale(y)
.orient("left").tickSize(0).ticks(0)
.tickFormat("");
var svg = d3.select(div_id)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "bg-color")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// function for the y grid lines
function make_y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
}
// Draw the y Grid lines
svg.append("g")
.attr("class", "grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat("")
);
// x.domain(d3.extent(data, function(d) {
// return d.x;
// }));
x.domain(data.map(function(d) { return d.x; }));
y.domain([0, d3.max(data, function(d) {
return d.y;
})]);
data.sort(function(a, b) {
return a.x - b.x;
});
/*x.domain([parseDate(data[0].x), parseDate(data[data.length - 1].x)]);
y.domain(d3.extent(data, function (d) {
return d.y;
}));*/
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add the valueline path
svg.append("path")
.attr("class", "line");
// Define the line
var lineGen = d3.svg.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
})
svg.append('path')
.attr("class", "line")
.attr('d', lineGen(data));
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", mousemove);
var bisectDate = d3.bisector(function(d) {
return d.x;
}).left;
function mousemove() {
var xPos = d3.mouse(this)[0];
var leftEdges = x.range();
var width = x.rangeBand();
var j;
for(j=0; xPos > (leftEdges[j] + width); j++) {}
var x0 = x.domain()[j];
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.x > d1.x - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.x) + "," + y(d.y) + ")");
focus.select("text").text(d.x+":"+d.y);
}
}
</script>
</body>
</html>
I have to add mousehover option for both the chart at same...
Example:
If I hover first line chart means I have to show tooltip for both charts at same time and same position.
I was following Mike's tutorial Let's make a bar graph part II, part III for the bar graph in d3.
I end up with a horizontal bar graph.
The Problem is I'm not able to set the y-axis labels aligned with its respective bar, maybe I'm somewhere wrong with ordinal scale or setting up the y position of the bars.
Also, the graph bars are starting from the top instead of baseline.
var options = {
margin: {
top: 0,
right: 30,
bottom: 0,
left: 50
},
width: 560,
height: 400,
element: 'body',
colors: ["#f44336", "#e91e63", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4", "#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722", "#795548", "#607d8b"]
};
options.mWidth = options.width - options.margin.left - options.margin.right;
options.mHeight = options.height - options.margin.top - options.margin.bottom;
var _colorScale = d3.scale.ordinal()
// .domain([minValue,maxValue])
.range(options.colors);
var items = Math.round((Math.random() * 10) + 3);
var data = [];
for (var i = 0; i < items; i++) {
data.push({
label: 'label' + i,
value: Math.random() * 100
});
}
var _barThickness = 20;
var width = options.mWidth,
height = options.mHeight;
var svg = d3.select(options.element)
.append("svg")
.attr("width", width).attr("height", height)
.attr("viewBox", "0 0 " + options.width + " " + options.height)
//.attr("preserveAspectRatio", "xMinYMin meet")
.append("g");
svg.attr("transform", "translate(" + options.margin.left * 2 + "," + options.margin.top + ")");
var maxValue = 0,
minValue = 0;
for (var i = 0; i < data.length; i++) {
maxValue = Math.max(maxValue, data[i].value);
minValue = Math.min(minValue, data[i].value);
}
var scales = {
x: d3.scale.linear().domain([0, maxValue]).range([0, width / 2]),
y: d3.scale.ordinal().rangeRoundBands([0, height], .1)
}
scales.y.domain(data.map(function(d) {
return d.label;
}));
var bars = svg.append("g")
.attr("class", "bar");
var xAxis = d3.svg.axis()
.scale(scales.x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(scales.y)
.orient("left");
var xaxis = svg.append("g")
.attr("class", "x axis")
.attr('transform', 'translate(' + 0 + ',' + height + ')')
.call(xAxis)
.append("text")
.attr("x", width / 2)
.attr("dy", "3em")
.style("text-anchor", "middle")
.text("Durations in hours");
var yaxis = svg.append("g")
.attr("class", "y axis")
.attr('transform', 'translate(' + 0 + ',' + 0 + ')')
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -options.margin.left)
.attr("x", -height / 2)
.attr("dy", ".71em")
.style("text-anchor", "middle")
.text("Labels");
var bar = bars.selectAll('rect.bar').data(data);
var rect = bar.enter()
.append('g')
.attr('transform', function(d, i) {
return 'translate(0,' + parseInt(i * _barThickness + 2) + ')';
});
rect.append('rect')
.attr('class', 'bar')
.attr('fill', function(d) {
return _colorScale(d.value);
})
.attr('width', function(d) {
return scales.x(d.value);
})
.attr('height', _barThickness - 1)
.attr('y', function(d, i) {
return (i * _barThickness);
})
rect.append('text')
.attr('x', function(d) {
return scales.x(d.value) + 2;
})
.attr('y', function(d, i) {
return (i * _barThickness);
})
.attr('dy', '0.35em')
.text(function(d) {
return Math.round(d.value);
});
svg {
width: 100%;
height: 100%;
}
text {
font-size: 0.8em;
line-height: 1em;
}
path.slice {
stroke-width: 2px;
}
polyline {
opacity: .3;
stroke: black;
stroke-width: 2px;
fill: none;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
Problem 1:
Instead of this:
var _barThickness = 20;
Do this:
var _barThickness = scales.y.rangeBand();
Read here
If you want to regulate the thickness
Instead of this:
y: d3.scale.ordinal().rangeRoundBands([0, height], .1)
Do this:
y: d3.scale.ordinal().rangeRoundBands([0, height], .7)
Problem 2:
Incorrect transform.
Instead of this:
var rect = bar.enter()
.append('g')
.attr('transform', function(d, i) {
return 'translate(0,' + parseInt(i * _barThickness + 2) + ')';
});
Do this:
var rect = bar.enter()
.append('g');
Problem 3:
Incorrect calculation of y for the bars.
.attr('y', function(d, i) {
return (i * _barThickness);
})
Do this:
.attr('y', function(d, i) {
return scales.y(d.label);
})
working code here
I build a D3 Hive plot for a series of data sets. For most of the data sets, the results are as expected, but for one data set, the axes are seriously misbehaving.
In particular, the axes are not respecting the inner radius, and thus they extend inward and intersect:
I was wondering if there was some mistake in how we are defining the axes that is leading to this; here is the code for these functions:
radius.domain(d3.extent(visa_lengths, function(d){ return d.length; }));
svg.selectAll(".axis")
.data(visa_lengths)
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d, i){ return "rotate(" + degrees(angle(i)) + ")";})
.attr("x1", radius.range()[0])
.attr("x2", function(d){return radius(d.length);})
;
The full html is provided here:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hive Link Plot</title>
<style type="text/css">
.link{
fill: none;
stroke: #000;
stroke-width: 1px;
stroke-opacity: .1;
}
.axis {
stroke: #000;
stroke-width: 1.5px;
}
.link.active {
stroke-width: 3px;
stroke-opacity: 1;
}
.node {
stroke: #000;
}
.node.active {
stroke: red;
stroke-width: 3px;
stroke-opacity: 1;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="../static/d3.hive.min.js"></script>
<script type="text/javascript">
var source_cont = "Asia";
var target_cont = "Asia";
plot(source_cont, target_cont);
function plot(source_cont, target_cont){
var width = 1200;
var height = 1200;
var outerRadius = 500;
var innerRadius = 50;
var minLength = 5;
var visa_types = {'free': 1, 'required': 4, 'refused': 2, 'onarrival': 3, 'origin': 0};
var angle = d3.scale.ordinal()
.domain(["origin", "free", "refused", "onarrival", "required"])
.range([0, Math.PI/2, 11 * Math.PI / 12, 13 * Math.PI / 12, 3 * Math.PI / 2]);
var radius = d3.scale.linear()
.range([innerRadius, outerRadius]);
var color = d3.scale.ordinal()
.domain(["origin", "free", "refused", "onarrival", "required"])
.range(["yellow", "blue", "red", "orange", "green"]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
d3.json("reduced_pairwise_datasets.json", function(error, graph){
nodes = graph.pairwise_nodes[source_cont + "-" + target_cont];
links = graph.pairwise_links[source_cont + "-" + target_cont];
// var origin_links =
var free_links = new Set(links.filter(function(p){ return p.target.type == 'free';}).map(function(d){return d.target.name}));
var onarrival_links = new Set(links.filter(function(p){ return p.target.type == 'onarrival';}).map(function(d){return d.target.name}));
var required_links = new Set(links.filter(function(p){ return p.target.type == 'required';}).map(function(d){return d.target.name}));
var refused_links = new Set(links.filter(function(p){ return p.target.type == 'refused';}).map(function(d){return d.target.name}));
var visa_lengths = [{'name': 'origin', 'length': 50}, {'name': 'free', 'length': free_links.size}, {'name': 'refused', 'length': refused_links.size}, {'name': 'onarrival', 'length': onarrival_links.size}, {'name': 'required', 'length': required_links.size}];
var visa_obj = {'origin': 55, 'free': free_links.size, 'required': required_links.size, 'onarrival': onarrival_links.size, 'refused': refused_links.size};
radius.domain(d3.extent(visa_lengths, function(d){ return d.length; }));
svg.selectAll(".axis")
.data(visa_lengths)
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d, i){ return "rotate(" + degrees(angle(i)) + ")";})
.attr("x1", radius.range()[0])
.attr("x2", function(d){return radius(d.length);})
;
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", d3.hive.link()
.angle(function(d) { return angle(visa_types[d.type]); })
.radius(function(d) { return radius(visa_obj[d.type] * d.y); }))
.style("stroke", function(d) { return color(d.target.type);})
;
svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + degrees(angle(visa_types[d.type])) + ")"; })
.attr("cx", function(d) { return radius(visa_obj[d.type] * d.y); })
.attr("r", 5)
.style("fill", function(d) { return color(d.type);})
;
});
}
function degrees(radians) {
return radians / Math.PI * 180 - 90;
}
</script>
</body>
</html>
I have some code which plots a certain mathematical function and enables the user to change certain specific parameters of the function on the fly enabling the user to see the change of it.
I have two questions with which I need some help.
i) I am using the jquery UI library which provides a slider. I use this slider to enable the user to change a specific parameter in a certain range. Normally there are no constraints on the parameter. However, in this case I need that the following equality holds min <= mode <= max. Currently I have solved this with some code in the slide event of the slider. The code basically checks the equality and if it is violated it sets the parameter to either its minimum or maximum possible value.
Visually, however, it is possible to slide further. However, after I the slider is let loose by the user, the change event has been called and I change the parameter to either its minimum or maximum possible value. Is it possible to make sure that also visually the slider is does not violates the constraint?
ii) When I load the page there is no plot visible yet on the page. That is because I seem to be unable to call the function redraw() when the page is loading, see the code at the bottom. Why is redraw() not being called?
Snippet, see below or http://jsfiddle.net/qraLt1p3/11/
<!DOCTYPE html>
<html lang="en">
<head>
<title>MPERT</title>
<meta charset="utf-8">
<script src="//d3js.org/d3.v3.min.js"></script>
<link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<style type="text/css">
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
rect {
fill: transparent;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
clip-path: url(#clip);
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
#slider-pertmin, #slider-pertmode, #slider-pertmax {
float: right;
width: 120px;
margin: 7px;
}
.ui-slider-horizontal {
height: 8px;
width: 200px;
}
.ui-slider .ui-slider-handle {
height: 15px;
width: 5px;
padding-left: 5px;
}
#plot {
background-color:#f9f9f9;
border:solid 1px #ddd;
padding:10px;
width:250px;
}
#plotOptions {
background-color:#f9f9f9;
border:solid 1px #ddd;
padding:10px;
}
.block label {
display: inline-block;
width: 60px;
text-align: right;
}
</style>
<script type="text/javascript">
// Log-gamma function
gammaln = function gammaln(x)
{
var cof = [ 76.18009172947146, -86.50532032941677, 24.01409824083091,
-1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5];
var j = 0;
var ser = 1.000000000190015;
var xx, y, tmp;
tmp = (y = xx = x) + 5.5;
tmp -= (xx + 0.5) * Math.log(tmp);
for (; j < 6; j++)
ser += cof[j] / ++y;
return Math.log(2.5066282746310005 * ser / xx) - tmp;
};
// Gamma function
gammafn = function gammafn(x)
{
var p = [ -1.716185138865495, 24.76565080557592, -379.80425647094563,
629.3311553128184, 866.9662027904133, -31451.272968848367,
-36144.413418691176, 66456.14382024054];
var q = [ -30.8402300119739, 315.35062697960416, -1015.1563674902192,
-3107.771671572311, 22538.118420980151, 4755.8462775278811,
-134659.9598649693, -115132.2596755535];
var fact = false,
n = 0,
xden = 0,
xnum = 0,
y = x,
i,
z,
yi,
res,
sum,
ysq;
if (y <= 0) {
res = y % 1 + 3.6e-16;
if (res) {
fact = (!(y & 1) ? 1 : -1) * Math.PI / Math.sin(Math.PI * res);
y = 1 - y;
} else
return Infinity;
}
yi = y;
if (y < 1)
z = y++;
else
z = (y -= n = (y | 0) - 1) - 1;
for (i = 0; i < 8; ++i) {
xnum = (xnum + p[i]) * z;
xden = xden * z + q[i];
}
res = xnum / xden + 1;
if (yi < y)
res /= yi;
else if (yi > y) {
for (i = 0; i < n; ++i) {
res *= y;
y++;
}
}
if (fact)
res = fact / res;
return res;
};
// Beta function
betafn = function betafn(x,y)
{
// ensure arguments are positive
if (x <= 0 || y <= 0)
return undefined;
// make sure x + y doesn't exceed the upper limit of usable values
return (x + y > 170)
? Math.exp(betaln(x, y))
: gammafn(x) * gammafn(y) / gammafn(x + y);
};
// Natural logarithm of Beta function
betaln = function betaln(x, y)
{
return gammaln(x) + gammaln(y) - gammaln(x + y);
};
$(function() {
$("#slider-pertmin").slider({
range: "min",
value: 1,
min: 1,
max: 20,
step: 0.1,
slide: function(event,ui) {
if (ui.value > $("#slider-pertmode").slider("value")) {
$("#pertmin").val($("#slider-pertmode").slider("value") - 0.1);
$("#slider-pertmin").slider("value",$("#slider-pertmode").slider("value") - 0.1)
} else
$("#pertmin").val(ui.value);
redraw();
},
change: function(event,ui) {
$("#slider-pertmin").slider("value",$("#pertmin").val());
}
});
$("#slider-pertmode").slider({
range: "min",
value: 3,
min: 1,
max: 20,
step: 0.1,
slide: function(event,ui) {
if (ui.value < $("#slider-pertmin").slider("value")) {
$("#pertmode").val($("#slider-pertmin").slider("value") + 0.1);
$("#slider-pertmode").slider("value",$("#slider-pertmin").slider("value") + 0.1);
} else if (ui.value > $("#slider-pertmax").slider("value")) {
$("#pertmode").val($("#slider-pertmax").slider("value") - 0.1);
$("#slider-pertmode").slider("value",$("#slider-pertmax").slider("value") - 0.1);
} else
$("#pertmode").val(ui.value);
redraw();
},
change: function(event,ui) {
$("#slider-pertmode").slider("value",$("#pertmode").val());
}
});
$("#slider-pertmax").slider({
range: "min",
value: 6,
min: 1,
max: 20,
step: 0.1,
slide: function(event,ui) {
if (ui.value < $("#slider-pertmode").slider("value")) {
$("#pertmax").val($("#slider-pertmode").slider("value") + 0.1);
$("#slider-pertmax").slider("value",$("#slider-pertmode").slider("value") + 0.1);
} else
$("#pertmax").val(ui.value);
redraw();
},
change: function(event,ui) {
$("#slider-pertmax").slider("value",$("#pertmax").val());
}
});
$("#pertmin").val($("#slider-pertmin").slider("value"));
$("#pertmode").val($("#slider-pertmode").slider("value"));
$("#pertmax").val($("#slider-pertmax").slider("value"));
});
</script>
</head>
<body>
<div id="plot"></div>
<div id="plotOptions" class="ui-widget" style="width:250px">
<div class="block">
<label for="pertmin">min:</label> <input type="value" id="pertmin" style="border:none; background: transparent; width:40px;" />
<div class="ui-slider" id="slider-pertmin" />
</div>
<div class="block">
<label for="pertmode">mode:</label> <input type="value" id="pertmode" style="border:none; background: transparent; width:40px;" />
<div class="ui-slider" id="slider-pertmode" />
</div>
<div class="block">
<label for="pertmax">max:</label> <input type="value" id="pertmax" style="border:none; background: transparent; width:40px;" />
<div class="ui-slider" id="slider-pertmax" />
</div>
</div>
<script type="text/javascript">
var pert = {
min: 2,
mode: 4,
max: 9
};
pert.mu = (pert.min + 4*pert.mode + pert.max)/6;
pert.a1 = 6*(pert.mu - pert.min)/(pert.max - pert.min);
pert.a2 = 6*(pert.max - pert.mu)/(pert.max - pert.min);
pert.beta = betafn(pert.a1,pert.a2);
var data = [];
for (var k = pert.min; k < pert.max; k += 0.1) {
data.push({
x: k,
y: (1/pert.beta)*(Math.pow(k - pert.min,pert.a1 - 1) *
Math.pow(pert.max - k,pert.a2 - 1)) /
Math.pow(pert.max - pert.min,pert.a1 + pert.a2 - 1)
});
}
var margin = {
top: 20,
right: 20,
bottom: 35,
left: 50
},
width = 250 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([pert.min, pert.max])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
var xAxis1 = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.innerTickSize(-6)
.outerTickSize(0)
.tickPadding(7);
var yAxis1 = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.innerTickSize(-6)
.outerTickSize(0)
.tickPadding(7);
var xAxis2 = d3.svg.axis()
.scale(x)
.orient("top")
.ticks(5)
.innerTickSize(-6)
.tickPadding(-20)
.outerTickSize(0)
.tickFormat("");
var yAxis2 = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.innerTickSize(6)
.tickPadding(-20)
.outerTickSize(0)
.tickFormat("");
var xGrid = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height, -height, 0)
.tickFormat("");
var yGrid = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickSize(-width, -width, 0)
.tickFormat("");
var line = d3.svg.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
})
.interpolate("linear");
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 1])
.on("zoom",redraw);
var svg = d3.select("#plot").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
// Add x grid
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + height + ")")
.call(xGrid);
// Add y grid
svg.append("g")
.attr("class", "y grid")
.call(yGrid);
svg.append("g")
.attr("class", "x1 axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis1);
svg.append("g")
.attr("class", "y1 axis")
.call(yAxis1);
/* append additional X axis */
svg.append("g")
.attr("class", "x2 axis")
.attr("transform", "translate(" + [0, 0] + ")")
.call(xAxis2);
/* append additional y axis */
svg.append("g")
.attr("class", "y2 axis")
.attr("transform", "translate(" + [width, 0] + ")")
.call(yAxis2);
// Add x axis label
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + margin.bottom) + ")")
.style("font-size","15")
.style("text-anchor", "middle")
.text("x axis");
// Add y axis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y",0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("font-size","15")
.style("text-anchor", "middle")
.text("y axis");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("path")
.attr("class", "line")
function redraw() {
pert.min = $("#slider-pertmin").slider("value");
pert.mode = $("#slider-pertmode").slider("value");
pert.max = $("#slider-pertmax").slider("value");
pert.mu = (pert.min + 4*pert.mode + pert.max)/6;
pert.a1 = 6*(pert.mu - pert.min)/(pert.max - pert.min);
pert.a2 = 6*(pert.max - pert.mu)/(pert.max - pert.min);
pert.beta = betafn(pert.a1,pert.a2);
x.domain([pert.min, pert.max]);
svg.select(".x1.axis").call(xAxis1);
svg.select(".y1.axis").call(yAxis1);
svg.select(".x2.axis").call(xAxis2);
svg.select(".y2.axis").call(yAxis2);
svg.select(".x.grid").call(xGrid);
svg.select(".y.grid").call(yGrid);
var data = [];
for (var k = pert.min; k < pert.max; k += 0.1) {
data.push({
x: k,
y: (1/pert.beta)*(Math.pow(k - pert.min,pert.a1 - 1) *
Math.pow(pert.max - k,pert.a2 - 1)) /
Math.pow(pert.max - pert.min,pert.a1 + pert.a2 - 1)
});
}
d3.select(".line").attr("d",line(data));
}
redraw();
</script>
</body>
</html>
1.) On your UI question, I would use a range slider to represent my min/max. I'd then get a bit fancy and update the min/max (and width) of my mode slider to force it always between the min/max.
2.) You are calling your initial redraw before the sliders have been initialized. This is because slider initialization is done inside the document.ready event while your initial redraw is not. A simple fix is:
$(function() {
redraw();
});
To expand on my answer a bit, here's a rough example of how I would implement your first question. Note, I swapped out the jquery slider with a d3 one based on this example. I always look for a d3 based answer.
<!DOCTYPE html>
<html lang="en">
<head>
<title>MPERT</title>
<meta charset="utf-8">
<script src="//d3js.org/d3.v3.min.js"></script>
<style type="text/css">
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
rect {
fill: transparent;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
clip-path: url(#clip);
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
circle {
-webkit-transition: fill-opacity 250ms linear;
}
.selecting circle {
fill-opacity: .2;
}
.selecting circle.selected {
stroke: #f00;
}
.resize path {
fill: #666;
fill-opacity: .8;
stroke: #000;
stroke-width: 1.5px;
}
.brush .extent {
fill-opacity: .125;
shape-rendering: crispEdges;
}
#plot {
background-color: #f9f9f9;
border: solid 1px #ddd;
padding: 10px;
width: 250px;
}
#plotOptions {
background-color: #f9f9f9;
border: solid 1px #ddd;
padding: 10px;
}
.block label {
display: inline-block;
width: 60px;
text-align: right;
}
</style>
<script type="text/javascript">
// Log-gamma function
gammaln = function gammaln(x) {
var cof = [76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5];
var j = 0;
var ser = 1.000000000190015;
var xx, y, tmp;
tmp = (y = xx = x) + 5.5;
tmp -= (xx + 0.5) * Math.log(tmp);
for (; j < 6; j++)
ser += cof[j] / ++y;
return Math.log(2.5066282746310005 * ser / xx) - tmp;
};
// Gamma function
gammafn = function gammafn(x) {
var p = [-1.716185138865495, 24.76565080557592, -379.80425647094563,
629.3311553128184, 866.9662027904133, -31451.272968848367, -36144.413418691176, 66456.14382024054
];
var q = [-30.8402300119739, 315.35062697960416, -1015.1563674902192, -3107.771671572311, 22538.118420980151, 4755.8462775278811, -134659.9598649693, -115132.2596755535];
var fact = false,
n = 0,
xden = 0,
xnum = 0,
y = x,
i,
z,
yi,
res,
sum,
ysq;
if (y <= 0) {
res = y % 1 + 3.6e-16;
if (res) {
fact = (!(y & 1) ? 1 : -1) * Math.PI / Math.sin(Math.PI * res);
y = 1 - y;
} else
return Infinity;
}
yi = y;
if (y < 1)
z = y++;
else
z = (y -= n = (y | 0) - 1) - 1;
for (i = 0; i < 8; ++i) {
xnum = (xnum + p[i]) * z;
xden = xden * z + q[i];
}
res = xnum / xden + 1;
if (yi < y)
res /= yi;
else if (yi > y) {
for (i = 0; i < n; ++i) {
res *= y;
y++;
}
}
if (fact)
res = fact / res;
return res;
};
// Beta function
betafn = function betafn(x, y) {
// ensure arguments are positive
if (x <= 0 || y <= 0)
return undefined;
// make sure x + y doesn't exceed the upper limit of usable values
return (x + y > 170) ? Math.exp(betaln(x, y)) : gammafn(x) * gammafn(y) / gammafn(x + y);
};
// Natural logarithm of Beta function
betaln = function betaln(x, y) {
return gammaln(x) + gammaln(y) - gammaln(x + y);
};
</script>
</head>
<body>
<div id="plot"></div>
<div id="plotOptions" class="ui-widget" style="width:250px">
</div>
<script>
var margin = {top: 5, right: 20, bottom: 5, left: 20},
width = 250 - margin.left - margin.right,
height = 50 - margin.top - margin.bottom
min = 0, max = 20;
var brushX = d3.scale.linear()
.range([0, width])
.domain([min, max]);
var brush = d3.svg.brush()
.x(brushX)
.extent([0, 20])
.on("brush", brushmove);
var arc = d3.svg.arc()
.outerRadius(height / 3)
.startAngle(0)
.endAngle(function(d, i) { return i ? -Math.PI : Math.PI; });
var svg = d3.select("#plotOptions").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height/2 + ")")
.call(d3.svg.axis().scale(brushX).orient("bottom"));
var brushg = svg.append("g")
.attr("class", "brush")
.call(brush);
brushg.selectAll(".resize").append("path")
.attr("transform", "translate(0," + height / 2 + ")")
.attr("d", arc);
brushg.selectAll("rect")
.attr("height", height);
var mode = 10;
var modeRect = svg.append("rect")
.attr("height", height)
.attr("width", 10)
.attr("y", 0)
.attr("x", brushX(mode) - 5)
.style("fill", "steelblue")
.style("opacity", 0.5);
var drag = d3.behavior.drag()
.on("drag", modeDrag);
modeRect.call(drag);
function modeDrag(modeX){
var xPos = d3.mouse(this)[0],
tmp = brushX.invert(xPos);
if (tmp > min && tmp < max){
mode = tmp;
modeRect
.attr('x', xPos);
redraw();
}
}
function brushmove() {
console.log(d3.event)
var extent = brush.extent();
min = extent[0];
max = extent[1];
if (min >= mode){
mode = min;
modeRect.attr('x', brushX(mode) - 5);
} else if (max <= mode){
mode = max;
modeRect.attr('x', brushX(mode) - 5);
}
redraw();
}
</script>
<script type="text/javascript">
var pert = {
min: 2,
mode: 4,
max: 9
};
pert.mu = (pert.min + 4 * pert.mode + pert.max) / 6;
pert.a1 = 6 * (pert.mu - pert.min) / (pert.max - pert.min);
pert.a2 = 6 * (pert.max - pert.mu) / (pert.max - pert.min);
pert.beta = betafn(pert.a1, pert.a2);
var data = [];
for (var k = pert.min; k < pert.max; k += 0.1) {
data.push({
x: k,
y: (1 / pert.beta) * (Math.pow(k - pert.min, pert.a1 - 1) *
Math.pow(pert.max - k, pert.a2 - 1)) /
Math.pow(pert.max - pert.min, pert.a1 + pert.a2 - 1)
});
}
var margin = {
top: 20,
right: 20,
bottom: 35,
left: 50
},
width = 250 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([pert.min, pert.max])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
var xAxis1 = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.innerTickSize(-6)
.outerTickSize(0)
.tickPadding(7);
var yAxis1 = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.innerTickSize(-6)
.outerTickSize(0)
.tickPadding(7);
var xAxis2 = d3.svg.axis()
.scale(x)
.orient("top")
.ticks(5)
.innerTickSize(-6)
.tickPadding(-20)
.outerTickSize(0)
.tickFormat("");
var yAxis2 = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.innerTickSize(6)
.tickPadding(-20)
.outerTickSize(0)
.tickFormat("");
var xGrid = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height, -height, 0)
.tickFormat("");
var yGrid = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickSize(-width, -width, 0)
.tickFormat("");
var line = d3.svg.line()
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
})
.interpolate("linear");
var zoom = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([1, 1])
.on("zoom", redraw);
var svg = d3.select("#plot").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
// Add x grid
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + height + ")")
.call(xGrid);
// Add y grid
svg.append("g")
.attr("class", "y grid")
.call(yGrid);
svg.append("g")
.attr("class", "x1 axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis1);
svg.append("g")
.attr("class", "y1 axis")
.call(yAxis1);
/* append additional X axis */
svg.append("g")
.attr("class", "x2 axis")
.attr("transform", "translate(" + [0, 0] + ")")
.call(xAxis2);
/* append additional y axis */
svg.append("g")
.attr("class", "y2 axis")
.attr("transform", "translate(" + [width, 0] + ")")
.call(yAxis2);
// Add x axis label
svg.append("text")
.attr("transform", "translate(" + (width / 2) + "," + (height + margin.bottom) + ")")
.style("font-size", "15")
.style("text-anchor", "middle")
.text("x axis");
// Add y axis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("font-size", "15")
.style("text-anchor", "middle")
.text("y axis");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("path")
.attr("class", "line")
function redraw() {
pert.min = min - 0.001;
pert.mode = mode;
pert.max = max + 0.001;
pert.mu = (pert.min + 4 * pert.mode + pert.max) / 6;
pert.a1 = 6 * (pert.mu - pert.min) / (pert.max - pert.min);
pert.a2 = 6 * (pert.max - pert.mu) / (pert.max - pert.min);
pert.beta = betafn(pert.a1, pert.a2);
x.domain([pert.min, pert.max]);
svg.select(".x1.axis").call(xAxis1);
svg.select(".y1.axis").call(yAxis1);
svg.select(".x2.axis").call(xAxis2);
svg.select(".y2.axis").call(yAxis2);
svg.select(".x.grid").call(xGrid);
svg.select(".y.grid").call(yGrid);
var data = [];
for (var k = pert.min; k < pert.max; k += 0.1) {
data.push({
x: k,
y: (1 / pert.beta) * (Math.pow(k - pert.min, pert.a1 - 1) *
Math.pow(pert.max - k, pert.a2 - 1)) /
Math.pow(pert.max - pert.min, pert.a1 + pert.a2 - 1)
});
}
d3.select(".line").attr("d", line(data));
}
redraw();
</script>
</body>
</html>