simple d3 line chart... need the line to span the whole svg and the labels to be anchored to the path line itself. bonus points if there's optional code to add markers as well. thanks in advance.
<html>
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
function createLineChart(data,id) {
var svg = d3.select("body").append("svg")
.attr("width", 800)
.attr("height", 600);
var line = d3.line()
.x(function(d, i) { return i * 50 + 50; })
.y(function(d) { return 300 - d; });
svg.append("path")
.datum(data)
.attr("d", line)
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "none");
data.forEach(function(d, i) {
if (i > 0) {
var percentChange = (d - data[i - 1]) / data[i - 1] * 100;
var color = percentChange >= 0 ? "green" : "red";
var y = percentChange >= 0 ? d - 15 : d + 15;
svg.append("text")
.text(percentChange.toFixed(1) + "%")
.attr("x", i * 50 + 50)
.attr("y", y)
.attr("text-anchor", "middle")
.attr("fill", color);
}
});
}
</script>
</head>
<body>
</body>
<script>
var data = [10, 20, 15, 40, 50, 60];
createLineChart(data);
</script>
</html>
I guess the main things you need to read up on are d3 scales and selections (particularly joins). In particular:
We'll define the width w and height h of the SVG and then defining scales to fit the path into the SVG.
We'll use the data to define each of the path, markers, and text in terms of the data.
createLineChart([10, 20, 15, 40, 50, 60], { mark_points: true })
function createLineChart(data, opts = {}) {
let { w = 800, h = 500, mark_points = false } = opts;
let pad = 50;
let x_scale = d3
.scaleLinear()
.domain([0, data.length - 1])
.range([pad, w - pad]);
let [ymin, ymax] = d3.extent(data);
let y_scale = d3
.scaleLinear()
.domain([ymin, ymax])
.range([h - pad, pad]);
let svg = d3.select('#container')
.append("svg")
.attr("width", w)
.attr("height", h)
.style("border", "solid 1px black");
let line = d3
.line()
.x(function (d, i) {
return x_scale(i);
})
.y(function (d) {
return y_scale(d);
});
svg
.append("path")
.datum(data)
.attr("d", line)
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "none");
svg
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", (_, i) => x_scale(i))
.attr("cy", (d) => y_scale(d))
.attr("r", 5)
.attr("fill", "black");
if (mark_points) {
svg
.selectAll("text")
.data(data)
.join("text")
.attr("x", (_, i) => x_scale(i))
.attr("y", (d) => y_scale(d))
.attr("dx", -3)
.attr("dy", 12)
.attr("font-size", 16)
.attr("text-anchor", "end")
.attr("fill", (_, i) =>
data[i] < data[i - 1]
? "red"
: data[i] == data[i - 1]
? "blue"
: "green"
)
.text(function (_, i) {
if (i > 0) {
let change = (data[i] - data[i - 1]) / data[i - 1];
return d3.format("0.2%")(change);
} else {
return "";
}
});
}
svg
.append("g")
.attr("transform", `translate(0, ${h - pad})`)
.call(d3.axisBottom(x_scale).ticks(data.length));
svg
.append("g")
.attr("transform", `translate(${pad})`)
.call(d3.axisLeft(y_scale).ticks(5));
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<div id="container"></div>
Related
I would like to create a rounded edge for a corner where the user can specify the corner's radius in D3js.
I found a post that has potential solutions, but the examples are in Observable notebook.
I tried converting to plain Javascript. But it didn't work for me.
https://observablehq.com/#carpiediem/svg-paths-with-circular-corners
Any help is much appreciated, thanks.
I think the post you shared may be overly complicated. Assuming you are using d3.line() or d3.area(), I would suggest looking into the different curve interpolators available in D3. Many of them allow an extra parameter to specify, for example, a tension that can be manipulated.
Here it is:
const drag = () => {
function dragstarted(d) {
d3.select(this).raise().attr("stroke", "black");
}
function dragged(d) {
d3.select(this)
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y);
d3.select('path.angled')
.attr('d', 'M' + points.map(d => `${d.x} ${d.y}`).join(','));
const angle = Math.atan2(points[1].y-points[0].y, points[1].x-points[0].x)
- Math.atan2(points[1].y-points[2].y, points[1].x-points[2].x);
const acuteAngle = Math.min(Math.abs(angle), 2*Math.PI-Math.abs(angle));
const shortestRay = Math.min(
Math.sqrt(Math.pow(points[1].x-points[0].x, 2) + Math.pow(points[1].y-points[0].y, 2)),
Math.sqrt(Math.pow(points[1].x-points[2].x, 2) + Math.pow(points[1].y-points[2].y, 2))
);
const radiusToUse = Math.min( cornerRadius, shortestRay * Math.tan(acuteAngle/2) );
const distanceToTangentPoint = Math.abs(radiusToUse / Math.tan(acuteAngle/2));
const determinant = (points[1].x-points[0].x)*(points[1].y-points[2].y) - (points[1].x-points[2].x)*(points[1].y-points[0].y);
const sweepFlag = determinant < 0 ? 1 : 0;
const anchorIn = alongSegment(points[1], points[0], distanceToTangentPoint);
const anchorOut = alongSegment(points[1], points[2], distanceToTangentPoint);
const manualPathDesc = `
M${points[0].x} ${points[0].y}
L${anchorIn.x} ${anchorIn.y}
A${radiusToUse} ${radiusToUse} 0 0 ${sweepFlag} ${anchorOut.x} ${anchorOut.y}
L${points[2].x} ${points[2].y}
`;
d3.select('path.arced').attr('d', manualPathDesc);
d3.select('rect.anchor.in')
.attr("x", anchorIn.x - 3)
.attr("y", anchorIn.y - 3);
d3.select('rect.anchor.out')
.attr("x", anchorOut.x - 3)
.attr("y", anchorOut.y - 3);
const circleCenter = alongSegment(
points[1],
{ x: (anchorIn.x + anchorOut.x)/2, y: (anchorIn.y + anchorOut.y)/2 },
Math.sqrt(Math.pow(radiusToUse, 2) + Math.pow(distanceToTangentPoint, 2))
);
d3.select('path.triangles')
.attr("d", `M${points[1].x} ${points[1].y} L${circleCenter.x} ${circleCenter.y} L${anchorIn.x} ${anchorIn.y} L${circleCenter.x} ${circleCenter.y} L${anchorOut.x} ${anchorOut.y}`);
d3.select('text.angle').text(`${Math.round(acuteAngle * 180 / Math.PI)}°`);
d3.select('text.shortest').text(Math.round(shortestRay));
d3.select('text.maxradius').text(Math.round(shortestRay * Math.tan(acuteAngle/2)));
d3.select('text.toAnchor').text(Math.round(distanceToTangentPoint));
d3.select('text.determinate').text(determinant < 0 ? 'neg.' : 'pos.');
}
function dragended(d) {
d3.select(this).attr("stroke", null);
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
function alongSegment(from, toward, distanceAlong) {
const bearing = Math.atan2(from.y-toward.y, from.x-toward.x);
return {
bearing,
x: from.x - distanceAlong * Math.cos(bearing),
y: from.y - distanceAlong * Math.sin(bearing)
};
}
const chart = () => {
var color = d3.scaleOrdinal().range(d3.schemeCategory20);
const svg = d3.select("svg")
.attr("viewBox", [0, 0, width, height]);
const angle = Math.atan2(points[1].y-points[0].y, points[1].x-points[0].x)
- Math.atan2(points[1].y-points[2].y, points[1].x-points[2].x);
const acuteAngle = Math.min(Math.abs(angle), 2*Math.PI-Math.abs(angle));
const shortestRay = Math.min(
Math.sqrt(Math.pow(points[1].x-points[0].x, 2) + Math.pow(points[1].y-points[0].y, 2)),
Math.sqrt(Math.pow(points[1].x-points[2].x, 2) + Math.pow(points[1].y-points[2].y, 2))
);
const radiusToUse = Math.min( cornerRadius, shortestRay * Math.tan(acuteAngle/2) );
const distanceToTangentPoint = Math.abs(radiusToUse / Math.tan(acuteAngle/2));
const determinant = (points[1].x-points[0].x)*(points[1].y-points[2].y) - (points[1].x-points[2].x)*(points[1].y-points[0].y);
const sweepFlag = determinant < 0 ? 1 : 0;
const anchorIn = alongSegment(points[1], points[0], distanceToTangentPoint);
const anchorOut = alongSegment(points[1], points[2], distanceToTangentPoint);
const circleCenter = alongSegment(
points[1],
{ x: (anchorIn.x + anchorOut.x)/2, y: (anchorIn.y + anchorOut.y)/2 },
Math.sqrt(Math.pow(radiusToUse, 2) + Math.pow(distanceToTangentPoint, 2))
);
const manualPathDesc = `M${points[0].x} ${points[0].y}
L${anchorIn.x} ${anchorIn.y}
A${radiusToUse} ${radiusToUse} 0 0 ${sweepFlag} ${anchorOut.x} ${anchorOut.y}
L${points[2].x} ${points[2].y}
`;
svg.append('rect')
.attr("x", 8)
.attr("y", 10)
.attr("width", 160)
.attr("height", 105)
.attr("fill", '#eee');
svg.append('text')
.attr("class", 'angle')
.attr("x", 35)
.attr("y", 25)
.attr("text-anchor", "end")
.text(`${Math.round(acuteAngle * 180 / Math.PI)}°`);
svg.append('text')
.attr("class", 'shortest')
.attr("x", 35)
.attr("y", 45)
.attr("text-anchor", "end")
.text(Math.round(shortestRay));
svg.append('text')
.attr("class", 'maxradius')
.attr("x", 35)
.attr("y", 65)
.attr("text-anchor", "end")
.text(Math.round(shortestRay * Math.tan(acuteAngle/2)));
svg.append('text')
.attr("class", 'toAnchor')
.attr("x", 35)
.attr("y", 85)
.attr("text-anchor", "end")
.text(Math.round(distanceToTangentPoint));
svg.append('text')
.attr("class", 'determinate')
.attr("x", 35)
.attr("y", 105)
.attr("text-anchor", "end")
.text(determinant < 0 ? 'neg.' : 'pos.');
svg.append('text')
.attr("x", 40)
.attr("y", 25)
.attr("text-anchor", "start")
.text('angle between rays');
svg.append('text')
.attr("x", 40)
.attr("y", 45)
.attr("text-anchor", "start")
.text('length of shortest ray');
svg.append('text')
.attr("x", 40)
.attr("y", 65)
.attr("text-anchor", "start")
.text('max radius, to fit');
svg.append('text')
.attr("x", 40)
.attr("y", 85)
.attr("text-anchor", "start")
.text('from vertex to anchors');
svg.append('text')
.attr("x", 40)
.attr("y", 105)
.attr("text-anchor", "start")
.text('determinant value');
svg.append('path')
.attr("class", 'arced')
.datum(points)
.attr("d", manualPathDesc)
.attr("stroke", 'orange')
.attr("stroke-width", 5)
.attr("fill", 'none');
svg.append('path')
.attr("class", 'angled')
.attr("d", 'M' + points.map(d => `${d.x} ${d.y}`).join(', '))
.attr("stroke", '#888')
.attr("fill", 'none');
svg.append('rect')
.attr("class", 'anchor in')
.attr("x", anchorIn.x - 3)
.attr("y", anchorIn.y - 3)
.attr("width", 6)
.attr("height", 6)
.attr("fill", '#888');
svg.append('rect')
.attr("class", 'anchor out')
.attr("x", anchorOut.x - 3)
.attr("y", anchorOut.y - 3)
.attr("width", 6)
.attr("height", 6)
.attr("fill", '#ccc');
svg.append('path')
.attr("class", 'triangles')
.attr("d", `M${points[1].x} ${points[1].y} L${circleCenter.x} ${circleCenter.y} L${anchorIn.x} ${anchorIn.y} L${circleCenter.x} ${circleCenter.y} L${anchorOut.x} ${anchorOut.y}`)
.attr("stroke", '#ccc')
.attr("fill", 'none');
svg.selectAll("circle")
.data(points)
.enter()
.append("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", 6)
.attr("fill", (d, i) => color(i))
.on("mouseover", function (d) {d3.select(this).style("cursor", "move");})
.on("mouseout", function (d) {})
.call(drag());
return svg.node();
}
const width = 1000;
const height = 600;
const cornerRadius = 50;
const points = d3.range(3).map(i => ({
x: Math.random() * (width - 10 * 2) + 10,
y: Math.random() * (300 - 10 * 2) + 10,
}));
chart();
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="1000" height="600"></svg>
I have made a graph using D3 but the problem is that I want its tooltip to appear on the graph as per nearest mouse point over in the graph. I have also seen one example on StackOverflow here on this link - D3: Get nearest value from ordinal axis on mouseover. Which is doing the exact what I want but when I implemented the same code on my graph then it is not working. Please take a look at my code and suggest me changes.
IMPORTANT!: I also want when someone double click on the graph then tooltip x'axis & y'axis coordinated stay there. Your valuable time is highly appreciated. Please Help!!!
<div id='chartdiv'></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
function getData(){
data1 = [{x:1,y:15},{x:2,y:26},{x:3,y:17},{x:4,y:21},];
return data1};
function drawChart(data) {
var coreheight = 720
var corewidth = 1280
var margin = {top: 10, right: 30, bottom: 100, left: 60}
, width = corewidth - margin.left - margin.right
, height = coreheight - margin.top - margin.bottom;
var xExtent = d3.extent(data, function(d) { return d.x; }),
xRange = xExtent[1] - xExtent[0],
yExtent = d3.extent(data, function(d) { return d.y; }).reverse(),
yRange = yExtent[1] - yExtent[0];
var xScale = d3.scaleLinear().range([50, width]).domain([xExtent[0] - (xRange * .05), xExtent[1] + (xRange * .05)]);;
var yScale = d3.scaleLinear().range([0, height]).domain([yExtent[0] - (yRange * .1), yExtent[1] + (yRange * .05)]);;
var line = d3.line()
.x(function(d, x) { return xScale(d.x); })
.y(function(d, y) { return yScale(d.y); })
.curve(d3.curveMonotoneX);
d3.select('#chartdiv')
.append('svg')
.attr('class','graph')
.style('background-color','#fff')
.attr("viewBox", "0 0 "+ corewidth +" "+ coreheight +"");
var svg = d3.select(".graph")
.append("g")
.attr("class", "dchart")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale).ticks(7))
.attr("transform", "translate(50, 0)")
.attr('font-size','25px');
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale).ticks(7))
.attr('font-size','25px');
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return xScale(d.x) })
.attr("cy", function(d) { return yScale(d.y) })
.attr("r", 8)
.on("mouseover", mousehover)
.on("mouseout", mousehoverout)
function mousehover(d) {
svg.append("text")
.attr('class','annot')
.attr('x', xScale(d.x) - 20)
.attr('y', yScale(d.y) - 23)
.text(d.x+','+d.y)
.style('font-size','30px')
svg.append('line')
.attr('class','annot')
.style("stroke", "black")
.attr('stroke-width','3')
.attr("x1", xScale(d.x))
.attr("y1", yScale(d.y))
.attr("x2", xScale(d.x))
.attr("y2", height);
}
function mousehoverout() {
d3.selectAll('.annot').remove()
}
}drawChart(getData());
</script>
<style>
.line {fill: none;stroke: darkblue;stroke-width: 3}
.dot {fill: darkblue;stroke-width:0}
</style>
You need to track where the mouse is on the chart, you're currently only checking if the mouse is hovering on a dot.
I've added a couple of event handlers to the entire chart:
.on("mousemove", mousemove)
.on("mouseout", mousehoverout);
One you have already implemented, mousehoverout, and the other, mousemove, which tracks where the mouse is relative to the points.
var lastIndex = -1;
function mousemove()
{
let x = d3.mouse(this)[0];
let closest = data.reduce((best, value, i) =>
{
let absx = Math.abs(xScale(value.x) - x)
if(absx < best.value)
{
return {index: i, value:absx};
}
else
{
return best;
}
}, {index:0, value:Number.MAX_SAFE_INTEGER});
if(lastIndex != closest.index)
{
d3.selectAll('.annot').remove();
lastIndex = closest.index;
}
mousehover(data[closest.index]);
}
Then to add the double click functionality, you can do as you have shown in the w3schools link, but add the handler to the chart:
.on('dblclick', function(){/* your code in here */});
function getData(){
data1 = [{x:1,y:15},{x:2,y:26},{x:3,y:17},{x:4,y:21},];
return data1};
function drawChart(data) {
var coreheight = 720
var corewidth = 1280
var margin = {top: 10, right: 30, bottom: 100, left: 60}
, width = corewidth - margin.left - margin.right
, height = coreheight - margin.top - margin.bottom;
var xExtent = d3.extent(data, function(d) { return d.x; }),
xRange = xExtent[1] - xExtent[0],
yExtent = d3.extent(data, function(d) { return d.y; }).reverse(),
yRange = yExtent[1] - yExtent[0];
var xScale = d3.scaleLinear().range([50, width]).domain([xExtent[0] - (xRange * .05), xExtent[1] + (xRange * .05)]);;
var yScale = d3.scaleLinear().range([0, height]).domain([yExtent[0] - (yRange * .1), yExtent[1] + (yRange * .05)]);;
var line = d3.line()
.x(function(d, x) { return xScale(d.x); })
.y(function(d, y) { return yScale(d.y); })
.curve(d3.curveMonotoneX);
d3.select('#chartdiv')
.append('svg')
.attr('class','graph')
.style('background-color','#fff')
.attr("viewBox", "0 0 "+ corewidth +" "+ coreheight +"")
.on('dblclick', function(){if(lastIndex > -1) { createpeak(data[lastIndex]); } })
.on("mousemove", mousemove)
.on("mouseout", mousehoverout);
var svg = d3.select(".graph")
.append("g")
.attr("class", "dchart")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale).ticks(7))
.attr("transform", "translate(50, 0)")
.attr('font-size','25px');
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale).ticks(7))
.attr('font-size','25px');
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return xScale(d.x) })
.attr("cy", function(d) { return yScale(d.y) })
.attr("r", 8);
var lastIndex = -1;
function mousemove()
{
let x = d3.mouse(this)[0];
let closest = data.reduce((best, value, i) =>
{
let absx = Math.abs(xScale(value.x) - x)
if(absx < best.value)
{
return {index: i, value:absx};
}
else
{
return best;
}
}, {index:0, value:Number.MAX_SAFE_INTEGER});
d3.selectAll('.annot').remove();
lastIndex = closest.index;
mousehover(data[closest.index]);
}
function mousehover(d) {
svg.append("text")
.attr('class','annot')
.attr('x', xScale(d.x) - 20)
.attr('y', yScale(d.y) - 23)
.text(d.x+','+d.y)
.style('font-size','30px')
svg.append('line')
.attr('class','annot')
.style("stroke", "black")
.attr('stroke-width','3')
.attr("x1", xScale(d.x))
.attr("y1", yScale(d.y))
.attr("x2", xScale(d.x))
.attr("y2", height);
}
var i = 1;
var m = 1;
function createpeak(d) {
console.log('redacted');
}
function dragstarted(d,e){
d3.select(this).raise().classed("active", true);
var current = d3.select(this);
deltaX = current.attr("x") - d3.event.x;
deltaY = current.attr("y") - d3.event.y;
}
function dragged(d,e){
d3.select(this).attr("x",d3.event.x + deltaX)
d3.select(this).attr("y",d3.event.y + deltaY)
for (var i=1; i <= 100; i++){
d3.select('.line'+i).attr("x2", function (d) {
return d3.select('.peak'+i).attr("x") - deltaX
});
d3.select('.line'+i).attr("y2", function (d) {
return d3.select('.peak'+i).attr("y") - deltaY
})}}
function dragended(d,e){}
function mousehoverout() {
}
}drawChart(getData());
.line {fill: none;stroke: darkblue;stroke-width: 3}
.dot {fill: darkblue;stroke-width:0}
<div id='chartdiv'></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
var w = 300;
var h = 150;
var padding = 2;
var dataset =[5, 10, 15, 20, 25];
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
function colorPicker(v){
if (v<=20) { return "#666666"; }
else if (v>20) { return "#FF0033"; }
}
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) { return (i*(w/dataset.length)); })
.attr("y", function(d) { return h-(d*4); })
.attr("width", w/dataset.length-padding)
.attr("height", function(d) { return d*4;})
.attr("fill", function(d){
return colorPicker(d);
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {return d; })
.attr({"text-anchor": "middle"})
.attr({
x: function(d, i) {return i* (w / dataset.length);},
y: function(d) {return h - (d*4); }
});
I am following a D3.js tutorial and I'm trying to get the text-anchor to work, but it won't append. No text appears, can anyone shed any light into what I'm doing wrong?
It should display the number above every rectangle
In the new (not so new, actually) V4.x version, you cannot use objects to set the attr() method.
Besides that you have another problem, which will avoid the texts to be rendered: there is no value property in your dataset (which is just an array of numbers). Thus, it should be:
.text(function(d){return d})
Here is your code with the necessary changes:
var w = 300;
var h = 150;
var padding = 2;
var dataset = [5, 10, 15, 20, 25];
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
function colorPicker(v) {
if (v <= 20) {
return "#666666";
} else if (v > 20) {
return "#FF0033";
}
}
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return (i * (w / dataset.length));
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - padding)
.attr("height", function(d) {
return d * 4;
})
.attr("fill", function(d) {
return colorPicker(d);
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("text-anchor", "middle")
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return i * (w / dataset.length) + ((w / dataset.length - padding) / 2);
})
.attr("y", function(d) {
return h - (d * 4);
});
<script src="https://d3js.org/d3.v4.min.js"></script>
I'm trying to add a legend to a d3 scatterplot matrix (using this example as a template: http://bl.ocks.org/mbostock/4063663), and while the scatterplot itself is displaying as expected, I have been unable to successfully add a legend. The code for the plot and one of the attempts at adding a legend are below:
var width = 960,
size = 150,
padding = 19.5;
var x = d3.scale.linear()
.range([padding / 2, size - padding / 2]);
var y = d3.scale.linear()
.range([size - padding / 2, padding / 2]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var color = d3.scale.category10();
d3.csv(datafilename, function(error, dataset) {
var domainByTrait = {},
traits = d3.keys(dataset[0]).filter(function(d) { return d !== "class"; }),
n = traits.length;
traits.forEach(function(trait) {
domainByTrait[trait] = d3.extent(dataset, function(d) { return d[trait]; });
});
xAxis.tickSize(size * n);
yAxis.tickSize(-size * n);
var brush = d3.svg.brush()
.x(x)
.y(y)
.on("brushstart", brushstart)
.on("brush", brushmove)
.on("brushend", brushend);
var svg = d3.select("#visualizationDiv").append("svg")
.attr("width", size * n + padding)
.attr("height", size * n + padding)
.append("g")
.attr("transform", "translate(" + padding + "," + padding / 2 + ")");
svg.selectAll(".x.axis")
.data(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function(d, i) { return "translate(" + (n - i - 1) * size + ",0)"; })
.each(function(d) { x.domain(domainByTrait[d]); d3.select(this).call(xAxis); });
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function(d, i) { return "translate(0," + i * size + ")"; })
.each(function(d) { y.domain(domainByTrait[d]); d3.select(this).call(yAxis); });
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")"; })
.each(plot);
// Titles for the diagonal.
cell.filter(function(d) { return d.i === d.j; }).append("text")
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text(function(d) { return d.x; });
cell.call(brush);
function plot(p) {
var cell = d3.select(this);
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
cell.append("rect")
.attr("class", "frame")
.attr("x", padding / 2)
.attr("y", padding / 2)
.attr("width", size - padding)
.attr("height", size - padding);
cell.selectAll("circle")
.data(dataset)
.enter().append("circle")
.attr("cx", function(d) { return x(d[p.x]); })
.attr("cy", function(d) { return y(d[p.y]); })
.attr("r", 3)
.style("fill", function(d) { return color(d.class); });
}
var brushCell;
// Clear the previously-active brush, if any.
function brushstart(p) {
if (brushCell !== this) {
d3.select(brushCell).call(brush.clear());
x.domain(domainByTrait[p.x]);
y.domain(domainByTrait[p.y]);
brushCell = this;
}
}
// Highlight the selected circles.
function brushmove(p) {
var e = brush.extent();
svg.selectAll("circle").classed("hidden", function(d) {
return e[0][0] > d[p.x] || d[p.x] > e[1][0]
|| e[0][1] > d[p.y] || d[p.y] > e[1][1];
});
}
// If the brush is empty, select all circles.
function brushend() {
if (brush.empty()) svg.selectAll(".hidden").classed("hidden", false);
}
function cross(a, b) {
var c = [], n = a.length, m = b.length, i, j;
for (i = -1; ++i < n;) for (j = -1; ++j < m;) c.push({x: a[i], i: i, y: b[j], j: j});
return c;
}
d3.select(self.frameElement).style("height", size * n + padding + 20 + "px");
// add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 100)
.attr('transform', 'translate(-20,50)');
legend.selectAll('rect')
.data(dataset)
.enter()
.append("rect")
.attr("x", width - 65)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d) { return color(d.class); });
legend.selectAll('text')
.data(dataset)
.enter()
.append("text")
.attr("x", width - 52)
.attr("y", function(d, i){ return i * 20 + 9;})
.text(function(d) { return d.class; });
});
Among my other unsuccessful attempts at adding a legend are
var legend = svg.selectAll("g")
.data(dataset)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 28)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) { return color(d.class); });
legend.append("text")
.attr("x", width - 34)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d.class; });
and
var legend = svg.selectAll('g').data(dataset)
.enter()
.append('g')
.attr("class", "legend");
legend.append("rect")
.attr("x", width - 45)
.attr("y", 25)
.attr("height", 50)
.attr("width", 50)
.each(function(d, i) {
var g = d3.select(this);
g.append("rect")
.attr("x", width - 65)
.attr("y", i*25)
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d) { return color(d.class); });
g.append("text")
.attr("x", width - 50)
.attr("y", i * 25 + 8)
.attr("height",30)
.attr("width",100)
.style("fill", function(d) { return color(d.class); })
.text(function(d) { return d.class; });
all based on examples I've found on the web. None of these approaches seem to be working - I must be missing something here. Any insights or suggestions would be greatly appreciated.
The problem is right at the beginning:
var legend = svg.selectAll('g').data(dataset)
.enter()
.append('g')
.attr("class", "legend");
The selectAll('g') is going to select one of the groups already in your diagram, and then nothing will happen because enter() indicates that everything from there on (including the value that gets saved to the legend variable) only applies to groups that don't exist yet.
I'm pretty sure this legend code is supposed to be run from within its own <g> element. That way, it won't interfere with the rest of your graph.
var legendGroup = svg.append('g')
.attr('class', 'legend')
.attr('transform', /* translate as appropriate */);
var legendEntry = legendGroup.selectAll('g')
.data(dataset);
//create one legend entry for each series in the dataset array
//if that's not what you want, create an array that has one
//value for every entry you want in the legend
legendEntry.enter().append("g")
.attr("class", "legend-entry")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
//shift each entry down by approx 1 line (20px)
legendEntry.append("rect") //add a square to each entry
/* and so on */
var margin = {top: 30, right: 20, bottom: 30, left: 70},
h= 500;
w = 960;
ruleColor = "#CCC";
minVal = 0;
maxVal = 100;
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var viz = d3.select("#radar")
.append('svg:svg')
.attr('width', w)
.attr('height', h)
.attr('class', 'vizSvg');
viz.append("svg:rect")
.attr('id', 'axis-separator')
.attr('x', 0)
.attr('y', 0)
.attr('height', 0)
.attr('width', 0)
.attr('height', 0);
vizBody = viz.append("svg:g")
.attr('id', 'body');
var heightCircleConstraint = 500 - margin.top - margin.bottom;
var widthCircleConstraint = width = 960 - margin.left - margin.right,
circleConstraint = d3.min([heightCircleConstraint, widthCircleConstraint]);
var radius = d3.scale.linear().domain([minVal, maxVal]).range([0, (circleConstraint / 2)]);
var radiusLength = radius(maxVal);
var centerXPos = widthCircleConstraint / 2 + margin.left;
var centerYPos = heightCircleConstraint / 2 + margin.top;
vizBody.attr("transform",
"translate(" + centerXPos + ", " + centerYPos + ")");
d3.json("data/radar.json", function(error, data) {
var hours = [];
var series = [[]];
data.QualitySummaryObject.forEach(function(d,i) {
series[0][i] = d.extractPercentage;
hours[i] = d.extractorName;
});
for (i = 0; i < series.length; i += 1) {
series[i].push(series[i][0]);
}
//console.log(series.length);
var radialTicks = radius.ticks(5);
vizBody.selectAll('.circle-ticks').remove();
vizBody.selectAll('.line-ticks').remove();
var circleAxes = vizBody.selectAll('.circle-ticks')
.data(radialTicks)
.enter().append('svg:g')
.attr("class", "circle-ticks");
circleAxes.append("svg:circle")
.attr("r", function (d, i) {
return radius(d);
})
.attr("class", "circle")
.style("stroke", ruleColor)
.style("fill", "none");
circleAxes.append("svg:text")
.attr("text-anchor", "middle")
.attr("dy", function (d) {
return -1 * radius(d);
})
.text(String);
var lineAxes = vizBody.selectAll('.line-ticks')
.data(hours)
.enter().append('svg:g')
.attr("transform", function (d, i) {
return "rotate(" + ((i / hours.length * 360) - 90) +
")translate(" + radius(maxVal) + ")";
})
.attr("class", "line-ticks");
lineAxes.append('svg:line')
.attr("x2", -1 * radius(maxVal))
.style("stroke", ruleColor)
.style("fill", "none");
lineAxes.append('svg:text')
.text(String)
.attr("text-anchor", "middle")
.attr("transform","translate(15) rotate(90)");
//var highlightedDotSize = 4;
var groups = vizBody.selectAll('.series').data(series);
//console.log(hours.length);
groups.enter().append("svg:g")
.attr('class', 'series')
.style('fill', "green")
.style('stroke',"#ccc");
//groups.exit().remove();
//console.log(Math.PI);
var lines = groups.append('svg:path')
.attr("class", "line")
/*.attr("d", d3.svg.line.radial()
.radius(function (d) {
return 10;
})
.angle(function (d, i) {
if (i == hours.length) {
i = 0;
} //close the line
return (i / hours.length) * 2 * Math.PI;
}))*/
.style("stroke-width", 1)
.style("fill", "rgba(124,240,10,0.1)");
/*groups.selectAll(".curr-point")
.data(function (d) {
return [d[0]];
})
.enter().append("svg:circle")
.attr("class", "curr-point")
.attr("r", 0);
groups.selectAll(".clicked-point")
.data(function (d) {
return [d[0]];
})
.enter().append("svg:circle")
.attr('r', 0)
.attr("class", "clicked-point");*/
lines.attr("d", d3.svg.line.radial()
.radius(function (d) {
return radius(d);
})
.angle(function (d, i) {
if (i === hours.length) {
i = 0;
} //close the line
return (i / hours.length) * 2 * Math.PI;
}));
});
i implemented this code to create radar chart with a json data here is the json data format
{
"QualitySummaryObject": [
{
"extractPercentage": 68.81964,
"extractorName": "Classification"
},
{
"extractPercentage": 74.09091,
"extractorName": "Keyword Match"
},
{
"extractPercentage": 54.62963,
"extractorName": "LocationBroadcast"
},
{
"extractPercentage": 98.91892,
"extractorName": "Qualification"
},
{
"extractPercentage": 98.76923,
"extractorName": "User Profile Location"
},
{
"extractPercentage": 80.15909,
"extractorName": "Valid Person Name"
},
]
}
Now i want to put tooltip on each node point .. but i am not able to get any idea how to do that any body can help?
Here is an example of Twitter Bootstrap tooltips running on SVGs with D3 http://markhansen.co.nz/stolen-vehicles-pt2/
To get it working on newer versions see Why doesn't Twitter Bootstrap popover work with SVG elements? You'll need to use a 2.3.0+ version of bootstrap or the fix I posted in that thread.