d3 pattern displacement issue on circles - javascript

http://jsfiddle.net/LsMZp/37/
I'm having issues stabilizing the patterns in this mock.
how do I ensure the image is always centrally aligned - it seems to jump around depending on its location/scale.
here is the code to this issue. Hoping the solution can be applied to this force chart example.
d3.js Force Chart - image/node linkage and animation
function addUserPatterns(patternsSvg, userData){
$.each(userData, function( index, value ) {
var defs = patternsSvg.append('svg:defs');
defs.append('svg:pattern')
.attr('id', "--"+index+"-"+value.userName.toLowerCase())
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', 100)
.attr('height',100)
.append('svg:image')
.attr('xlink:href', value.userImage)
.attr('x', 0)
.attr('y', 0)
.attr('width', 100)
.attr('height', 100);
});
var circle = patternsSvg.append("svg:g")
.selectAll("circle")
.data(userData);
//enter
circle
.enter()
.append("svg:circle")
.attr("r", 50)
.style("fill", function(d, i) {
var imgUrl = "--"+i+"-"+d.userName.toLowerCase();
return "url(#"+imgUrl+")";
})
.attr("cy", function(d){
return random(0, 143);
})
.attr("cx", function(d){
return random(0, 143);
})
function random(min, max){
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
var patternsSvg = d3.select("body")
.append('svg')
.attr('class', 'patterns')
.attr('width', 300)
.attr('height', 300)
.append('g')
.attr("transform","translate(100, 100)")
var userData =[
{
"userName": "Ria",
"userImage" : "https://pbs.twimg.com/profile_images/427892889092231168/4c4Qwynr.png"
},
{
"userName": "Barry",
"userImage" : "https://lh3.googleusercontent.com/-XdASQvEzIzE/AAAAAAAAAAI/AAAAAAAAAls/5vbx7yVLDnc/photo.jpg"
}
]
addUserPatterns(patternsSvg, userData);
I have the images being appended to the circles ok now - but if the images are of different sizes/dimensions - is there a way to ensure the image will be fitted properly?
Is it just an assumption to ensure the images are of the same dimensions as of each other - or is there a more sophisticated way to calculate image width/height and then alter the pattern attributes as required?
http://jsfiddle.net/LsMZp/48/
var defs = patternsSvg.append('svg:defs');
defs.append('svg:pattern')
.attr('id', "--"+index+"-"+value.userName.toLowerCase())
//.attr('patternUnits', 'userSpaceOnUse')
.attr('x', 0)
.attr('y', 0)
.attr('width', 50)
.attr('height', 50)
.append('svg:image')
.attr('xlink:href', value.userImage)
.attr('x', 0)
.attr('y', 0)
.attr('width', 100)
.attr('height', 100);
});

Related

d3js mask to show dots over bar chart

