Paintcode and Snap SVG - javascript
I am trying to bridge examples from Paintcode and Snap SVG.
Here is a simple project that has the gears that spin when a slider is dragged. This is great but I would like to do the interaction with the mouse instead.
This is an example of the gears as svg:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="200" xml:space="preserve" id="gears">
<!-- Generated by PaintCode - http://www.paintcodeapp.com -->
<path id="gears-largeGear" stroke="none" fill="rgb(69, 131, 212)" d="M 2.78,-67.94 C 3.71,-63.88 4.74,-59.43 5.59,-55.72 7.73,-55.51 9.84,-55.18 11.9,-54.73 13.87,-58 16.21,-61.91 18.36,-65.49 20.15,-64.99 21.91,-64.42 23.64,-63.78 23.27,-59.62 22.87,-55.08 22.54,-51.28 24.5,-50.42 26.4,-49.45 28.23,-48.37 31.11,-50.87 34.55,-53.87 37.69,-56.6 39.24,-55.58 40.73,-54.49 42.18,-53.34 40.55,-49.5 38.77,-45.3 37.28,-41.79 38.87,-40.37 40.37,-38.87 41.79,-37.28 45.3,-38.77 49.5,-40.55 53.34,-42.18 54.49,-40.73 55.58,-39.24 56.6,-37.69 53.87,-34.55 50.87,-31.11 48.37,-28.23 49.45,-26.4 50.42,-24.5 51.28,-22.54 55.08,-22.87 59.62,-23.27 63.78,-23.64 64.42,-21.91 64.99,-20.15 65.49,-18.36 61.91,-16.21 58,-13.87 54.73,-11.9 55.18,-9.84 55.51,-7.73 55.72,-5.59 59.43,-4.74 63.88,-3.71 67.94,-2.78 67.98,-1.86 68,-0.93 68,0 68,0.93 67.98,1.86 67.94,2.78 63.88,3.71 59.43,4.74 55.72,5.59 55.51,7.73 55.18,9.84 54.73,11.9 58,13.87 61.91,16.21 65.49,18.36 64.99,20.15 64.42,21.91 63.78,23.64 59.62,23.27 55.08,22.87 51.28,22.54 50.42,24.5 49.45,26.4 48.37,28.23 50.87,31.11 53.87,34.55 56.6,37.69 55.58,39.24 54.49,40.73 53.34,42.18 49.5,40.55 45.3,38.77 41.79,37.28 40.37,38.87 38.87,40.37 37.28,41.79 38.77,45.3 40.55,49.5 42.18,53.34 40.73,54.49 39.24,55.58 37.69,56.6 34.55,53.87 31.11,50.87 28.23,48.37 26.4,49.45 24.5,50.42 22.54,51.28 22.87,55.08 23.27,59.62 23.64,63.78 21.91,64.42 20.15,64.99 18.36,65.49 16.21,61.91 13.87,58 11.9,54.73 9.84,55.18 7.73,55.51 5.59,55.72 4.74,59.43 3.71,63.88 2.78,67.94 1.86,67.98 0.93,68 -0,68 -0.93,68 -1.86,67.98 -2.78,67.94 -3.71,63.88 -4.74,59.43 -5.59,55.72 -7.73,55.51 -9.84,55.18 -11.9,54.73 -13.87,58 -16.21,61.91 -18.36,65.49 -20.15,64.99 -21.91,64.42 -23.64,63.78 -23.27,59.62 -22.87,55.08 -22.54,51.28 -24.5,50.42 -26.4,49.45 -28.23,48.37 -31.11,50.87 -34.55,53.87 -37.69,56.6 -39.24,55.58 -40.73,54.49 -42.18,53.34 -40.55,49.5 -38.77,45.3 -37.28,41.79 -38.87,40.37 -40.37,38.87 -41.79,37.28 -45.3,38.77 -49.5,40.55 -53.34,42.18 -54.49,40.73 -55.58,39.24 -56.6,37.69 -53.87,34.55 -50.87,31.11 -48.37,28.23 -49.45,26.4 -50.42,24.5 -51.28,22.54 -55.08,22.87 -59.62,23.27 -63.78,23.64 -64.42,21.91 -64.99,20.15 -65.49,18.36 -61.91,16.21 -58,13.87 -54.73,11.9 -55.18,9.84 -55.51,7.73 -55.72,5.59 -59.43,4.74 -63.88,3.71 -67.94,2.78 -67.98,1.86 -68,0.93 -68,-0 -68,-0.93 -67.98,-1.86 -67.94,-2.78 -63.88,-3.71 -59.43,-4.74 -55.72,-5.59 -55.51,-7.73 -55.18,-9.84 -54.73,-11.9 -58,-13.87 -61.91,-16.21 -65.49,-18.36 -64.99,-20.15 -64.42,-21.91 -63.78,-23.64 -59.62,-23.27 -55.08,-22.87 -51.28,-22.54 -50.42,-24.5 -49.45,-26.4 -48.37,-28.23 -50.87,-31.11 -53.87,-34.55 -56.6,-37.69 -55.58,-39.24 -54.49,-40.73 -53.34,-42.18 -49.5,-40.55 -45.3,-38.77 -41.79,-37.28 -40.37,-38.87 -38.87,-40.37 -37.28,-41.79 -38.77,-45.3 -40.55,-49.5 -42.18,-53.34 -41.55,-53.84 -40.91,-54.33 -40.26,-54.81 -39.42,-55.43 -38.56,-56.03 -37.69,-56.6 -34.55,-53.87 -31.11,-50.87 -28.23,-48.37 -26.4,-49.45 -24.5,-50.42 -22.54,-51.28 -22.87,-55.08 -23.27,-59.62 -23.64,-63.78 -21.91,-64.42 -20.15,-64.99 -18.36,-65.49 -16.21,-61.91 -13.87,-58 -11.9,-54.73 -9.84,-55.18 -7.73,-55.51 -5.59,-55.72 -4.74,-59.43 -3.71,-63.88 -2.78,-67.94 -1.93,-67.98 -1.08,-68 -0.23,-68 L 0,-68 C 0.93,-68 1.86,-67.98 2.78,-67.94 Z M 0,-36 C -6.22,-36 -12.07,-34.42 -17.18,-31.65 -28.39,-25.55 -36,-13.66 -36,-0 -36,19.88 -19.88,36 0,36 19.88,36 36,19.88 36,-0 36,-19.88 19.88,-36 0,-36 Z M 0,-36" transform="translate(150, 96) rotate(-0.5)" />
<path id="gears-smallGear" stroke="none" fill="rgb(115, 152, 218)" d="M 2.12,-37.94 L 2.45,-37.92 C 3.29,-34.48 4.2,-30.71 4.97,-27.56 6.23,-27.34 7.46,-27.03 8.65,-26.64 9.87,-26.24 11.04,-25.77 12.18,-25.22 14.65,-27.32 17.61,-29.83 20.31,-32.12 21.69,-31.25 23.02,-30.28 24.27,-29.24 22.92,-25.96 21.45,-22.37 20.22,-19.37 21.97,-17.55 23.47,-15.49 24.68,-13.24 27.91,-13.49 31.78,-13.78 35.32,-14.05 35.92,-12.54 36.42,-10.99 36.83,-9.39 33.81,-7.53 30.5,-5.49 27.75,-3.79 27.91,-2.55 28,-1.29 28,-0 28,1.29 27.91,2.55 27.75,3.79 30.51,5.49 33.81,7.53 36.83,9.39 36.42,10.99 35.92,12.54 35.32,14.05 31.78,13.78 27.91,13.49 24.68,13.24 23.47,15.49 21.97,17.55 20.22,19.37 21.45,22.37 22.92,25.96 24.27,29.24 23.02,30.28 21.69,31.25 20.31,32.12 17.61,29.83 14.65,27.32 12.18,25.22 9.93,26.31 7.51,27.11 4.97,27.56 4.2,30.71 3.29,34.48 2.45,37.92 1.64,37.97 0.82,38 -0,38 -0.82,38 -1.64,37.97 -2.45,37.92 -3.29,34.48 -4.2,30.71 -4.97,27.56 -7.51,27.11 -9.93,26.31 -12.18,25.22 -14.65,27.32 -17.61,29.83 -20.31,32.12 -21.69,31.25 -23.02,30.28 -24.27,29.24 -22.92,25.96 -21.45,22.37 -20.22,19.37 -21.97,17.55 -23.47,15.49 -24.68,13.24 -27.91,13.49 -31.78,13.78 -35.32,14.05 -35.92,12.54 -36.42,10.99 -36.83,9.39 -33.81,7.53 -30.5,5.49 -27.75,3.79 -27.91,2.55 -28,1.29 -28,-0 -28,-1.29 -27.91,-2.55 -27.75,-3.79 -30.51,-5.49 -33.81,-7.53 -36.83,-9.39 -36.42,-10.99 -35.92,-12.54 -35.32,-14.05 -31.78,-13.78 -27.91,-13.49 -24.68,-13.24 -23.47,-15.49 -21.97,-17.55 -20.22,-19.37 -21.45,-22.37 -22.92,-25.96 -24.27,-29.24 -23.02,-30.28 -21.69,-31.25 -20.31,-32.12 -17.61,-29.83 -14.65,-27.32 -12.18,-25.22 -9.93,-26.31 -7.51,-27.11 -4.97,-27.56 -4.2,-30.71 -3.29,-34.48 -2.45,-37.92 -1.64,-37.97 -0.82,-38 0,-38 0.71,-38 1.42,-37.98 2.12,-37.94 Z M 0,-14 C -7.73,-14 -14,-7.73 -14,-0 -14,7.73 -7.73,14 0,14 7.73,14 14,7.73 14,-0 14,-5.48 10.85,-10.22 6.27,-12.52 4.38,-13.47 2.25,-14 0,-14 Z M 0,-14" transform="translate(62, 137) rotate(1)" />
</svg>
What I am trying to sort out is how to click inside either gears-largeGear or gears-smallGear one or the other gear and drag the mouse to cause them to rotate similar to how the slider works.
Searching around it seems like Snap SVG is a good way to do this but I've struggled to bridge the SVG and the Snap documentation together. In Snap I see how to load the SVG but I don't quite see how to get the path or group to setup the interaction and rotation.
If I used groups like this gears-largeGroup and gears-smallGroup would it be the same sort of code as if it were just paths being animated?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="200" xml:space="preserve" id="gears">
<!-- Generated by PaintCode - http://www.paintcodeapp.com -->
<g id="gears-largeGroup" transform="translate(149.59, 96.41)" >
<path id="gears-largeGear" stroke="none" fill="rgb(69, 131, 212)" d="M 2.78,-67.94 C 3.71,-63.88 4.74,-59.43 5.59,-55.72 7.73,-55.51 9.84,-55.18 11.9,-54.73 13.87,-58 16.21,-61.91 18.36,-65.49 20.15,-64.99 21.91,-64.42 23.64,-63.78 23.27,-59.62 22.87,-55.08 22.54,-51.28 24.5,-50.42 26.4,-49.45 28.23,-48.37 31.11,-50.87 34.55,-53.87 37.69,-56.6 39.24,-55.58 40.73,-54.49 42.18,-53.34 40.55,-49.5 38.77,-45.3 37.28,-41.79 38.87,-40.37 40.37,-38.87 41.79,-37.28 45.3,-38.77 49.5,-40.55 53.34,-42.18 54.49,-40.73 55.58,-39.24 56.6,-37.69 53.87,-34.55 50.87,-31.11 48.37,-28.23 49.45,-26.4 50.42,-24.5 51.28,-22.54 55.08,-22.87 59.62,-23.27 63.78,-23.64 64.42,-21.91 64.99,-20.15 65.49,-18.36 61.91,-16.21 58,-13.87 54.73,-11.9 55.18,-9.84 55.51,-7.73 55.72,-5.59 59.43,-4.74 63.88,-3.71 67.94,-2.78 67.98,-1.86 68,-0.93 68,0 68,0.93 67.98,1.86 67.94,2.78 63.88,3.71 59.43,4.74 55.72,5.59 55.51,7.73 55.18,9.84 54.73,11.9 58,13.87 61.91,16.21 65.49,18.36 64.99,20.15 64.42,21.91 63.78,23.64 59.62,23.27 55.08,22.87 51.28,22.54 50.42,24.5 49.45,26.4 48.37,28.23 50.87,31.11 53.87,34.55 56.6,37.69 55.58,39.24 54.49,40.73 53.34,42.18 49.5,40.55 45.3,38.77 41.79,37.28 40.37,38.87 38.87,40.37 37.28,41.79 38.77,45.3 40.55,49.5 42.18,53.34 40.73,54.49 39.24,55.58 37.69,56.6 34.55,53.87 31.11,50.87 28.23,48.37 26.4,49.45 24.5,50.42 22.54,51.28 22.87,55.08 23.27,59.62 23.64,63.78 21.91,64.42 20.15,64.99 18.36,65.49 16.21,61.91 13.87,58 11.9,54.73 9.84,55.18 7.73,55.51 5.59,55.72 4.74,59.43 3.71,63.88 2.78,67.94 1.86,67.98 0.93,68 -0,68 -0.93,68 -1.86,67.98 -2.78,67.94 -3.71,63.88 -4.74,59.43 -5.59,55.72 -7.73,55.51 -9.84,55.18 -11.9,54.73 -13.87,58 -16.21,61.91 -18.36,65.49 -20.15,64.99 -21.91,64.42 -23.64,63.78 -23.27,59.62 -22.87,55.08 -22.54,51.28 -24.5,50.42 -26.4,49.45 -28.23,48.37 -31.11,50.87 -34.55,53.87 -37.69,56.6 -39.24,55.58 -40.73,54.49 -42.18,53.34 -40.55,49.5 -38.77,45.3 -37.28,41.79 -38.87,40.37 -40.37,38.87 -41.79,37.28 -45.3,38.77 -49.5,40.55 -53.34,42.18 -54.49,40.73 -55.58,39.24 -56.6,37.69 -53.87,34.55 -50.87,31.11 -48.37,28.23 -49.45,26.4 -50.42,24.5 -51.28,22.54 -55.08,22.87 -59.62,23.27 -63.78,23.64 -64.42,21.91 -64.99,20.15 -65.49,18.36 -61.91,16.21 -58,13.87 -54.73,11.9 -55.18,9.84 -55.51,7.73 -55.72,5.59 -59.43,4.74 -63.88,3.71 -67.94,2.78 -67.98,1.86 -68,0.93 -68,-0 -68,-0.93 -67.98,-1.86 -67.94,-2.78 -63.88,-3.71 -59.43,-4.74 -55.72,-5.59 -55.51,-7.73 -55.18,-9.84 -54.73,-11.9 -58,-13.87 -61.91,-16.21 -65.49,-18.36 -64.99,-20.15 -64.42,-21.91 -63.78,-23.64 -59.62,-23.27 -55.08,-22.87 -51.28,-22.54 -50.42,-24.5 -49.45,-26.4 -48.37,-28.23 -50.87,-31.11 -53.87,-34.55 -56.6,-37.69 -55.58,-39.24 -54.49,-40.73 -53.34,-42.18 -49.5,-40.55 -45.3,-38.77 -41.79,-37.28 -40.37,-38.87 -38.87,-40.37 -37.28,-41.79 -38.77,-45.3 -40.55,-49.5 -42.18,-53.34 -41.55,-53.84 -40.91,-54.33 -40.26,-54.81 -39.42,-55.43 -38.56,-56.03 -37.69,-56.6 -34.55,-53.87 -31.11,-50.87 -28.23,-48.37 -26.4,-49.45 -24.5,-50.42 -22.54,-51.28 -22.87,-55.08 -23.27,-59.62 -23.64,-63.78 -21.91,-64.42 -20.15,-64.99 -18.36,-65.49 -16.21,-61.91 -13.87,-58 -11.9,-54.73 -9.84,-55.18 -7.73,-55.51 -5.59,-55.72 -4.74,-59.43 -3.71,-63.88 -2.78,-67.94 -1.93,-67.98 -1.08,-68 -0.23,-68 L 0,-68 C 0.93,-68 1.86,-67.98 2.78,-67.94 Z M 0,-36 C -6.22,-36 -12.07,-34.42 -17.18,-31.65 -28.39,-25.55 -36,-13.66 -36,-0 -36,19.88 -19.88,36 0,36 19.88,36 36,19.88 36,-0 36,-19.88 19.88,-36 0,-36 Z M 0,-36" transform="rotate(-38)" />
</g>
<g id="gears-smallGroup" transform="translate(62, 137)" >
<path id="gears-smallGear" stroke="none" fill="rgb(115, 152, 218)" d="M 2.12,-37.94 L 2.45,-37.92 C 3.29,-34.48 4.2,-30.71 4.97,-27.56 6.23,-27.34 7.46,-27.03 8.65,-26.64 9.87,-26.24 11.04,-25.77 12.18,-25.22 14.65,-27.32 17.61,-29.83 20.31,-32.12 21.69,-31.25 23.02,-30.28 24.27,-29.24 22.92,-25.96 21.45,-22.37 20.22,-19.37 21.97,-17.55 23.47,-15.49 24.68,-13.24 27.91,-13.49 31.78,-13.78 35.32,-14.05 35.92,-12.54 36.42,-10.99 36.83,-9.39 33.81,-7.53 30.5,-5.49 27.75,-3.79 27.91,-2.55 28,-1.29 28,-0 28,1.29 27.91,2.55 27.75,3.79 30.51,5.49 33.81,7.53 36.83,9.39 36.42,10.99 35.92,12.54 35.32,14.05 31.78,13.78 27.91,13.49 24.68,13.24 23.47,15.49 21.97,17.55 20.22,19.37 21.45,22.37 22.92,25.96 24.27,29.24 23.02,30.28 21.69,31.25 20.31,32.12 17.61,29.83 14.65,27.32 12.18,25.22 9.93,26.31 7.51,27.11 4.97,27.56 4.2,30.71 3.29,34.48 2.45,37.92 1.64,37.97 0.82,38 -0,38 -0.82,38 -1.64,37.97 -2.45,37.92 -3.29,34.48 -4.2,30.71 -4.97,27.56 -7.51,27.11 -9.93,26.31 -12.18,25.22 -14.65,27.32 -17.61,29.83 -20.31,32.12 -21.69,31.25 -23.02,30.28 -24.27,29.24 -22.92,25.96 -21.45,22.37 -20.22,19.37 -21.97,17.55 -23.47,15.49 -24.68,13.24 -27.91,13.49 -31.78,13.78 -35.32,14.05 -35.92,12.54 -36.42,10.99 -36.83,9.39 -33.81,7.53 -30.5,5.49 -27.75,3.79 -27.91,2.55 -28,1.29 -28,-0 -28,-1.29 -27.91,-2.55 -27.75,-3.79 -30.51,-5.49 -33.81,-7.53 -36.83,-9.39 -36.42,-10.99 -35.92,-12.54 -35.32,-14.05 -31.78,-13.78 -27.91,-13.49 -24.68,-13.24 -23.47,-15.49 -21.97,-17.55 -20.22,-19.37 -21.45,-22.37 -22.92,-25.96 -24.27,-29.24 -23.02,-30.28 -21.69,-31.25 -20.31,-32.12 -17.61,-29.83 -14.65,-27.32 -12.18,-25.22 -9.93,-26.31 -7.51,-27.11 -4.97,-27.56 -4.2,-30.71 -3.29,-34.48 -2.45,-37.92 -1.64,-37.97 -0.82,-38 0,-38 0.71,-38 1.42,-37.98 2.12,-37.94 Z M 0,-14 C -7.73,-14 -14,-7.73 -14,-0 -14,7.73 -7.73,14 0,14 7.73,14 14,7.73 14,-0 14,-5.48 10.85,-10.22 6.27,-12.52 4.38,-13.47 2.25,-14 0,-14 Z M 0,-14" transform="rotate(-3)" />
</g>
</svg>
Would it be easier to do the mouse hit-test and interaction in Javascript instead of using Snap?
EDIT: SEE LOWER DOWN FOR IMPROVED METHOD
Firstly, it may be easier to move the transform from the group in the 2nd example to the path. Then you can add any rotation transforms to the group without getting too messy. Eg.
<g id="gears-largeGroup" >
<path transform="translate(149.59, 96.41)" id="gears-largeGear".....
Once you have your SVG, you can select it in Snap with the Snap.select() method. This takes a css selector.
So we can find one with
Snap.select('#gears-smallGroup')
Now we have our gear, we can add a drag handler to it.
Snap.select('#gears-smallGroup').drag( dragRotate, dragStart )
So we just need to write our handler. Firstly we want to store the start of the drag (see updated solution at end, which is better), so we can write our startRotate function. This just stores an x,y object alongside the element.
function dragRotateStart( x, y ) {
this.data('oxy', { x: x, y: y })
}
Then we can write the main drag rotation handler.
This takes the original start position oxy stored above, and adds it to the drags delta increments that is passes over.
Then we uses Snaps angle() method to calculate the angle between two points. One the x,y we just had, and the other the center of the element.
Now we have the angle, we can just do a rotation transform. Snap can use a shortform for a transform (r=rotation, t=translate, s=scale and r&s with Snap will transform from their centers unless specified).
So this becomes
function dragRotate( dx, dy, x, y ) {
this.transform('r' + Snap.angle( this.getBBox().cx, this.getBBox().cy, dx + this.data('oxy').x, dy + this.data('oxy').y ) );
}
So whole code...
Snap.select('#gears-smallGroup').drag( dragRotate, dragRotateStart )
Snap.select('#gears-largeGroup').drag( dragRotate, dragRotateStart )
function dragRotate( dx, dy, x, y ) {
this.transform('r' + Snap.angle( this.getBBox().cx, this.getBBox().cy, dx + this.data('oxy').x, dy + this.data('oxy').y ) );
}
function dragRotateStart( x, y ) {
this.data('oxy', { x: x, y: y })
}
jsfiddle
Now you have this, you can fiddle with it, so that one will rotate the other. You may also have to check the starting points after a redrag depending on where your new drag starts from.
(quick stab at making smaller gear rotate bigger one jsfiddle)
UPDATED AND IMPROVED VERSION:
Extending this further, you will need to adapt it, to take into account both the starting rotation (also if it's pre-rotated) and the starting angle in the drag, we only want the difference in angles, so it doesn't reset.
So we can store out starting bits. What angle was the start of the drag at (I've set default to 10 as the image dot was offset by about 10deg). What was the old rotation of the element.
function dragRotateStart(x, y) {
this.data('startingAngle', Snap.angle(this.getBBox().cx, this.getBBox().cy, x, y));
this.data('startingRotation', this.data('rotation')||10)
}
And then calculate the correct rotation from the starting rotation, plus the angle difference, storing it as we go (so if we drag again later, we know what its last rotation was)...
function dragRotate(dx, dy, x, y) {
var angleDiff = Snap.angle(this.getBBox().cx, this.getBBox().cy, x, y) - this.data('startingAngle')
var newRotation = angleDiff + +this.data('startingRotation');
this.data('rotation', newRotation)
this.transform('r' + newRotation);
}
jsfiddle
Related
How to get the exact BBox for svg <tspan>
I am trying to figure out why getBBox() for tspan element of a svg does not return the dimension. To demonstrate this with an example, if I run BBox on both tsp1 and rect1, it returns the correct dimension for rect1 but not for tsp1 var tsp = document.getElementById('tsp1'); var tspBBox = tsp.getBBox(); var rect = document.getElementById('rect1'); var rectBBox = rect.getBBox(); console.log(tspBBox); console.log(rectBBox); <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720"> <text class="t1" id="t1" font-size="20" font-family="PT Mono" text-decoration="underline"> <tspan class="tsp1" id="tsp1" x="10.23" y="135.05">Abc ef ghi</tspan> </text> <rect class="rect1" id="rect1" x="9.23" y="112.73368530273439" height="31.546314697265625" width="1" fill="orange" /> </svg> I was expecting BBox to return the exact x and y for tsp1 but it does not. I don't know why. I need to pass on the exact values to the succeeding class dynamically. How can javascript return the exact dimension for the tspan element?
There are a number of methods for measuring text, and they are a bit more complex than defining a simple box. This is because with the dx, dy and rotate attributes, each addressable character can be be positioned individually - moved and rotated in every direction. Therefore, it makes more sense to answer the question where a single character is positioned, and where, after completing one sequence, the next character would be positioned. In your case none of the above attributes are set ( on the <tspan> or <text> element). In this case is is possible to retrieve the start position of the <tspan> with .getStartPositionOfChar(0) and the horizontal width with .getComputedTextLength().* The height according to the font metrics is the same for all characters in the tspan, so it is enough to return one .getExtentOfChar(0) - 0 refers to the first character within the sequence of addressable characters. As chrwahl pointed out in his answer, the start position refers to the font-specific baseline and normally will not be identical to the top left corner of a bounding box. *There is a subtle trick here: if the letter-spacing or word-spacing CSS properties were defined, the "length" returned would not only return the width from the start of the first character to the end of the last, but also would add (or subtract) a spacing value that is defined after the end of the string. In other words: despite its name, the method returns the relative horizontal start position of the next character after the string examined. var tsp = document.getElementById('tsp1'); var tspPos = tsp.getStartPositionOfChar(0); console.log('start position', tspPos.x, tspPos.y); console.log('horizontal advance', tsp.getComputedTextLength()); console.log('vertical extent', tsp.getExtentOfChar(0).height); <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720"> <text class="t1" id="t1" font-size="20" font-family="PT Mono" text-decoration="underline"> <tspan class="tsp1" id="tsp1" x="10.23" y="135.05">Abc ef ghi</tspan> </text> </svg>
It is all about the dominant-baseline. So, there is a differences between where the text is placed according to the dominant-baseline and the box that the text takes up. The value text-before-edge will place the text according to the upper left corner of the box. var tsp = document.getElementById('tsp1'); var tspBBox = tsp.getBBox(); var rect = document.getElementById('rect1'); var rectBBox = rect.getBBox(); console.log('tspBBox', tspBBox.x, tspBBox.y); console.log('rectBBox', rectBBox.x, rectBBox.y); <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 100 400 200"> <text class="t1" id="t1" font-size="20" font-family="PT Mono" text-decoration="underline" dominant-baseline="text-before-edge"> <tspan class="tsp1" id="tsp1" x="10.23" y="135.05">Abc ef ghi</tspan> </text> <rect class="rect1" id="rect1" x="9.23" y="112.73368530273439" height="31.546314697265625" width="1" fill="orange" /> </svg>
Add text to SVG path dynamically
I have an SVG exported from Adobe Illustrator with several paths like this, which produces a small polygon I intend to use as a text box <svg viewbox="387 390 74 20"> <g> <path class="st37" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0 C460,404.42,456.42,408,452,408z" /> </g> </svg> I'd like to dynamically add text to it. I've seen many similar questions here, but most of them involed specifying a x and y property for a text element based on the x and y property the path element. My path, however, does not have such properties. I've tried to use a textPath element with xlink:href pointing to my path. I gets attached to the path, but the text wraps my path element instead of being inside it. So, is there a way to achieve this? I'm open to different solutions here. My dream would be to use javascript to get the path element and add the text from there. But I could also edit the base SVG file to add a text or any other relevant element and attributes to make this work, as long as I can change the text dynamically from javascript later. And since this SVG is produced by Illustrator, I could also try different export options there in order to get a proper output for my goal.
Here's some sample code that takes a label path and adds a <text> element after it with whatever text you choose. let label1 = document.querySelector("#label1"); addLabelText(label1, "Something"); function addLabelText(bgPath, labelText) { let bbox = bgPath.getBBox(); let x = bbox.x + bbox.width / 2; let y = bbox.y + bbox.height / 2; // Create a <text> element let textElem = document.createElementNS(bgPath.namespaceURI, "text"); textElem.setAttribute("x", x); textElem.setAttribute("y", y); // Centre text horizontally at x,y textElem.setAttribute("text-anchor", "middle"); // Give it a class that will determine the text size, colour, etc textElem.classList.add("label-text"); // Set the text textElem.textContent = labelText; // Add this text element directly after the label background path bgPath.after(textElem); } .st37 { fill: linen; } .label-text { font-size: 10px; fill: rebeccapurple; transform: translate(0, 3px); /* adjust vertical position to centre text */ } <svg viewbox="387 390 74 20"> <g> <path id="label1" class="st37" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0 C460,404.42,456.42,408,452,408z" /> </g> </svg>
Since you can edit your base SVG align create a proper SVG to work with Your path is a background label starting (red circle) at a large offset x=452 y=408 I would recreate it, starting at the green circle, (editor: https://yqnn.github.io/svg-path-editor/) in a viewBox="0 0 80 20" To get single coordinates for both backgroundlabel and (blue) textPath line after that use JavaScript to add text dynamically. No need for text x,y calculations, pathLength and startoffset do the work Or if you go fancy you can create the blue line dynamically from getBBox() That will also work with your current path; just more calculations required It is all about coordinates, and positioning the blue line (with stroke="transparent" then): playground: <svg viewbox="387 390 74 20"> <path fill="lightgreen" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0C460,404.42,456.42,408,452,408z" /> <circle cx="452" cy="408" r="2" fill="red"/> <circle cx="388" cy="400" r="2" fill="green"/> <path id="P" pathLength="100" d="M388 400h72" stroke="blue"/> <text> <textPath href="#P" startoffset="50" text-anchor="middle" dominant-baseline="middle" fill="green" font-size="14px">My Text</textPath> </text> </svg>
Thanks for the answers! I end up using a tweaked version of Paul LeBeau's function to take into account the structure suggested by Danny '365CSI' Engelman so I don't have to use translate to center the text vertically. let label = document.querySelector("#mylabel"); addLabelTextPath(label, "Something"); function addLabelTextPath(bgPath, labelText) { let bbox = bgPath.getBBox(); let x = bbox.x + bbox.width / 2; let y = bbox.y + bbox.height / 2; // Create the path line let pathElem = document.createElementNS(bgPath.namespaceURI, "path"); pathElem.setAttribute("pathLength", 100); pathElem.setAttribute("d", `M${bbox.x} ${y}h${bbox.width}`); pathElem.id = `baseline-${bgPath.id}`; // Create a <text> element let textElem = document.createElementNS(bgPath.namespaceURI, "text"); // Create a <textPath> element let textPath = document.createElementNS(bgPath.namespaceURI, "textPath"); textPath.setAttribute("href", `#${pathElem.id}`); //Center text textPath.setAttribute("dominant-baseline", "Middle"); textPath.setAttribute("startOffset", 50); textPath.setAttribute("text-anchor", "middle"); // Give it a class that will determine the text size, colour, etc textPath.classList.add("label-text"); // Set the text textPath.textContent = labelText; // Add the elements directly after the label background path bgPath.after(pathElem); pathElem.after(textElem); textElem.appendChild(textPath); } .st37 { fill: lightblue; } .label-text { font-size: 10px; fill: darkblue; } <svg viewbox="387 390 74 20"> <g> <path id="mylabel" class="st37" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0 C460,404.42,456.42,408,452,408z" /> </g> </svg>
SVG - Duplicate image along path
I am currently trying to create a bicycle chain via SVG and javascript. The result I am trying to create will look something like this: The problem I am having is how to repeat a single chainlink image along the path of the chain. I want to be able to use a chainlink image (For example: and then repeat this along the path that I define. I know that it is trivial to repeat text along a path via SVG but is it possible to do the same with an image? The complexity comes from the fact that the chainlink will have to appear as one continuous line. Here is what I have so far: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <svg width="700" height="500"> <path id="ChainPath" fill="none" stroke="red" d="M150 100 L400 100 C650 100 650 400 400 400 L150 400" /> <text> <textPath href="#ChainPath" alignment-baseline="middle"> 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 </textPath> </text> </svg> </body> </html> Any help would be greatly appreciated. Thanks
I tried to practically implement Robert Monfera's answer and found a few extra hitches. I started out computing the points of a polygon sitting, as he described, at the axes/connecting pins of the individual chain links, and all with exactly identical distance between them. One thing that immediately strikes if you look at it is that a chain is not a smooth line. For the human eye, it might look like one, but describing the chain running around the gear as a circular arc is a bad approximation. Look at the picture in the question. The right gear has 15 teeth. That means the angle between neighbouring chain links is 24°, and the length difference between an arc and the cord from point to point (which is the constructive length of the link) is approx. 0.8%. That doesn't sound like much, but for a link that you draw with a screen size of 50px, it's almost half a pixel. That is a difference that can be seen. The next propostion was to use <marker> elements for the individual chain links with the origin in the center between the two pins. Since the polygon defined above has its points at the position of the pins, for this a second one is needed that connects the mids of all polygon segments. When implementing that, the resulting chain looked like this (I've drawn the polygon on top to illustrate): Where the "straight" part of the chain morphs to the "curved" one, the chain links are seriously misaligned. The reason is that the marker orientation bisects the incoming and outgoing tangent at a vertex. Both for the straight section and while following the arc of the gear, that works out correctly, but where both meet, it obviously doesn't. To get the orientation of the markers correct, the tangents at the points must match the direction of the original polygon. Robert proposed to draw an arc between the points. That probaböy would work, but it is complicated to compute the correct arc radii and positions. I've come up with a method that produces a smooth path that looks a bit crooked, but the line will never show in the end, and its computation is really straightforward - I actually did that with an Excel sheet. Lets say we have a list of points marking the pin positions: a b c d e f g ... There are two kinds of links (front and back), so mark every other middle of two points: a b c d e f g ... ab cd ef a, ab, c are in a straight line, same as c, cd, d and so on. Now if you draw a path with the following command <path d="M a L ab C b c cd d e ef f g ..." marker-mid="url(#link1)" /> point a will hold no marker, as its not in the middle. The next vertices, which position the markers, are ab, cd, ef, ..., while b, c, d, e, ... are control points of a cubic Bezier curve. What that means is: the path tangent in vertex ab is the straight line from a to b, in cd from c to d, and so on. Here is a screenshot from Inkscape to illustrate: The other links can be describes accordingly as <path d="M b L bc C c d de e f fg g ..." marker-mid="url(#link2)" /> If one wants to get fancy, it is even possible to shorten that a bit with the S command that takes the control point before a vertex and implicitely adds its reflection as the next control point after (points e, g, ... are computed): <path d="M b L bc C c d de S f fg h hj ..." marker-mid="url(#link2)" /> For the grande finale, here is the finished drawing. Just for the heck of it, I've drawn the gear teeths with the same marker technique. <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 800 600" width="600" height="450"> <defs> <marker id="blade1" markerUnits="userSpaceOnUse" orient="auto" overflow="visible"> <path style="fill:#ececf4" d="M 16.5,-1.556 13.947,-1.78 A 14,14 0 0 1 -13.947,-1.78 L -16.5,-1.556 0,200 Z" /> <path style="fill:none;stroke:#000" d="M 16.5,-1.556 13.947,-1.78 A 14,14 0 0 1 -13.947,-1.78 L -16.5,-1.556" /> </marker> <marker id="blade2" markerUnits="userSpaceOnUse" orient="auto" overflow="visible"> <path style="fill:#ececf4" d="M 16.5,0.507 13.694,-0.089 A 14,14 0 0 1 -13.694,-0.089 L -16.5,0.507 0,90 Z" /> <path style="fill:none;stroke:#000" d="M 16.5,0.507 13.694,-0.089 A 14,14 0 0 1 -13.694,-0.089 L -16.5,0.507" /> </marker> <path id="plate" d="M -9.689,-10.392 C -4.782 -7.56 4.782 -7.56 9.689,-10.392 A 12,12 0 1 1 9.689,10.392 C 4.782 7.56 -4.782 7.56 -9.689,10.392 A 12,12 0 1 1 -9.689,-10.392 Z" /> <marker id="link1" style="stroke:#000" markerUnits="userSpaceOnUse" orient="auto" overflow="visible"> <use xlink:href="#plate" style="fill:#ddd" /> <circle id="pin" style="fill:#888;stroke:#000" cx="15.689" cy="0" r="4" /> <use xlink:href="#pin" x="-31.378" /> </marker> <marker id="link2" style="stroke:#000" markerUnits="userSpaceOnUse" orient="auto" overflow="visible"> <use xlink:href="#plate" style="fill:#bbb" /> </marker> <g id="center1"> <circle r="80" cx="250" cy="300" /> <g id="cq"> <circle r="10" cx="345" cy="265.42" /> <path d="M 252.984,200.049 A 100,100 0 0 1 324.557,233.367 25,25 0 0 0 361.957,200.182 150,150 0 0 0 254.883,150.086 25,25 0 0 0 252.984,200.049 Z" /> </g> <use xlink:href="#cq" transform="rotate(90 250,300)" /> <use xlink:href="#cq" transform="rotate(180 250,300)" /> <use xlink:href="#cq" transform="rotate(270 250,300)" /> </g> <mask id="m1"> <rect fill="white" x="50" y="100" width="400" height="400" /> <use xlink:href="#center1" fill="black" /> </mask> <path id="center2" d="M 647.75 277.2 L 642.04 279.05 L 643.04 282.13 A 20 20 0 0 0 633.31 292.96 L 630.13 292.28 L 628.88 298.15 L 632.06 298.83 A 20 20 0 0 0 636.55 312.68 L 634.38 315.08 L 638.84 319.1 L 641.01 316.69 A 20 20 0 0 0 655.25 319.73 L 656.25 322.8 L 661.96 320.95 L 660.96 317.87 A 20 20 0 0 0 670.7 307.04 L 673.87 307.72 L 675.12 301.85 L 671.94 301.17 A 20 20 0 0 0 667.45 287.32 L 669.62 284.92 L 665.16 280.9 L 662.99 283.31 A 20 20 0 0 0 648.75 280.27 L 647.75 277.2 z" /> <mask id="m2"> <rect fill="white" x="572" y="220" width="160" height="160" /> <use xlink:href="#center2" fill="black" /> </mask> </defs> <g mask="url(#m1)"> <path style="fill:none;marker-mid:url(#blade1)" d="M 430,300 427.27,331.26 419.14,361.56 405.88,390 387.89,415.7 365.7,437.89 340,455.88 311.56,469.14 281.26,477.27 250,480 218.74,477.27 188.44,469.14 160,455.88 134.3,437.89 112.11,415.7 94.12,390 80.86,361.56 72.73,331.26 70,300 72.73,268.74 80.86,238.44 94.12,210 112.11,184.3 134.3,162.11 160,144.12 188.44,130.86 218.74,122.73 250,120 281.26,122.73 311.56,130.86 340,144.12 365.7,162.11 387.89,184.3 405.88,210 419.14,238.44 427.27,268.74 430,300 427.27,331.26" /> </g> <use xlink:href="#center1" style="fill:none;stroke:#000" /> <g mask="url(#m2)"> <path style="fill:none;marker-mid:url(#blade2)" d="M 578.19,315.69 V 284.31 L 590.95,255.65 614.27,234.65 644.11,224.95 675.32,228.23 702.49,243.92 720.94,269.31 727.46,300 720.94,330.69 702.49,356.08 675.32,371.77 644.11,375.05 614.27,365.35 590.95,344.35 578.19,315.69 V 284.31"/> </g> <use xlink:href="#center2" style="fill:none;stroke:#000" /> <path style="fill:none;marker-mid:url(#link2)" d="M 281.26 477.27 L 265.63 478.63 C 250 480 218.74 477.27 203.59 473.21 S 160 455.88 147.15 446.89 112.11 415.7 103.11 402.85 80.86 361.56 76.79 346.41 70 300 71.37 284.37 80.86 238.44 87.49 224.22 112.11 184.3 123.21 173.21 160 144.12 174.22 137.49 218.74 122.73 234.37 121.37 281.26 122.73 296.41 126.79 341.88 138.97 357.03 143.03 402.5 155.2 417.66 159.26 463.13 171.43 478.28 175.49 523.75 187.66 538.91 191.72 584.38 203.89 599.54 207.95 645.01 220.12 660.16 224.18 702.49 243.92 711.71 256.61 727.46 300 724.2 315.35 702.49 356.08 688.91 363.92 645.01 379.88 629.85 383.94 584.38 396.11 569.22 400.17 523.75 412.34 508.6 416.4 463.13 428.57 447.97 432.63 402.5 444.8 387.35 448.86 341.88 461.03 326.72 465.09 281.26 477.27 265.63 478.63 L 250 480" /> <path style="fill:none;marker-mid:url(#link1)" d="M 311.56 469.14 L 296.41 473.21 C 281.26 477.27 250 480 234.37 478.63 S 188.44 469.14 174.22 462.51 134.3 437.89 123.21 426.79 94.12 390 87.49 375.78 72.73 331.26 71.37 315.63 72.73 268.74 76.79 253.59 94.12 210 103.11 197.15 134.3 162.11 147.15 153.11 188.44 130.86 203.59 126.79 250 120 265.63 121.37 311.56 130.86 326.72 134.91 372.19 147.08 387.35 151.14 432.82 163.31 447.97 167.37 493.44 179.54 508.6 183.6 554.07 195.77 569.22 199.83 614.69 212 629.85 216.06 675.32 228.23 688.91 236.08 720.94 269.31 724.2 284.65 720.94 330.69 711.71 343.39 675.32 371.77 660.16 375.82 614.69 388 599.54 392.05 554.07 404.23 538.91 408.28 493.44 420.46 478.28 424.51 432.82 436.69 417.66 440.74 372.19 452.92 357.03 456.97 311.56 469.14 296.41 473.21 L 281.26 477.27" /> </svg>
In SVG, I define one chainlink and create as many copies as necessary (reference point being at the center of the chainlink) : <use class="chain chainlink1" xlink:href="#chainlink" /> <use class="chain chainlink2" xlink:href="#chainlink" /> <use class="chain chainlink3" xlink:href="#chainlink" /> <use class="chain chainlink4" xlink:href="#chainlink" /> .... in the css, I define the path to follow .chain { offset-path: path("m 50,150 c -2,-19 12,-36 24,-44 13,-8 176,-54 226,-56 60,0 100,49.17776 100,100 0,51 -42,99 -100,99 C 262,250 95,206 72,191 58,182 50,166 50,150 Z"); animation: cycle 20s linear infinite; } #keyframes cycle { 0% { offset-distance: 0%; } 100% { offset-distance: 100%; } } And the delay of each chainlink .chainlink2 { animation-delay: 1s; } .chainlink3 { animation-delay: 2s; } ... Durations and delays depends on length of the chainlink, path... and desired speed
Here is one solution: you can make a <polyline> or <polygon> with <marker> elements as long as you're willing to do the math to ensure that the distance between the polygon points exactly match the axis-to-axis distance of your chain element (ie. it's not the total length of the element, but the repeated placement distance that counts). It requires the use of a^2 + b^2 = c^2 where c is this pitch distance (a constant), and a and b are the horizontal and vertical distances from the preceding point, respectively. In other words, you need to calculate the x, y coordinates of the chain axis centers (*) (if you do that, then you might as well place them one by one, but then you'll have a lot of DOM elements). You'll need orient="auto" to align the chain elements with the polygon, and also use the properties viewBox,refX, refY, markerWidth, markerHeight so that one of the axes of the chain element aligns perfectly with the polygon point (and then, via using the above Pythagoras theorem, the other chain element center will correspond to the next polygon point). Here are a few examples for the <marker>: https://codepen.io/monfera/pen/ppaRNK https://codepen.io/monfera/pen/xXmpbY https://codepen.io/monfera/pen/oLoRgX As I look at your chain (normal bicycle chain), it's best to make two almost identical polygons atop of one another, with a half-pitch offset between the two: in the background, the partially occluded chain elements in the foreground, the 8-shaped, fully visible link plates As a result, your chain will be seamless, as long as your points are at the right place. You can even set up a loop of transitioning between adjacent elements for an animated chain :-) (it's enough to transition with the chain pitch only, because a chain pitch translation will get you back to an indistinguishable view) (*) As ccprog points out below, to make the individual chain elements better aligned, it's not an axis center but the chain center that must sit on the polygon. This might make the math a bit more tedious if there are sharp turns, but in this specific case (bicycle chain) the circle radii seem sufficiently large to not lead to a noticeable separation of the chain elements.
The XY coordinates in a d3 drag event are inverting
I have a force layout with the following structure: <svg width="300" height="220"> <g class="scaleWrapper" transform="scale(0.3)"> <g class="transformWrapper" transform="translate(-110, -80)"> <g class="backgroundWrapper"> <rect class="backgroundRect" width="300" height="220"></rect> </g> <g class="forceNode"></g> <g class="forceNode"></g> <g class="forceLink"></g> </g> </g> </svg> I also have a drag behavior linked to the rect to drag it around (scaling is handled through a separate slider). let transformElement = d3.select('.transformWrapper'); let svgBackground = transformElement.append('g') .classed('backgroundWrapper', true); function originFunction() { let d = d3.select('.transformWrapper'); return { x: d.attr('x'), y: d.attr('y') }; } let svgDrag = d3.behavior.drag() .origin(originFunction) .on('dragstart', function(){ d3.event.sourceEvent.stopPropagation(); }) .on('drag', function(){ transformElement.attr("transform", `translate(${d3.event.x}, ${d3.event.y})`); }); svgBackground.call(svgDrag); It mostly works, but it jumps around as i drag it. I did a log and saw that the d3.event object is alternating between relative XY coordinates and absolute ones, here's a sample of what I'm seeing: -111 -80 -29 -6 -110 -80 -29 -5 I don't see any other elements that have behavior bound to them. All the d3.event objects have the 'drag' type property and the same srcElement. How can I silence the events that are returning the relative positions?
Found the problem, it's the same bug as in d3.event.y has strange values during drag behavior Moving the drag behavior to the transform group fixed it right up.
Setting D3 svg.transition to go from slow to fast to slow
I have a D3 graph that allows a user to click a button to take them to a specified node. The button looks like this: <button type="button" class="btn btn-primary" ng-click="ctrl.panGraph(9)">Go to End</button> This button will take the user from wherever they are in the svg at the time of click, to the x and y coordinates of the last node, with the id of 9. On click this function is called: function panGraph (nodeId:any) { svgWidth = parseInt(svg.style("width").replace(/px/, ""), 10); svgHeight = parseInt(svg.style("height").replace(/px/, ""), 10); for (var i = 0; i < renderedNodes.length; i++) { if (nodeID === renderedNodes[i].id) { ctrl.selectedNode = renderedNodes[i]; var translate = [svgWidth / 2 - renderedNodes[i].x, svgHeight / 2 - renderedNodes[i].y]; var scale = 1; svg.transition().duration(4000).ease(d3.easeExpInOut).call(zoom.translate(translate).scale(scale).event); } } } In the above function I have all the rendered nodes that have been rendered on the page, once I find the matching id I use its x and y coordinates to center the specified node in the middle of the svg. That all works fine. I am trying to use some animations during the time that the graph is translating to the specified node on button click. When the user clicks the button that takes him or her to the specified node, is it possible to animate the transition so that the transition initially starts slow, then speeds up, but then slows down again at the end as it gets close to the specified node? Thanks UPDATE: The above code with the "ease" incluided gives me this console error: angular.js:13550 TypeError: Cannot read property 'indexOf' of undefined at Object.d3.ease (d3.js:5844) at Array.d3_transitionPrototype.ease (d3.js:8838) at zoomOnNode (DiagramComponent.ts:1128) at DiagramComponent.ts:1072 at Scope.$digest (angular.js:17073) at Scope.$apply (angular.js:17337) at HTMLButtonElement.<anonymous> (angular.js:25023) at HTMLButtonElement.dispatch (jquery.js:4737) at HTMLButtonElement.elemData.handle (jquery.js:4549)
Here is the v3 equivalent to Gerardo's post regarding v4: svg.transition().duration(1000).ease("exp-in-out").call(zoom.translate(translate).scale(scale).event); For a list of all the easing equivalents from v3 to v4 and other changes: https://github.com/d3/d3/blob/master/CHANGES.md
One (out of several) solution is to use ease with d3.easeExpInOut, or d3.easePolyInOut.exponent(x) with a high exponent (like x=4 or x=5). See this snippet. Click the circle to see it moving from left to right, starting slow, speeding up and then slowing down again: d3.select("circle").on("click", function(){ d3.select(this).transition() .duration(4000) .ease(d3.easeExpInOut) .attr("cx", 360) }); <script src="https://d3js.org/d3.v4.min.js"></script> <svg width="400" height="200"> <circle cx="40" cy="100" r="30" fill="teal"></circle> <line x1="40" x2="40" y1="100" y2="150" stroke="black" stroke-width="1"></line> <line x1="360" x2="360" y1="100" y2="150" stroke="black" stroke-width="1"></line> </svg>