Embedding SVG shapes in a d3.js animation - javascript

I created a jsfiddle here. There are a couple of things I need to do.
How do you properly embed svg code (from an existing shape) into a d3.js animation? My svg is popping up twice.
How do you attach that svg to another shape so that the animation happens simultaneously for both shapes?
#slider {
width: 490px;
}
#_rb {
color: #000;
}
5
JavaScript:
var width = 500,
height = 500,
minRadius = 10,
maxRadius = 250,
minHeight = 100,
maxWidth = 500;
var duration = 5000;
d3.select("body").append("input")
.attr("type", "range")
.attr("id", "slider")
.attr("min", 1000)
.attr("max", 10000)
.attr("value", duration)
.on("change", function () {
duration = this.value;
d3.select("#_rb").property("value", d3.round(duration / 1000));
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("circle")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("r", minRadius)
.style("fill", "steelblue")
.call(transition, minRadius, maxRadius);
svg.append("use")
.attr("xlink:href", "#Layer_1")
.attr("width", width / 2)
.attr("height", height / 2)
.attr("x", 100)
.attr("y", 100)
.call(transition, minHeight, maxWidth);
function transition(element, start, end) {
element.transition()
.duration(duration)
.attr("r", end)
.attr("height", end)
.attr("width", end)
.each("end", function () {
d3.select(this).call(transition, end, start);
});
}

Related

different animation for the same event

The basic idea is that every time that I click the plane will drop a bomb. The moving plane and the dropping bomb are ok, i have two problems:
1) If I drop multiple bombs every explosion animation starts at the same moment of the previous one. Is there is a way to have a different animation for every dropped bomb?
2) I am trying to use ease("cubic"), but there is some mistake using it so if possible can you share a tip on how to use it well?
let svg = d3.select("svg"),
width = svg.attr("width"),
height = svg.attr("height"),
speed = 0.3,
movement = 600;
let x = 0;
let y = 50;
let w = 100;
let plane = svg.append("svg:image")
.attr("x", x)
.attr("y", y)
.attr("width", 100)
.attr("height", 100)
.attr("xlink:href", "b52js.png");
transition();
svg.on("click", function() {
var bombingx = plane.attr("x")
let bomb = svg.append("svg:image")
.attr("x", bombingx - 2.5 + 50)
.attr("y", y + 50)
.attr("width", 15)
.attr("height", 20)
.attr("xlink:href", "bomb.png");
bomb
.transition()
.duration(1200)
.attr("y", height - 10)
.ease("cubic")
.on("end", function() {
let exp = svg.append("svg:image")
.attr("x", bombingx)
.attr("y", height - 190)
.attr("height", 250)
.attr("width", 150)
.attr("xlink:href", "giphy.gif");
d3.timer(function(elapsed) {
exp.remove()
}, 1500);
bomb.remove();
})
});
Referring to my comment, lets change the xlink:href randomly on every click. Gifs of the same dimensions and short length are preferred. Or just create multiple copies of the same gif and put them in the array.
Here's a fiddle:
let svg = d3.select("svg"),
width = svg.attr("width"),
height = svg.attr("height"),
speed = 0.3,
movement = 600;
let x = 0;
let y = 50;
let w = 100;
let g = svg.append('g').attr('id', 'gg')
let plane = svg.append("svg:image")
.attr("x", x)
.attr("y", y)
.attr("width", 100)
.attr("height", 100)
.attr("xlink:href", "https://pngimg.com/uploads/plane/plane_PNG5243.png");
/* transition(); */
svg.on("click", function() {
var bombingx = plane.attr("x")
let bomb = svg.append("svg:image")
.attr("x", bombingx - 2.5 + 50)
.attr("y", y + 50)
.attr("width", 15)
.attr("height", 20)
.attr("xlink:href", "https://pngimg.com/uploads/bomb/bomb_PNG38.png");
bomb
.transition()
.duration(1200)
.attr("y", height - 10)
.ease("cubic")
.each("end", function() {
let exp = g.append("g:image")
.attr("x", bombingx)
.attr("y", height - 190)
.attr("height", 250)
.attr("width", 150)
.attr("xlink:href", function() {
// Lets create an array of gif links
let gifs = ["https://media.giphy.com/media/xA6evoIAtqSzK/giphy.gif", "https://media.giphy.com/media/rkkMc8ahub04w/giphy.gif", "https://media.giphy.com/media/oe33xf3B50fsc/giphy.gif"]
// A function to return random integers
function randInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
//randInt(3) will return 0,1 or 2.
//This number should be the number of gif links you have in your gif link array.
return gifs[randInt(3)];
});
setTimeout(function() {
exp.remove();
}, 1500);
bomb.remove();
})
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.13/d3.min.js"></script>
<svg width='300' height='300'></svg>

How to create different colour shades to column bar in d3 [duplicate]

I have created a D3 bar chart, which takes in my data, applies a scale and then draws my rectangles. Here's my current output:
I would like to make it more visual. The values are temperatures, so I'd like to create something like the first bar here:
Basically I want to make a bar that is made up of many rectangles of different colors.
After considering several options, I decided to try to inject a pattern into my bars. I tried making my pattern from paths, and then I attempted rectangles, but no success.
I'm not tied to the 'pattern' approach, so an different approach, or how I can make this pattern, is what I'm after. Here's my code:
// Set variables.
var dataset = // an array of objects, each with two properties: location and temperature
w = 500,
h = 800,
xScale = d3.scale.linear()
.domain([-30, 40])
.range([5, 350]),
yScale = d3.scale.ordinal();
// Create SVG element.
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
/* svg.append("defs")
.append("pattern")
.attr("id", "heatHatch")
.attr("patternUnits", "userSpaceOnUse")
.attr("width", 350)
.attr("height", 25);
.append("path")
.attr("d", "M10 0 L10 20 L0 20 L0 0 Z M22 0 L22 20 L12 20 L12 0 Z")
.attr("fill", "pink")
.append("path")
.attr("d", "M22 0 L22 20 L12 20 L12 0 Z")
.attr("fill", "red");
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 10)
.attr("height", 20)
.attr("fill", "pink"); */
// Create bars.
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", function(d, i) {
return i * 25;
})
.attr("width", function(d) {
return xScale(d[1]);
})
.attr("height", 20);
/*.attr("fill", "url(#heatHatch)"); */
Thanks.
If you want pattern that scales to the bar size, use a gradient pattern:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<script>
var w = 500,
h = 500;
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
var color = d3.scale.category10();
var gradient = svg.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "50%")
.attr("x2", "100%")
.attr("y2", "50%")
.attr("spreadMethod", "pad");
d3.range(10).forEach(function(d, i) {
var c = color(i);
gradient.append("svg:stop")
.attr("offset", (i * 10) + "%")
.attr("stop-color", c)
.attr("stop-opacity", 1);
gradient.append("svg:stop")
.attr("offset", ((i + 1) * 10) + "%")
.attr("stop-color", c)
.attr("stop-opacity", 1);
});
svg.selectAll("bar")
.data(d3.range(10))
.enter()
.append("rect")
.attr("class", "bar")
.attr("width", function(d,i){
return (w / 10) * i;
})
.attr("height", (h / 10) - 10)
.attr("y", function(d,i){
return (h / 10) * i;
})
.style("fill", "url(#gradient)");
</script>
</body>
</html>
If you want a pattern with statically sized colors, create a pattern of rects:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<script>
var w = 500,
h = 500;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
var color = d3.scale.category10();
var pattern = svg.append("svg:defs")
.append("svg:pattern")
.attr("width", w)
.attr("height", 4)
.attr("id", "myPattern")
.attr("patternUnits","userSpaceOnUse");
d3.range(10).forEach(function(d, i) {
var c = color(i);
pattern.append("rect")
.attr("height", "4px")
.attr("width", w / 10)
.attr("x", (w / 10) * i)
.attr("fill", c);
});
svg.selectAll("bar")
.data(d3.range(10))
.enter()
.append("rect")
.attr("class","bar")
.attr("width", function(d,i){
return (w / 10) * i;
})
.attr("height", (h / 10) - 10)
.attr("y", function(d,i){
return (h / 10) * i;
})
.style("fill", "url(#myPattern)");
</script>
</body>
</html>

