I've tried to create a legend using inspiration from http://zeroviscosity.com/d3-js-step-by-step/step-3-adding-a-legend. However, despite having almost the exact same code, the legend isn't visualized. Here's the jsfiddle and the code: http://jsfiddle.net/u5hd25qs/
var width = $(window).width();
var height = $(window).height() / 2;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var legendRectSize = 36; // 18
var legendSpacing = 8; // 4
var recordTypes = []
recordTypes.push({
text : "call",
color : "#438DCA"
});
recordTypes.push({
text : "text",
color : "#70C05A"
});
var legend = svg.selectAll('.legend')
.data(recordTypes)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * recordTypes.length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', function (d) {
return d.color
})
.style('stroke', function (d) {
return d.color
});
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function (d) {
return d.text;
});
Your code works okay, but this is what you generate:
<g class="legend" transform="translate(-72,-44)">...
Because your translate rule has negative values in it, the legend is positioned outside the screen (it is simply not visible).
Now, the example you're basing your work on has a pie chart that has already been translated to the center of the screen, so negative values are not an issue.
You need to change your math or wrap the legend in some container which you can position in the same way as the pie chart example:
legendContainer
.attr('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')');
Related
I have a half donut I based a bit off http://bl.ocks.org/mikeyao/1c5c69b562cc4dc915a7af157e9c967e and some code I already had for a full donut chart, it currently that looks like this:
As shown in the image, the value is zero, but zero should not fill up half the chart. How can I set zero to start at the left most corner? Or in other words, how can I tell d3js that the chart should fill values from left corner to right corner. This is my code:
let initChart = function() {
let width = 148;
let height = 148;
let radius = Math.min(width, height) / 2;
let color = d3.scale.ordinal().range(scope.colors);
let selector = '#half-donut-' + scope.section;
let angle = 0.5 * Math.PI;
let data = [
{
label: 'Data',
value: _data
}
];
let backgroundArc = d3.svg
.arc()
.innerRadius(58)
.outerRadius(radius)
.cornerRadius(20)
.startAngle(angle * -1)
.endAngle(angle);
let mainArc = d3.svg
.arc()
.innerRadius(58)
.outerRadius(radius)
.cornerRadius(20)
.startAngle(angle * -1)
.endAngle(function(d) {
return (d.value / 100) * angle;
});
let svg = d3
.select(selector)
.append('svg')
.attr('width', width)
.attr('height', height);
let charts = svg
.selectAll('g')
.data(data)
.enter()
.append('g')
.attr('transform', function() {
return (
'translate(' + width / 2 + ',' + height / 2 + ')'
);
});
let legend = svg
.selectAll('.legend')
.data(data)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function() {
return 'translate(' + -21 + ',' + -21 + ')';
});
legend
.append('text')
.attr('x', 22)
.attr('y', 12)
.attr('text-anchor', 'middle')
.attr(
'transform',
'translate(' + width / 2 + ',' + height / 2 + ')'
)
.text(function(d) {
return d.value + '%';
});
charts
.append('path')
.attr('d', backgroundArc)
.attr('fill', '#F3F3F4');
charts
.append('path')
.attr('d', mainArc)
.attr('fill', color);
};
Thanks in advance.
Calc endAngle relative to startAngle
.endAngle(function(d) { return -angle + (d.value / 100) * 2 * angle; });
or use the pie method used in the example
In my transition, an axis rotates 90 degree and then the labels rotate in the opposition direction in order to remain upright. Below is a minimal example of what I want, except the transition is not as smooth as it could be. If you watch closely, you can see the labels shift (translate) up before rotating into place. How can I get rid of this shift? I've fiddled with rotate and translate to no avail.
(If you think this isn't too bad, I agree, but the shift is actually significantly more noticeable in my actual plot for some reason.)
Update. The culprit is the text-anchor property's getting switched back and forth between middle and start. Since these are discrete values, I can't think of a simple way to transition between them.
var width = 170;
var scale = d3.scaleLinear().domain([0, 5])
.range([0, width]);
var axis = d3.axisBottom()
.scale(scale)
.ticks(6);
var graph = d3.select('svg').append('g')
.attr('transform', 'translate(10,10)');
graph.append('g')
.attr('transform', 'translate(0,' + width + ')')
.call(axis);
var tickLabels = d3.selectAll('text');
var toggle = false;
d3.select('button').on('click', function() {
toggle = !toggle;
if (toggle) {
graph.transition().duration(1000)
// .attr('transform','rotate(-90)');
.attr('transform', 'rotate(-90 ' + (width / 2 + 10) + ' ' + (width / 2 + 10) + ')');
tickLabels.transition().duration(1500).delay(1000)
.attr("y", 0)
.attr("x", 9)
.attr("dy", ".3em")
.attr("transform", "rotate(90)")
.style("text-anchor", "start");
} else {
graph.transition().duration(1000)
.attr('transform', 'rotate(0) translate(10,10)');
tickLabels.transition().duration(1500).delay(1000)
.attr('y', 9)
.attr('x', 0.5)
.attr('dy', '0.71em')
.attr('transform', 'rotate(0)')
.style('text-anchor', null);
}
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width='200' height='200'>
</svg>
<div>
<button>Rotate</button>
</div>
Found the solution, which is actually fairly simple. The key is to alter the x attribute to offset the text-anchor shift before rotating the labels. The result is actually quite nice.
var width = 170;
var scale = d3.scaleLinear().domain([0, 5])
.range([0, width]);
var axis = d3.axisBottom()
.scale(scale)
.ticks(6);
var graph = d3.select('svg').append('g')
.attr('transform', 'translate(10,10)');
graph.append('g')
.attr('transform', 'translate(0,' + width + ')')
.call(axis);
var tickLabels = d3.selectAll('text');
var toggle = false;
d3.select('button').on('click', function() {
toggle = !toggle;
if (toggle) {
graph.transition().duration(1000)
// .attr('transform','rotate(-90)');
.attr('transform', 'rotate(-90 ' + (width / 2 + 10) + ' ' + (width / 2 + 10) + ')');
tickLabels.transition().duration(0).delay(1000)
.attr('x', -3)
.style("text-anchor", "start")
.transition().duration(1000)
.attr("y", 0)
.attr("x", 9)
.attr("dy", ".3em")
.attr("transform", "rotate(90)");
} else {
graph.transition().duration(1000)
.attr('transform', 'rotate(0) translate(10,10)');
tickLabels.transition().duration(0).delay(1000)
.attr('x', 12)
.style('text-anchor', null)
.transition().duration(1000)
.attr('y', 9)
.attr('x', 0.5)
.attr('dy', '0.71em')
.attr('transform', 'rotate(0)');
}
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width='200' height='200'>
</svg>
<div>
<button>Rotate</button>
</div>
Lost on this especially since I've done it successfully before. The code below is within a function called on a click. This first example works just fine:
y.domain([1, get_max(data)]);
svg.select('.y.axis')
.call(yAxis);
svg.selectAll('.bars')
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); });
This second example doesn't do anything:
y.domain([1, get_max(data)]);
svg.select('.y.axis')
.transition()
.duration(80)
.call(yAxis);
svg.selectAll('.bars')
.transition()
.duration(80)
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); });
No javascript errors are produced. It simply doesn't do anything.
Any help would be greatly appreciated. Thank you!
Note: get_max(data) is a special function to get the max of some very oddly formatted data. When I replace it with a hard coded value of 10,000 the problem persists. Again it works fine until I add the transition.
EDIT:
function render(parent, data, brands){
var time_format = parent.attr('data-chart-size') > 3 ? '%b %e' : '%e',
margin = { top: 30, right: 20, bottom: 21, left: 45 },
width = parent.width() - margin.left - margin.right - 110; // -110 for the legends on the right side
height = 205 - margin.top - margin.bottom;
var x = d3.time.scale().domain([ML._dates.start(), ML._dates.end()]).range([0, width]);
y = d3.scale.log().clamp(true).range([height, 1]);
var xAxis = d3.svg.axis().scale(x).orient('bottom')
.tickFormat(d3.time.format(time_format)).tickSize(0).tickPadding(8);
yAxis = d3.svg.axis().scale(y).orient('left').ticks(5, 's').tickSize(0);
svg = d3.select(parent.get(0)).append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var brands_length = brands.length,
values_length = data.posts[brands[0]].values.length,
bar_width_adjustment = 60 / values_length,
bar_width = ((width / values_length) / brands_length) - bar_width_adjustment;
range_band = width / values_length;
y.domain([1, get_max(data)]);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(' + (bar_width * 2) + ',' + height + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(0, 0)')
.call(yAxis);
// BUILD THE BARS AND TOOLTIP HIGHLIGHT AREA
for(var brands_loop = 0; brands_loop < brands_length; ++brands_loop){
svg.selectAll('.chart-hover')
.data(data.posts[brands[brands_loop]].values)
.enter().append('rect')
.attr('width', width / values_length)
.attr('x', function (d, i) { return i * range_band - ((range_band - bar_width * 3) / 2); })
.attr('y', 1)
.attr('height', height - y(y.domain()[1]))
.attr('transform', 'translate(' + ((bar_width * (brands_loop + 1) - (bar_width / 2)) + ', 0)'))
.attr('data-index', function (d, i) { return i; })
.attr('class', 'chart-hover')
.style('opacity', 0.01);
svg.selectAll('.bar-' + brands_loop)
.data(data.posts[brands[brands_loop]].values)
.enter().append('rect')
.attr('data-legend-listener-brand', brands[brands_loop])
.attr('data-legend-listener-metric', 'posts')
.attr('data-hover-dispatcher-index', function (d, i) { return i; })
.attr('width', bar_width)
.attr('x', function (d, i) { return i * range_band; })
.attr('y', function (d) { return y(d); })
.attr('height', function (d) { return height - y(d); })
.attr('transform', 'translate(' + ((bar_width * (brands_loop + 1) - (bar_width / 2)) + ', 0)'))
.attr('class', 'posts bars bar-' + brands_loop);
// POPULATE LEGEND TEXTS FOR POSTS, EXPOSURE AND ENGAGEMENT
$('.brand-' + (brands_loop + 1))
.text(data.posts[brands[brands_loop]].label)
.attr('data-legend-brand', brands[brands_loop])
.attr('data-legend-listener-brand', brands[brands_loop])
.prev('i')
.attr('data-legend-brand', brands[brands_loop])
.attr('data-legend-listener-brand', brands[brands_loop]);
}
etc. etc.
Fiddle Example
How can I get the tooltip to show up closer to the hovered slice? I can't get d3.pageXOffset and d3.pageYOffset (like this example) to work. What is the way to get the hovered pie slice position for the tooltip?
Not working:
path.on('mouseover', function(d) {
tooltip.style("top", d3.pageYOffset + "px").style("left", d3.pageXOffset + "px")
});
Full code:
var dataset =
[
{item:"Boy",result:12},
{item:"Girl",result:24},
{item:"Woman",result:60},
{item:"Man",result:10}
]
var width = 280;
var height = 280;
var radius = Math.min(width, height) / 2;
var color = d3.scale.category10();
var svg = d3.select('#area')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + (width / 2) +
',' + (height / 2) + ')');
var arc = d3.svg.arc()
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function(d) { return d.result; })
.sort(null);
var path = svg.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return color(d.data.item);
});
var tooltip = d3.select('#area').append('div').attr('class', 'tooltip');
path.on('mouseover', function(d) {
var matrix = this.getScreenCTM()
.translate(+this.getAttribute("cx"),
+this.getAttribute("cy"));
var total = d3.sum(dataset.map(function(d) {
return d.result;
}));
var percent = Math.round(1000 * d.data.result / total) / 10;
tooltip.style("top", d3.pageYOffset + "px")
.style("left",d3.pageXOffset + "px")
.style('display', 'block')
.style("opacity", 1)
.append('div')
.attr('class', 'label')
.append('div')
.attr('class', 'count')
.append('div')
.attr('class', 'percent');
tooltip.select('.label').html(d.data.item);
tooltip.select('.count').html(d.data.result);
tooltip.select('.percent').html(percent + '%');
});
path.on('mouseout', function() {
tooltip.style('display', 'none');
});
Use d3.event:
tooltip.style("top", d3.event.y + "px").style("left", d3.event.x + "px");
I would also put it into a mousemove handler for better results.
Updated fiddle.
I have this D3 radial tree graph and what I need is the images to not being rotated, but appear straight together it's corresponding blue circle. Here is the code working in Codepen and I copy it here:
var radius = 960 / 2;
var tree = d3.layout.tree()
.size([360, radius - 120])
.separation(function (a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var diagonal = d3.svg.diagonal.radial()
.projection(function (d) { return [d.y, d.x / 180 * Math.PI]; })
var vis = d3.select('#graph').append('svg:svg')
.attr('width', radius * 2)
.attr('height', radius * 2 - 150)
.append('g')
.attr('transform', 'translate(' + radius + ',' + radius + ')');
d3.json('flare2.json', function (json) {
var nodes = tree.nodes(json);
var link = vis.selectAll('path.link')
.data(tree.links(nodes))
.enter().append('path')
.attr('class', 'link')
.attr('d', diagonal);
var node = vis.selectAll('g.node')
.data(nodes)
.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; });
node.append('circle')
.attr('r', 4.5);
node.append('image')
.attr('xlink:href', 'img/avatar.40.gif')
.attr('width', 40)
.attr('height', 40)
.attr('x', 10);
node.append('image')
.attr('xlink:href', 'img/avatar.41.gif')
.attr('width', 40)
.attr('height', 40)
.attr('x', 50 );
});
Your nodes are rotated
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
And your images are appended to your nodes
node.append('image')
So you need to rotate the images back
.attr("transform", function(d) { return "rotate(" + (90 - d.x) + ")"; })
I'm not sure exactly how you want to position them, but you need to translate them on both x and y.
See a working example: http://codepen.io/anon/pen/qiCeG