在 Javascript 中简化线条的缩放
Easing the scaling of a line in Javascript
我正在尝试在 Javascript 中的起点和终点之间沿 X 中的点积缩放一条线,从 0 到 1 不等。我正在寻找类似红线的东西图片:
我可以使用线性刻度,但我不确定如何缓和刻度。我尝试将缓动应用于线性点,然后从线性量中减去该量,但这是不对的:缓动点超过了起点。 运行 下面的代码片段应该可以说明这个问题。谁能帮我解决这个问题?我将不胜感激。
function magnitude(p) {
return Math.sqrt(p.x ** 2 + p.y ** 2);
}
function normalize(p) {
let m = magnitude(p);
if (m > 0) {
return { x: p.x / m, y: p.y / m };
}
}
function dot(pt1, pt2) {
return pt1.x * pt2.x + pt1.y * pt2.y;
}
//ease functions
function easeInQuad(n) {
return n ** 2;
}
function easeOutQuad(n) {
return -n * (n - 2);
}
function render_line_graph() {
let points = [
{ x: 2, y: 2 },
{ x: 3, y: 1 },
{ x: 4, y: 4 },
{ x: 5, y: 3 },
{ x: 6, y: 6 },
{ x: 7, y: 5 },
{ x: 8, y: 6 }
];
let scale_amount = 1-slider.value/100;
//first & last points x,y respectively
let x1 = points[0].x;
let y1 = points[0].y;
let x2 = points.slice(-1)[0].x;
let y2 = points.slice(-1)[0].y;
//distance between the first & last points' coordinates
let x_dist = x2 - x1;
let y_dist = y2 - y1;
let vec = { x: x2 - x1, y: y2 - y1 };
let line = normalize(vec);
let normal_data = [];
let linear_data = [];
let ease_data = [];
let chart = null;
for (let i = 0; i < points.length; i++) {
let p = points[i];
let dot_product = dot({ x: p.x - x1, y: p.y - y1 }, line);
if (normal_check.checked)
{
normal_data.push({ x: p.x, y: p.y });
}
if (linear_check.checked)
{
let linear_x = p.x - dot_product * scale_amount * line.x;
let linear_y = p.y - dot_product * scale_amount * line.y;
linear_data.push({ x: linear_x, y: linear_y });
}
if (ease_check.checked)
{
let alpha = dot({ x: p.x - x1, y: p.y - y1 }, line) / magnitude(vec);
let alpha_eased = 1 - easeInQuad(1 - alpha);
let ease_x = p.x - alpha_eased * scale_amount * vec.x;
let ease_y = p.y - alpha_eased * scale_amount * vec.y;
ease_data.push({ x: ease_x, y: ease_y });
}
}
chart = new CanvasJS.Chart("chartContainer", {
animationEnabled: false,
theme: "light2",
title: {
text: "Scaling a Line with Ease"
},
data: [
{
type: "line",
color: "blue",
lineDashType: "dash",
indexLabelFontSize: 16,
dataPoints: [points[0],points.slice(-1)[0]]
},
{
type: "line",
color: "blue",
indexLabelFontSize: 16,
dataPoints: normal_data
},
{
type: "line",
color: "green",
indexLabelFontSize: 16,
dataPoints: linear_data
},
{
type: "line",
color: "red",
indexLabelFontSize: 16,
dataPoints: ease_data
},
]
});
chart.render();
}
var slider = document.getElementById("myRange");
var output = document.getElementById("demo");
var normal_check = document.getElementById("normal");
var linear_check = document.getElementById("linear");
var ease_check = document.getElementById("ease");
output.innerHTML = slider.value + "%"; // Display the default slider value
render_line_graph();
slider.oninput = function () {
scale_amount = this.value / 100;
output.innerHTML = this.value + "%";
render_line_graph();
};
normal_check.oninput = function () {
render_line_graph();
};
linear_check.oninput = function () {
render_line_graph();
};
ease_check.oninput = function () {
render_line_graph();
};
html
{
font-family: sans-serif;
}
#controls
{
display: flex;
flex-direction: column;
border: 1px solid #ddd;
padding: 20px;
width: fit-content;
position: absolute;
top: 0;
right: 0;
}
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
<div id="chartContainer" style="height: 180px; width: 320px;"></div>
<div id="controls">
<div><input type="checkbox" id="normal" name="normal" value="True" checked>
<label for="normal"> Normal Line</label>
</div>
<div><input type="checkbox" id="linear" name="linear" value="True" checked>
<label for="linear"> Linear Scale</label>
</div>
<div><input type="checkbox" id="ease" name="ease" value="True" checked>
<label for="ease"> Scale with Ease</label>
</div>
<div class="slidecontainer">
<input type="range" min="0" max="100" value="50" class="slider" id="myRange">
<p>Scale: <span id="demo"></span></p>
</div>
</div>
让我们考虑一下这段代码应该实现的目标:我们并不是真正地“缩放”,而是“重新定位点,平行于趋势线”。虽然我们可以将其视为缩放 x
坐标,但我们绝对不会缩放 y
:相反,我们希望保留趋势线上方(或下方)点的高度,所以让我们把它写下来.
首先,我们捕捉趋势线数据:
const first = points[0];
const last = points.slice(-1)[0];
const d = {
x: last.x - first.x,
y: last.y - first.y
};
有了这些信息,我们现在可以将每个点表示为不是位于某个值 x
,而是位于某个值 start.x + r * d.x
,其中 r
的第一个坐标为 0,最后一个坐标为 1:
function getXratio(p) {
let cx = p.x - first.x;
return cx / d.x;
}
现在我们可以用这些比率来表示坐标:每个点都有一个 x
坐标,我们可以表示为 first.x + ratio * d.x
,还有一个 y
坐标,我们可以表示作为“x
处的趋势线高度加上趋势线上方或下方的一些高度偏移 h
”:first.y + ratio * d.y + h
.
现在是诀窍:如果我们想要像您展示的那样进行缩放,我们可以引入我们的缩放因子,我们称之为 factor
,如:
function rewritePoint(p, factor) {
let ratio = getXratio(p);
// scale x "as normal"
let x = first.x + factor * d.x;
// don't scale y, but _reposition_ it along the trend line instead.
let h = p.y - (first.y + ratio * d.y);
let y = first.y + factor * ratio * d.y + h;
return { x, y };
}
也就是我们保留高度above/below趋势线h
,我们根据缩放 x
坐标。
如果我们只是这样做,我们最终会得到简单的线性缩放。所以让我们添加一个映射函数,让 rewritePoint
与 ratio
值混淆:
function rewritePoint(p, factor, mapRatio) {
let ratio = getXratio(p);
// scale x "as normal", corrected for mapping
let x = first.x + factor * mapRatio(ratio) * d.x;
// reposition y along the trend line, using height
// above/below the trend at the original x position,
// adjusted for the mapped x position.
let t = first.y + ratio * d.y;
let h = p.y - t;
let y = first.y + h + factor * mapRatio(ratio) * d.y;
return { x, y };
}
function linear(p, factor) {
return rewritePoint(p, factor, v => v);
}
const easingFactor = 3;
function easeIn(p, factor) {
return rewritePoint(p, factor, v => v ** (1/easingFactor));
}
function easeOut(p, factor) {
return rewritePoint(p, factor, v => v ** easingFactor);
}
当然有一些“更好”的公式可用于缓动,但这些都是您在理解要点时所能获得的基本知识。因此,将所有内容放在一起,蓝色为纯数据,红色为线性压缩,绿色为缓和,紫色为缓和:
const points = [
{ x: 2, y: 2 },
{ x: 3, y: 1 },
{ x: 4, y: 4 },
{ x: 5, y: 3 },
{ x: 6, y: 6 },
{ x: 7, y: 5 },
{ x: 8, y: 6 }
];
const first = points[0];
const last = points.slice(-1)[0];
const d = {
x: last.x - first.x,
y: last.y - first.y
};
function getXratio(p) {
let cx = p.x - first.x;
return cx / d.x;
}
function rewritePoint(p, factor, mapRatio) {
let ratio = getXratio(p);
// scale x "as normal"
let x = first.x + factor * mapRatio(ratio) * d.x;
// don't scale y, but _reposition_ it along the trend line instead.
let t = first.y + ratio * d.y;
let h = p.y - t;
let y = first.y + h + factor * mapRatio(ratio) * d.y;
return { x, y };
}
function linear(p, factor) {
return rewritePoint(p, factor, v => v);
}
const easingFactor = 2.2;
function easeIn(p, factor) {
return rewritePoint(p, factor, v => v ** (1/easingFactor));
}
function easeOut(p, factor) {
return rewritePoint(p, factor, v => v ** easingFactor);
}
function drawChart() {
let factor = slider.value/100;
let scaled = points.map(p => linear(p, factor));
let easedIn = points.map(p => easeIn(p, factor));
let easedOut = points.map(p => easeOut(p, factor));
chart = new CanvasJS.Chart("chartContainer", {
animationEnabled: false,
theme: "light2",
data: [
{
type: "line",
color: "blue",
lineDashType: "dash",
indexLabelFontSize: 16,
dataPoints: [points[0],points.slice(-1)[0]]
},
{
type: "line",
color: "blue",
indexLabelFontSize: 16,
dataPoints: points
},
{
type: "line",
color: "red",
indexLabelFontSize: 16,
dataPoints: scaled
},
{
type: "line",
color: "green",
indexLabelFontSize: 16,
dataPoints: easedIn
},
{
type: "line",
color: "purple",
indexLabelFontSize: 16,
dataPoints: easedOut
}
]
});
chart.render();
}
slider.addEventListener(`input`, drawChart);
drawChart();
#chartContainer { height: 180px; width: 320px; }
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
<div id="chartContainer"></div>
<input type="range" min="0" max="100" value="100" id="slider">
我正在尝试在 Javascript 中的起点和终点之间沿 X 中的点积缩放一条线,从 0 到 1 不等。我正在寻找类似红线的东西图片:
我可以使用线性刻度,但我不确定如何缓和刻度。我尝试将缓动应用于线性点,然后从线性量中减去该量,但这是不对的:缓动点超过了起点。 运行 下面的代码片段应该可以说明这个问题。谁能帮我解决这个问题?我将不胜感激。
function magnitude(p) {
return Math.sqrt(p.x ** 2 + p.y ** 2);
}
function normalize(p) {
let m = magnitude(p);
if (m > 0) {
return { x: p.x / m, y: p.y / m };
}
}
function dot(pt1, pt2) {
return pt1.x * pt2.x + pt1.y * pt2.y;
}
//ease functions
function easeInQuad(n) {
return n ** 2;
}
function easeOutQuad(n) {
return -n * (n - 2);
}
function render_line_graph() {
let points = [
{ x: 2, y: 2 },
{ x: 3, y: 1 },
{ x: 4, y: 4 },
{ x: 5, y: 3 },
{ x: 6, y: 6 },
{ x: 7, y: 5 },
{ x: 8, y: 6 }
];
let scale_amount = 1-slider.value/100;
//first & last points x,y respectively
let x1 = points[0].x;
let y1 = points[0].y;
let x2 = points.slice(-1)[0].x;
let y2 = points.slice(-1)[0].y;
//distance between the first & last points' coordinates
let x_dist = x2 - x1;
let y_dist = y2 - y1;
let vec = { x: x2 - x1, y: y2 - y1 };
let line = normalize(vec);
let normal_data = [];
let linear_data = [];
let ease_data = [];
let chart = null;
for (let i = 0; i < points.length; i++) {
let p = points[i];
let dot_product = dot({ x: p.x - x1, y: p.y - y1 }, line);
if (normal_check.checked)
{
normal_data.push({ x: p.x, y: p.y });
}
if (linear_check.checked)
{
let linear_x = p.x - dot_product * scale_amount * line.x;
let linear_y = p.y - dot_product * scale_amount * line.y;
linear_data.push({ x: linear_x, y: linear_y });
}
if (ease_check.checked)
{
let alpha = dot({ x: p.x - x1, y: p.y - y1 }, line) / magnitude(vec);
let alpha_eased = 1 - easeInQuad(1 - alpha);
let ease_x = p.x - alpha_eased * scale_amount * vec.x;
let ease_y = p.y - alpha_eased * scale_amount * vec.y;
ease_data.push({ x: ease_x, y: ease_y });
}
}
chart = new CanvasJS.Chart("chartContainer", {
animationEnabled: false,
theme: "light2",
title: {
text: "Scaling a Line with Ease"
},
data: [
{
type: "line",
color: "blue",
lineDashType: "dash",
indexLabelFontSize: 16,
dataPoints: [points[0],points.slice(-1)[0]]
},
{
type: "line",
color: "blue",
indexLabelFontSize: 16,
dataPoints: normal_data
},
{
type: "line",
color: "green",
indexLabelFontSize: 16,
dataPoints: linear_data
},
{
type: "line",
color: "red",
indexLabelFontSize: 16,
dataPoints: ease_data
},
]
});
chart.render();
}
var slider = document.getElementById("myRange");
var output = document.getElementById("demo");
var normal_check = document.getElementById("normal");
var linear_check = document.getElementById("linear");
var ease_check = document.getElementById("ease");
output.innerHTML = slider.value + "%"; // Display the default slider value
render_line_graph();
slider.oninput = function () {
scale_amount = this.value / 100;
output.innerHTML = this.value + "%";
render_line_graph();
};
normal_check.oninput = function () {
render_line_graph();
};
linear_check.oninput = function () {
render_line_graph();
};
ease_check.oninput = function () {
render_line_graph();
};
html
{
font-family: sans-serif;
}
#controls
{
display: flex;
flex-direction: column;
border: 1px solid #ddd;
padding: 20px;
width: fit-content;
position: absolute;
top: 0;
right: 0;
}
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
<div id="chartContainer" style="height: 180px; width: 320px;"></div>
<div id="controls">
<div><input type="checkbox" id="normal" name="normal" value="True" checked>
<label for="normal"> Normal Line</label>
</div>
<div><input type="checkbox" id="linear" name="linear" value="True" checked>
<label for="linear"> Linear Scale</label>
</div>
<div><input type="checkbox" id="ease" name="ease" value="True" checked>
<label for="ease"> Scale with Ease</label>
</div>
<div class="slidecontainer">
<input type="range" min="0" max="100" value="50" class="slider" id="myRange">
<p>Scale: <span id="demo"></span></p>
</div>
</div>
让我们考虑一下这段代码应该实现的目标:我们并不是真正地“缩放”,而是“重新定位点,平行于趋势线”。虽然我们可以将其视为缩放 x
坐标,但我们绝对不会缩放 y
:相反,我们希望保留趋势线上方(或下方)点的高度,所以让我们把它写下来.
首先,我们捕捉趋势线数据:
const first = points[0];
const last = points.slice(-1)[0];
const d = {
x: last.x - first.x,
y: last.y - first.y
};
有了这些信息,我们现在可以将每个点表示为不是位于某个值 x
,而是位于某个值 start.x + r * d.x
,其中 r
的第一个坐标为 0,最后一个坐标为 1:
function getXratio(p) {
let cx = p.x - first.x;
return cx / d.x;
}
现在我们可以用这些比率来表示坐标:每个点都有一个 x
坐标,我们可以表示为 first.x + ratio * d.x
,还有一个 y
坐标,我们可以表示作为“x
处的趋势线高度加上趋势线上方或下方的一些高度偏移 h
”:first.y + ratio * d.y + h
.
现在是诀窍:如果我们想要像您展示的那样进行缩放,我们可以引入我们的缩放因子,我们称之为 factor
,如:
function rewritePoint(p, factor) {
let ratio = getXratio(p);
// scale x "as normal"
let x = first.x + factor * d.x;
// don't scale y, but _reposition_ it along the trend line instead.
let h = p.y - (first.y + ratio * d.y);
let y = first.y + factor * ratio * d.y + h;
return { x, y };
}
也就是我们保留高度above/below趋势线h
,我们根据缩放 x
坐标。
如果我们只是这样做,我们最终会得到简单的线性缩放。所以让我们添加一个映射函数,让 rewritePoint
与 ratio
值混淆:
function rewritePoint(p, factor, mapRatio) {
let ratio = getXratio(p);
// scale x "as normal", corrected for mapping
let x = first.x + factor * mapRatio(ratio) * d.x;
// reposition y along the trend line, using height
// above/below the trend at the original x position,
// adjusted for the mapped x position.
let t = first.y + ratio * d.y;
let h = p.y - t;
let y = first.y + h + factor * mapRatio(ratio) * d.y;
return { x, y };
}
function linear(p, factor) {
return rewritePoint(p, factor, v => v);
}
const easingFactor = 3;
function easeIn(p, factor) {
return rewritePoint(p, factor, v => v ** (1/easingFactor));
}
function easeOut(p, factor) {
return rewritePoint(p, factor, v => v ** easingFactor);
}
当然有一些“更好”的公式可用于缓动,但这些都是您在理解要点时所能获得的基本知识。因此,将所有内容放在一起,蓝色为纯数据,红色为线性压缩,绿色为缓和,紫色为缓和:
const points = [
{ x: 2, y: 2 },
{ x: 3, y: 1 },
{ x: 4, y: 4 },
{ x: 5, y: 3 },
{ x: 6, y: 6 },
{ x: 7, y: 5 },
{ x: 8, y: 6 }
];
const first = points[0];
const last = points.slice(-1)[0];
const d = {
x: last.x - first.x,
y: last.y - first.y
};
function getXratio(p) {
let cx = p.x - first.x;
return cx / d.x;
}
function rewritePoint(p, factor, mapRatio) {
let ratio = getXratio(p);
// scale x "as normal"
let x = first.x + factor * mapRatio(ratio) * d.x;
// don't scale y, but _reposition_ it along the trend line instead.
let t = first.y + ratio * d.y;
let h = p.y - t;
let y = first.y + h + factor * mapRatio(ratio) * d.y;
return { x, y };
}
function linear(p, factor) {
return rewritePoint(p, factor, v => v);
}
const easingFactor = 2.2;
function easeIn(p, factor) {
return rewritePoint(p, factor, v => v ** (1/easingFactor));
}
function easeOut(p, factor) {
return rewritePoint(p, factor, v => v ** easingFactor);
}
function drawChart() {
let factor = slider.value/100;
let scaled = points.map(p => linear(p, factor));
let easedIn = points.map(p => easeIn(p, factor));
let easedOut = points.map(p => easeOut(p, factor));
chart = new CanvasJS.Chart("chartContainer", {
animationEnabled: false,
theme: "light2",
data: [
{
type: "line",
color: "blue",
lineDashType: "dash",
indexLabelFontSize: 16,
dataPoints: [points[0],points.slice(-1)[0]]
},
{
type: "line",
color: "blue",
indexLabelFontSize: 16,
dataPoints: points
},
{
type: "line",
color: "red",
indexLabelFontSize: 16,
dataPoints: scaled
},
{
type: "line",
color: "green",
indexLabelFontSize: 16,
dataPoints: easedIn
},
{
type: "line",
color: "purple",
indexLabelFontSize: 16,
dataPoints: easedOut
}
]
});
chart.render();
}
slider.addEventListener(`input`, drawChart);
drawChart();
#chartContainer { height: 180px; width: 320px; }
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
<div id="chartContainer"></div>
<input type="range" min="0" max="100" value="100" id="slider">