d3js svg text and shapes on same line

I am using SVG to draw text and shapes in D3.js, and want to draw shapes inline with text and with similar dimensions to the text. The only way I can think of doing this is draw a rect around each tspan then draw the shape in relative position to the tspan rect. The result being:
This is a rectangle [] this is a circle ()
Where the brackets above represent the SVG shapes. Current code is below.
js:
function setupSVG(){
d3.select("div#chartId")
.append("div")
.classed("svg-container", true)
.append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 200 200")
.attr("id", "svg_area_id")
}
function renderSVGText(){
var svgArea = d3.select("svg#svg_area_id");
svgArea.append("rect")
.attr("x", 100)
.attr("y", 0)
.attr("height", 10)
.attr("width", 10)
.attr("id", "shape");
var group = svgArea.append("g")
.attr("width", "100%")
.attr("height", "100%")
.style("stroke", "red") //I only want to draw rect stroke
.style("fill", "none");
var text = group.append("text")
.attr("y", "0")
.attr("font-size",52)
.attr("dy", "1em")
.style('fill', 'black')
var tspan1 = text.append('tspan')
tspan1.text("This is a square");
var tspan2 = text.append('tspan')
tspan2.text("and this is a triangle");
var boundingRect = group.append("rect")
//see http://phrogz.net/SVG/tspan_bounding_box.xhtml
var bbox = tspan1.getBoundingClientRect();
var pt = svg.createSVGPoint();
pt.x = bbox.left;
pt.y = bbox.top;
var pt2 = pt.matrixTransform(xform);
rect.setAttribute('x',pt2.x);
rect.setAttribute('y',pt2.y);
pt.x = bbox.right;
pt.y = bbox.bottom;
pt = pt.matrixTransform(xform);
boundingRect.attr('width', pt.x-pt2.x);
boundingRect.attr('height',pt.y-pt2.y);
/* this draws a rect around all text
var textSize = text.node().getBBox();
boundingRect.attr("width", textSize.width)
.attr("height", textSize.height);
*/
}
html:
<div class="svg-container" id="chartId"></div>
css:
.svg-container {
display: inline-block;
width: 512px;
height: 512px;
padding-top: 50px;
padding-bottom: 100%; /* aspect ratio */
vertical-align: top;
overflow: hidden;
border: 1px solid black;
}
Any ideas on how to do this? Any easier ways than the track I am following?
I tried to get tspan dimensions using tspan.node().getComputedTextLength() but this returned an error, I presume because it hadn't been rendered at the call time. I just used text.node().getBBox() to get each text blocks dimensions instead:
function renderSVGText(){
var svgArea = d3.select("svg#svg_area_id");
var group = svgArea.append("g")
.attr("width", "100%")
.attr("height", "100%")
.style("stroke", "red")
.style("fill", "none");
var text = group.append("text")
.attr("y", "0")
.attr("font-size",52)
.attr("dy", "1em")
.attr("id", "text_id")
.style('fill', 'black');
var tspan1 = text.append('tspan')
.attr("id", "tspan1_id")
tspan1.text("This is a square");
var boundingRect = svgArea.append("rect")
.style("stroke", "pink")
.style("fill", "none");
var textSize = text.node().getBBox();
boundingRect.attr("width", textSize.width)
.attr("height", textSize.height);
svgArea.append("rect")
.attr("x", textSize.width+10)
.attr("y", 0)
.attr("height", textSize.height)
.attr("width", textSize.height)
.attr("id", "shape");
var text2 = group.append("text")
.attr("x", textSize.width+textSize.height+20)
.attr("y", "0")
.attr("font-size",52)
.attr("dy", "1em")
.attr("id", "text2_id")
.style('fill', 'black');
var tspan2 = text2.append('tspan')
tspan2.text("and this is a triangle");
}

