I have been working with dc.js for a year now. Recently I have been tasked to implement a pie chart as below:
I want to replace the text labels in the pie chart slices with appropriate images.
I saw this implemented in pure d3.js. Can someone help me translate the implementation to dc.js?
http://jsfiddle.net/LLwr4q7s/
pie
.width(600)
.height(500)
.radius(200)
.innerRadius(120)
.dimension(disastersDimension)
.group(disastersGroup)
.on("filtered", function (chart, filter) {
var sel = filter;
let percentage = 0,
value = 0;
let disastersBuffer = [];
totalAmount = 0;
pie.selectAll("text.pie-slice").text((d) => {
percentage = dc.utils.printSingleValue(
((d.endAngle - d.startAngle) / (2 * Math.PI)) * 100
);
disastersBuffer.push({ ...d.data, percentage });
totalAmount += parseFloat(d.data.value);
});
filterPiechart(sel, percentage, totalAmount, disastersBuffer, value);
})
.on("renderlet", (chart) => {
if (!chart.selectAll("g.selected")._groups[0].length) {
chart.filter(null);
filterPiechart("", 100, totalAmount, [], 0);
}
var arc = chart.radius(250).innerRadius(100);
console.log(arc);
var g = chart.selectAll(".pie-slice");
chart
.selectAll(".pie-slice")
.append("image")
.attr("xlink:href", "img/disasters/Floods.png")
.attr("transform", "translate(-10,10) rotate(315)")
.attr("width", "26px")
.attr("hight", "26px")
.style("background-color", "white")
.attr("x", function (d) {
var bbox = this.parentNode.getBBox();
return bbox.x;
})
.attr("y", function () {
var bbox = this.parentNode.getBBox();
return bbox.y;
});
g.append("g")
.append("svg:image")
.attr("xlink:href", function (d) {
let filteredImage = self.piedata.find(
(i) => i.label == d.data.key
);
let image = filteredImage ? filteredImage.image : "";
return image;
})
.attr("width", 30)
.attr("height", 40)
.attr("x", function (d) {
var bbox = this.parentNode.getBBox();
return bbox.x;
})
.attr("y", function (d) {
var bbox = this.parentNode.getBBox();
return bbox.y;
});
})
.addFilterHandler(function (filters, filter) {
filters.length = 0; // empty the array
filters.push(filter);
return filters;
});
I took the fiddle and added a couple of 'Meteoicons' I found here.
(Of course, those icons are taken as an example and I have no permission to use them commercially)
The icons are stored in a separated <svg> elements. To render an icon, just select its root <g> element and copy its content to another <g> you create in your piechart:
g.append("g")
.attr("transform", d => `translate(${arc.centroid(d)}) scale(0.25)`)
.append('g')
.attr('transform', 'translate(-256,-256)') // The original icons are 256 x 256
.html(d => d3.select(`#meteo-icon-${... some attribute of d ...} > g`).html())
The code is for demonstration purposes only, you will need to modify it for your needs.
See the result in the snippet below:
var width = 550,
height = 550,
radius = 250,
colors = d3.scale.ordinal()
.range(['#336699 ','#336699 ','#ACD1E9','#ACD1E9','#ACD1E9']);
var image_width=40,
image_height=40;
var piedata = [
{
label: "test",
image: "http://placeimg.com/40/40/any",
value: 50
},
{
label: "",
image: "http://placeimg.com/42/42/any",
value: 50
},
{
label: "Jonathan",
image: "http://placeimg.com/44/44/any",
value: 50
},
{
label: "Lorenzo",
image: "http://placeimg.com/46/46/any",
value: 50
},
{
label: "Hillary",
image: "http://placeimg.com/38/38/any",
value: 50
}
]
var pie = d3.layout.pie()
.value(function(d) {
return d.value;
})
var arc = d3.svg.arc()
.outerRadius(250)
.innerRadius(100)
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate('+(width-radius)+','+(height-radius)+')');
var g = svg.selectAll(".arc")
.data(pie(piedata))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d,i) { return colors(i); });
g.append("g")
.attr("transform", d => `translate(${arc.centroid(d)}) scale(0.25)`)
.append('g')
.attr('transform', 'translate(-256,-256)')
.html(() => d3.select(`#meteo-icon-${Math.random() < 0.5 ? 1 : 2} > g`).html())
.selectAll('path')
.style('fill', 'orange');
path {
stroke: #fff;
fill-rule: evenodd;
}
text {
font-family: Arial, sans-serif;
font-size: 12px;
}
.meteo-icon {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<svg id="meteo-icon-1" class="meteo-icon" width="24" height="24" viewBox="0 0 512 512">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#1D1D1B" d="M177.615,288c7.438-36.521,39.688-64,78.396-64
c38.709,0,70.958,27.479,78.376,64h32c-7.834-54.125-54.084-96-110.376-96c-56.271,0-102.541,41.875-110.375,96H177.615z
M256.011,160c8.833,0,16-7.167,16-16v-32c0-8.833-7.167-16-16-16c-8.832,0-16,7.167-16,16v32
C240.011,152.833,247.179,160,256.011,160z M403.073,156.917c-6.249-6.25-16.374-6.25-22.625,0l-22.625,22.625
c-6.249,6.25-6.249,16.375,0,22.625c6.251,6.25,16.376,6.25,22.625,0l22.625-22.625
C409.323,173.292,409.323,163.167,403.073,156.917z M154.177,179.542l-22.625-22.625c-6.249-6.25-16.373-6.25-22.625,0
c-6.249,6.25-6.249,16.375,0,22.625l22.625,22.625c6.252,6.25,16.376,6.25,22.625,0
C160.429,195.917,160.429,185.792,154.177,179.542z M352.011,320h-192c-8.832,0-16,7.167-16,16s7.168,16,16,16h192
c8.833,0,16-7.167,16-16S360.844,320,352.011,320z M320.011,384h-128c-8.832,0-16,7.167-16,16s7.168,16,16,16h128
c8.833,0,16-7.167,16-16S328.844,384,320.011,384z"/>
</g>
</svg>
<svg id="meteo-icon-2" class="meteo-icon" width="24" height="24" viewBox="0 0 512 512">
<g>
<path fill="#1D1D1B" d="M349.852,343.15c-49.876,49.916-131.083,49.916-181,0c-49.916-49.917-49.916-131.125,0-181.021
c13.209-13.187,29.312-23.25,47.832-29.812c5.834-2.042,12.293-0.562,16.625,3.792c4.376,4.375,5.855,10.833,3.793,16.625
c-12.542,35.375-4,73.666,22.249,99.917c26.209,26.228,64.501,34.75,99.917,22.25c5.792-2.062,12.271-0.583,16.625,3.792
c4.376,4.333,5.834,10.812,3.771,16.625C373.143,313.838,363.06,329.941,349.852,343.15z M191.477,184.754
c-37.438,37.438-37.438,98.354,0,135.771c40,40.021,108.125,36.417,143-8.167c-35.959,2.25-71.375-10.729-97.75-37.084
c-26.375-26.354-39.333-61.771-37.084-97.729C196.769,179.796,194.039,182.192,191.477,184.754z"/>
</g>
</svg>
First, apologies for an incomplete example, but I ran out of time and I think this shows the principles.
I agree with #MichaelRovinsky that SVG icons would be better than images, but I couldn't find a CDN for SVG icons that would be suitable for the example, and I think the principles are exactly the same, since you could embed SVGs as image just as well.
Using placeimg.com for this purpose leads to weird results because the same URL will yield different results when read twice, so e.g. two slices may end up with the same image, and images change when the chart redraws.
Luckily these are both beside the point of customizing dc.js!
Adding things to dc.js pie slices
It would be nice if dc.js used an svg g group element to put the text in. Then we could just add to it and the position would be correct.
Instead, we have to add our image element and read the corresponding data from the pie label to get the placement:
chart.on('pretransition', chart => {
let labelg = chart.select('g.pie-label-group');
let data = labelg.selectAll('text.pie-label').data();
console.log('data', data);
Then we can add image elements in the same layer/g:
let pieImage = labelg.selectAll('image.pie-image');
let arcs = chart._buildArcs();
pieImage.data(data)
.join(
enter => enter.append('image')
.attr('class', 'pie-image')
.attr('x', -19)
.attr('y', -19))
.attr('href', d => images[d.data.key === 'Others' ? 'Others' : d.data.key.slice(4)])
.attr('transform', d => chart._labelPosition(d, arcs));
});
Notice that the attributes which only need to be set once (on enter) are inside the join call, and the attributes which need to be set every redraw (on update) are outside the join call.
x and y are negative one half the image size to center the images.
I used an object to store the URLs but you could use whatever.
Demo fiddle
Limitation
As with any customization of the pie chart, this doesn't account for animations well. The images will move before the animation is complete. If you care, I think I wrote an answer some years ago which dealt with this properly. I can probably dig it up but it was quite complicated and IMHO not worth it.
Related
I would like to position circles on a d3 scale and relax them in such a way that they do not overlap. I know that this decreases accuracy, but that's okay for the type of chart that I would like to generate.
This is my minimum (non-)working example: https://jsfiddle.net/wmxh0gpb/1/
<body>
<div id="content">
<svg width="700" height="200">
<g transform="translate(50, 100)"></g>
</svg>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<script>
var width = 600, height = 400;
var xScale = d3.scaleLinear().domain([0, 1]).range([0, 300]);
var numNodes = 5;
var nodes = d3.range(numNodes).map(function(d, i) {
return {
value: Math.random()
}
});
var simulation = d3.forceSimulation(nodes)
.force('x', d3.forceX().strength(0.5).x(function(d) {
return xScale(d.value);
}))
.force('collision', d3.forceCollide().strength(1).radius(50))
.on('tick', ticked);
function ticked() {
var u = d3.select('svg g')
.selectAll('circle')
.data(nodes);
u.enter()
.append('circle')
.attr('r', function(d) {
return 25;
})
.style('fill', function(d) {
return "black";
})
.merge(u)
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return 0
})
.attr("opacity", 0.5)
u.exit().remove();
}
</script>
</body>
The circles are positioned using the forceX force and collision should be prevented using forceCollide. However, the circles seem to find a stable position regardless of the overlap instead of avoiding it.
What am I doing wrong?
The technical name for this is beeswarm chart: only one axis contains meaningful information, the other one is used only to separate the nodes.
For creating a beeswarm chart in D3 you have to pass the y position to the force (as d3.forceY) as well, in this case with 0 (since you're already translating the group), like:
var simulation = d3.forceSimulation(nodes)
.force('x', d3.forceX(function(d) {
return xScale(d.value);
}).strength(0.8))
.force('y', d3.forceY(0).strength(0.2))
As you can see, the forceX and forceY have different strength values. You have to play with them until you find a combination that suits you: after all, a beeswarm chart is a trade-off between accuracy and avoiding overlap the nodes.
Not related to the question, but very important: remove everything from the ticked function that is not related to repositioning the nodes. The ticked function will run dozens of times per second, normally running 300 times before the simulation cools down. There is no sense in updating, entering and exiting selections 300 times!
Here is your code with those changes:
<body>
<div id="content">
<svg width="700" height="200">
<g transform="translate(50, 100)"></g>
</svg>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<script>
var width = 600,
height = 400;
var xScale = d3.scaleLinear().domain([0, 1]).range([0, 300]);
var numNodes = 5;
var nodes = d3.range(numNodes).map(function(d, i) {
return {
value: Math.random()
}
});
var simulation = d3.forceSimulation(nodes)
.force('x', d3.forceX(function(d) {
return xScale(d.value);
}).strength(0.8))
.force('y', d3.forceY(0).strength(0.2))
.force('collision', d3.forceCollide().strength(1).radius(25))
.on('tick', ticked);
var u = d3.select('svg g')
.selectAll('circle')
.data(nodes);
u = u.enter()
.append('circle')
.attr('r', function(d) {
return 25;
})
.style('fill', function(d) {
return "black";
})
.merge(u)
.attr("opacity", 0.5)
u.exit().remove();
function ticked() {
u.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y
})
}
</script>
</body>
Because you ignore the y coord of the force simulation
Add this as the last line of the tick function. Now you force the nodes to be at y==0
nodes.forEach(e => { e.fy = 0 });
And set the radius of the collision force to the real radius (25)
.force('collision', d3.forceCollide().strength(1).radius(25))
I have data whose values have a range (0, 100) but most of them have values ranging between 80 and 100.
Example of data: 97.00 93.30 92.20 92.70 91.10 89.10 89.90 89.10 89.70 88.90
89.00 89.30 88.76 88.46 87.45 85.05
I have to do a visualization using colors and using a linear scale is not the best because it does not allow me to distinguish colors quite easily.
So I thought about using a scaleQuantile.
I read this post that uses colors from black to red but I would like to use the Viridis scale.
How can I do that?
This is my piece of code:
var colorScale = d3.scaleQuantile(d3.interpolateViridis)
.domain([0, 100]);
// other code
var cells = svg.selectAll('rect')
.data(data)
.enter().append('g').append('rect')
.attr('class', 'cell')
.attr('width', cellSize)
.attr('height', cellSize)
.attr("rx", 4)
.attr("ry", 4)
.attr('y', function(d) {
return yScale(d.nuts_name);
})
.attr('x', function(d) {
return xScale(d.year);
})
.attr('fill', function(d) {
return colorScale(d.value);
}
})
Thanks
You have two problems here:
The domain in a quantile scale, unlike a quantize scale, is not a range between two values. It has to be the array with all the values. The API is clear about that:
If domain is specified, sets the domain of the quantile scale to the specified set of discrete numeric values. (emphasis mine)
That's not the correct way to use d3.interpolateViridis. Again, the API is clear:
Given a number t in the range [0,1], returns the corresponding color from the “viridis” perceptually-uniform color scheme
So, a simple solution is creating the quantile scale in such a way that it returns a number from 0 to 1 according to your data array (here, I'm creating 10 bins):
var colorScale = d3.scaleQuantile()
.domain(data)
.range(d3.range(0, 1.1, 0.1));
And then pass that value to d3.interpolateViridis:
d3.interpolateViridis(colorScale(d))
Here is a demo. The first row of <divs> use the data as they are, the second one uses a sorted array:
var data = [97.00, 93.30, 92.20, 92.70, 91.10, 89.10, 89.90, 89.10, 89.70, 88.90, 89.00, 89.30, 88.76, 88.46, 87.45, 85.05];
var sortedData = data.concat().sort();
var colorScale = d3.scaleQuantile()
.domain(data)
.range(d3.range(0, 1.1, 0.1));
var divs = d3.select("body").selectAll(null)
.data(data)
.enter()
.append("div")
.attr("class", "cell")
.style("background-color", function(d) {
return d3.interpolateViridis(colorScale(d))
});
d3.select("body").append("div")
.style("height", "40px")
var div2 = d3.select("body").selectAll(null)
.data(sortedData)
.enter()
.append("div")
.attr("class", "cell")
.style("background-color", function(d) {
return d3.interpolateViridis(colorScale(d))
});
.cell {
width: 20px;
height: 20px;
margin: 2px;
display: inline-block;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
I am creating a group with arc, and appending a image tag in it. in the image tag i am attaching a image using xlink:href but in the out put i am able to see anything.
any one help me to find the issue with my code:
here is my full code:
var dashBoardViewer = function (params) {
var container = params.container,
width = params.width,
height = params.height,
groups = params.requiredGroup,
inRadius = params.inRadius,
outRadius = params.outRadius;
return {
init : function () {
this.createSvg();
this.createGroup();
},
createSvg : function () {
this.svg = d3.select(container).append('svg').attr({
width:width,
height:height
});
},
createGroup : function () {
for(var i = 1; i <= groups; ++i) {
var group = this.svg.append('svg:g')
.attr({
id : 'group'+i,
class:'group'
});
this.arcCreator(group, i);
}
},
arcCreator : function (group, index) {
//move group
switch (index) {
case 1 :
group.attr('transform', "translate("+outRadius+','+outRadius+")");
break;
case 2 :
group.attr('transform', "translate("+(width/2)+','+(outRadius*2)+")");
break;
case 3 :
group.attr('transform', "translate("+(width-outRadius)+','+outRadius+")");
break;
case 4 :
group.attr('transform', "translate("+outRadius*1.5+','+(height-outRadius)+")");
break;
case 5 :
group.attr('transform', "translate("+(width-(outRadius*1.5))+','+(height-outRadius)+")");
break;
}
var arc = d3.svg.arc()
.innerRadius(inRadius)
.outerRadius(outRadius-10) //deducting the stroke width
.startAngle(0)
.endAngle(360)
return group.append("path")
.attr({"fill":"none",'stroke-width':5,'stroke':'#fff'})
.attr("id", function(d,i){return "s"+i;})
.attr("d",arc)
.append('image')
.attr({
"xlink:href":"images/circleImg.png", //nothing visible
width:257,
height:258
});
}
}
}
dashBoardViewer({
container:'.container',
width:1100,
height:630,
requiredGroup : 5,
inRadius : 0,
outRadius : 129
}).init();
my html output : one of the group
<g id="group1" class="group" transform="translate(129,129)"><path fill="none" stroke-width="5" stroke="#fff" id="s0" d="M0,119A119,119 0 1,1 0,-119A119,119 0 1,1 0,119Z"><image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="images/circleImg.png" width="257" height="258"></image></path></g>
The way you're doing the xlink:href isn't correct. Try this:
.attr('xlink:href', 'path/to/image.png')
.attr('height', '258')
.attr('width', '257')
It's easier for d3 to understand those different attributes when they are split up. Also, if the xlink:href doesn't work, try just using href
EDIT
If you wanted to add a click handler, d3 has the ability to do that as well, but order is what matters. So let's take your example from earlier
return group.append("path")
.attr({"fill":"none",'stroke-width':5,'stroke':'#fff'})
.attr("id", function(d,i){return "s"+i;})
.attr("d",arc)
.append('image')
.attr('xlink:href', 'path/to/image.png')
.attr('height', '258')
.attr('width', '257')
If you wanted a click handler onto the path, you would put it right before you append the image, like so:
return group.append("path")
.attr({"fill":"none",'stroke-width':5,'stroke':'#fff'})
.attr("id", function(d,i){return "s"+i;})
.attr("d",arc)
.on('click', /*insert function name or anonymous function*/)
.append('image')
.attr('xlink:href', 'path/to/image.png')
.attr('height', '258')
.attr('width', '257')
If you want it to be appended to the image, you would do that same little line of code, just further down so it is attached to the image
return group.append("path")
.attr({"fill":"none",'stroke-width':5,'stroke':'#fff'})
.attr("id", function(d,i){return "s"+i;})
.attr("d",arc)
.append('image')
.attr('xlink:href', 'path/to/image.png')
.attr('height', '258')
.attr('width', '257')
.on('click', /*insert function name or anonymous function*/)
d3 also has the ability to do transitions which follow the format of d3.transition(). I haven't really gone too far into the transitions with d3, but Visual.ly did a great blog post explaining the basics of them. d3's API is also very thorough in explaining how they work
I having problem of zoom over map. The actual problem is when i zoom map, the location showing on map using smiley could also zoom but i don't want to zoom smiley. It could stay at same size and place. Sometime smiley get overlap so to avoid this i am trying to solve the above problem but i don't have idea how to transform attribute contains many things like images and text on map of d3.js. Please have a look at jsfiddle link and you can see that at japan 3 smiley get overlap and keep overlapped even after zooming map.
My JSfiddle link
my code is following:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
stroke: white;
stroke-width: 0.25px;
fill: grey;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script>
var width = 960,
height = 500;
var data = [
{
"code":"TYO",
"city":"TOKYO",
"country":"JAPAN",
"lat":"35.68",
"lon":"139.76"
},
{
"code":"OSK",
"city":"Osaka",
"country":"JAPAN",
"lat":" 34.40",
"lon":"135.37"
},
{
"code":"HISH",
"city":"Hiroshima",
"country":"JAPAN",
"lat":"34.3853",
"lon":"132.4553"
},
{
"code":"BKK",
"city":"BANGKOK",
"country":"THAILAND",
"lat":"13.75",
"lon":"100.48"
},
{
"code":"DEL",
"city":"DELHI",
"country":"INDIA",
"lat":"29.01",
"lon":"77.38"
},
{
"code":"SEA",
"city":"SEATTLE",
"country":"USA",
"lat":"38.680632",
"lon":"-96.5001"
}
];
var projection = d3.geo.mercator()
.center([0, 5 ])
.scale(200)
.rotate([-180,0]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var path = d3.geo.path()
.projection(projection);
var g = svg.append("g");
// load and display the World
d3.json("world-110m2.json", function(error, topology) {
// load and display the cities
function drawMap(data){
var circle = g.selectAll("circle")
.data(data)
.enter()
.append("g")
circle.append("circle")
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
.attr("r", 5)
.style("fill", "red");
circle.append("image")
.attr("xlink:href", "http://fc08.deviantart.net/fs71/f/2013/354/8/7/blinking_smiley__animated__by_mondspeer-d6ylwn3.gif")//http://t2.gstatic.//com/images?q=tbn:ANd9GcT6fN48PEP2-z-JbutdhqfypsYdciYTAZEziHpBJZLAfM6rxqYX";})
.attr("class", "node")
.attr("x", function(d) {
return (projection([d.lon, d.lat])[0]) - 8;
})
.attr("y", function(d) {
return (projection([d.lon, d.lat])[1])-8;
})
.attr("width",20)
.attr("height",20)
//});
}
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d", path)
drawMap(data);
});
// zoom and pan
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("d", path.projection(projection));
g.selectAll("path")
.attr("d", path.projection(projection));
});
svg.call(zoom)
</script>
</body>
</html>
Any body help me to zoom only map image not smiley
Implement semantic zooming :)
Try use this example to change your code :) :
Semantic zoom on map with circle showing capital
JSFIDDLE : http://jsfiddle.net/xf7222dg/2/
The code below shrinks the 'circles' depending on scale
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("r", function(){
var self = d3.select(this);
var r = 8 / d3.event.scale; // set radius according to scale
self.style("stroke-width", r < 4 ? (r < 2 ? 0.5 : 1) : 2); // scale stroke-width
return r;
});
});
Here is it working with your smileys: http://jsfiddle.net/dmn0d11f/7/
You have to change the 'width' of the nodes (images) not the radius like with the circles. So select the nodes and instead of changing 'r' change 'width' :
g.selectAll(".node")
.attr("width", function(){
var self = d3.select(this);
var r = 28 / d3.event.scale; // set radius according to scale
self.style("stroke-width", r < 4 ? (r < 2 ? 0.5 : 1) : 2); // scale stroke-width
return r;
});
I have problem adding text in my histogram. I can do this in more simple example.
I try to do this:
// try to add bar value
var barnum = g.selectAll('text')
.data(layout)
.enter()
.append("text")
.attr('y',-10)
.attr('x',10)
.attr("text-anchor", "middle")
.style("fill","black")
.text('testtest')
.style("pointer-events", "none")
;
barnum.transition();
I can't see any text in my figure. The code include definition is here:
var dateFormat = d3.time.format("%Y-%m-%d");
var g;
var data;
var margin = {top: 30, right: 30, bottom: 80, left: 80},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var cx = 10;
var numberBins = 5;
var dispatch = d3.dispatch(chart, "hover");
function chart(container) {
g = container;
update();
}
chart.update = update;
function update() {
// create hist layout
var hist = d3.layout.histogram()
.value(function(d) { return d.selectvar })
.range([d3.min(data, function(d){ return d.selectvar }) , d3.max(data, function(d){ return d.selectvar }) ])
.bins(numberBins);
var layout = hist(data);
var maxLength = d3.max(layout, function(d) { return d.length });
var widthScale = d3.scale.linear()
.domain([0, maxLength])
.range([0, width])
var yScale = d3.scale.ordinal()
.domain(d3.range(numberBins))
.rangeBands([height, 0], 0)
var colorScale = d3.scale.category20();
// create svg
var rects = g.selectAll("rect")
.data(layout)
rects.enter().append("rect")
rects .transition()
.duration(500)
.attr({
y: function(d,i) {
return yScale(i)
},
x: 50,
height: yScale.rangeBand(),
width: function(d,i) {
return widthScale(d.length)
},
fill: function(d, i) { return colorScale(i) }
});
rects.exit().transition().remove();
// try to add bar value
var barnum = g.selectAll('text')
.data(layout)
.enter()
.append("text")
.attr('y',-10)
.attr('x',10)
.attr("text-anchor", "middle")
.style("fill","black")
.text('testtest')
.style("pointer-events", "none")
;
barnum.transition();
is there something wrong with my way to create svg element? I found out some successful case use append('g') from the beginning. New to d3.js! thank you.
You're using d3.dispatch, which is documented on a page titled Internals. That doesn't mean you shouldn't use it, but rather, it shouldn't be your first choice.
You're correct that there's "something wrong with my way to create svg element" -- you're not creating one! Try:
var svg = d3.select("body").append("svg");
var g = svg.append("g");
At this point, you need to have a good understanding of the DOM, the SVG standard, CSS selectors, and D3's selection API to make things work. You don't tell D3 to put labels on your bars and that's it. You have to instruct it what elements to create, and where, keeping track of translates and offsets and stuff like that. You're best off copying and studying one of Mike Bostock's many examples.
D3 is not learned quickly. You need to invest time learning it before you can make any chart you like.