D3 Text Slice based on the width of rect - javascript
I am building a tree map with D3 v4 and all good so far. However, some of the text within their respective rectangles goes out over the edge of the rectangle. I want to use text slice to cut off the text if it does this, and instead put in three dots.
As a test, I have been able to get the slice function to truncate text that goes beyond let's say 5 characters, but when I try to specify that I want the slice function to truncate based on the width of the corresponding rectangle, it doesn't work on all except one (which I think is because it goes out over the edge of the whole tree map.
I can't seem to find a way to pull in the width of the rectangles to the slice function in order to compare it to the width of the text.
// set the dimensions and margins of the graph
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 945 - margin.left - margin.right,
height = 1145 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Read data
d3.csv('https://raw.githubusercontent.com/rootseire/survey/main/treemap-data.csv', function(data) {
// stratify the data: reformatting for d3.js
var root = d3.stratify()
.id(function(d) {
return d.name; }) // Name of the entity (column name is name in csv)
.parentId(function(d) { return d.parent; }) // Name of the parent (column name is parent in csv)
(data);
root.sum(function(d) { return +d.value }) // Compute the numeric value for each entity
// Then d3.treemap computes the position of each element of the hierarchy
// The coordinates are added to the root object above
d3.treemap()
.size([width, height])
.padding(4)
(root)
// use this information to add rectangles:
svg
.selectAll("rect")
.data(root.leaves())
.enter()
.append("rect")
.attr('x', function (d) { return d.x0; })
.attr('y', function (d) { return d.y0; })
.attr('width', function (d) { return d.x1 - d.x0; })
.attr('height', function (d) { return d.y1 - d.y0; })
.style("stroke", "black")
.style("fill", "#94C162")
.attr("class", "label")
.on("mouseover", function(d) {
tip.style("opacity", 1)
.html("Genre: " + d.data.name + "<br/> Number: " + d.value + "<br/>")
.style("left", (d3.event.pageX-25) + "px")
.style("top", (d3.event.pageY-25) + "px")
})
.on("mouseout", function(d) {
tip.style("opacity", 0)
});
svg
.selectAll("text")
.data(root.leaves())
.enter()
.append("text")
.attr("x", function(d){ return d.x0+6}) // +10 to adjust position (more right)
.attr("y", function(d){ return d.y0+15}) // +20 to adjust position (lower)
.attr('dy', 0) // here
.text(function(d){ return d.data.name + ' (' + d.data.value +')' })
.attr("font-size", "15px")
.attr("fill", "black")
.each(slice);
})
// Define the div for the tooltip
var tip = d3.select("#my_dataviz").append("div")
.attr("class", "tooltip")
.style("opacity", 0)
// Add events to circles
d3.selectAll('.label')
.attr("x", function(t) {
return Math.max(0, 100-this.textLength.baseVal.value);
});
function slice(d) {
var self = d3.select(this),
textLength = self.node().getComputedTextLength(),
text = self.text();
while (textLength > text.getBoundingClientRect().width && text.length > 0) {
text = text.slice(0, 5);
self.text(text + '...');
textLength = self.node().getComputedTextLength();
}
}
.tooltip {
position: absolute;
pointer-events: none;
background: #000;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<!DOCTYPE html>
<head>
<script type="text/javascript" src="https://raw.githubusercontent.com/rootseire/survey/main/word_wrap.js"></script>
</head>
<meta charset="utf-8">
<body>
<div id="my_dataviz"></div>
</body>
</html>
Any help greatly appreciated.
Related
D3: tooltip displaying [object,Object]. Not able to access values to display in tooltip
I'm trying to display values from a dictionary in d3. I'm using two different datasets. One for constructing a Treemap, and the other for displaying information in a hovering tooltip. I've gotten to where the user can hover over the treemap and the display() function can access the object using an element id. But when I run index.html the display shows [Object,object] for each of the 5 values in the key. Treemap The data I want to display in the tooltip is shown in the console Console How can I access the values from this dictionary to display in the tooltip? function main() { // set dimensions and margins of the graph var margin = {top: 10, right: 10, bottom: 10, left: 10}, width = 645 - margin.left - margin.right, height = 645 - margin.top - margin.bottom; // append the svg object to the body of the page var svg = d3.select("#my_dataviz") .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 + ")"); d3.queue() .defer(d3.csv, "data/Genre Average Score Treemap Data.csv") .defer(d3.csv, "data/Top Five Albums Each Genre.csv") .await(function(error, file1, file2) { if (error) { console.error('Oh dear, something went wrong: ' + error); } else { makeTreemap(file1,file2); } }); function makeTreemap(treeData,toolData) { // stratify the data: reformatting for d3.js var root = d3.stratify() .id(function(d) { return d.genre; }) // name of the entity .parentId(function(d) { return d.parent; }) // name of the parent (column name is parent in csv) (treeData); root.sum(function(d) { return +d.score }) // compute the numeric value for each entity // d3.treemap computes the position of each element of the hierarchy // the coordinates are added to the root object above d3.treemap() .size([width, height]) .padding(4) (root) let leafs = root.leaves() var entries = d3.nest() .key(function(d) { return d.genre; }) .entries(toolData); // d.id to get genre name // console.log(leafs); // mapped each key from each object in nested array var eachKey = d3.values(entries).map(function(d){ return d.values[0].genre }); // mapped each values from each object in the nested array var eachValues = d3.values(entries).map(function(d){ return d.values }); function makeDict(keys, values) { let x = {} for (let i=0; i < keys.length; i++){ tools[keys[i]] = values[i]; } return x; } const toolDisplay = makeDict(eachKey, eachValues); function display(elem) { // elem.id to get genre string // console.log(elem.id) // toolDisplay which gives the values from each genre console.log(toolDisplay[elem.id]); return toolDisplay[elem.id]; } // use the above information to add rectangles: svg .selectAll("rect") .data(leafs) .enter() .append("rect") .attr('x', function(d) {return d.x0; }) .attr('y', function(d) {return d.y0; }) .attr('width', function(d) {return d.x1 - d.x0; }) .attr('height', function(d) {return d.y1 - d.y0; }) .style("stroke", "black") .style("fill", "#FBFFF1") .on('mouseover', function(d) { d3.select(this) .style("fill", "#DE9E36") display(d); }) .on("mouseout", function() { d3.select(this) .style("fill", "#FBFFF1"); }); // create variable for rounding review score 2 decimal places const f = d3.format(".2f"); // add text labels svg .selectAll("text") .data(root.leaves()) .enter() .append("text") .attr('x', function(d) { return d.x0+10 }) // +10 to adjust position (more right) .attr('y', function(d) { return d.y0+20 }) // +20 to adjust position (lower) .text(function(d){ return d.data.genre + "\r\n " + f(+d.data.score) }) .attr("font-size", "16px") .attr("fill", "black") } } main() <!DOCTYPE html> <meta charset="utf-8"> <!-- load d3.js --> <script src="https://d3js.org/d3.v4.js"></script> <script src="https://d3js.org/queue.v1.min.js" type="text/javascript"></script> <!-- Create div where the graph takes place --> <div id="my_dataviz"></div> <body> <script src="treemap2.js"></script> </body>
D3 Hierarchical Bar Chart - Change X and Y axis values
Currently, in case of D3 Hierarchical Bar Chart, Each blue bar represents a folder, whose length encodes the total size of all files in that folder (and all subfolders). I want that the blue bar should encode the length (not size) of the total no. of items within it. For example, in below example if I click on 'vis', it has 7 items within it. Therefore, the the length of the bar for 'vis' should be 7. Similarly for all items in all folders. Here's the link - https://bl.ocks.org/mbostock/1283663 <!DOCTYPE html> <html> <head> <style> text { font: 10px sans-serif; } rect.background { fill: white; } .axis { shape-rendering: crispEdges; } .axis path, .axis line { fill: none; stroke: #000; } </style> <title>D3</title> </head> <body> <script src="http://d3js.org/d3.v3.js"></script> <script> var margin = {top: 30, right: 30, bottom: 40, left: 50}, width = window.innerWidth - margin.left - margin.right-100, height = window.innerHeight - margin.top - margin.bottom-100; var y = d3.scale.linear() .range([height, 0]); var barHeight = 20; var color = d3.scale.ordinal() .range(["steelblue", "#ccc"]); var duration = 750, delay = 25; var partition = d3.layout.partition() .value(function(d) { return d.size; }); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var svg = d3.select("body").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 + ")"); svg.append("rect") .attr("class", "background") .attr("width", width) .attr("height", height) .on("click", up); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0, " + height + ")") .append("line") .attr("x1", "100%"); svg.append("g") .attr("class", "y axis"); d3.json("https://jsonblob.com/api/f577d19c-0f2b-11e7-a0ba-09040711ce47", function(error, root) { if (error) throw error; partition.nodes(root); y.domain([0, root.value]).nice(); down(root, 1000); }); function down(d,i) { if (!d.children || this.__transition__) return; var end = duration + d.children.length * delay; // Mark any currently-displayed bars as exiting. var exit = svg.selectAll(".enter") .attr("class", "exit"); // Entering nodes immediately obscure the clicked-on bar, so hide it. exit.selectAll("rect").filter(function(p) { return p === d; }) .style("fill-opacity", 1e-6); // Enter the new bars for the clicked-on data. // Per above, entering bars are immediately visible. var enter = bar(d) .attr("transform", stack(i)) .style("opacity", 1); // Have the text fade-in, even though the bars are visible. // Color the bars as parents; they will fade to children if appropriate. enter.select("text").style("fill-opacity", 1e-6); enter.select("rect").style("fill", color(true)); // Update the x-scale domain. y.domain([0, d3.max(d.children, function(d) { return d.value; })]).nice(); // Update the x-axis. svg.selectAll(".y.axis").transition() .duration(duration) .call(yAxis); // Transition entering bars to their new position. var enterTransition = enter.transition() .duration(duration) .delay(function(d, i) { return i * delay; }) .attr("transform", function(d, i) { return "translate(" + barHeight * i * 2.5 + "," + 0 + ")"; }); // Transition entering text. enterTransition.select("text") .style("fill-opacity", 1); // Transition entering rects to the new y-scale. enterTransition.select("rect") .attr("y", function(d) { return y(d.value); }) .attr("height", function(d) { return height - y(d.value); }) .style("fill", function(d) { return color(!!d.children); }); // Transition exiting bars to fade out. var exitTransition = exit.transition() .duration(duration) .style("opacity", 1e-6) .remove(); // Transition exiting bars to the new y-scale. exitTransition.selectAll("rect") .attr("height", function(d) { return height - y(d.value); }); // Rebind the current node to the background. svg.select(".background") .datum(d) .transition() .duration(end); d.index = i; } function up(d) { if (!d.parent || this.__transition__) return; var end = duration + d.children.length * delay; // Mark any currently-displayed bars as exiting. var exit = svg.selectAll(".enter") .attr("class", "exit"); // Enter the new bars for the clicked-on data's parent. var enter = bar(d.parent) .attr("transform", function(d, i) { return "translate(" + barHeight * i * 2.5 + "," + 0 + ")"; }) .style("opacity", 1e-6); // Color the bars as appropriate. // Exiting nodes will obscure the parent bar, so hide it. enter.select("rect") .style("fill", function(d) { return color(!!d.children); }) .filter(function(p) { return p === d; }) .style("fill-opacity", 1e-6); // Update the y-scale domain. y.domain([0, d3.max(d.parent.children, function(d) { return d.value; })]).nice(); // Update the y-axis. svg.selectAll(".y.axis").transition() .duration(duration) .call(yAxis); // Transition entering bars to fade in over the full duration. var enterTransition = enter.transition() .duration(end) .style("opacity", 1); // Transition entering rects to the new y-scale. // When the entering parent rect is done, make it visible! enterTransition.select("rect") .attr("height", function(d) { return height - y(d.value); }) .attr("y", function(d) { return y(d.value); }) .each("end", function(p) { if (p === d) d3.select(this).style("fill-opacity", null); }); // Transition exiting bars to the parent's position. var exitTransition = exit.selectAll("g").transition() .duration(duration) .delay(function(d, i) { return i * delay; }) .attr("transform", stack(d.index)); // Transition exiting text to fade out. exitTransition.select("text") .style("fill-opacity", 1e-6); // Transition exiting rects to the new scale and fade to parent color. exitTransition.select("rect") .attr("height", function(d) { return height - y(d.value); }) .attr("y", function(d) { return y(d.value); }) .style("fill", color(true)); // Remove exiting nodes when the last child has finished transitioning. exit.transition() .duration(end) .remove(); // Rebind the current parent to the background. svg.select(".background") .datum(d.parent) .transition() .duration(end); } // Creates a set of bars for the given data node, at the specified index. function bar(d) { var bar = svg.insert("g", ".x.axis") .attr("class", "enter") .attr("transform", "translate(15,0)") .selectAll("g") .data(d.children) .enter().append("g") .style("cursor", function(d) { return !d.children ? null : "pointer"; }) .on("click", down); bar.append("text") .attr("x", barHeight / 2) .attr("y", height + 10) .attr("dy", ".35em") .style("text-anchor", "start") .text(function(d) { return d.name; }) .attr("transform", "rotate(45 " + (barHeight / 2) + " " + (height + 10) + ")") bar.append("rect") .attr("width", barHeight) .attr("height", function(d) { return height - y(d.value); }) .attr("y", function(d) { return y(d.value); }); return bar; } // A stateful closure for stacking bars horizontally. function stack(i) { var y0 = 0; return function(d) { var tx = "translate(" + barHeight * i * 1.5 + "," + y0 + ")"; y0 += y(d.value); return tx; }; }
bar.append("rect") .attr("width", barHeight) .attr("height", function(d) { return height - y(d.value); }) the d.value defines the number that is used to display. if you need something else, pick the property of the object (d) that is relevant to you.
Make responsive D3 Hierarchical bar chart and show tooltip
Hi I want to make the D3 chart as Responsive and show tool-tip on the Hierarchical bar chart. The following code is used to responsive for my chart but this not is not working window.onresize = function () { debugger; d3.select('svg>g').remove(); down(d,i); bar(d); up(d); svg.select(".background") .datum(d) .transition() .duration(end); d.index = i; down(root, 1000); } can any one tell me your suggestions. <!DOCTYPE html> <html> <head> <style> text { font: 10px sans-serif; } rect.background { fill: white; } .axis { shape-rendering: crispEdges; } .axis path, .axis line { fill: none; stroke: #000; } </style> <title>D3</title> </head> <body> <script src="http://d3js.org/d3.v3.js"></script> <script> var margin = {top: 30, right: 30, bottom: 40, left: 50}, width = window.innerWidth - margin.left - margin.right-100, height = window.innerHeight - margin.top - margin.bottom-100; var y = d3.scale.linear() .range([height, 0]); var barHeight = 20; var color = d3.scale.ordinal() .range(["steelblue", "#ccc"]); var duration = 750, delay = 25; var partition = d3.layout.partition() .value(function(d) { return d.size; }); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var svg = d3.select("body").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 + ")"); svg.append("rect") .attr("class", "background") .attr("width", width) .attr("height", height) .on("click", up); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0, " + height + ")") .append("line") .attr("x1", "100%"); svg.append("g") .attr("class", "y axis"); d3.json("https://jsonblob.com/api/f577d19c-0f2b-11e7-a0ba-09040711ce47", function(error, root) { if (error) throw error; partition.nodes(root); y.domain([0, root.value]).nice(); down(root, 1000); }); function down(d,i) { if (!d.children || this.__transition__) return; var end = duration + d.children.length * delay; // Mark any currently-displayed bars as exiting. var exit = svg.selectAll(".enter") .attr("class", "exit"); // Entering nodes immediately obscure the clicked-on bar, so hide it. exit.selectAll("rect").filter(function(p) { return p === d; }) .style("fill-opacity", 1e-6); // Enter the new bars for the clicked-on data. // Per above, entering bars are immediately visible. var enter = bar(d) .attr("transform", stack(i)) .style("opacity", 1); // Have the text fade-in, even though the bars are visible. // Color the bars as parents; they will fade to children if appropriate. enter.select("text").style("fill-opacity", 1e-6); enter.select("rect").style("fill", color(true)); // Update the x-scale domain. y.domain([0, d3.max(d.children, function(d) { return d.value; })]).nice(); // Update the x-axis. svg.selectAll(".y.axis").transition() .duration(duration) .call(yAxis); // Transition entering bars to their new position. var enterTransition = enter.transition() .duration(duration) .delay(function(d, i) { return i * delay; }) .attr("transform", function(d, i) { return "translate(" + barHeight * i * 2.5 + "," + 0 + ")"; }); // Transition entering text. enterTransition.select("text") .style("fill-opacity", 1); // Transition entering rects to the new y-scale. enterTransition.select("rect") .attr("y", function(d) { return y(d.value); }) .attr("height", function(d) { return height - y(d.value); }) .style("fill", function(d) { return color(!!d.children); }); // Transition exiting bars to fade out. var exitTransition = exit.transition() .duration(duration) .style("opacity", 1e-6) .remove(); // Transition exiting bars to the new y-scale. exitTransition.selectAll("rect") .attr("height", function(d) { return height - y(d.value); }); // Rebind the current node to the background. svg.select(".background") .datum(d) .transition() .duration(end); d.index = i; } function up(d) { if (!d.parent || this.__transition__) return; var end = duration + d.children.length * delay; // Mark any currently-displayed bars as exiting. var exit = svg.selectAll(".enter") .attr("class", "exit"); // Enter the new bars for the clicked-on data's parent. var enter = bar(d.parent) .attr("transform", function(d, i) { return "translate(" + barHeight * i * 2.5 + "," + 0 + ")"; }) .style("opacity", 1e-6); // Color the bars as appropriate. // Exiting nodes will obscure the parent bar, so hide it. enter.select("rect") .style("fill", function(d) { return color(!!d.children); }) .filter(function(p) { return p === d; }) .style("fill-opacity", 1e-6); // Update the y-scale domain. y.domain([0, d3.max(d.parent.children, function(d) { return d.value; })]).nice(); // Update the y-axis. svg.selectAll(".y.axis").transition() .duration(duration) .call(yAxis); // Transition entering bars to fade in over the full duration. var enterTransition = enter.transition() .duration(end) .style("opacity", 1); // Transition entering rects to the new y-scale. // When the entering parent rect is done, make it visible! enterTransition.select("rect") .attr("height", function(d) { return height - y(d.value); }) .attr("y", function(d) { return y(d.value); }) .each("end", function(p) { if (p === d) d3.select(this).style("fill-opacity", null); }); // Transition exiting bars to the parent's position. var exitTransition = exit.selectAll("g").transition() .duration(duration) .delay(function(d, i) { return i * delay; }) .attr("transform", stack(d.index)); // Transition exiting text to fade out. exitTransition.select("text") .style("fill-opacity", 1e-6); // Transition exiting rects to the new scale and fade to parent color. exitTransition.select("rect") .attr("height", function(d) { return height - y(d.value); }) .attr("y", function(d) { return y(d.value); }) .style("fill", color(true)); // Remove exiting nodes when the last child has finished transitioning. exit.transition() .duration(end) .remove(); // Rebind the current parent to the background. svg.select(".background") .datum(d.parent) .transition() .duration(end); } // Creates a set of bars for the given data node, at the specified index. function bar(d) { var bar = svg.insert("g", ".x.axis") .attr("class", "enter") .attr("transform", "translate(15,0)") .selectAll("g") .data(d.children) .enter().append("g") .style("cursor", function(d) { return !d.children ? null : "pointer"; }) .on("click", down); bar.append("text") .attr("x", barHeight / 2) .attr("y", height + 10) .attr("dy", ".35em") .style("text-anchor", "start") .text(function(d) { return d.name; }) .attr("transform", "rotate(45 " + (barHeight / 2) + " " + (height + 10) + ")") bar.append("rect") .attr("width", barHeight) .attr("height", function(d) { return height - y(d.value); }) .attr("y", function(d) { return y(d.value); }); return bar; } // A stateful closure for stacking bars horizontally. function stack(i) { var y0 = 0; return function(d) { var tx = "translate(" + barHeight * i * 1.5 + "," + y0 + ")"; y0 += y(d.value); return tx; }; } window.onresize = function () { debugger; d3.select('svg>g').remove(); down(d,i); bar(d); up(d); svg.select(".background") .datum(d) .transition() .duration(end); d.index = i; down(root, 1000); } </script> </body> </html>
To make d3 chart responsive just redraw the chart on browser resize: d3.select(window).on('resize', function() { _this.redrawGraph(); //your function that draws the graph });
Animate position of svg rect on transition
Edit: here is an example Fiddle: https://jsfiddle.net/3c9dtLyh/6/ I have a layout that I am attempting to animate to compare the arrangement on two different dates. What I would like to accomplish is a transition where items whose x,y position is different on the second date smoothly fly to the new position. I have attempted to do this using an updateData function set to trigger onclick. The layout looks like this: I do not neccesarily expect this approach to work because how would the transition know which (x,y) pairs correspond to the correct item name in the new arrangement. What am I missing about how these transitions work and how could I improve my approach? Here is the code I am using. It's a relatively simple sequence of appending and svg element, drawing the rectangles, then (failing) to update their position on click. <!DOCTYPE html> <meta charset="utf-8"> <style> body { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .dot { stroke: #000; } </style> <body> <div id = "chart"> </div> <div id = "select_params"> <input name="updateButton" type="button" value="Update" onclick="updateData()" /> </div> </body> <!-- load js libraries --> <script src="https://d3js.org/d3.v4.min.js"></script> <!-- uses v4 of d3 --> <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script> <!-- need to use this older version for tipsy --> <script type="text/javascript" src="jquery.tipsy.js"></script> <!-- load from locally hosted source code --> <!-- build the visualization --> <script type='text/javascript'> var item_width = 40, item_height = 60; var margin = {top: 20, right: 50, bottom: 75, left: 40}, width = 700 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.scaleLinear() .range([0, width]); var y = d3.scaleLinear() .range([height, 0]); var color = d3.scaleOrdinal(d3.schemeCategory10); var svg = d3.select("#chart").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 + ")"); d3.csv("http://localhost:8080/udacity_test_vis_1/combined_item_pos.csv", function(data) { // cast string to numeric data.forEach(function(d) { d.x_pos = +d.x_pos; d.y_pos = +d.y_pos; d.sales = +d.sales; }); console.log(data); var x_offset = 5, y_offset = 5; x.domain(d3.extent(data, function(d) { return d.x_pos; })); // set the x domain y.domain(d3.extent(data, function(d) { return d.y_pos; })); // set the y domain svg.selectAll("g") .data(data) .enter() .append("rect") .filter(function(d){ return d.date == '1-20-2017'}) .attr("class", "dot") .attr("width", item_width) .attr("height", item_height) .attr("x", function(d) { return x(d.x_pos) + x_offset; }) // x position of dots .attr("y", function(d) { return y(d.y_pos) + y_offset; }) // y position of dots .attr("rx", 5) .attr("ry", 5) .style("fill", "#1f5fc6") // color factor variable .style("fill-opacity", 0.5); svg.selectAll("g") .data(data) .enter() .append("text") .filter(function(d){ return d.date == '1-20-2017'}) .attr("x", function(d) { return x(d.x_pos) + item_width/2 + x_offset; }) .attr("y", function(d) { return y(d.y_pos) + item_height/2 + y_offset; }) .attr("font-size", 10) .attr("text-anchor", "middle") .attr("fill", "black") .text(function(d){ return d.item_name}); }); function updateData() { // grab the data again d3.csv("http://localhost:8080/udacity_test_vis_1/combined_item_pos.csv", function(data) { // cast string to numeric data.forEach(function(d) { d.x_pos = +d.x_pos; d.y_pos = +d.y_pos; d.sales = +d.sales; }); var svg = d3.select("#chart").transition(); svg.selectAll("g") .data(data) .enter() .append("rect") .filter(function(d){ return d.date == '2-10-2017'}) .attr("class", "dot") .attr("width", item_width) .attr("height", item_height) .attr("x", function(d) { return x(d.x_pos) + x_offset; }) // x position of dots .attr("y", function(d) { return y(d.y_pos) + y_offset; }) // y position of dots .attr("rx", 5) .attr("ry", 5) .style("fill", "#1f5fc6") // color factor variable .style("fill-opacity", 0.5); svg.selectAll("g") .data(data) .enter() .append("text") .filter(function(d){ return d.date == '2-10-2017'}) .attr("x", function(d) { return x(d.x_pos) + item_width/2 + x_offset; }) .attr("y", function(d) { return y(d.y_pos) + item_height/2 + y_offset; }) .attr("font-size", 10) .attr("text-anchor", "middle") .attr("fill", "black") .text(function(d){ return d.item_name}); }); } </script> Here is my data: ,x_pos,y_pos,item_name,sales,date 0,1,1,S8221,2022,1-20-2017 1,2,1,NLC11,518,1-20-2017 2,3,1,35UUY,1614,1-20-2017 3,4,1,PPTNV,1059,1-20-2017 4,5,1,G0CWS,2183,1-20-2017 5,6,1,3JHUA,2513,1-20-2017 6,7,1,4HXGA,2251,1-20-2017 7,8,1,RYM9K,2330,1-20-2017 8,9,1,T8PUB,1476,1-20-2017 9,10,1,PLULW,1225,1-20-2017 10,1,2,YJ6S0,2403,1-20-2017 11,2,2,E9RGD,1361,1-20-2017 12,3,2,E2SW4,1131,1-20-2017 13,4,2,BZPGX,698,1-20-2017 14,5,2,0K682,1855,1-20-2017 15,6,2,D8UZW,2371,1-20-2017 16,7,2,USKY7,1851,1-20-2017 17,8,2,D0L0Y,1767,1-20-2017 18,9,2,P1AGP,1025,1-20-2017 19,10,2,9LT7O,1380,1-20-2017 20,1,3,1J184,1108,1-20-2017 21,2,3,RJDEG,2106,1-20-2017 22,3,3,LTSLR,1980,1-20-2017 23,4,3,ET3DF,2700,1-20-2017 24,5,3,42W1W,2194,1-20-2017 25,6,3,5QTJN,958,1-20-2017 26,7,3,O8XKY,2381,1-20-2017 27,8,3,LS9NW,516,1-20-2017 28,9,3,0MPZ7,2198,1-20-2017 29,10,3,R4E3J,2494,1-20-2017 30,1,4,WFPPY,2349,1-20-2017 31,2,4,MT2DB,2525,1-20-2017 32,3,4,6DRYS,600,1-20-2017 33,4,4,NVV0S,1556,1-20-2017 34,5,4,ODGZ2,912,1-20-2017 35,6,4,E3NLS,931,1-20-2017 36,7,4,9FFZ7,722,1-20-2017 37,8,4,UKZGF,2170,1-20-2017 38,9,4,XXORI,896,1-20-2017 39,10,4,QYU9Q,1104,1-20-2017 40,1,5,4KQPU,1562,1-20-2017 41,2,5,S3AYK,2298,1-20-2017 42,3,5,5W3CE,2580,1-20-2017 43,4,5,T0S7H,1677,1-20-2017 44,5,5,02SJG,1972,1-20-2017 45,6,5,GBMNZ,1845,1-20-2017 46,7,5,2Y7KH,982,1-20-2017 47,8,5,3WMOL,1952,1-20-2017 48,9,5,93KLU,2240,1-20-2017 49,10,5,K80OQ,2467,1-20-2017 50,1,6,2SIJS,1788,1-20-2017 51,2,6,5ZJ7V,2277,1-20-2017 52,3,6,HTL99,873,1-20-2017 53,4,6,C06QP,2185,1-20-2017 54,5,6,2S1YI,580,1-20-2017 55,6,6,IQ0L8,2395,1-20-2017 56,7,6,PEE2Y,2299,1-20-2017 57,8,6,6DEWK,2019,1-20-2017 58,9,6,9FY5B,1517,1-20-2017 59,10,6,NZQ54,2624,1-20-2017 60,1,7,C4SVV,1823,1-20-2017 61,2,7,Q4C4I,2339,1-20-2017 62,3,7,996OQ,1621,1-20-2017 63,4,7,PISK6,895,1-20-2017 64,5,7,KOKHE,1315,1-20-2017 65,6,7,6P4FT,1467,1-20-2017 66,7,7,3FY75,2085,1-20-2017 67,8,7,9YCNB,992,1-20-2017 68,9,7,NXXK1,2080,1-20-2017 69,10,7,4RDHV,2031,1-20-2017 0,6,1,9FFZ7,592,2-10-2017 1,1,6,E2SW4,622,2-10-2017 2,6,7,PLULW,1699,2-10-2017 3,8,3,ET3DF,784,2-10-2017 4,9,4,KOKHE,1092,2-10-2017 5,2,6,5ZJ7V,1691,2-10-2017 6,4,5,9FY5B,630,2-10-2017 7,9,4,G0CWS,1523,2-10-2017 8,9,2,PISK6,1778,2-10-2017 9,6,4,35UUY,2107,2-10-2017 10,3,5,5QTJN,1751,2-10-2017 11,6,6,NLC11,526,2-10-2017 12,8,2,C06QP,2308,2-10-2017 13,8,3,XXORI,1453,2-10-2017 14,5,1,E9RGD,1864,2-10-2017 15,7,2,HTL99,1222,2-10-2017 16,3,3,PEE2Y,2050,2-10-2017 17,9,7,GBMNZ,1941,2-10-2017 18,3,1,T8PUB,1440,2-10-2017 19,5,1,3WMOL,2692,2-10-2017 20,7,7,S3AYK,523,2-10-2017 21,1,5,BZPGX,2245,2-10-2017 22,2,1,S8221,2241,2-10-2017 23,9,7,IQ0L8,566,2-10-2017 24,8,5,D8UZW,1769,2-10-2017 25,3,1,RYM9K,1044,2-10-2017 26,4,6,4HXGA,2650,2-10-2017 27,2,2,WFPPY,2203,2-10-2017 28,2,4,93KLU,2289,2-10-2017 29,7,3,P1AGP,1084,2-10-2017 30,4,3,3JHUA,1364,2-10-2017 31,1,4,9LT7O,1198,2-10-2017 32,4,6,4RDHV,771,2-10-2017 33,10,7,T0S7H,873,2-10-2017 34,3,6,NXXK1,2391,2-10-2017 35,8,2,2SIJS,811,2-10-2017 36,8,4,LTSLR,1670,2-10-2017 37,6,7,02SJG,1880,2-10-2017 38,9,3,0MPZ7,2090,2-10-2017 39,2,6,E3NLS,2350,2-10-2017 40,7,6,QYU9Q,1092,2-10-2017 41,6,3,0K682,894,2-10-2017 42,1,5,LS9NW,1928,2-10-2017 43,7,7,NVV0S,951,2-10-2017 44,9,4,996OQ,670,2-10-2017 45,7,6,USKY7,706,2-10-2017 46,10,4,Q4C4I,2270,2-10-2017 47,4,2,UKZGF,1691,2-10-2017 48,10,3,RJDEG,597,2-10-2017 49,10,2,1J184,1921,2-10-2017 50,2,3,5W3CE,2604,2-10-2017 51,5,5,3FY75,1260,2-10-2017 52,1,1,6DEWK,2491,2-10-2017 53,7,5,9YCNB,1743,2-10-2017 54,4,7,6DRYS,2450,2-10-2017 55,5,2,MT2DB,1292,2-10-2017 56,8,5,C4SVV,1395,2-10-2017 57,3,7,ODGZ2,2685,2-10-2017 58,10,4,2S1YI,2617,2-10-2017 59,1,2,YJ6S0,1611,2-10-2017 60,6,3,2Y7KH,2188,2-10-2017 61,5,4,4KQPU,1413,2-10-2017 62,10,1,D0L0Y,2291,2-10-2017 63,5,1,NZQ54,1405,2-10-2017 64,5,2,6P4FT,1885,2-10-2017 65,3,1,PPTNV,1442,2-10-2017 66,1,5,K80OQ,2140,2-10-2017 67,4,5,42W1W,1697,2-10-2017 68,2,7,O8XKY,1007,2-10-2017 69,10,6,R4E3J,887,2-10-2017
So, I took a few minutes to completely refactor your code into proper d3 style. This aims to demonstrate a couple things: The proper use of the enter, update, exit pattern. Removed cut / paste duplicate code. The proper way to use g to group elements and position them together. How to add the transitions. Here is the code running. Commented code: <!DOCTYPE html> <meta charset="utf-8"> <style> body { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .dot { stroke: #000; } </style> <body> <div id="chart"> </div> <div id="select_params"> <input name="updateButton" type="button" value="Update" onclick="updateData()" /> </div> </body> <!-- load js libraries --> <script src="https://d3js.org/d3.v4.min.js"></script> <!-- uses v4 of d3 --> <!-- build the visualization --> <script type='text/javascript'> var item_width = 40, item_height = 60; var margin = { top: 20, right: 50, bottom: 75, left: 40 }, width = 700 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.scaleLinear() .range([0, width]); var y = d3.scaleLinear() .range([height, 0]); var color = d3.scaleOrdinal(d3.schemeCategory10); var svg = d3.select("#chart").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 + ")"); // a single function to draw function draw(data, someDate) { data.forEach(function(d) { d.x_pos = +d.x_pos; d.y_pos = +d.y_pos; d.sales = +d.sales; }); // pre-filter data data = data.filter(function(d) { return d.date === someDate }); var x_offset = 5, y_offset = 5; x.domain(d3.extent(data, function(d) { return d.x_pos; })); // set the x domain y.domain(d3.extent(data, function(d) { return d.y_pos; })); // set the y domain // create an update selection with a key function var g_sel = svg.selectAll("g") .data(data, function(d) { return d.item_name; }); // get rid of those leaving the update g_sel.exit().remove(); // our entering g var g_ent = g_sel.enter() .append("g"); // add our rects to our g g_ent.append("rect") .attr("class", "dot") .attr("width", item_width) .attr("height", item_height) .attr("rx", 5) .attr("ry", 5) .style("fill", "#1f5fc6") // color factor variable .style("fill-opacity", 0.5); // add our text to our g g_ent.append("text") .attr("font-size", 10) .attr("text-anchor", "middle") .attr("fill", "black") .attr("dx", item_width / 2) .attr("dy", item_height / 2) .text(function(d) { return d.item_name }); // UPDATE + ENTER selection g_sel = g_ent.merge(g_sel); // move them into position with transition g_sel .transition() .attr("transform", function(d) { return "translate(" + (x(d.x_pos) + x_offset) + "," + (y(d.y_pos) + y_offset) + ")"; }); } d3.csv("test.csv", function(data) { draw(data, '1-20-2017'); }); function updateData() { d3.csv("test.csv", function(data) { draw(data, '2-10-2017'); }); } </script>
Heres my attempt: https://jsfiddle.net/guanzo/3c9dtLyh/10/ There are multiple data points that share the same position, which is why some rectangles are overlapping. I made a lot of changes to your code that resulted in less repetition. Your data contains duplicate item_names with different dates/positions, but in your visualization you seem to want to only show items at a single date. Therefore, you only need to pass d3 data for a certain date, versus passing d3 ALL the data and then filtering. Your code: svg.selectAll("g") .data(data) .enter() .append("rect") .filter(function(d){ return d.date == '1-20-2017'}) My code: var firstDateData = data.filter(d=>d.date == '1-20-2017'); var groups = svg.selectAll("g") .data(firstDateData, d=> d.item_name) The difference between these 2 is that in my example, D3 is only aware of a single set of item_names on date 1-20-2017. Therefore when i update the date with item_names on date 2-10-2017, D3 will automatically move all updated rectangles to their new position. How? Here is where your question comes into play: I do not neccesarily expect this approach to work because how would the transition know which (x,y) pairs correspond to the correct item name in the new arrangement This is because i associated each rectangle with an item_name. D3s data function can take an optional 2nd parameter that specifies HOW the data is bound to the rectangles. This is called the key function. svg.selectAll("g").data(firstDateData, d=> d.item_name) In this case, i told d3 that each group (rectangles and their text) is bound to item_name. Therefore, the next time i pass data to D3, it tries to match existing elements (that are associated with an item_name) to the data (which contains item_names). If in my new data, i pass an item_name that corresponds to an existing element, and the data contains a new x and y position, D3 will move to element to that new position. Note that even though i'm talking about rectangles, i bound data to the g element, which contains the rectangle and the text. Feel free to ask any questions, i made a lot of changes that i didn't discuss.
D3 bar char x-axis line not displaying
I am trying to draw a line in x-axis (bottom of bars in the chart) using the following script but it draws the on the top. What is the correct way of adding line on the bottom? Please help me to solve it. var datasetBarChart = ${barList}; // set initial group value var group = "All"; function datasetBarChosen(group) { var ds = []; for (x in datasetBarChart) { if (datasetBarChart[x].group == group) { ds.push(datasetBarChart[x]); } } return ds; } function dsBarChartBasics() { var margin = {top: 30, right: 5, bottom: 20, left: 50}, width = 1000 - margin.left - margin.right, height = 450 - margin.top - margin.bottom, colorBar = d3.scale.category20(), barPadding = 1 ; return { margin: margin, width: width, height: height, colorBar: colorBar, barPadding: barPadding } ; } function dsBarChart() { var firstDatasetBarChart = datasetBarChosen(group); var basics = dsBarChartBasics(); var margin = basics.margin, width = basics.width, height = basics.height, colorBar = basics.colorBar, barPadding = basics.barPadding ; var xScale = d3.scale.linear() .domain([0, firstDatasetBarChart.length]) .range([0, width]) ; // Create linear y scale // Purpose: No matter what the data is, the bar should fit into the svg area; bars should not // get higher than the svg height. Hence incoming data needs to be scaled to fit into the svg area. var yScale = d3.scale.linear() // use the max funtion to derive end point of the domain (max value of the dataset) // do not use the min value of the dataset as min of the domain as otherwise you will not see the first bar .domain([0, d3.max(firstDatasetBarChart, function (d) { return d.measure; })]) // As coordinates are always defined from the top left corner, the y position of the bar // is the svg height minus the data value. So you basically draw the bar starting from the top. // To have the y position calculated by the range function .range([height, 0]) ; //Create SVG element var svg = d3.select("#barChart") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .attr("id", "barChartPlot") ; var plot = svg .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") ; var median = svg.append("line") .attr("x2", width) .attr("y2", (xScale/width)) .attr("stroke-width", 2) .attr("stroke", "black"); plot.selectAll("rect") .data(firstDatasetBarChart) .enter() .append("rect") .attr("x", function (d, i) { return xScale(i); }) .attr("width", width / firstDatasetBarChart.length - barPadding) .attr("y", function (d) { return yScale(d.measure); }) .attr("height", function (d) { return height - yScale(d.measure); }) .attr("fill", "lightgrey") ; // Add y labels to plot plot.selectAll("text") .data(firstDatasetBarChart) .enter() .append("text") .text(function (d) { return formatAsInteger(d3.round(d.measure)); }) .attr("text-anchor", "middle") // Set x position to the left edge of each bar plus half the bar width .attr("x", function (d, i) { return (i * (width / firstDatasetBarChart.length)) + ((width / firstDatasetBarChart.length - barPadding) / 2); }) .attr("y", function (d) { return yScale(d.measure) + 14; }) .attr("class", "yAxis") /* moved to CSS .attr("font-family", "sans-serif") .attr("font-size", "11px") .attr("fill", "white") */ ; // Add x labels to chart var xLabels = svg .append("g") .attr("transform", "translate(" + margin.left + "," + (margin.top + height) + ")") ; xLabels.selectAll("text.xAxis") .data(firstDatasetBarChart) .enter() .append("text") .text(function (d) { return d.category; }) .attr("text-anchor", "middle") // Set x position to the left edge of each bar plus half the bar width .attr("x", function (d, i) { return (i * (width / firstDatasetBarChart.length)) + ((width / firstDatasetBarChart.length - barPadding) / 2); }) .attr("y", 15) .attr("class", "xAxis") //.attr("style", "font-size: 12; font-family: Helvetica, sans-serif") ; // Title svg.append("text") .attr("x", (width + margin.left + margin.right) / 2) .attr("y", 15) .attr("class", "title") .attr("text-anchor", "middle") .text Breakdown 2015") ; } dsBarChart(); script for the line; var median = svg.append("line") .attr("x2", width) .attr("y2", (xScale/width)) .attr("stroke-width", 2) .attr("stroke", "black");
I don't quite understand your y2 attribute. It looks like you want the line to render as <line x1="0" y1="{height}" x2="{width}" y2="{height}" /> Ideally you want to express this in terms of your scale functions so if they change you won't have to update this statement. The corresponding d3 call for that would be: var median = svg.append("line") .attr("stroke-width", 2) .attr("stroke", "black") .attr("x1", xScale.range()[0]) .attr("x2", xScale.range()[1]) .attr("y1", yScale.range()[0]) .attr("y2", yScale.range()[0]); Also, I think something is up with the xScale/width calculation. xScale is a function. Though you should look into d3.svg.axis too