Trying to incorporate svg circle within a d3 donut

I am trying to add a SVG circle within my d3 donut. My SVG circle displays the percentage as a fill of circle, for example, if the D3 donut is at 50%, the SVG will show 50% filled. I want to put the SVG circle within the inside of my D3 donut.
Here is my codepen for this. http://codepen.io/iamsok/pen/MwdPpx
class ProgressWheel {
constructor(patient, steps, container){
this._patient = patient;
this._steps = steps;
this.$container = $(container);
var τ = 2 * Math.PI,
width = this.$container.width(),
height = this.$container.height(),
innerRadius = Math.min(width,height)/4,
//innerRadius = (outerRadius/4)*3,
fontSize = (Math.min(width,height)/4);
var tooltip = d3.select(".tooltip");
var status = {
haveNot: 0,
taken: 1,
ignored: 2
}
var daysProgress = patient.progress
var percentComplete = Math.round(_.countBy(daysProgress)[status.taken] / daysProgress.length * 100);
var participation = 100;
var color = ["#CCC", "#FDAD42", "#EFD8B5"];
var pie = d3.layout.pie()
.value(function(d) { return 1; })
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select(container).append("svg")
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox','0 0 '+Math.min(width,height) +' '+Math.min(width,height) )
.attr('preserveAspectRatio','xMinYMin')
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var innerCircle = d3.select("svg")
.append("svg")
.attr("width", 250)
.attr("height", 250);
var grad = innerCircle.append("defs")
.append("linearGradient").attr("id", "grad")
.attr("x1", "0%").attr("x2", "0%").attr("y1", "100%").attr("y2", "0%");
grad.append("stop").attr("offset", percentComplete + "%").style("stop-color", "lightblue");
grad.append("stop").attr("offset", percentComplete + "%").style("stop-color", "white");
innerCircle.append("circle")
.attr("r", 40)
.attr("cx", 70)
.attr("cy", 70)
.style("stroke", "black")
.style("fill", "url(#grad)");
var gs = svg.selectAll(".arc")
.data(pie(daysProgress))
.enter().append("g")
.attr("class", "arc");
var path = gs.append("path")
.attr("fill", function(d, i) { return color[d.data]; })
.attr("d", function(d, i, j) { return arc.innerRadius(innerRadius+(20*j)).outerRadius(innerRadius+20+(20*j))(d); })
.attr("class", function(d, i, j) { if (i>=participation && j<1) return "passed" ; })
svg.append("text")
.attr("dy", "0.5em")
.style("text-anchor", "middle")
.attr("class", "inner-circle")
.attr("fill", "#36454f")
.text(Math.round(_.countBy(daysProgress)[status.taken] / daysProgress.length * 100) + "%");
}
}
var patient = {progress: [0, 2, 2, 1, 0, 0, 0, 1, 1, 0, 1, 1, 2, 2]}
var progressWheel = new ProgressWheel(patient, 14, '.chart-container' )
Simply put the d3 donut and inner circle under the same svg so that they have the same coordinate system.
Check out here http://codepen.io/anon/pen/ojbQNE
Modified code is on codepen

