如何同步路径和区域的动画?
How to synchronize animation of path and area?
我被D3.js v4的动画困住了两行 & 面积:
- 线&面积分开做动画就可以了
- 当 2 个动画组合在一起时,即使在相同的过渡持续时间内,它们也不会同时出现。
- 由于样式原因,我不能把线去掉。
见下图:
为了制作上面的东西,我做了 2 个大步骤:
- 通过设置属性
stroke-dasharrow
和 stroke-dashoffset
为线条制作动画。 (灵感来自 Visual Cinnamon)
- 通过调整 d3.area() 函数的参数为区域制作动画(灵感来自 other Stackoverlfow post)
结果有点令人失望,因为下面的线和区域并不平行。
我的目标是模仿 Highchart 库,请参见示例 here,其插图如下:
似乎 Highchart 库使用了不同的动画技术,因为在 DOM 检查期间,DOM 动画路径没有任何变化的迹象。
如果有人能给我一些想法来进行试验,我将不胜感激。
我的代码示例如下:
let animationDuration = 5000;
// set the dimensions and margins of the graph
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 480 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%d-%b-%y");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the area
var area = function (datum, boolean) {
return d3.area()
.y0(height)
.y1(function (d) { return boolean ? y(d.close) : y(d.close); })
.x(function (d) { return boolean ? x(d.date) : 0; })
(datum);
}
// define the line
var valueline = d3.line()
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.close); });
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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 = d3.csvParse(d3.select("pre#data").text());
data.reverse();
// format the data
data.forEach(function (d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
// scale the range of the data
x.domain(d3.extent(data, function (d) { return d.date; }));
y.domain([0, d3.max(data, function (d) { return d.close; })]);
// add the area
svg.append("path")
.data([data])
.attr("class", "area")
.attr("d", d => area(d, false))
.attr("fill", "lightsteelblue")
.transition()
.duration(animationDuration)
.attr("d", d => area(d, true));
// add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline)
.style("stroke-dasharray", d => {
let path = document.querySelector(".line");
const totalLength = path.getTotalLength();
return `${totalLength} ${totalLength}`;
})
.style("stroke-dashoffset", d => {
let path = document.querySelector(".line");
const totalLength = path.getTotalLength();
return `${totalLength}`;
})
.transition()
.duration(animationDuration)
.style("stroke-dashoffset", 0);
// add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
pre#data {display:none;}
<script src="https://d3js.org/d3.v4.min.js"></script>
<pre id="data">
date,close
1-May-12,58.13
30-Apr-12,53.98
27-Apr-12,67.00
26-Apr-12,89.70
25-Apr-12,99.00
24-Apr-12,130.28
23-Apr-12,166.70
20-Apr-12,234.98
19-Apr-12,345.44
18-Apr-12,443.34
17-Apr-12,543.70
16-Apr-12,580.13
13-Apr-12,605.23
12-Apr-12,622.77
11-Apr-12,626.20
10-Apr-12,628.44
9-Apr-12,636.23
5-Apr-12,633.68
4-Apr-12,624.31
3-Apr-12,629.32
2-Apr-12,618.63
30-Mar-12,599.55
29-Mar-12,609.86
28-Mar-12,617.62
27-Mar-12,614.48
26-Mar-12,606.98
</pre>
有一种方法可以使用自定义插值器为线条和区域设置动画。
但是,由于您的目标是模仿 Highcharts animation you linked,因此有一种 更简单的方法 替代方法:使用 <clipPath>
.
在我提出的解决方案中,我们以常规方式创建区域和线。但是,我们引用了一个剪切路径...
.attr("clip-path", "url(#clip)");
...在区域和线条中。剪切路径是用 0
宽度创建的:
var clip = svg.append("clipPath")
.attr("id", "clip");
var clipRect = clip.append("rect")
.attr("width", 0)
然后,之后,只需将过渡应用到剪切路径即可:
clipRect.transition()
.duration(5000)
.ease(d3.easeLinear)
.attr("width", someValue)
这是一个演示:
var svg = d3.select("svg");
var data = d3.range(30).map(d => Math.random() * 150);
var clip = svg.append("clipPath")
.attr("id", "clip");
var clipRect = clip.append("rect")
.attr("width", 0)
.attr("height", 150)
var lineGenerator = d3.line()
.x((_, i) => i * 10)
.y(d => d)
.curve(d3.curveMonotoneX)
var areaGenerator = d3.area()
.x((_, i) => i * 10)
.y1(d => d)
.y0(150)
.curve(d3.curveMonotoneX)
svg.append("path")
.attr("d", areaGenerator(data))
.attr("class", "area")
.attr("clip-path", "url(#clip)");
svg.append("path")
.attr("d", lineGenerator(data))
.attr("class", "line")
.attr("clip-path", "url(#clip)");
clipRect.transition()
.duration(5000)
.ease(d3.easeLinear)
.attr("width", 300)
.line {
fill: none;
stroke: #222;
stroke-width: 2px;
}
.area {
fill: limegreen;
stroke: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
下面是您的代码,其中包含这些更改:
let animationDuration = 5000;
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 480 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%d-%b-%y");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the area
var area = function(datum, boolean) {
return d3.area()
.y0(height)
.y1(function(d) {
return boolean ? y(d.close) : y(d.close);
})
.x(function(d) {
return boolean ? x(d.date) : 0;
})
(datum);
}
// define the line
var valueline = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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 clip = svg.append("clipPath")
.attr("id", "clip");
var clipRect = clip.append("rect")
.attr("width", 0)
.attr("height", height);
var data = d3.csvParse(d3.select("pre#data").text());
data.reverse();
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
// scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.close;
})]);
// add the area
svg.append("path")
.data([data])
.attr("class", "area")
.attr("d", d => area(d, true))
.attr("fill", "lightsteelblue")
.attr("clip-path", "url(#clip)");
// add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline)
.attr("clip-path", "url(#clip)");
// add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
clipRect.transition()
.duration(5000)
.ease(d3.easeLinear)
.attr("width", width)
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
pre#data {
display: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<pre id="data">
date,close
1-May-12,58.13
30-Apr-12,53.98
27-Apr-12,67.00
26-Apr-12,89.70
25-Apr-12,99.00
24-Apr-12,130.28
23-Apr-12,166.70
20-Apr-12,234.98
19-Apr-12,345.44
18-Apr-12,443.34
17-Apr-12,543.70
16-Apr-12,580.13
13-Apr-12,605.23
12-Apr-12,622.77
11-Apr-12,626.20
10-Apr-12,628.44
9-Apr-12,636.23
5-Apr-12,633.68
4-Apr-12,624.31
3-Apr-12,629.32
2-Apr-12,618.63
30-Mar-12,599.55
29-Mar-12,609.86
28-Mar-12,617.62
27-Mar-12,614.48
26-Mar-12,606.98
</pre>
我被D3.js v4的动画困住了两行 & 面积:
- 线&面积分开做动画就可以了
- 当 2 个动画组合在一起时,即使在相同的过渡持续时间内,它们也不会同时出现。
- 由于样式原因,我不能把线去掉。
见下图:
为了制作上面的东西,我做了 2 个大步骤:
- 通过设置属性
stroke-dasharrow
和stroke-dashoffset
为线条制作动画。 (灵感来自 Visual Cinnamon) - 通过调整 d3.area() 函数的参数为区域制作动画(灵感来自 other Stackoverlfow post)
结果有点令人失望,因为下面的线和区域并不平行。
我的目标是模仿 Highchart 库,请参见示例 here,其插图如下:
似乎 Highchart 库使用了不同的动画技术,因为在 DOM 检查期间,DOM 动画路径没有任何变化的迹象。
如果有人能给我一些想法来进行试验,我将不胜感激。
我的代码示例如下:
let animationDuration = 5000;
// set the dimensions and margins of the graph
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 480 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%d-%b-%y");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the area
var area = function (datum, boolean) {
return d3.area()
.y0(height)
.y1(function (d) { return boolean ? y(d.close) : y(d.close); })
.x(function (d) { return boolean ? x(d.date) : 0; })
(datum);
}
// define the line
var valueline = d3.line()
.x(function (d) { return x(d.date); })
.y(function (d) { return y(d.close); });
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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 = d3.csvParse(d3.select("pre#data").text());
data.reverse();
// format the data
data.forEach(function (d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
// scale the range of the data
x.domain(d3.extent(data, function (d) { return d.date; }));
y.domain([0, d3.max(data, function (d) { return d.close; })]);
// add the area
svg.append("path")
.data([data])
.attr("class", "area")
.attr("d", d => area(d, false))
.attr("fill", "lightsteelblue")
.transition()
.duration(animationDuration)
.attr("d", d => area(d, true));
// add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline)
.style("stroke-dasharray", d => {
let path = document.querySelector(".line");
const totalLength = path.getTotalLength();
return `${totalLength} ${totalLength}`;
})
.style("stroke-dashoffset", d => {
let path = document.querySelector(".line");
const totalLength = path.getTotalLength();
return `${totalLength}`;
})
.transition()
.duration(animationDuration)
.style("stroke-dashoffset", 0);
// add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
pre#data {display:none;}
<script src="https://d3js.org/d3.v4.min.js"></script>
<pre id="data">
date,close
1-May-12,58.13
30-Apr-12,53.98
27-Apr-12,67.00
26-Apr-12,89.70
25-Apr-12,99.00
24-Apr-12,130.28
23-Apr-12,166.70
20-Apr-12,234.98
19-Apr-12,345.44
18-Apr-12,443.34
17-Apr-12,543.70
16-Apr-12,580.13
13-Apr-12,605.23
12-Apr-12,622.77
11-Apr-12,626.20
10-Apr-12,628.44
9-Apr-12,636.23
5-Apr-12,633.68
4-Apr-12,624.31
3-Apr-12,629.32
2-Apr-12,618.63
30-Mar-12,599.55
29-Mar-12,609.86
28-Mar-12,617.62
27-Mar-12,614.48
26-Mar-12,606.98
</pre>
有一种方法可以使用自定义插值器为线条和区域设置动画。
但是,由于您的目标是模仿 Highcharts animation you linked,因此有一种 更简单的方法 替代方法:使用 <clipPath>
.
在我提出的解决方案中,我们以常规方式创建区域和线。但是,我们引用了一个剪切路径...
.attr("clip-path", "url(#clip)");
...在区域和线条中。剪切路径是用 0
宽度创建的:
var clip = svg.append("clipPath")
.attr("id", "clip");
var clipRect = clip.append("rect")
.attr("width", 0)
然后,之后,只需将过渡应用到剪切路径即可:
clipRect.transition()
.duration(5000)
.ease(d3.easeLinear)
.attr("width", someValue)
这是一个演示:
var svg = d3.select("svg");
var data = d3.range(30).map(d => Math.random() * 150);
var clip = svg.append("clipPath")
.attr("id", "clip");
var clipRect = clip.append("rect")
.attr("width", 0)
.attr("height", 150)
var lineGenerator = d3.line()
.x((_, i) => i * 10)
.y(d => d)
.curve(d3.curveMonotoneX)
var areaGenerator = d3.area()
.x((_, i) => i * 10)
.y1(d => d)
.y0(150)
.curve(d3.curveMonotoneX)
svg.append("path")
.attr("d", areaGenerator(data))
.attr("class", "area")
.attr("clip-path", "url(#clip)");
svg.append("path")
.attr("d", lineGenerator(data))
.attr("class", "line")
.attr("clip-path", "url(#clip)");
clipRect.transition()
.duration(5000)
.ease(d3.easeLinear)
.attr("width", 300)
.line {
fill: none;
stroke: #222;
stroke-width: 2px;
}
.area {
fill: limegreen;
stroke: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
下面是您的代码,其中包含这些更改:
let animationDuration = 5000;
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 480 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%d-%b-%y");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the area
var area = function(datum, boolean) {
return d3.area()
.y0(height)
.y1(function(d) {
return boolean ? y(d.close) : y(d.close);
})
.x(function(d) {
return boolean ? x(d.date) : 0;
})
(datum);
}
// define the line
var valueline = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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 clip = svg.append("clipPath")
.attr("id", "clip");
var clipRect = clip.append("rect")
.attr("width", 0)
.attr("height", height);
var data = d3.csvParse(d3.select("pre#data").text());
data.reverse();
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
// scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.close;
})]);
// add the area
svg.append("path")
.data([data])
.attr("class", "area")
.attr("d", d => area(d, true))
.attr("fill", "lightsteelblue")
.attr("clip-path", "url(#clip)");
// add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline)
.attr("clip-path", "url(#clip)");
// add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
clipRect.transition()
.duration(5000)
.ease(d3.easeLinear)
.attr("width", width)
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
pre#data {
display: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<pre id="data">
date,close
1-May-12,58.13
30-Apr-12,53.98
27-Apr-12,67.00
26-Apr-12,89.70
25-Apr-12,99.00
24-Apr-12,130.28
23-Apr-12,166.70
20-Apr-12,234.98
19-Apr-12,345.44
18-Apr-12,443.34
17-Apr-12,543.70
16-Apr-12,580.13
13-Apr-12,605.23
12-Apr-12,622.77
11-Apr-12,626.20
10-Apr-12,628.44
9-Apr-12,636.23
5-Apr-12,633.68
4-Apr-12,624.31
3-Apr-12,629.32
2-Apr-12,618.63
30-Mar-12,599.55
29-Mar-12,609.86
28-Mar-12,617.62
27-Mar-12,614.48
26-Mar-12,606.98
</pre>