Related
I have a radar chart working in a regular js file, and a react app component that can render and return a simple bar chart. I'm trying to get the much more complex radar chart to render and return in react's component. I'm currently getting an error: ./src/App.js Attempted import error: 'scale' is not exported from 'd3' (imported as 'd3'). Any other feedback/suggestions in general would be appreciated. I'm new to D3. Thanks so much in advance.
import * as d3 from "d3";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
/////////////////////////////////////////////////////////
/////////////// The Radar Chart Function ////////////////
/////////////// Written by Nadieh Bremer ////////////////
////////////////// VisualCinnamon.com ///////////////////
/////////// Inspired by the code of alangrafu ///////////
/////////////////////////////////////////////////////////
/* Radar chart design created by Nadieh Bremer - VisualCinnamon.com */
componentDidMount() {
//////////////////////// Set-Up //////////////////////////////
var margin = { top: 100, right: 100, bottom: 100, left: 100 },
width = Math.min(700, window.innerWidth - 10) - margin.left - margin.right,
height = Math.min(
width,
window.innerHeight - margin.top - margin.bottom - 20
);
////////////////////////// Data //////////////////////////////
var data = [
[
//Tsircon
{ axis: "BALLISTIC", value: 65 },
{ axis: "Glide", value: 39 },
{ axis: "20G TURN", value: 43 },
{ axis: "FISH HOOK DIVE", value: 50 },
{ axis: "PHUGOID MOTION", value: 60 },
{ axis: "ENERGY SCRUB", value: 52 },
],
// [
// //Kinzhal
// { axis: "BALLISTIC", value: 0.27 },
// { axis: "Glide", value: 0.16 },
// { axis: "20G TURN", value: 0.35 },
// { axis: "FISH HOOK DIVE", value: 0.13 },
// { axis: "PHUGOID MOTION", value: 0.2 },
// { axis: "ENERGY SCRUB", value: 0.13 },
// ],
// [
// //Example Missile Overlay
// { axis: "BALLISTIC", value: 0.26 },
// { axis: "Glide", value: 0.1 },
// { axis: "20G TURN", value: 0.3 },
// { axis: "FISH HOOK DIVE", value: 0.14 },
// { axis: "PHUGOID MOTION", value: 0.22 },
// { axis: "ENERGY SCRUB", value: 0.04 },
// ],
];
//////////////////// Draw the Chart //////////////////////////
var color = d3.scale.ordinal().range(["#739CC4", "#CC333F", "#00A0B0"]);
var radarChartOptions = {
w: width,
h: height,
margin: margin,
maxValue: 0.5,
levels: 5,
roundStrokes: false,
color: color,
};
//Call function to draw the Radar chart
RadarChart(".radarChart", data, radarChartOptions);
function RadarChart(id, data, options) {
var cfg = {
w: 600, //Width of the circle
h: 600, //Height of the circle
margin: { top: 20, right: 20, bottom: 20, left: 20 }, //The margins of the SVG
levels: 3, //How many levels or inner circles should there be drawn
maxValue: 0, //What is the value that the biggest circle will represent
labelFactor: 1.25, //How much farther than the radius of the outer circle should the labels be placed
wrapWidth: 60, //The number of pixels after which a label needs to be given a new line
opacityArea: 0.35, //The opacity of the area of the blob
dotRadius: 4, //The size of the colored circles of each blog
opacityCircles: 0.1, //The opacity of the circles of each blob
strokeWidth: 2, //The width of the stroke around each blob
roundStrokes: false, //If true the area and stroke will follow a round path (cardinal-closed)
color: d3.scale.category10(), //Color function
};
//Put all of the options into a variable called cfg
if ("undefined" !== typeof options) {
for (var i in options) {
if ("undefined" !== typeof options[i]) {
cfg[i] = options[i];
}
} //for i
} //if
//If the supplied maxValue is smaller than the actual one, replace by the max in the data
var maxValue = Math.max(
cfg.maxValue,
d3.max(data, function (i) {
return d3.max(
i.map(function (o) {
return o.value;
})
);
})
);
var allAxis = data[0].map(function (i, j) {
return i.axis;
}), //Names of each axis
total = allAxis.length, //The number of different axes
radius = Math.min(cfg.w / 2, cfg.h / 2), //Radius of the outermost circle
Format = d3.format("%"), //Percentage formatting
angleSlice = (Math.PI * 2) / total; //The width in radians of each "slice"
//Scale for the radius
var rScale = d3.scale.linear().range([0, radius]).domain([0, maxValue]);
//////////// Create the container SVG and g /////////////
//Remove whatever chart with the same id/class was present before
d3.select(id).select("svg").remove();
//Initiate the radar chart SVG
var svg = d3
.select(id)
.append("svg")
.attr("width", cfg.w + cfg.margin.left + cfg.margin.right)
.attr("height", cfg.h + cfg.margin.top + cfg.margin.bottom)
.attr("class", "radar" + id);
//Append a g element
var g = svg
.append("g")
.attr(
"transform",
"translate(" +
(cfg.w / 2 + cfg.margin.left) +
"," +
(cfg.h / 2 + cfg.margin.top) +
")"
);
////////// Glow filter ///////////
//Filter for the outside glow
var filter = g.append("defs").append("filter").attr("id", "glow"),
feGaussianBlur = filter
.append("feGaussianBlur")
.attr("stdDeviation", "2.5")
.attr("result", "coloredBlur"),
feMerge = filter.append("feMerge"),
feMergeNode_1 = feMerge.append("feMergeNode").attr("in", "coloredBlur"),
feMergeNode_2 = feMerge.append("feMergeNode").attr("in", "SourceGraphic");
/////////////// Draw the Circular grid //////////////////
//Wrapper for the grid & axes
var axisGrid = g.append("g").attr("class", "axisWrapper");
//Draw the background circles
axisGrid
.selectAll(".levels")
.data(d3.range(1, cfg.levels + 1).reverse())
.enter()
.append("circle")
.attr("class", "gridCircle")
.attr("r", function (d, i) {
return (radius / cfg.levels) * d;
})
.style("fill", "#CDCDCD")
.style("stroke", "#CDCDCD")
.style("fill-opacity", cfg.opacityCircles)
.style("filter", "url(#glow)");
//Text indicating at what % each level is
axisGrid
.selectAll(".axisLabel")
.data(d3.range(1, cfg.levels + 1).reverse())
.enter()
.append("text")
.attr("class", "axisLabel")
.attr("x", 4)
.attr("y", function (d) {
return (-d * radius) / cfg.levels;
})
.attr("dy", "0.4em")
.style("font-size", "10px")
.attr("fill", "#737373")
.text(function (d, i) {
return Format((maxValue * d) / cfg.levels);
});
//////////////////// Draw the axes //////////////////////
//Create the straight lines radiating outward from the center
var axis = axisGrid
.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis");
//Append the lines
axis
.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", function (d, i) {
return rScale(maxValue * 1.1) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("y2", function (d, i) {
return rScale(maxValue * 1.1) * Math.sin(angleSlice * i - Math.PI / 2);
})
.attr("class", "line")
.style("stroke", "white")
.style("stroke-width", "2px");
//Append the labels at each axis
axis
.append("text")
.attr("class", "legend")
.style("font-size", "11px")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("x", function (d, i) {
return (
rScale(maxValue * cfg.labelFactor) *
Math.cos(angleSlice * i - Math.PI / 2)
);
})
.attr("y", function (d, i) {
return (
rScale(maxValue * cfg.labelFactor) *
Math.sin(angleSlice * i - Math.PI / 2)
);
})
.text(function (d) {
return d;
})
.call(wrap, cfg.wrapWidth);
svg.append("text")
.attr("x", (width / 4.8))
.attr("y", 75 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "24px")
.style("font-weight", "bold")
.style("fill", "white")
.text("MANEUVERS");
svg.append("text")
.attr("x", (width / 4.2))
.attr("y", 95 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "white")
.text("initiate: % ground track");
svg.append("text")
.attr("x", (width / 3.6))
.attr("y", 115 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "#8CD9FF")
.text("% variability");
svg.append("text")
.attr("x", (width / 8))
.attr("y", 135 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "white")
.text("sample:");
svg.append("text")
.attr("x", (width / 3.4))
.attr("y", 135 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "#76ADDB")
.text("% trajectories");
///////////// Draw the radar chart blobs ////////////////
//The radial line function
var radarLine = d3.svg.line
.radial()
.interpolate("linear-closed")
.radius(function (d) {
return rScale(d.value);
})
.angle(function (d, i) {
return i * angleSlice;
});
if (cfg.roundStrokes) {
radarLine.interpolate("cardinal-closed");
}
//Create a wrapper for the blobs
var blobWrapper = g
.selectAll(".radarWrapper")
.data(data)
.enter()
.append("g")
.attr("class", "radarWrapper");
//Append the backgrounds
blobWrapper
.append("path")
.attr("class", "radarArea")
.attr("d", function (d, i) {
return radarLine(d);
})
.style("fill", function (d, i) {
return cfg.color(i);
})
.style("fill-opacity", cfg.opacityArea)
.on("mouseover", function (d, i) {
//Dim all blobs
d3.selectAll(".radarArea")
.transition()
.duration(200)
.style("fill-opacity", 0.1);
//Bring back the hovered over blob
d3.select(this).transition().duration(200).style("fill-opacity", 0.7);
})
.on("mouseout", function () {
//Bring back all blobs
d3.selectAll(".radarArea")
.transition()
.duration(200)
.style("fill-opacity", cfg.opacityArea);
});
//Create the outlines
blobWrapper
.append("path")
.attr("class", "radarStroke")
.attr("d", function (d, i) {
return radarLine(d);
})
.style("stroke-width", cfg.strokeWidth + "px")
.style("stroke", function (d, i) {
return cfg.color(i);
})
.style("fill", "none")
.style("filter", "url(#glow)");
//Append the circles
blobWrapper
.selectAll(".radarCircle")
.data(function (d, i) {
return d;
})
.enter()
.append("circle")
.attr("class", "radarCircle")
.attr("r", cfg.dotRadius)
.attr("cx", function (d, i) {
return rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function (d, i) {
return rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style("fill", function (d, i, j) {
return cfg.color(j);
})
.style("fill-opacity", 0.8);
//////// Append invisible circles for tooltip ///////////
//Wrapper for the invisible circles on top
var blobCircleWrapper = g
.selectAll(".radarCircleWrapper")
.data(data)
.enter()
.append("g")
.attr("class", "radarCircleWrapper");
//Append a set of invisible circles on top for the mouseover pop-up
blobCircleWrapper
.selectAll(".radarInvisibleCircle")
.data(function (d, i) {
return d;
})
.enter()
.append("circle")
.attr("class", "radarInvisibleCircle")
.attr("r", cfg.dotRadius * 1.5)
.attr("cx", function (d, i) {
return rScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2);
})
.attr("cy", function (d, i) {
return rScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2);
})
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function (d, i) {
newX = parseFloat(d3.select(this).attr("cx")) - 10;
newY = parseFloat(d3.select(this).attr("cy")) - 10;
tooltip
.attr("x", newX)
.attr("y", newY)
.text(Format(d.value))
.transition()
.duration(200)
.style("opacity", 1);
})
.on("mouseout", function () {
tooltip.transition().duration(200).style("opacity", 0);
});
//Set up the small tooltip for when you hover over a circle
var tooltip = g.append("text").attr("class", "tooltip").style("opacity", 0);
/////////////////// Helper Function /////////////////////
//Taken from http://bl.ocks.org/mbostock/7555321
//Wraps SVG text
function wrap(text, width) {
text.each(function () {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.4, // ems
y = text.attr("y"),
x = text.attr("x"),
dy = parseFloat(text.attr("dy")),
tspan = text
.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while ((word = words.pop())) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
} //wrap
} //RadarChart
} //componentDidMount
render() {
const styles = {
container: {
display: "grid",
justifyItems: "center"
}
};
return (
// <div ref="chart" style={styles.container}>
<div className=".radarChart" style={styles.container}>
<h1 style={{ textAlign: "center" }}>Hi, I'm the radar chart</h1>
</div>
);
}
}
export default App;
Edit: update: so I found some answers elsewhere, thank you #Michael Rovinsky for pointing me in the right direction. Some of this code was pre D3 version 4.
d3.scale.linear() vs d3.scaleLinear()
What is the d3.js v4.0 equivalent for d3.scale.category10()?
Current error: I am now getting
src\App.js
Line 406:8: 'newX' is not defined no-undef
Line 407:8: 'newY' is not defined no-undef
Line 410:21: 'newX' is not defined no-undef
Line 411:21: 'newY' is not defined no-undef
Which was fixed by defining them with "let" so the new code is
let newX = parseFloat(d3.select(this).attr("cx")) - 10; let newY = parseFloat(d3.select(this).attr("cy")) - 10;
Finally I was getting an error TypeError: Cannot read property 'radial' of undefined
which was solved by updating var radarLine = d3.svg.line.radial().interpolate("linear-closed").radius(...
to radarLine = d3.lineRadial().radius(...
The graph rendered once I fixed the typo className=".radarChart" to className="radarChart"
I'm using D3 library to draw an interconnected graph of elements. My nodes are circles and rects connected by oriented line paths.
My problem is that lines pointing to rects element have an ugly visualisation because the line ends on the top-left corner of the rect rather then the center of it (as it does for circles).
How can I make path lines target the center of both circles elements and rect elements?
Code for definition of defs arrow heads:
svg.append('defs')
.append('marker')
.attr('id', 'arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 17) // As far as I understood this provides the distance from the end of the path line.
.attr('refY', -0.1)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.attr('fill', function() {
return 'red';
})
.append('path')
.attr('d', 'M0,-5L10,0L0,5');
Definition of oriented links:
let links = svg.selectAll('.link')
.data(data.links)
.enter()
.append('path')
.attr('id', function (d) {
return d.id;
})
.attr('class', 'link')
.attr('fill', 'none')
.attr('stroke-width', 1.2)
.attr('marker-end', 'url(#arrow)')
.attr('stroke', function() {
return 'blue';
})
.style('cursor', 'pointer');
Definition of squares
let squares = svg.selectAll('.square')
.data(data.squares, function(d) {
return d.id;
})
.enter().append('g')
.call(dragger)
.attr('class', 'square')
.style('cursor', 'pointer');
squares.append('rect')
.attr('width', 10)
.attr('height', 10)
.attr('fill', function (d) {
return '#fff';
})
.style('opacity', 0.1)
.style('stroke', function() {
return '#555';
})
.style('stroke-width', '2');
In following screenshot you can see how it behaves. Circles and rects have a low opacity to show up the issue with the path target.
UPDATE
Added tick function definition and usage.
simulation
.nodes(data.nodes)
.on('tick', _tick);
simulation
.force('link')
.distance(80)
.links(data.links);
simulation.alpha(1).restart();
function _tick() {
links.attr('d', function(d) {
let dx = d.target.x - d.source.x;
let dy = d.target.y - d.source.y;
let dr = Math.sqrt(dx * dx + dy * dy);
return ('M' + d.source.x + ',' + d.source.y +
'A' + dr + ',' + dr + ' 0 0,1 ' + d.target.x + ',' + d.target.y);
});
circles.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
squares.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
}
What you have right now is the expected behaviour. In a force simulation (I suppose you're running a force simulation), the tick function changes the x and y properties of the datum object, and you can use them the way you want.
As you didn't shared your tick function, I suppose that you are updating the rectangles' x and y position like this:
squares.attr("x", function(d) {
return d.x
}).attr("y", function(d) {
return d.y
});
If that in fact is correct, the top-left corner of the rectangles correspond to d.x and d.y coordinates. And, since you're using the same properties to draw the path, the paths will go from one top-left corner to the other.
This is easy to show, have a look at this demo:
var width = 200;
var height = 200;
var rectSide = 40;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
name: "foo",
color: "blue"
}, {
name: "bar",
color: "green"
}, {
name: "baz",
color: "red"
}];
var links = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}];
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().distance(100))
.force("charge", d3.forceManyBody().strength(-50))
.force("center", d3.forceCenter(width / 2, height / 2));
var node = svg.selectAll(null)
.data(nodes)
.enter()
.append("rect")
.attr("width", rectSide)
.attr("height", rectSide)
.attr("fill", function(d) {
return d.color
});
var link = svg.selectAll(null)
.data(links)
.enter()
.append("line")
.style("stroke", "#222")
.style("stroke-width", 2);
simulation.nodes(nodes);
simulation.force("link")
.links(links);
simulation.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
})
node.attr("x", function(d) {
return d.x
}).attr("y", function(d) {
return d.y
});
});
<script src="https://d3js.org/d3.v4.js"></script>
Solution: You can either move the rectangles or the paths.
As your question specifically asks about the paths, the solution is simple: add half-width and half-height to the target and source coordinates:
link.attr("x1", function(d) {
return d.source.x + rectangleWidth / 2;
})
.attr("y1", function(d) {
return d.source.y + rectangleHeight / 2;
})
.attr("x2", function(d) {
return d.target.x + rectangleWidth / 2;
})
.attr("y2", function(d) {
return d.target.y + rectangleHeight / 2;
})
Here is a demo:
var width = 200;
var height = 200;
var rectSide = 40;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
name: "foo",
color: "blue"
}, {
name: "bar",
color: "green"
}, {
name: "baz",
color: "red"
}];
var links = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}];
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().distance(100))
.force("charge", d3.forceManyBody().strength(-50))
.force("center", d3.forceCenter(width / 2, height / 2));
var node = svg.selectAll(null)
.data(nodes)
.enter()
.append("rect")
.attr("width", rectSide)
.attr("height", rectSide)
.attr("fill", function(d) {
return d.color
});
var link = svg.selectAll(null)
.data(links)
.enter()
.append("line")
.style("stroke", "#222")
.style("stroke-width", 2);
simulation.nodes(nodes);
simulation.force("link")
.links(links);
simulation.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x + rectSide / 2;
})
.attr("y1", function(d) {
return d.source.y + rectSide / 2;
})
.attr("x2", function(d) {
return d.target.x + rectSide / 2;
})
.attr("y2", function(d) {
return d.target.y + rectSide / 2;
})
node.attr("x", function(d) {
return d.x
}).attr("y", function(d) {
return d.y
});
});
<script src="https://d3js.org/d3.v4.js"></script>
Can you translate the square? If you can translate it half of the square
width.
Can you find the end of that path on square it like the x2,y2? Plus that half of your square width,.
"hope this inspired you"
squares.append('rect')
.attr('width', 10)
.attr('height', 10)
.attr("transform", "translate(-5,0)")
.attr('fill', function (d) {
return '#fff';
})
I am having difficulty trying to place rectangles behind text as background in d3.js. I read that in order to do that you have to append to the same g element but in my case it does not work like that.
my code: plunker
var urls = [{
"wor": "Nordmerika",
"number": "10.9",
"lon": "-100.33",
"lat": "47.61"
}, {
"wor": "Latinamerika",
"number": "14.2",
"lon": "-56.62",
"lat": "-8.53"
}, {
"wor": "Afrika",
"number": "51.8",
"lon": "24.5085",
"lat": "8.7832"
}, {
"wor": "Asien",
"number": "27.5",
"lon": "104.238281",
"lat": "34.51561"
}, {
"wor": "GUS | Russland",
"number": "3.4",
"lon": "62.753906",
"lat": "47.923705"
}, {
"wor": "Europa | MSOE",
"number": "10.9",
"lon": "15.2551",
"lat": "54.526"
}]
//starting map
var margin = {
top: 10,
left: 10,
bottom: 10,
right: 10
},
width = parseInt(d3.select('#map').style('width')),
width = width - margin.left - margin.right,
mapRatio = .5,
height = width * mapRatio;
//Map projection
var projection = d3.geo.equirectangular()
.scale(width / 5.8)
.translate([width / 2, height / 2]) //translate to center the map in view
//Generate paths based on projection
var path = d3.geo.path()
.projection(projection);
//Create an SVG
var svg = d3.select("#map")
.append("svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMinYMin");
//Group for the map features
var features = svg.append("g")
.attr("class", "features");
var labelWidths = [];
d3.json("countries.topojson", function(error, geodata) {
if (error) return console.log(error); //unknown error, check the console
var layerOne = svg.append("g");
var layerTwo = svg.append("g");
var layerThree = svg.append("g");
//Create a path for each map feature in the data
features.selectAll("path")
.data(topojson.feature(geodata, geodata.objects.subunits).features) //generate features from TopoJSON
.enter()
.append("path")
.attr("d", path)
.on("click", clicked)
.style('fill', '#cdd5db')
.style('stroke', '#ffffff')
.style('stroke-width', '0.5px')
.on('mouseover', function(d, i) {
d3.select(this).style('stroke-width', '2px');
})
.on('mouseout', function(d, i) {
d3.select(this).style('stroke-width', '0.5px');
});
var bubbles = layerOne.attr("class", "bubble")
.selectAll("circle")
.data(urls)
.enter()
.append("circle")
.attr("cx", function(d, i) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d, i) {
return projection([d.lon, d.lat])[1];
})
.attr("r", function(d) {
if (width >= 1000) {
return (d.number)
} else {
return d.number
}
})
.style('fill', function(d) {
if (d.wor == 'Afrika') {
return '#dc0f6e'
} else {
return '#3e3e3e'
}
});
var text = layerTwo
.attr('class', 'text')
.selectAll('text')
.data(urls)
.enter()
.append('text')
.attr('x', function(d, i) {
if (d.number < 10) {
return projection([d.lon, d.lat])[0] + 60;
} else {
return projection([d.lon, d.lat])[0]
}
})
.attr('y', function(d, i) {
return projection([d.lon, d.lat])[1];
})
.text(function(d) {
return d.number;
})
.attr("dy", function(d) {
if (this.getBBox().width > d.number * 3) {
return '-2em'
} else {
return "0.3em"
}
})
.attr("text-anchor", "middle")
.style('fill', '#fff')
.style('font-weight', 'bold')
.style('font-size', '1em');
var labels = layerThree
.attr('class', 'labels')
.selectAll('text')
.data(urls)
.enter()
.append('text')
.attr('x', function(d, i) {
return projection([d.lon, d.lat])[0] + 60;
})[plunker][1]
.attr('y', function(d, i) {
return projection([d.lon, d.lat])[1];
})
.attr("text-anchor", "middle")
.text(function(d) {
return d.wor;
})
.attr('dy', function(d) {
labelWidths.push(this.getBBox().width)
var radius = d.number * 2
if (radius > 10) {
return d.number * 4;
} else {
return '-0.5em'
}
})
.style('font-size', '1em')
.style('font-weight', 'bold');
var rect = layerThree
.attr('class', 'rectlabels')
.selectAll('rect')
.data(urls)
.enter()
.append('rect')
.attr('x', function(d, i) {
return projection([d.lon, d.lat])[0] + 60;
})
.attr('y', function(d, i) {
return projection([d.lon, d.lat])[1];
})
.attr('dy', function(d) {
return '1em'
})
.style('fill', '#ffffff')
.attr('width', function(d, i) {
return labelWidths[i] / 10 + 'em'
})
.attr('height', '1em');
function clicked(d, i) {
}
});
It looks like the rectangles don't line up correctly with the text, there are a couple of reasons for this:
Your <text> is anchored at the middle, and your <rects> are anchored at the top-left point of the <rect>.
You're setting dy on your <text> elements at varying amounts, this moves the text down by that distance, but all your <text> elements have dy=1em, so they're not moving down by the same amount.
I would suggest that you create 6 <g> elements, one for each label, each containing the <rect> element and the <text> element. Then you only need to set x & y attributes on the <g> element, and it's children should stay together.
I think you should also try removing the text-anchor attribute, and instead move the <g> element left by half its width ((labelWidths[i] / 10) / 2) to get it to center over the coordinates. This is made difficult by the fact that your width is in em units, so you might need to do it in pixels and adjust accordingly.
Try it without the dy elements too and see if that helps with the vertical alignment.
I've recently began trying to teach myself D3, and I'm to get my head round the enter, update, exit paradigm.
Below I have an example of some progress circles I'm trying to work with;
http://plnkr.co/edit/OoIL8v6FemzjzoloJxtQ?p=preview
Now, as the aim here is to update the circle path without deleting them, I believe I shouldn't be using the exit function? In which case, I was under the impression that I could update my data source inside a new function and then call for the path transition, and I would get my updated value. However, this is not the case.
I was wondering if someone could help me out and show me where I'm going wrong?
var dataset = [{
"vendor-name": "HP",
"overall-score": 45
}, {
"vendor-name": "CQ",
"overall-score": 86
}];
var dataset2 = [{
"vendor-name": "HP",
"overall-score": 22
}, {
"vendor-name": "CQ",
"overall-score": 46
}];
var width = 105,
height = 105,
innerRadius = 85;
var drawArc = d3.svg.arc()
.innerRadius(innerRadius / 2)
.outerRadius(width / 2)
.startAngle(0);
var vis = d3.select("#chart").selectAll("svg")
.data(dataset)
.enter()
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
vis.append("circle")
.attr("fill", "#ffffff")
.attr("stroke", "#dfe5e6")
.attr("stroke-width", 1)
.attr('r', width / 2);
vis.append("path")
.attr("fill", "#21addd")
.attr('class', 'arc')
.each(function(d) {
d.endAngle = 0;
})
.attr('d', drawArc)
.transition()
.duration(1200)
.ease('linear')
.call(arcTween);
vis.append('text')
.text(0)
.attr("class", "perc")
.attr("text-anchor", "middle")
.attr('font-size', '36px')
.attr("y", +10)
.transition()
.duration(1200)
.tween(".percentage", function(d) {
var i = d3.interpolate(this.textContent, d['overall-score']),
prec = (d.value + "").split("."),
round = (prec.length > 1) ? Math.pow(10, prec[1].length) : 1;
return function(t) {
this.textContent = Math.round(i(t) * round) / round + "%";
};
});
function updateChart() {
vis = vis.data(dataset2)
vis.selectAll("path")
.transition()
.duration(1200)
.ease('linear')
.call(arcTween);
vis.selectAll('text')
.transition()
.duration(1200)
.tween(".percentage", function(d) {
var i = d3.interpolate(this.textContent, d['overall-score']),
prec = (d.value + "").split("."),
round = (prec.length > 1) ? Math.pow(10, prec[1].length) : 1;
return function(t) {
this.textContent = Math.round(i(t) * round) / round + "%";
};
});
}
function arcTween(transition, newAngle) {
transition.attrTween("d", function(d) {
var interpolate = d3.interpolate(0, 360 * (d['overall-score'] / 100) * Math.PI / 180);
return function(t) {
d.endAngle = interpolate(t)
return drawArc(d);
};
});
}
Any help/advice is much appreciated!
Thanks all
You need to refresh your data through the DOM - svg > g > path :
// SET DATA TO SVG
var svg = d3.selectAll("svg")
.data(selectedDataset)
// SET DATA TO G
var g = svg.selectAll('g')
.data(function(d){return [d];})
// SET DATA TO PATH
var path = g.selectAll('path')
.data(function(d){ return [d]; });
Storing the d3 DOM data bind object for each step you can have control of the enter(), extit(), and transition() elements. Put changing attributes of elements in the transition() function:
// PATH ENTER
path.enter()
.append("path")
.attr("fill", "#21addd")
.attr('class', 'arc')
// PATH TRANSITION
path.transition()
.duration(1200)
.ease('linear')
.attr('d', function(d){ console.log(d);drawArc(d)})
.call(arcTween);
http://plnkr.co/edit/gm2zpDdBdQZ62YHhDbLb?p=preview
I need to create a legend for the bubble/circle pack chart. I'm displaying the values inside the circle. I need the names as the legend. For an instance, in the below provided data, if the value is 60, i need that name "Petrol" in the legend. How could i achieve it?
Snippet:
var diameter = 200,
format = d3.format(",d"),
color = ["#7b6888", "#ccc", "#aaa", "#6b486b"];
var bubble = d3.layout.pack().size([diameter, diameter]);
var svg = d3.select("#bubbleCharts").append("svg")
.attr("width", diameter + 10)
.attr("height", diameter)
.attr("class", "bubble");
d3.json("flare.json", function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + 20 + "," + d.y + ")"; });
node.append("circle").attr("r", function(d) { return d.r+ 7; })
.style("fill", function(d,i) { return color[i];} );
node.append("text").attr("dy", ".3em").style("text-anchor", "middle")
.text(function(d) { return d.value+"%"; });
});
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children)
node.children.forEach(function(child){
recurse(node.name, child);
});
else
classes.push({packageName: name, value: node.value});
}
recurse(null, root);
return {children: classes};
}
var legend = d3.select("#bubbleChart").append("svg")
.selectAll("g").data(node.children).enter().append("g")
.attr("class","legend")
.attr("width", radius)
.attr("height", radius * 2)
.attr("transform", function(d, i) {return "translate(" + i *10 + "0" + ")"; });
legend.append("rect").attr("width", 18).attr("height", 10)
.style("fill", function(d, i) { return color[i];});
legend.append("text").attr("x", 24).attr("y", 5).attr("dy", ".35em")
.text(function(d) { return d; });
My data:
{
"name": "Spending Activity",
"children": [
{"name": "Petrol", "value": 10},
{"name": "Travel", "value": 60},
{"name": "Medical", "value": 25},
{"name": "Shopping", "value": 5}
]
}
How would i take the values from json and create a legend? Thanks.
You can simply iterate through your data set and add those values:
legend.selectAll("circle").data(data.children)
.enter()
.append("circle")
.attr("cy", function(d,i) { return (i+1) * 10; })
.attr("r", function(d) { return d.r+ 7; })
.style("fill", function(d,i) {
return color[i];
});
legend.selectAll("text").data(data.children)
.enter()
.append("text")
.attr("transform", function(d,i) {
return "translate(10," + ((i+1) * 10) + ")";
});