Reputation: 492
this is my next question to enhance original question.
So what I am trying is to animate triangle mark and text tooltip along the filling path.
var chart = d3.select("#speedometer");
const arc = d3
.arc()
.outerRadius(120)
.innerRadius(90)
.startAngle(-Math.PI / 2);
chart
.append("path")
.datum({
endAngle: Math.PI / 2
})
.attr("transform", "translate(160, 180)")
.attr("class", "background")
.style("fill", "#495270")
.attr("d", arc);
const mainGroup = chart.append('g')
.datum({
endAngle: -Math.PI / 2
});
const triangle = mainGroup
.append('path')
.attr("d", "M3.937,0,7.873,14H0Z")
.datum({
endAngle: -Math.PI / 2
})
const text = mainGroup.append('text')
.text('hello there')
.attr('transform', 'rotate(180)')
.datum({
endAngle: -Math.PI / 2
})
.transition()
.duration(3000)
.attrTween("transform", function(d) {
const topVal = (300 / 2 - 16);
const interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
const angleRadians = interpolate(t);
const angleDegrees = 360 * angleRadians / (2 * Math.PI);
return `
rotate(${angleDegrees})
`;
};
});
const newAngle = (70 / 100) * Math.PI - Math.PI / 2;
const foreground = chart
.append("path")
.datum({
endAngle: -Math.PI / 2
})
.style("fill", "rgb(50, 188, 228)")
.attr("transform", "translate(160, 180)")
.attr("d", arc);
foreground
.transition()
.duration(3000)
.attrTween("d", function(d) {
const interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
d.endAngle = interpolate(t);
return arc(d);
};
});
mainGroup
.transition()
.duration(3000)
.attrTween("transform", function(d) {
const topVal = (300 / 2 - 16);
const interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
const angleRadians = interpolate(t);
const angleDegrees = 360 * angleRadians / (2 * Math.PI);
return `
translate(158 176)
rotate(${angleDegrees + 180} 3.5 7)
translate(0 ${topVal})
`;
};
});
function pathTween(path) {
const length = path.node().getTotalLength(); // Get the length of the path
const r = d3.interpolate(0, length); // Set up interpolation from 0 to the path length
return function(t) {
const point = path.node().getPointAtLength(r(t)); // Get the next point along the path
d3
.select(this) // Select the circle
.attr("transform", `translate(${point.x}, ${point.y})`);
};
}
.main-wrapper {
max-width: 80%;
margin: 20px auto;
}
.element {
display: flex;
flex-flow: column nowrap;
margin-bottom: 20px;
border: 1px solid rgba(0, 0, 0, 0.4);
padding: 20px;
border-radius: 6px;
}
.element .title {
margin-bottom: 4px;
font-weight: 500;
}
.element .description {
margin-bottom: 10px;
color: rgba(0, 0, 0, 0.4);
}
#speedometer {
width: 300px;
height: 300px;
overflow: visible !important;
}
canvas {
width: 100%;
height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div class="main-wrapper">
<section class="ui-section">
<div class="element">
<div class="title">
Speedometr
</div>
<div class="content">
<svg id="speedometer" viewbox="0 0 300 300"></svg>
</div>
</div>
</section>
</div>
So the main problem here is rotating text. It supposes to be in a normal position. And how should animation be in this case? How I can animate the whole group and control each node??
Upvotes: 3
Views: 104
Reputation: 13129
Firstly, you can offset the turning by using 180 - angleDegrees
instead of angleDegrees
for the text.
Secondly, you probably want the text to be at about the same distance from the arc at all times, not too close so it overlaps, or too far away because it looks weird. For this, one solution is to make the node text-anchor: middle
, and then position it during the transition.
var chart = d3.select("#speedometer");
const arc = d3
.arc()
.outerRadius(120)
.innerRadius(90)
.startAngle(-Math.PI / 2);
chart
.append("path")
.datum({
endAngle: Math.PI / 2
})
.attr("transform", "translate(160, 180)")
.attr("class", "background")
.style("fill", "#495270")
.attr("d", arc);
const mainGroup = chart.append('g')
.datum({
endAngle: -Math.PI / 2
});
const triangle = mainGroup
.append('path')
.attr("d", "M3.937,0,7.873,14H0Z")
.datum({
endAngle: -Math.PI / 2
})
const text = mainGroup.append('text')
.text('hello there')
.datum({
endAngle: -Math.PI / 2
})
.attr("text-anchor", "middle") // to more easily position the text
.transition()
.duration(3000)
.attrTween("transform", function(d) {
const topVal = (300 / 2 - 16);
const interpolateAngle = d3.interpolate(d.endAngle, newAngle);
// We want to add some offset so the text is always easily visible
// Think about what happens when the following functions are called with
// angles -90, -45, 0, 45, 90
const textWidth = this.getBBox().width;
const offsetX = function(angle) {
return (angle / 90) * textWidth / 2;
};
const offsetY = function(angle) {
if (angle < 0) {
return offsetY(-angle);
}
// The -4 and -3 are a little bit trial and error, and can be
// tweaked further to
return -4 + (1 - angle / 90) * -3;
};
return function(t) {
const angleRadians = interpolateAngle(t);
const angleDegrees = 360 * angleRadians / (2 * Math.PI);
return `
translate(0 15)
rotate(${180 - angleDegrees})
translate(${offsetX(angleDegrees)} ${offsetY(angleDegrees)})
`;
};
});
const newAngle = (70 / 100) * Math.PI - Math.PI / 2;
const foreground = chart
.append("path")
.datum({
endAngle: -Math.PI / 2
})
.style("fill", "rgb(50, 188, 228)")
.attr("transform", "translate(160, 180)")
.attr("d", arc);
foreground
.transition()
.duration(3000)
.attrTween("d", function(d) {
const interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
d.endAngle = interpolate(t);
return arc(d);
};
});
mainGroup
.transition()
.duration(3000)
.attrTween("transform", function(d) {
const topVal = (300 / 2 - 16);
const interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
const angleRadians = interpolate(t);
const angleDegrees = 360 * angleRadians / (2 * Math.PI);
return `
translate(158 176)
rotate(${angleDegrees + 180} 3.5 7)
translate(0 ${topVal})
`;
};
});
function pathTween(path) {
const length = path.node().getTotalLength(); // Get the length of the path
const r = d3.interpolate(0, length); // Set up interpolation from 0 to the path length
return function(t) {
const point = path.node().getPointAtLength(r(t)); // Get the next point along the path
d3
.select(this) // Select the circle
.attr("transform", `translate(${point.x}, ${point.y})`);
};
}
.main-wrapper {
max-width: 80%;
margin: 20px auto;
}
.element {
display: flex;
flex-flow: column nowrap;
margin-bottom: 20px;
border: 1px solid rgba(0, 0, 0, 0.4);
padding: 20px;
border-radius: 6px;
}
.element .title {
margin-bottom: 4px;
font-weight: 500;
}
.element .description {
margin-bottom: 10px;
color: rgba(0, 0, 0, 0.4);
}
#speedometer {
width: 300px;
height: 300px;
overflow: visible !important;
}
canvas {
width: 100%;
height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div class="main-wrapper">
<section class="ui-section">
<div class="element">
<div class="title">
Speedometr
</div>
<div class="content">
<svg id="speedometer" viewbox="0 0 300 300"></svg>
</div>
</div>
</section>
</div>
Upvotes: 3