i saw this example here: https://jsfiddle.net/gruc1vod/4/
and i want to add these dots over my bar chart using mask.
Here is my JavaScript code:
var svg = d3.select("body").append("svg");
var dotsPatternDefs = svg.append('defs');
dotsPatternDefs.append('pattern')
.attr('id', 'dotsPattern')
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', 10)
.attr('height', 10)
.append('circle')
.attr('cx', 5)
.attr('cy', 5)
.attr('r', 3)
.style('fill', 'white');
dotsPatternDefs.append('mask')
.attr('id', 'mask-dots')
.append('rect')
.attr('width', '100%')
.attr('height', '100%')
.attr('x', 0)
.attr('y', 0)
.style('fill', 'url(#dotsPattern)');
svg.append('rect')
.attr('class', 'dotsPattern')
.attr('width', '200')
.attr('height', '200')
.attr('x', 0)
.attr('y', 0)
.style('fill', '#F189b2');
Here is my CSS code:
rect.dotsPattern {
mask: url(#mask-dots);
}
and here my live example: https://jsfiddle.net/uao5yfhm/6/
Where is the problem and i cannot see this outcome correct outcome but i see this one wrong outcome?
Solution:
just change the circle color into black and add one more white rectangle in mask.
var svg = d3.select("body").append("svg");
var dotsPatternDefs = svg.append('defs');
dotsPatternDefs.append('pattern')
.attr('id', 'dotsPattern')
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', 10)
.attr('height', 10)
.append('circle')
.attr('cx', 5)
.attr('cy', 5)
.attr('r', 3)
.style('fill', 'black');
let mask = dotsPatternDefs.append('mask').attr('id', 'mask-dots')
mask.append('rect')
.attr('width', '100%')
.attr('height', '100%')
.attr('x', 0)
.attr('y', 0)
.style('fill', 'white');
mask.append('rect')
.attr('width', '100%')
.attr('height', '100%')
.attr('x', 0)
.attr('y', 0)
.style('fill', 'url(#dotsPattern)');
svg.append('rect')
.attr('class', 'dotsPattern')
.attr('width', '200')
.attr('height', '200')
.attr('x', 0)
.attr('y', 0)
.style('fill', '#F189b2');
rect.dotsPattern {
mask: url(#mask-dots);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.12.1/d3.min.js"></script>
working example: https://jsfiddle.net/gspn7a3o/35/
I think you have misunderstood the usage of mask. If you fill the pattern circle into white, it means that "Everything under a white pixel will be visible"(See MDN). So the pink rectangle will be seen through these white circles.
So if you are trying to not see through circles, put them in black("Everything under a black pixel will be invisible") and also give a white rectangle mask to make sure the pink can be seen.
My first answer here, ask me if you have any more questions.

D3 tooltip not appearing near mouse

I want tooltips to appear next to my mouse when it hovers over a node. I tried solutions I found on SO, but so far, only got this solution by Boxun to work, although it's not quite what I had in mind (D3.js: Position tooltips using element position, not mouse position?).
I was wondering why in my listener function,
.on('mousemove', function(d) {})
, the functions
Tooltips
.style("left", d3.mouse(this)[0])
.style("top", (d3.mouse(this)[1]))
or
Tooltips
.style("left", d3.event.pageX + 'px')
.style("top", d3.event.pageY + 'px')
shows up on top of the svg instead of where my mouse is.
From reading the answers to the link above, I think I have to transform my coordinates somehow, but I was not able to get that to work.
Here, I am using d3.event.pageX and my mouse is over cherry node.
import * as d3_base from "d3";
import * as d3_dag from "d3-dag";
const d3 = Object.assign({}, d3_base, d3_dag);
drawDAG({
graph: [
["apples", "banana"],
["cherry", "tomato"],
["cherry", "avocado"],
["squash", "banana"],
["lychee", "cherry"],
["dragonfruit", "mango"],
["tomato", "mango"]
]
})
async function drawDAG(response) {
loadDag(response['graph'])
.then(layoutAndDraw())
.catch(console.error.bind(console));
}
async function loadDag(source) {
const [key, reader] = ["zherebko", d3_dag.dagConnect().linkData(() => ({}))]
return reader(source);
}
function layoutAndDraw() {
const width = 800;
const height = 800;
const d3 = Object.assign({}, d3_base, d3_dag);
function sugiyama(dag) {
const layout = d3.sugiyama()
.size([width, height])
.layering(d3.layeringSimplex())
.decross(d3.decrossOpt())
.coord(d3.coordVert());
layout(dag);
draw(dag);
}
return sugiyama;
function draw(dag) {
// Create a tooltip
const Tooltip = d3.select("root")
.append("div")
.attr("class", "tooltip")
.style('position', 'absolute')
.style("opacity", 0)
.style("background-color", "black")
.style("padding", "5px")
.style('text-align', 'center')
.style('width', 60)
.style('height', 30)
.style('border-radius', 10)
.style('color', 'white')
// This code only handles rendering
const nodeRadius = 100;
const svgSelection = d3.select("root")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `${-nodeRadius} ${-nodeRadius} ${width + 2 * nodeRadius} ${height + 2 * nodeRadius}`);
const defs = svgSelection.append('defs');
const steps = dag.size();
const interp = d3.interpolateRainbow;
const colorMap = {};
dag.each((node, i) => {
colorMap[node.id] = interp(i / steps);
});
// How to draw edges
const line = d3.line()
.curve(d3.curveCatmullRom)
.x(d => d.x)
.y(d => d.y);
// Plot edges
svgSelection.append('g')
.selectAll('path')
.data(dag.links())
.enter()
.append('path')
.attr('d', ({
data
}) => line(data.points))
.attr('fill', 'none')
.attr('stroke-width', 3)
.attr('stroke', ({
source,
target
}) => {
const gradId = `${source.id}-${target.id}`;
const grad = defs.append('linearGradient')
.attr('id', gradId)
.attr('gradientUnits', 'userSpaceOnUse')
.attr('x1', source.x)
.attr('x2', target.x)
.attr('y1', source.y)
.attr('y2', target.y);
grad.append('stop').attr('offset', '0%').attr('stop-color', colorMap[source.id]);
grad.append('stop').attr('offset', '100%').attr('stop-color', colorMap[target.id]);
return `url(#${gradId})`;
});
// Select nodes
const nodes = svgSelection.append('g')
.selectAll('g')
.data(dag.descendants())
.enter()
.append('g')
.attr('width', 100)
.attr('height', 100)
.attr('transform', ({
x,
y
}) => `translate(${x}, ${y})`)
.on('mouseover', function(d) {
Tooltip
.style('opacity', .8)
.text(d.id)
})
.on('mouseout', function(d) {
Tooltip
.style('opacity', 0)
})
.on('mousemove', function(d) {
var matrix = this.getScreenCTM()
.translate(+this.getAttribute("cx"), +this.getAttribute("cy"));
Tooltip
.html(d.id)
.style("left", (window.pageXOffset + matrix.e - 50) + "px")
.style("top", (window.pageYOffset + matrix.f - 60) + "px");
})
// Plot node circles
nodes.append('rect')
.attr('y', -30)
.attr('x', (d) => {
return -(d.id.length * 15 / 2)
})
.attr('rx', 10)
.attr('ry', 10)
.attr('width', (d) => {
return d.id.length * 15;
})
.attr('height', (d) => 60)
.attr('fill', n => colorMap[n.id])
// Add text to nodes
nodes.append('text')
.text(d => {
let id = '';
d.id.replace(/_/g, ' ').split(' ').forEach(str => {
if (str !== 'qb')
id += str.charAt(0).toUpperCase() + str.substring(1) + '\n';
});
return id;
})
.attr('font-size', 25)
.attr('font-weight', 'bold')
.attr('font-family', 'sans-serif')
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.attr('fill', 'white')
.attr();
}
}
You can try using this instead, this will make sure that the tooltip is displayed on the exact mouse position.
d3.event.offsetY

Transition Panels Sequentially Instead of all together (D3)

I have a bunch of square grids being constructed using two while loops. I am attempting to make them fade in individually, one at a time using the .transition function. However, I am noticing that all are transitioning in together at once. I realize D3 is asynchronous, but I am wondering if there is any way to override this.
See snippet below and link for fiddle: https://jsfiddle.net/nxtjddvr/
Thanks!
var x = 0;
var y = 1;
var xLoc = 0;
var yLoc = 100;
while (x < 3) {
svg.append('rect')
.transition()
.delay(function(d,i) {
return i*2000
})
.duration(5000)
.attr('width', '100')
.attr('height', '100')
.attr('x', xLoc)
.attr('y', 0)
.style('stroke', 'white' )
while (y < 3) {
svg.append('rect')
.transition()
.duration(5000)
.attr('id', 'trans')
.attr('width', '100')
.attr('height', '100')
.attr('x', xLoc)
.attr('y', yLoc)
.style('stroke', 'white' )
yLoc += 100;
y++;
}
yLoc = 100;
y=1;
xLoc += 100
x++;
}
You don't need loops in d3 and you don't need window timers, here is a rough idea of how to use d3 to do what you want...
var data = [32, 57, 112];
var height = 300;
var width = 300;
d3.select('#chart')
.append('svg')
.style('background-color', 'lightgrey')
var svg=d3.select('svg')
.attr('height', height)
.attr('width', width)
svg.selectAll("rect")
.data([1, 2, 3, 1, 2, 3, 1, 2, 3])
.enter().append('rect')
.attr('id', 'trans')
.attr('width', '100')
.attr('height', '100')
.attr('x', function(d, i){
return (d-1)*100
})
.attr('y', function(d,i){
return Math.floor((i/3))*100
})
.attr("opacity",0)
.style('stroke', 'white' )
.transition()
.delay(function(d,i) {
return i*2000
})
.duration(5000)
.attr("opacity",1)
You could use a sort of recursive setTimeout approach. The idea is this: fade out, wait, fade out, wait, etc. Until you're done.
Something like this:
var elementCount = 3;
function chainReaction() {
// remove your element here
elementCount--;
if(elementCount > 0) {
window.setTimeout(function(){
chainReaction();
}, 500);
}
}
chainReaction();
Here's a quick JSFiddle that does it: http://jsfiddle.net/dgrundel/5poes17s/2/

D3 Plot array of circles

I've tried the circle plot example as the following:
var x=20, y=20, r=50;
var sampleSVG = d3.select("#viz")
.append("svg")
.attr("width", 800)
.attr("height", 600);
sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", r)
.attr("cx", x)
.attr("cy", y);
But I want to figure out how to plot without a loop a sequence of circles from an array like:
data = [
[10,20,30],
[20,30,15],
[30,10,25]
];
Maybe this example could help?
var data = [
[10,20,30],
[20,30,15],
[30,10,25]
];
var height = 300,
width = 500;
var svg = d3.select('body').append('svg')
.attr('height', height)
.attr('width', width)
.append('g')
.attr('transform', 'translate(30, 30)');
// Bind each nested array to a group element.
// This will create 3 group elements, each of which will hold 3 circles.
var circleRow = svg.selectAll('.row')
.data(data)
.enter().append('g')
.attr('transform', function(d, i) {
return 'translate(30,' + i * 60 + ')';
});
// For each group element 3 circle elements will be appended.
// This is done by binding each element in a nested array to a
// circle element.
circleRow.selectAll('.circle')
.data(function(d, i) { return data[i]; })
.enter().append('circle')
.attr('r', function(d) { return d; })
.attr('cx', function(d, i) { return i * 60; })
.attr('cy', 0);
Live Fiddle

Fit Text into SVG Element (Using D3/JS)

I want to write text inside a rectangle I create as follows:
body = d3.select('body')
svg = body.append('svg').attr('height', 600).attr('width', 200)
rect = svg.append('rect').transition().duration(500).attr('width', 150)
.attr('height', 100)
.attr('x', 40)
.attr('y', 100)
.style('fill', 'white')
.attr('stroke', 'black')
text = svg.append('text').text('This is some information about whatever')
.attr('x', 50)
.attr('y', 150)
.attr('fill', 'black')​
However, as you can see (http://jsfiddle.net/Tmj7g/3/) the text gets cut off. Any nifty ways to write a paragraph inside of the svg rectangle created? Thanks,
The answer to this question might be relevant. SVG provides no way of wrapping text automatically, but you can embed HTML within SVGs and then use a div for example.
I've updated the jsfiddle here, but it doesn't work that well together with the animation. If you want to make it work properly and behave like any other SVG element, you'll have to pre-compute the line breaks and insert them manually.
To make it work with the animations just enclose in a group element and animate that one instead.
http://jsfiddle.net/MJJEc/
body = d3.select('body')
svg = body.append('svg')
.attr('height', 600)
.attr('width', 200);
var g = svg.append('g').attr("transform" ,"scale(0)");
rect = g.append('rect')
.attr('width', 150)
.attr('height', 100)
.attr('x', 40)
.attr('y', 100)
.style('fill', 'none')
.attr('stroke', 'black')
text = g.append('foreignObject')
.attr('x', 50)
.attr('y', 130)
.attr('width', 150)
.attr('height', 100)
.append("xhtml:body")
.html('<div style="width: 150px;">This is some information about whatever</div>')
g.transition().duration(500).attr("transform" ,"scale(1)");
I had a similar issue and found a reasonable solution by calculating the width of my box.
Secondly, I figured out that on average the character width for my current font is about 8.
Next I simply do a substring on the text to be displayed.
That seems to work perfectly in most cases.
var rectText = rectangles.append("text")
.text(function(d) {
TextBoxLength = timeScale(dateFormat.parse(d.endTime)) - timeScale(dateFormat.parse(d.startTime));
return d.task.substring(0, Math.floor(TextBoxLength / 8));
})
.attr("x", function(d) {
return (timeScale(dateFormat.parse(d.endTime)) - timeScale(dateFormat.parse(d.startTime))) / 2 + timeScale(dateFormat.parse(d.startTime)) + theSidePad;
})
.attr("y", function(d, i) {
return d.position * theGap + 14 + theTopPad;
})
.attr("font-size", 12)
.attr("text-anchor", "middle")
.attr("text-height", theBarHeight)
.attr("fill", "#000000");
Another approach, when trying to fit a straight line of text into an svg element, could use the strategy found in http://bl.ocks.org/mbostock/1846692:
node.append("text")
.text(function(d) { return d.name; })
.style("font-size", function(d) { return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 24) + "px"; })
.attr("dy", ".35em");

Categories

Resources