D3.JS add zoom and listitems to rect

I want to achieve followings,
1 - Create a given column matrix with rectangles with provided colours
Done
2 - Make this matrix zoom able
3 - Add list items to each rectangle which will only show numbers of list items in it if completely zoomed out and on zoom in, it will show the list items e.g. there Titles.
Now I want to achieve Number 2 here, this is what I am trying,
http://jsfiddle.net/nhe613kt/25/
When I add code for zooming it fails,
var svgContainer = d3.select("body").append("svg")
.attr("width", 300)
.attr("height", 300)
.style("background-color", "black");
var zoomed = function () {
svgContainer.attr("transform", "translate("+ d3.event.translate + ")
scale(" + d3.event.scale + ")");
};
var zoom = d3.behavior.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed);
.size([width, height]);
var rectangle1 = svgContainer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "red")
.call(zoom);;
var rectangle2 = svgContainer.append("rect")
.attr("x", 100)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "yellow");
var rectangle3 = svgContainer.append("rect")
.attr("x", 200)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "red");
var rectangle4 = svgContainer.append("rect")
.attr("x", 0)
.attr("y", 100)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "yellow");
var rectangle5 = svgContainer.append("rect")
.attr("x", 100)
.attr("y", 100)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "red");
var rectangle6 = svgContainer.append("rect")
.attr("x", 200)
.attr("y", 100)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "yellow");
var rectangle7 = svgContainer.append("rect")
.attr("x", 0)
.attr("y", 200)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "red");
var rectangle8 = svgContainer.append("rect")
.attr("x", 100)
.attr("y", 200)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "yellow");
var rectangle9 = svgContainer.append("rect")
.attr("x", 200)
.attr("y", 200)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "red");
My desired result will be this,
http://bl.ocks.org/mbostock/3680957
The code you provided has several problems:
1. There is a syntax error in definition of zoom (.on("zoom", zoomed);)
2. You haven't defined width and height.
3. zoomed function possibly couldn't be parsed because of wrong line breaks (notice point where you define scale of transformation).
Here is JSFiddle, where zoom works correctly for first element of matrix. Main points is:
// don't forget about width and height
var width = 960,
height = 500;
// make sure that string defining transform attribute is correct. scale isn't a method, but part of string
var zoomed = function () {
svgContainer.attr("transform", "translate("+ d3.event.translate + ")scale(" + d3.event.scale + ")");
};
// don't place semicolon after on("zoom", zoomed)
var zoom = d3.behavior.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed)
.size([width, height]);
// add zoom behaviour to desired rectangle
var rectangle1 = svgContainer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "red")
.call(zoom);

Categories