var SplineEdit = function (elTarget, colour, duration, constraints, name) {
var self = this;
var targetName = "#" + elTarget.attr("id");
var svg;
var path;
var line = d3.line();
var yStep;
var selected;
var dragged;
var yScale = [constraints[0], constraints[1]];
var region = [0, 1];
this.changed = false;
Object.defineProperty(this, "width", {
get: function() { return parseFloat(elTarget.width()); },
set: function(x) {}
});
Object.defineProperty(this, "height", {
get: function() { return parseFloat(elTarget.height()); },
set: function(x) {}
});
if (constraints[3] > 0.0001) {
yStep = self.height / ((constraints[1] - constraints[0]) / constraints[3]);
}
var dfaultPos = ((constraints[2] - constraints[0]) / (constraints[1] - constraints[0]));
var rawPoints = [[0, dfaultPos], [1, dfaultPos]];
var pointRange = [0, 0];
var points = [...rawPoints];
var circlePoints = [];
this.getRawPoints = function() {
return rawPoints;
};
this.setRawPoints = function(p, noredraw) {
rawPoints = p;
if (!noredraw) self.redraw();
};
Object.defineProperty(this, "displayPoints", {
get: function() {
var output = [];
var x;
for (let p of points) {
output.push([p[0] * self.width, (1 - p[1]) * self.height]);
}
return output;
},
set: function(x) {}
});
this.resize = function(ratio, noredraw) {
for (var i in points) {
if (i != 0 && i != rawPoints.length - 1) {
rawPoints[i][0] = rawPoints[i][0] * ratio;
}
}
if (!noredraw) self.redraw();
};
function regionToAbsolute(xVal) {
return (xVal - region[0]) / (region[1] - region[0]);
}
function absoluteToRegion(xVal) {
return (xVal * (region[1] - region[0])) + region[0];
}
function interpolateRange(scaled, start, end) {
if (!start) start = region[0];
if (!end) end = region[1];
var scaling = 1 - (end - start);
var output = [];
var lastIndex;
var firstIndex = 0;
var lastIndex;
var yVal;
var xVal;
var allPoints = rawPoints;
if (start == 0 && end == 1) {
return {points: allPoints, indexes: [0, allPoints.length - 1]};
}
for (var i in allPoints) {
var x = allPoints[i][0];
if (x < start) {
firstIndex = parseInt(i);
}
}
for (var i = parseInt(allPoints.length - 1); i > firstIndex; i--) {
var x = allPoints[i][0];
if (x >= end) {
lastIndex = i;
}
}
for (var i = firstIndex; i <= lastIndex; i++) {
var v = allPoints[i];
xVal = (i == lastIndex) ? end : v[0];
if (i == firstIndex && v[0] != start) {
var next = allPoints[parseInt(i + 1)];
//yVal = v[1] + (start - v[0]) * (next[1] - v[1]) / (next[0] - v[0]);
yVal = v[1] + (v[0] - start) * (next[1] - v[1]) / (next[0] - v[0]);
} else if (i == lastIndex && v[0] != end) {
var last = allPoints[parseInt(i - 1)];
yVal = last[1] + (end - last[0]) * (v[1] - last[1]) / (v[0] - last[0]);
} else {
yVal = v[1];
}
if (scaled) xVal = regionToAbsolute(xVal);
output.push([xVal, yVal]);
}
return {points: output, indexes: [firstIndex, lastIndex]};
}
function getIndex(point) {
for (var i = 0; i < points.length; i ++) {
if (points[i][0] == point[0] && points[i][1] == point[1]) {
return i;
}
}
}
function getDuration() {
if (typeof(duration) == "function") {
return duration();
} else {
return duration;
}
}
var redrawing = false;
this.redraw = function() {
if (redrawing) return;
redrawing = true;
build();
redraw();
redrawing = false;
};
function setRangePoints() {
/*
if (points.length == rawPoints.length) {
//rawPoints.length = 0;
for (var i in points) {
//rawPoints[i] = [regionToAbsolute(points[i][0]), points[i][1]];
}
}*/
var res = interpolateRange(true);
pointRange = res.indexes;
points.length = 0;
circlePoints.length = 0;
for (let i in res.points) {
points.push(res.points[i]);
if ((region[0] == 0 && region[1] == 1)
|| ((i != 0 || region[0] == 0) && (i != res.points.length - 1 || region[1] == 1)))
{
circlePoints.push(res.points[i]);
}
}
}
this.setRange = function(start, end) {
if (start != null) region[0] = start;
if (end != null) region[1] = end;
self.redraw();
};
this.setYScale = function(min, max) { // TODO unused
if (!min && !max) {
yScale = [constraints[0], constraints[1]];
}
};
this.setDuration = function(val) {
duration = val;
};
this.setConstraints = function(val) {
constraints = val;
}
function calculateValue(xy, asDuration) {
var x = xy[0];
if (asDuration) {
x *= getDuration();
}
var y = ((constraints[1] - constraints[0]) * xy[1]) + constraints[0];
return [x, y];
}
this.getData = function() {
var output = [];
var val;
var allPoints = rawPoints;
for (var p of allPoints) {
val = calculateValue(p, true);
output.push([Math.round(val[0] * 1000) / 1000, Math.round(val[1] * 1000) / 1000]);
}
return output;
};
this.getLinsegData = function(start, end, nullOnEmpty) {
if (nullOnEmpty && rawPoints.length == 2) return;
var duration = getDuration(); //(end - start) * getDuration(); // IF we are having dynamic area view
var rounding = 10000;
var output = [];
var lastTime = 0;
var time;
var lastIndex;
var firstIndex;
var lastIndex;
var allPoints = rawPoints;
for (var i in allPoints) {
var x = allPoints[i][0];
if (x <= start) {
firstIndex = parseInt(i);
}
}
for (var i = parseInt(allPoints.length - 1); i > firstIndex; i--) {
var x = allPoints[i][0];
if (x >= end) {
lastIndex = i;
}
}
for (var i = firstIndex; i <= lastIndex; i++) {
var v = calculateValue(allPoints[i], false);
if (i != firstIndex) {
time = (((i == lastIndex) ? end : v[0]) - start) * duration;
output.push((Math.round((time - lastTime) * rounding)) / rounding);
lastTime = time;
}
if (i == firstIndex && v[0] != start) {
var next = calculateValue(allPoints[parseInt(i + 1)], false);
//var interpVal = v[1] + (v[0] - start) * (next[1] - v[1]) / (next[0] - v[0]);
var interpVal = v[1] + (start - v[0]) * (next[1] - v[1]) / (next[0] - v[0]);
output.push(Math.round(interpVal * rounding) / rounding);
} else if (i == lastIndex && v[0] != end) {
var last = calculateValue(allPoints[parseInt(i - 1)], false);
var interpVal = last[1] + (end - last[0]) * (v[1] - last[1]) / (v[0] - last[0]);
//var interpVal = last[1] + (last[0] - end) * (v[1] - last[1]) / (v[0] - last[0]);
output.push(Math.round(interpVal * rounding) / rounding);
} else {
output.push(Math.round(v[1] * rounding) / rounding);
}
}
return output.join(",");
}
function getLabel(d) {
var val = calculateValue([absoluteToRegion(d[0]), d[1]], true);
var label = (Math.round(val[0] * 100) / 100) + "s
" + (Math.round(val[1] * 1000) / 1000);
if (name) {
label = name + "
" + label;
}
return label;
}
function redraw() {
setRangePoints();
path.datum(self.displayPoints);
svg.select("path").attr("d", line);
const circle = svg.selectAll("g").data(circlePoints, function(point){
return [point[0] * self.width, (1 - point[1]) * self.height];
});
circle.enter().append("g").call(
g => g.append("circle").attr("r", 0).attr("stroke", colour).attr("stroke-width", 1).attr("r", 7)
).merge(circle).attr("transform", function(d){
return "translate(" + (d[0] * self.width) + "," + ((1 - d[1]) * self.height) + ")";
}).select("circle:last-child").attr("fill", function(d) {
if (selected && d[0] == selected[0] && d[1] == selected[1]) {
return "#000000";
} else {
return colour;
}
}).on("mouseover", function(event, d) {
twirl.tooltip.show(event, getLabel(d), colour);
}).on("mouseout", function(event) {
twirl.tooltip.hide();
}).on("dblclick", function(event, d) {
return; // TODO: not working properly particularly when zoomed
if (!window.twirl) return;
var index = getIndex(d);
var duration = getDuration();
console.log(d, regionToAbsolute(d[0]));
var minTime = ((points[index - 1]) ? absoluteToRegion(points[index - 1] + 0.00001) : 0) * duration;
var maxTime = ((points[index + 1]) ? absoluteToRegion(points[index + 1] - 0.00001) : 1) * duration;
var el = $("