Related
I am working on a multi-line scatterplot with zoom using d3 v6. I am new to d3 and based on different examples, I could get the zoom function working for the images/points. The problem is that the lines aren't zooming. I looked at many similar questions, but none of those solutions are working for me.
The code I am using:
var margin = {
top: 50,
right: 30,
bottom: 30,
left: 210,
};
var svg = d3.select("svg"),
width = 1410 - margin.left - margin.right,
height = 620 - margin.top - margin.bottom;
svg
.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
d3.csv("CSV_files/NSW_pathway.csv").then(function (data1) {
var groupData = d3.group(data1, (d) => d.pathway_name);
var xScale = d3.scaleLinear().domain([0, 1]).range([0, width]);
var yScale = d3.scaleLinear().domain([0, 1]).range([height, 0]);
var xAxis = d3.axisBottom(xScale).ticks(0).tickSize(-height);
var yAxis = d3.axisLeft(yScale).ticks(0).tickSize(-width);
var gX = svg
.append("g")
.attr(
"transform",
"translate(" + margin.left + "," + (margin.top + height) + ")"
)
.call(xAxis);
var gY = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
var focus = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "line")
.attr("clip-path", "url(#clip)");
const color = d3
.scaleOrdinal()
.range(["#e41a1c", "#377eb8", "#4daf4a", "#984ea3"]);
var points_g = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("clip-path", "url(#clip)")
.classed("points_g", true);
var label = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "label")
.attr("clip-path", "url(#clip)");
var div = d3
.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
const mouseover = function (event, d) {
div.style("opacity", 1);
};
const mousemove = function (event, d) {
div
.html(function (d1) {
if (d.type != "learner")
return `The resource name is ${d.resource_name}`;
else return `This is ${d.name}`;
})
.style("position", "absolute")
.style("left", event.pageX + 15 + "px")
.style("top", event.pageY + 15 + "px");
};
const mouseleave = function (event, d) {
div.transition().duration(200).style("opacity", 0);
};
var points = points_g.selectAll("point").data(data1);
points = points
.enter()
.append("image")
.attr("xlink:href", function (d) {
if (d.type == "video") return "Images/3.jpg";
else if (d.type == "pdf") return "Images/4.png";
else if (d.type == "none") return "Images/5.png";
})
.attr("x", function (d) {
return xScale(+d.x) - 10;
})
.attr("y", function (d) {
return yScale(+d.y) - 10;
})
.attr("width", 20)
.attr("height", 20)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
label
.selectAll(".text")
.data(data1)
.enter()
.append("text")
.text(function (d) {
return d.topic;
})
.attr("x", function (d) {
return xScale(+d.x) + 10;
})
.attr("y", function (d) {
return yScale(+d.y) + 10;
});
focus
.selectAll("line")
.data(groupData)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function (d) {
return color(d[0]);
})
.attr("stroke-width", 1)
.attr("d", function (d) {
return d3
.line()
.curve(d3.curveMonotoneX)
.x(function (d) {
return xScale(+d.x);
})
.y(function (d) {
return yScale(+d.y);
})(d[1]);
});
var zoom = d3
.zoom()
.scaleExtent([0.5, 20])
.extent([
[0, 0],
[width, height],
])
.on("zoom", zoomed);
svg
.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.lower();
svg.call(zoom).call(zoom.transform, d3.zoomIdentity);
function zoomed({ transform }) {
var new_xScale = transform.rescaleX(xScale);
var new_yScale = transform.rescaleY(yScale);
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
points
.data(data1)
.attr("x", function (d) {
return new_xScale(d.x) - 10;
})
.attr("y", function (d) {
return new_yScale(d.y) - 10;
});
label
.selectAll("text")
.data(data1)
.attr("x", function (d) {
return new_xScale(d.x) + 15;
})
.attr("y", function (d) {
return new_yScale(d.y) + 15;
});
focus.selectAll("line").attr("d", function (d) {
return d3
.line()
.curve(d3.curveMonotoneX)
.x(function (d) {
return xScale(+d.x);
})
.y(function (d) {
return yScale(+d.y);
})(d[1]);
});
}
});
A sample of the csv file:
x,y,name,type,topic,resource_name,pathway_name
0,0,start,none,Sponsored Search Markets,Networks Crowd and Markets_NCMch15.pdf,pathwayOne
0,0,start,none,Sponsored Search Markets,Networks Crowd and Markets_NCMch15.pdf,pathwayTwo
0.086511627906977,0.16,horse,pdf,Graphs,Networks Crowd and Markets_NCMch2.pdf,pathwayOne
0.12,0.283768436578171,choice,pdf,Network Centrality,Notes_CGT BASED network CENTRALITY - L2.pdf,pathwayTwo
0.32,0.27217943628424,plex,video,Network Models,Network Analysis_LNch13.pdf,pathwayOne
0.775398773006135,0.33,social,pdf,Clustering,Network Analysis_LNch8.pdf,pathwayTwo
1,1,end,none,Allocation in Networks,Notes_Allocation in networks with DON-L3.pdf,pathwayOne
1,1,end,none,Allocation in Networks,Notes_Allocation in networks with DON-L3.pdf,pathwayTwo
Thank you for your help.
It's not zooming the whole page, it's zooming the whole svg, your large margins extend beyond the charting area. One solution is to add the g element not on your svg but only on your chart area.
But using your code, there are 2 things preventing your lines from zooming.
1: your selection is empty - line is a d3 abstraction that returns a path
function zoomed() {
...
// empty selection
console.log(focus.selectAll('line'))
// try instead
console.log(focus.selectAll('path'))
}
2: Simple mistake - you're using the old scale not the new one
function zoomed() {
...
focus.selectAll('path').attr('d', d => {
return d3.line()
// using old scale
.x(di => xScale(+di.x))
// change to
.x(di => new_xScale(+di.x))
})
}
I don't have a sample of your csv file so this isn't tested, but if you want to zoom the whole chart just add a parent g after your svg and transform that..
...
svg
.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
// NEW - add g
.append('g')
// NEW - adjust scaleExtent to your needs
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', updateChart)
svg.call(zoom)
function updateChart(event) {
svg.attr('transform', event.transform)
}
Note that this also adds pan, but if you only want zoom you can use:
let scale = 1
...
function updateChart(event) {
if(event.transform.k === scale) { return }
svg.attr('transform', event.transform)
scale = event.transform.k
}
I am trying to insert some text inside each bar of the given chart with it's values and I just can't figure it out. I'm still new to d3 and struggle with most of the things.
I also need to have different text inside each bar, dependent on the data.
I've looked up around and found that I need to use the following structure:
<g class="barGroup">
<rect />
<text />
</g>
This is how the chart looks:
This is how I want to achieve:
This is the function which creates the whole chart, the part where I attempt to insert the text is at the end:
function createBarChart(divChartId, receivedData, chartDimensions) {
// Set dimensions
var margin = chartDimensions["margin"];
var width = chartDimensions["width"];
var height = chartDimensions["height"];
// Create Svg and group
var svg = d3.select(divChartId)
.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 + ")");
// Create and append X axis
var xAxis = d3.scaleBand()
.range([0, width])
.domain(receivedData.map(function (d) { return d.answerText; }))
.padding(0.2);
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xAxis))
.selectAll("text")
.attr("class", "font-weight-bold")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
.style("font-size", "1rem");
// Create and append Y axis
var yAxis = d3.scaleLinear()
.domain([0, Math.max.apply(Math, receivedData.map(function (d) { return d.answerCount; }))])
.range([height, 0]);
svg.append("g")
.attr("class", "y-axis")
.call(d3.axisLeft(yAxis)
.ticks(5)
.tickFormat(d3.format("d")));
// create a tooltip
var tooltip = d3.select("body")
.append("div")
.attr("class", "font-weight-bold text-dark px-2 py-2")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("background", "lavender")
.style("border", "1px solid gray")
.style("border-radius", "12px")
.style("text-align", "center")
.style("visibility", "hidden")
.style("visibility", "hidden")
.style("visibility", "hidden")
// Generate random color
var colorObject = generateRandomColorObject();
// Create and append bars, set values to 0 for animation start
svg.selectAll("rect").data(receivedData)
.enter()
.append("g")
.attr("class", "barGroup")
.append("rect")
.attr("x", function (d) { return xAxis(d.answerText); })
.attr("y", function (d) { return yAxis(0); })
.attr("width", xAxis.bandwidth())
.attr("height", function (d) { return height - yAxis(0); })
.attr("fill", colorObject.color)
.style("stroke", colorObject.border)
.style("stroke-width", 1)
.on("mouseover", function (d) { return tooltip.style("visibility", "visible").html(d.answerText + ": " + d.answerCount); })
.on("mousemove", function () { return tooltip.style("top", (event.pageY - 45) + "px").style("left", (event.pageX) + 5 + "px"); })
.on("mouseout", function (d) { return tooltip.style("visibility", "hidden").html(""); });
// Set correct values for animation end
svg.selectAll("rect")
.transition().duration(1500)
.attr("y", function (d) { return yAxis(d.answerCount); })
.attr("height", function (d) { return height - yAxis(d.answerCount); })
.delay(function (d, i) { return (i * 100) });
// Append text to each bar
svg.selectAll("barGroup")
.append("text")
.attr("font-size", "2em")
.attr("color", "black")
.text("Test");
}
In this line you want your selector to look for a class: svg.selectAll("barGroup") should be svg.selectAll(".barGroup"). Then, once you see your text show up, you'll need to position them.
Consider positioning your g.barGroups's first, using .attr('transform', function(d){ return 'translate(...)' }, before you .append('rect') and .append('text'). That way the entire group will be in approximately the right position, and you can make small adjustments to the rect and text.
I create a scatterplot which is defined on the following data (note that only first two fields are currently using for plotting):
var data = [[5,3,"{'text':'word1',size:4},{'text':'word2','size':1}"],
[3,5,"{'text':'word3',size:5},{'text':'word4','size':4}"],
[1,4,"{'text':'word1',size:3},{'text':'word2','size':5},{'text':'word3','size':2}"],
[2,3,"{'text':'word2',size:1},{'text':'word3','size':5}"]];
Next, when we click on each particular point in the scatterplot the application should attach a wordcloud which is defined from words stored in the 3rd field of the data variable. I use Jason Davies's implementation of wordcloud. Currently (for demo purposes), the wordcloud is generating onlyfrom the static data stored in variable frequency_list. The current code is also stored on JSFiddle.
Any idea how to proceed?
var data = [[5,3,"{'text':'word1',size:4},{'text':'word2','size':1}"],
[3,5,"{'text':'word3',size:5},{'text':'word4','size':4}"],
[1,4,"{'text':'word1',size:3},{'text':'word2','size':5},{'text':'word3','size':2}"],
[2,3,"{'text':'word2',size:1},{'text':'word3','size':5}"]];
var margin = {top: 20, right: 15, bottom: 60, left: 60},
width = 500 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d[0]; })])
.range([ 0, width ]);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d[1]; })])
.range([ height, 0 ]);
var chart = d3.select('body')
.append('svg:svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'chart')
var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width)
.attr('height', height)
.attr('class', 'main')
// Draw the x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + height + ')')
.attr('class', 'main axis date')
.call(xAxis);
// draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
main.append('g')
.attr('transform', 'translate(0,0)')
.attr('class', 'main axis date')
.call(yAxis);
var g = main.append("svg:g");
g.selectAll("scatter-dots")
.data(data)
.enter().append("svg:circle")
.attr("cx", function (d,i) { return x(d[0]); } )
.attr("cy", function (d) { return y(d[1]); } )
.attr("r", 5)
.on("mouseover", function(){d3.select(this).style("fill", "red")})
.on("mouseout", function(){d3.select(this).style("fill", "black")});
// FUNCTION TO DISPLAY CIRCLE
g.on('mouseover', function(){
div.style("display", "block")
d3.select("krog").style("fill", "orange");
generate();
});
g.on('mouseout', function(){
//div.style("display", "none")
div.select("svg").remove();
});
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("display", "none");
// Functions to draw wordcloud
var frequency_list = [{"text":"study","size":40},{"text":"motion","size":15},{"text":"forces","size":10},{"text":"electricity","size":15},{"text":"movement","size":10},{"text":"relation","size":5},{"text":"things","size":10},{"text":"force","size":5},{"text":"ad","size":5}];
var color = d3.scale.linear()
.domain([0,1,2,3,4,5,6,10,15,20,100])
.range(["#ddd", "#ccc", "#bbb", "#aaa", "#999", "#888", "#777", "#666", "#555", "#444", "#333", "#222"]);
// Generates wordcloud
function generate(){
d3.layout.cloud().size([800, 300])
.words(frequency_list)
.rotate(0)
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
}
function draw(words) {
d3.select("div").append("svg")
.attr("width", 850)
.attr("height", 350)
.attr("class", "wordcloud")
.append("g")
// without the transform, words words would get cutoff to the left and top, they would
// appear outside of the SVG area
.attr("transform", "translate(320,200)")
.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("fill", function(d, i) { return color(i); })
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
You have a couple of problems here.
First, your data has strings for the words. I changed that for an array of objects:
var data = [[5,3,[{'text':'word1',size:4},{'text':'word2','size':1}]],
[3,5,[{'text':'word3',size:5},{'text':'word4','size':4}]],
[1,4,[{'text':'word1',size:3},{'text':'word2','size':5},{'text':'word3','size':2}]],
[2,3,[{'text':'word2',size:1},{'text':'word3','size':5}]]];
After that, I changed the function draw: instead of appending a new div every time you hover a circle, it just change the div content:
div.append("svg")
.attr("width", 300)
.attr("height", 300)
.attr("class", "wordcloud")
.append("g")
But now comes the most important change:
You are displaying the wordcloud every time the user hover a circle, but you're calling the mouseover for the group element. That way, we cannot access the data bound to each specific circle.
Instead of that, we'll set a variable for the circles:
var circle = g.selectAll("scatter-dots")
.data(data)
.enter()
.append("svg:circle");
Thus, we can get the data for each hovered circle, which is the third element in the array:
circle.on('mouseover', function(d){
div.style("display", "block")
d3.select("krog").style("fill", "orange");
generate(d[2]);//here, d[2] is the third element in the data array
});
And we pass this third element (d[2]) to the function generate as a parameter named thisWords:
function generate(thisWords){
d3.layout.cloud().size([800, 300])
.words(thisWords)
.rotate(0)
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
}
here is your fiddle: https://jsfiddle.net/jwrbps4j/
PS: you'll have to improve the translate for that words.
After plotting a donut chart, I'm trying to add some legend, based on the following example:
http://bl.ocks.org/ZJONSSON/3918369
However, I'm receiving this error:
TypeError: undefined is not an object (evaluating 'n.apply')
I've printed the return of line 131 and all the legend names are printed.
I don't know what's is causing the undefined error print.
This is my main code:
var width = 300,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(colorrange);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var svg = d3.select("#info").attr("align", "center").append("svg")
.attr("class", "piechart")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.attr("data-legend", function(d) {
return d.data.name;
})
.style("fill", function(d, i) {
return color(i);
});
g.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.text(function(d) {
return d.data.label;
});
legend = svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(50,30)")
.style("font-size", "12px")
.call(d3.legend)
And this is a minimal example:
https://jsfiddle.net/g6vyk7t1/12/
You need to upload the code http://bl.ocks.org/ZJONSSON/3918369#d3.legend.js for the legend in your Javascript (just copy-and-paste it the code, it's the function d3.legend).
Long time lurker 1st time poster. I am trying to display the text value form a CSV file when the relevant segment of pie chart is hovered over. I have the pie chart (thanks to Mike Bostock) and the display when hovering but cant remove it on the mouse out. Any help would be greatly appreciated at this stage.
var width = 960,
height = 600,
radius = Math.min(width, height) / 2.5;
var arc = d3.svg.arc()
.outerRadius(radius + 10)
.innerRadius(radius - 70);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var color = d3.scale.ordinal()
.range(["#0bd0d2", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.Total; });
var pieSlice = svg.selectAll("g.slice");
d3.csv("childcare.csv", function(error, data) {
data.forEach(function(d) {
d.population = +d.population;
});
var arcs = svg.selectAll("g.slice")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
arcs.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.place); })
.on("mouseenter", function(d) {
//console.log("mousein")
arcs.append("text")
.attr("transform", arc.centroid(d))
.attr("dy", ".5em")
.style("text-anchor", "middle")
.style("fill", "blue")
.attr("class", "on")
.text(d.data.place);
})
.on("mouseout", function(d) {
console.log("mouseout")
});
});
You can just save your text and remove it on mouseout:
var text;
var arcs = svg.selectAll("g.slice")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
arcs.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.place); })
.on("mouseenter", function(d) {
//console.log("mousein")
text = arcs.append("text")
.attr("transform", arc.centroid(d))
.attr("dy", ".5em")
.style("text-anchor", "middle")
.style("fill", "blue")
.attr("class", "on")
.text(d.data.place);
})
.on("mouseout", function(d) {
text.remove();
});