I need a heatmap matrix like this,
Now I tried using D3.js, I am able to make matrix and also made it zoomable too, but now I am stuck at adding Notes to rectangles as SVG doesn't supports it.
Am I on right track or can I use Div and jQuery instead to develop this heatmap, I visited hundreds of plugins but non satisfies my needs.
Proper matrix - http://jsfiddle.net/nhe613kt/49/
Trying adding Notes with one rectangle - http://jsfiddle.net/nhe613kt/60
Can I use any other plugin or simple HTML ?
var width = 600,
height = 600;
var margin = {top: -5, right: -5, bottom: -5, left: -5};
var zoom = d3.behavior.zoom()
.scaleExtent([1, 15])
.on("zoom", zoomed);
var svgContainer = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.style("background-color", "black")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom);
var zoomed = function () {
svgContainer.attr("transform", "translate("+ d3.event.translate + ")scale(" + d3.event.scale + ")");
};
var zoom = d3.behavior.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed)
.size([width, height]);
svgContainer.call(zoom);
var rectangle1 = svgContainer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 100)
.attr("height", 100)
.attr("fill", "red")
.append("text")
.text("Hahaha");
You need to configure the text node properly:
svgContainer.append("text")
.attr("x", 10)
.attr("y", 20)
.attr("fill", "blue")
.text("Hahaha");
Otherwise, it might be outside of the visible are (y=0 is a common mistake; the Y coordinate moves the baseline of the text, not the top-right corner). Also without a color, the text will inherit one from the parent.
Demo: http://jsfiddle.net/nhe613kt/65/
Related
There are grid lines from points.
Is there another solution with better performance, because if I add many svg elements(etc. rects, circles, paths) and increase the dimension of the grid I will see the freeze effect when I use zoom, move element...
The size of the grid is changed.
Also, how can I create endless grid lines, instead limited (gridCountX, gridCountY)?
Thanks
var svg = d3.select("body").append("svg");
var svgG = svg.append("g");
var gridLines = svgG.append("g").classed("grid-lines-container", true).data(["gridLines"]);
var gridCountX = _.range(100);
var gridCountY = _.range(100);
var size = 10;
gridLines.selectAll("g").data(gridCountY)
.enter()
.append("g")
.each(function(d) {
d3.select(this).selectAll("circle").data(gridCountX).enter()
.append("circle")
.attr("cx", function(_d) {return _d*size;})
.attr("cy", function(_d) {return d*size;})
.attr("r", 0.5)
.attr("style", function() {
return "stroke: black;";
});
});
var zoomSvg = d3.zoom()
.scaleExtent([1, 10])
.on("zoom", function(){
svgG.attr("transform", d3.event.transform);
});
svg.call(zoomSvg);
svg {
width: 100%;
height: 100%;
border: 1px solid #a1a1a1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
As you note, this approach is not really scalable and has a larger impact on performance. I have found the approach of utilizing d3 axes for grids to have minimal performance impact while also being relatively straightforward to incorporate with zoom such that you can have infinite zoom with the grid lines updating in a sensible manner due to the "magic" of automatic generation of sensible tick locations in d3.
To implement something similar in d3 v4, you can do something along these lines:
var svg = d3.select("svg"),
margin = {top: 20, right: 140, bottom: 50, left: 70},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"),
innerSvg = g.append("svg").attr("width", width).attr("height", height);
// Calculate domain for x and y from data and store in x0, y0 (not shown here)
x.domain(x0);
y.domain(y0);
xGridAxis = d3.axisBottom(x).ticks(10);
yGridAxis = d3.axisLeft(y).ticks(10 * height / width);
// Create grouping and additional set of axes for displaying grid
innerSvg.append("g")
.attr("class", "grid x-grid")
.attr("transform", "translate (0," + height + ")")
.call(xGridAxis
.tickSize(-height, 0, 0)
.tickFormat("")
)
.selectAll(".tick");
innerSvg.append("g")
.attr("class", "grid y-grid")
.attr("transform", "translate (" + width + ", 0)")
.call(yGridAxis
.tickSize(width)
.tickFormat("")
);
// Add element to capture mouse events for drag and pan of plots
var zoom = d3.zoom()
.on("zoom", zoomed);
var scrollZoom = innerSvg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all") // Defaults to panning with mouse
.call(zoom);
// Mouse panning and scroll-zoom implementation using d3.zoom
// Modification of : http://bl.ocks.org/lorenzopub/013c0c41f9ffab4d27f860127f79c5f5
function zoomed() {
lastEventTransform = d3.event.transform;
// Rescale the grid using the new transform associated with zoom/pan action
svg.select(".x-grid").call(xGridAxis.scale(lastEventTransform.rescaleX(x)));
svg.select(".y-grid").call(yGridAxis.scale(lastEventTransform.rescaleY(y)));
// Calculate transformed x and y locations which are used to redraw all plot elements
var xt = lastEventTransform.rescaleX(x),
yt = lastEventTransform.rescaleY(y);
// Code below just shows how you might do it. Will need to tweak based on your plot
var line = d3.line()
.x(function(d) { return xt(d.x); })
.y(function(d) { return yt(d.y); });
innerSvg.selectAll(".line")
.attr("d", function(d) { return line(d.values); });
innerSvg.selectAll(".dot")
.attr("cx", function(d) {return xt(d.x); })
.attr("cy", function(d) {return yt(d.y); });
}
Here is a worked out example in d3 v4 that inspired my version above:
http://bl.ocks.org/lorenzopub/013c0c41f9ffab4d27f860127f79c5f5
I have code to plot pie chart. Problem is when i zoom Pie chart it goes out of Division inside which it is placed.I searched on google and got to know there is .zoom function for D3 charts to achieve this.Can anyone help me how can i do it?
Graph should be visible in all the media like in Desktop , mobile , ipad
var canvasWidth = this.getWidth(), //width
canvasHeight = this.getHeight(), //height
outerRadius = 75,
margin = {top: 20, right: 20, bottom: 30, left: 40},//radius
color = d3.scale.category20(); //builtin range of colors
var vis = d3.select("#"+this.htmlObject)
.append("svg:svg") //create the SVG element inside the <body>
.data([data]) //associate our data with the document
.attr("width", canvasWidth) //set the width of the canvas
.attr("height", canvasHeight) //set the height of the canvas
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + 1.5*outerRadius + "," + 1.5*outerRadius + ")") // relocate center of pie to 'outerRadius,outerRadius'
.attr('transform', 'translate(' + (canvasWidth/2 - 20) + ',' + canvasHeight/2 +')');
var arc = d3.svg.arc()
.outerRadius(outerRadius);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.magnitude; }); // Binding each value to the pie
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice"); //allow us to style things in the slices (like text)
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } )
.attr("d", arc);
arcs.append("svg:text")
.attr("transform", function(d) { //set the label's origin to the center of the arc
d.outerRadius = outerRadius + 50; // Set Outer Coordinate
d.innerRadius = outerRadius + 45; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle") //center the text on it's origin
.style("fill", "Purple")
.style("font", "bold 12px Arial")
.text(function(d, i) { return data[i].legendLabel; }); //get the label from our original data array
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function(d) { //set the label's origin to the center of the arc
d.outerRadius = outerRadius; // Set Outer Coordinate
d.innerRadius = outerRadius/2; // Set Inner Coordinate
return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")";
})
.style("fill", "White")
.style("font", "bold 12px Arial")
.text(function(d) { return d.data.magnitude; });
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
I found one small code
var zoom = d3.behavior.zoom()
.x(xScale)
.on('zoom', zoomed);
You have not implemented a proper zoom function. D3 has this. Here is an example :
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); //the container here is the part of the SVG you wish to zoom into
}
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.right + ")")
.call(zoom); //here is the main call.
Here is an example : https://bl.ocks.org/mbostock/6123708
This is not a pie chart but it will work either way. Just where I have container, just put your pie chart container here. There are plenty of examples online for zooming in D3
I have a force directed graph with fixed nodes. I have allowed zoom in the graph :
var force = d3.layout.force()
.on("tick", tick);
var zoom = d3.behavior.zoom()
.scaleExtent([0, 50])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom);
var graphGroup = svg.append("g")
.attr("height", graphHeight)
.attr("width", graphWidth)
.attr("class", "graph-group")
.attr("transform", "translate(" + margin + "," + margin + ")");
graphGroup.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", graphHeight)
.attr("class", "graph-limit")
.attr("width", graphWidth);
...
function zoomed() {
graphContainer.attr("transform", "translate(" + t + ")scale(" + d3.event.scale + ")");
}
I would like to limit the zoom (scale and translation) of the graph within the rectangle with class "graph-limit".
I've tried to put overflow:hidden in style, but without success.
Any idea ?
EDIT :
Here a jsfiddle
http://jsfiddle.net/Lxc43v8d/20/
This is how you can do it.
Use clip path with dimension exactly same as that off the rectangle:
//make a clip path
svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", graphHeight)
.attr("width", graphWidth);
To the group which holds the rectangle all nodes and links add the clip path like this
var graphGroup = svg.append("g")
.attr("clip-path", "url(#clip)")//add the clip path
.attr("height", graphHeight)
.attr("width", graphWidth)
.attr("class", "graph-group");
Working code here
Hope this helps!
Just started using d3.js and javascript. I have this weird chart requirement. Want to create the chart exactly like pie chart but, in square shaped. Just like below.
So, I thought, may be I create the pie chart and add the square between the pie chart and erase the part outside square. But, it is not working out yet.
Secondly, I thought, I can do this with CSS. I did this. But, I am not happy with this solution. It is too hacky. Can someone help me with good solution.
This is my jsfiddle link.
//// Done this to create the square.
var svgContainer = d3.select("#square").append("svg")
.attr("width", 200)
.attr("height", 200);
var rectangle = svgContainer.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 200)
.attr("fill", '#ec4c4a')
.attr("height", 200);
// Done this to create the pie chart. Found this example some where.
var element_id = 'pie'
var elementSelector = '#pie';
svgWidth = 390;
svgHeight = 320;
svgInnerRadius = 0;
svgOuterRadius = 145;
heightOffset = 0;
scoreFontSize = '49px';
$(elementSelector).replaceWith('<svg id="'+ element_id +'" class="scoreBar" width="'+ svgWidth +'" height="'+ (svgHeight - heightOffset) +'"></svg>');
$(elementSelector).css({'width': svgWidth + 'px', 'height': (svgHeight-heightOffset) + 'px'});
var anglePercentage = d3.scale.linear().domain([0, 100]).range([0, 2 * Math.PI]);
var fullAnglePercentage = 100;
var color = d3.scale.ordinal().range(["#ACACAC", "#EAEAEA", "#123123", "#DDEEAA", "#BACBAC"]);
data = [[50, 90, 1],
[50, 30, 2],
[30, 10, 3],
[10, -1, 4],
[-1, -10, 5]]
var vis = d3.select(elementSelector);
var arc = d3.svg.arc()
.innerRadius(svgInnerRadius)
.outerRadius(svgOuterRadius)
.startAngle(function(d){return anglePercentage(d[0]);})
.endAngle(function(d){return anglePercentage(d[1]);});
vis.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", arc)
.style("fill", function(d){return color(d[2]);})
.attr("transform", "translate(" + svgWidth / 2 + ", " + svgHeight / 2 + ")");
Thanks in advance.
You can achieve this using clip path. What is a clip path?
To SVG add defs of clippath
var svg1 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
//making a clip square as per your requirement.
svg1.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", -120)
.attr("y", -100)
.attr("width", radius)
.attr("height", radius);
Make your normal d3 pie chart like:
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d) {
return color(d.data.age);
});
To the main group add the clip like this:
var svg = svg1.append("g").attr("clip-path", "url(#clip)")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
Full working code here.
D3 has a force directed layout here. Is there a way to add zooming to this graph? Currently, I was able to capture the mouse wheel event but am not really sure how to write the redraw function itself. Any suggestions?
var vis = d3.select("#graph")
.append("svg:svg")
.call(d3.behavior.zoom().on("zoom", redraw)) // <-- redraw function
.attr("width", w)
.attr("height", h);
Update 6/4/14
See also Mike Bostock's answer here for changes in D3 v.3 and the related example. I think this probably supersedes the answer below.
Update 2/18/2014
I think #ahaarnos's answer is preferable if you want the entire SVG to pan and zoom. The nested g elements in my answer below are really only necessary if you have non-zooming elements in the same SVG (not the case in the original question). If you do apply the behavior to a g element, then a background rect or similar element is required to ensure that the g receives pointer events.
Original Answer
I got this working based on the zoom-pan-transform example - you can see my jsFiddle here: http://jsfiddle.net/nrabinowitz/QMKm3/
It was a bit more complex than I had hoped - you have to nest several g elements to get it to work, set the SVG's pointer-events attribute to all, and then append a background rectangle to receive the pointer events (otherwise it only works when the pointer is over a node or link). The redraw function is comparatively simple, just setting a transform on the innermost g:
var vis = d3.select("#chart")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('svg:g');
vis.append('svg:rect')
.attr('width', w)
.attr('height', h)
.attr('fill', 'white');
function redraw() {
console.log("here", d3.event.translate, d3.event.scale);
vis.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
This effectively scales the entire SVG, so it scales stroke width as well, like zooming in on an image.
There is another example that illustrates a similar technique.
Why the nested <g>'s?
This code below worked well for me (only one <g>, with no random large white <rect>:
var svg = d3.select("body")
.append("svg")
.attr({
"width": "100%",
"height": "100%"
})
.attr("viewBox", "0 0 " + width + " " + height )
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("pointer-events", "all")
.call(d3.behavior.zoom().on("zoom", redraw));
var vis = svg
.append('svg:g');
function redraw() {
vis.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
Where all the elements in your svg are then appended to the vis element.
The provided answers work in D3 v2 but not in v3. I've synthesized the responses into a clean solution and resolved the v3 issue using the answer provided here: Why does d3.js v3 break my force graph when implementing zooming when v2 doesn't?
First the main code. This is a cleaned up version of #ahaarnos' answer:
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().on("zoom", redraw))
.append('g');
function redraw() {
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
Now you have pan and zoom, but you won't be able to drag nodes because the pan functionality will override the drag functionality. So we need to do this:
var drag = force.stop().drag()
.on("dragstart", function(d) {
d3.event.sourceEvent.stopPropagation(); // to prevent pan functionality from
//overriding node drag functionality.
// put any other 'dragstart' actions here
});
Here's #nrabinowitz' fiddle modified to use this cleaner zoom implementation, but illustrating how D3v3 breaks node drag: http://jsfiddle.net/QMKm3/718/
And here's the same fiddle modified to work with D3v3: http://jsfiddle.net/QMKm3/719/
I got my graph to work without the second "svg:g" append.
[...].attr("pointer-events", "all")
.attr("width", width2)
.attr("height", height2)
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw));
The rest is the same.
I got a solution for D3 force directed graph with zooming option.
var m = [40, 240, 40, 240],
width = 960,
height = 700,
root;
var svg = d3.select("body").append("svg")
.attr("class", "svg_container")
.attr("width", width)
.attr("height", height)
.style("overflow", "scroll")
.style("background-color", "#EEEEEE")
.append("svg:g")
.attr("class", "drawarea")
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
//applying zoom in&out for svg
d3.select("svg")
.call(d3.behavior.zoom()
.scaleExtent([0.5, 5])
.on("zoom", zoom));
//zooming
function zoom() { //zoom in&out function
var scale = d3.event.scale,
translation = d3.event.translate,
tbound = -height * scale,
bbound = height * scale,
lbound = (-width + m[1]) * scale,
rbound = (width - m[3]) * scale;
// limit translation to thresholds
translation = [
Math.max(Math.min(translation[0], rbound), lbound),
Math.max(Math.min(translation[1], bbound), tbound)
];
d3.select(".drawarea")
.attr("transform", "translate(" + translation + ")" +
" scale(" + scale + ")");
}
If you want to zoom and pan force layout without changing node-size, try below.
You can also drag nodes without trembling.
This code is based on original force layout example. As for nodes and links data, please refer to original sample data. http://bl.ocks.org/mbostock/4062045
Plz note the variables xScale and yScale, the functions dragstarted(), dragged(), and dragended().
Function tick() was changed as well.
You can see the result at http://steelblue.tistory.com/9
The language on the site is Korean. However you can easily find the result at the third example on the page.
var graph = {
"nodes": [
{ "name": "Myriel", "group": 1 },
{ "name": "Napoleon", "group": 1 },
// ......
{ "name": "Mme.Hucheloup", "group": 8 }
],
"links": [
{ "source": 1, "target": 0, "value": 1 },
{ "source": 2, "target": 0, "value": 8 },
// .......
{ "source": 76, "target": 58, "value": 1 }
]
};
var width = 640,
height = 400;
var color = d3.scale.category20();
var xScale = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var yScale = d3.scale.linear()
.domain([0, height])
.range([0, height]);
var zoomer = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([0.1, 8]).on("zoom", zoom);
function zoom() {
tick();
};
var drag = d3.behavior.drag()
.origin(function (d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d.fixed |= 2;
}
function dragged(d) {
var mouse = d3.mouse(svg.node());
d.x = xScale.invert(mouse[0]);
d.y = yScale.invert(mouse[1]);
d.px = d.x;
d.py = d.y;
force.resume();
}
function dragended(d) {
d.fixed &= ~6; }
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.call(zoomer);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function (d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function (d) { return color(d.group); })
.call(drag);
node.append("title")
.text(function (d) { return d.name; });
force.on("tick",tick);
function tick(){
link.attr("x1", function (d) { return xScale(d.source.x); })
.attr("y1", function (d) { return yScale(d.source.y); })
.attr("x2", function (d) { return xScale(d.target.x); })
.attr("y2", function (d) { return yScale(d.target.y); });
node.attr("transform", function (d) {
return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")";
});
};