Why is scale only working the first time I doubleclick? - javascript

I have a d3 globe, and I have it scaling up (zooming in) when I doubleclick it. However, the zoom only works the first time I doubleclick. After that, I see that the program is entering the dblclick function, but no zooming is taking place. This is probably a stupid question, but I would be grateful if anyone were able to tell me how to make the zoom happen each time the globe is doubleclicked.
var width = 800,
height = 800,
centered;
var feature;
var projection = d3.geo.azimuthal()
.scale(380)
.origin([-71.03,42.37])
.mode("orthographic")
.translate([380, 400]);
var circle = d3.geo.greatCircle()
.origin(projection.origin());
// TODO fix d3.geo.azimuthal to be consistent with scale
var scale = {
orthographic: 380,
stereographic: 380,
gnomonic: 380,
equidistant: 380 / Math.PI * 2,
equalarea: 380 / Math.SQRT2
};
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#globe").append("svg:svg")
.attr("width", 800)
.attr("height", 800)
.on("dblclick", dblclick)
.on("mousedown", mousedown);
var g = svg.append("g");
d3.json("simplified.geojson", function(collection) {
g.append("g")
.attr("id", "countries")
.selectAll("path")
.data(collection.features)
.enter().append("svg:path")
.attr("d", clip)
.attr("id", function(d) { return d.properties.ISO3; })
.on("mouseover", pathOver)
.on("mouseout", pathOut)
.on( "dblclick", dblclick)
.on("click", click);
feature = svg.selectAll("path");
feature.append("svg:title")
.text(function(d) { return d.properties.NAME; });
});
...
function dblclick(d) {
var x, y, k;
/*if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });*/
g.transition()
.duration(750)
//.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.attr("transform", "scale(1.5)");
//.style("stroke-width", 1.5 / k + "px");
}

I agree with Erik E. Lorenz (no way to link to Erik's answer, it appears). Right now you're setting the zoomscale in the line
.attr("transform", "scale(1.5)");
The problem is that each time you call dblclick(), you're "resetting" it to 1.5. It's not multiplying by 1.5 it's just getting set. D3 doesn't remember what it used to be. That's why the first time you call dblclick() it works (because you're transforming the scale to 1.5 from 1). But from then on, the scale is already transformed to 1.5 and you just keep setting the scale transform to 1.5.
You need to keep track of "how far you've zoomed". And to do that you need a variable that keeps it's value between calls to dblclick(). I'd do something like this:
/* given the structure of your code, you can probably just declare the
variable before the function declaration. the function `dblclick` will
have access to the variable via closure */
var zoomScale = 1;
/* then you can just do this */
function dblclick(d) {
// you'll probably want to play with the math here
// that is, "1.5" might not be best
zoomScale = zoomScale * 1.5; // or some shorthand
g.transition()
.duration(750)
.attr("transform", "scale(" + zoomScale + ")");
}

I think that that scale(1.5) might be the problem. Have you tried dynamically increasing that factor every time dblclick() is called?

Related

Javascript / D3.js - draw large data set - improve the speed of zoom and pan in svg chart ploted by d3.js

Edit
Just found the post plotting 50 million points with d3.js.
Sluggish interaction with zoom and pan are due to too many elements in the svg. The key is to use hierarchical levels of detail, just like the image pyramid. , to limit the maximum elements in svg.
Original post
I am trying to read some data points from csv/excel file and plot them using d3.js.
The data set contains 100,000s of rows, each row contains a time stamp and a value at that time.
Time stamp, pressure
12/17/2019 12:00:00 AM, 600
I followed this example to plot the time-pressure chart with zoom and pan.
There is no issue and worked perfectly.
One issue is that when working with large data set, say 500,000 of data points, the interaction with the chart is sluggish.
The chart with 500,000 data points shows an overall shape, and the details would only come up when zoomed in at large scale.
When zoomed in, all the data points are re-plotted and clipped out by the clip path. Would there be some room to improve the speed?
Updated Code
function draw(res){
//clear the current content in the div
document.getElementById("spectrum-fig").innerHTML = '';
var fullwidth = d3.select('#spectrum-fig').node().getBoundingClientRect().width;
fullwidth = fullwidth < 500? 500:fullwidth;
var fullheight = 500;
var resLevelOne = getWindowed(res, 1);
var resLevelTwo = getWindowed(res, 2);
var designMax= getMaxPressureKPa();
var resMax = getPsiTopTen(res);
const SMYSKPa = getSMYSPressureKPa();
const avePsi = getAvePsi(res);
var psiRange = d3.max(res, d=>d.psi) - d3.min(res, d=>d.psi);
var resSmallChart = getWindowed(res, 2);//
//filtSpectrum(res, 0.05*psiRange); //0.05 magic numbers
//var resSmallChart = res;
//margin for focus chart, margin for small chart
var margin = {left:50, right: 50, top: 30, bottom:170},
margin2 = {left:50, right: 50, top: 360, bottom:30},
width = fullwidth - margin.left - margin.right,
height = fullheight - margin.top - margin.bottom,
height2 = fullheight - margin2.top-margin2.bottom;
//x, y, for big chart, x2, y2 for small chart
var x = d3.scaleTime().domain(d3.extent(res, d => d.Time)).range([0, width]),
x2 = d3.scaleTime().domain(d3.extent(res, d => d.Time)).range([0, width]),
y = d3.scaleLinear().domain([0, SMYSKPa]).range([height, 0]),
y2 = d3.scaleLinear().domain([0, SMYSKPa]).range([height2, 0]);
//clear the content in Spectrum-fig div before drawring
//avoid multiple drawings;
var xAxis =d3.axisBottom(x).tickFormat(d3.timeFormat("%m-%d")),
xAxis2 = d3.axisBottom(x2).tickFormat(d3.timeFormat("%b")),
yAxis = d3.axisLeft(y);
var brush = d3.brushX() // Add the brush feature using the d3.brush function
.extent( [ [0,0], [width,height2] ] ) // initialise the brush area: start at 0,0 and finishes at width,height: it means I select the whole graph area
.on("brush end", brushed); // trigger the brushed function
var zoom = d3.zoom()
.scaleExtent([1, 100]) //defined the scale extend
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed); //at the zoom end trigger zoomed function
//line for big chart line
var line = d3.line()
.x(function(d) { return x(d.Time) })
.y(function(d) { return y(d.psi) });
//line2 for small chart line
var line2 = d3.line()
.x(function(d) { return x2(d.Time) })
.y(function(d) { return y2(d.psi) });
var svg = d3.select("#spectrum-fig")
.append("svg")
.attr("width", fullwidth)
.attr("height", fullheight);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
focus.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate (0," + height +")")
.call(xAxis);
focus.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
focus.append("g")
.attr("transform", "translate (" + width + ", 0)")
.call(d3.axisRight(y).tickFormat('').tickSize(0));
focus.append("g")
.attr("transform", "translate (0, 0)")
.call(d3.axisTop(x).tickFormat('').tickSize(0));
// Add the line
focus.insert("path")
//.datum(res)
.attr("class", "line") // I add the class line to be able to modify this line later on.
.attr("fill", "none")
.attr('clip-path', 'url(#clip)')
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", line(resLevelTwo));
context.insert("path")
//.datum(resSmallChart)
.attr("class", "line")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("fill", "none")
.attr("d", line2(resSmallChart));
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
svg.append("rect")
.attr("class", "zoom")
.attr('fill', 'none')
.attr('cursor', 'move')
.attr('pointer-events', 'all')
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
function getWindowed(arr, level){
var windowed = new Array();
var arrLength = arr.length;
var windowSize =Math.pow(16, level); //set the window size
for(let i = 0; i * windowSize < arrLength; i++ ){ //each to be the window size
let startIndex = i * windowSize;
let endIndex = (i+1) * windowSize;
endIndex = endIndex >= arrLength ? arrLength-1 : endIndex;
let localExtreme = findLocalExtreme(arr.slice(startIndex, endIndex));
if (localExtreme.Max.Time.getTime() === localExtreme.Min.Time.getTime()){ //anything include = need getTime
windowed.push(localExtreme.Max)
}else if(localExtreme.Max.Time < localExtreme.Min.Time){
windowed.push(localExtreme.Max);
windowed.push(localExtreme.Min);
}else{
windowed.push(localExtreme.Min);
windowed.push(localExtreme.Max);
}
}
let firstElement = {...arr[0]};
let lastElement = {...arr[arr.length-1]};
if(firstElement.Time.getTime() != windowed[0].Time.getTime()){ //insert to the position zero
windowed.unshift(firstElement);
}
if(lastElement.Time.getTime() != windowed[windowed.length-1].Time.getTime()){
windowed.push(lastElement);
}//insert to the end last member;
return windowed;
}
function findLocalExtreme(slicedArr){
if(slicedArr === undefined || slicedArr.length == 0){
throw 'error: no array members';
}
let slicedArrLength = slicedArr.length;
let tempMax = {...slicedArr[0]};
let tempMin = {...slicedArr[0]};
if(slicedArrLength === 1){
return {
Max: tempMax,
Min: tempMin
}
}
for (let i = 1; i < slicedArrLength; i++){
if (slicedArr[i].psi > tempMax.psi){
tempMax = {...slicedArr[i]};
}
if (slicedArr[i].psi < tempMin.psi){
tempMin = {...slicedArr[i]};
}
}
return {
Max: tempMax,
Min: tempMin
}
}
function getDataToDraw(timeRange){ //timeRange [0,1] , [startTime, endTime]
const bisect = d3.bisector(d => d.Time).left;
const startIndex = bisect(res, timeRange[0]);
const endIndex = bisect(res, timeRange[1]);
const numberInOriginal = endIndex-startIndex+1;
const windowSize =16;
const maxNumber = 8000;
let level = Math.ceil(Math.log(numberInOriginal/maxNumber ) / Math.log(windowSize));
if(level <=0 ) level =0;
console.log(endIndex, startIndex, endIndex-startIndex+1, level);
if(level === 0){
return res.slice(startIndex, endIndex);
}if(level === 1){
let start_i = bisect(resLevelOne, timeRange[0]);
let end_i =bisect(resLevelOne, timeRange[1]);
return resLevelOne.slice(start_i, end_i);
}else { //if level 2 or higher, never happen
let start_i = bisect(resLevelTwo, timeRange[0]);
let end_i =bisect(resLevelTwo, timeRange[1]);
return resLevelTwo.slice(start_i, end_i);
}
}
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.select(".line").attr("d", line(getDataToDraw(x.domain())));
focus.select(".axis--x").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
//console.log(t);
x.domain(t.rescaleX(x2).domain());
focus.select(".line").attr("d", line(getDataToDraw(t.rescaleX(x2).domain())));
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
}
Here's my thoughts.
Re-plot seems a must-have, because how could you expect to have same position when you zoom in the points ?
However there's some frequency of the replot you can control. For example, people use debounce to decrease the number of firing below 50ms during any event (ex. pan especailly). Debounce is a general solution, you can check lodash library for some implementation.
.on("zoom", debounced(zoomed)) // lower the chance if you get 5 calls under 500ms
Also if there's any animation involved, you can defer the animation until the last stage of the zoom (or pan), which is similar to debounce concept. Or just simply disable animation.
Note: React does support another mode called concurrent, it's not enabled by default, not yet. However what it does is that, assuming each plot is captured by a small component, and it spends 1ms for render, then after it renders 16 components, it believes it spend too much time in this rendering, and give the response back to the browser to handle other things, ex. user input etc. This way you can start to scroll your page or move your mouse. And in the next cycle it can pick up the next 16 components. Assuming you have 1000 components, it'll take couple of cycles before it can finish all the rendering. And if you zooms again in the middle, it'll skip the first 16 components and move to the new render all over again. Hope you get the idea. It might help your problem with the latest React 18.
Refer to the post plotting 50 million points with d3.js.
Sluggish interaction with zoom and pan are due to too many elements in the svg. The key is to use hierarchical levels of detail, to limit the maximum elements in svg.

Use getCtm() at end of d3 translate event

I have the following function that translates a set of circle objects along a predefined SVG path. Per this post, I am attempting to use the getCTM() function to capture the new position of each circle element after each transition runs on each of the respective elements. However, when the below code is executed, it isn't returning the updated transition after each transform. When I look at the matrix values that the getCTM() function is returning for each element, they are:
SVGMatrix {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}
Each circle moves along the SVG path without a hitch, but I can't figure out why the transform values aren't being returned in the SVGMatrix using the code below. Here is a sample of the data being bound to each circle:
trip_headsign
:
"Ashmont"
trip_id
:
"31562570"
trip_name
:
"11:05 pm from Alewife to Ashmont - Outbound"
vehicle_lat
:
42.33035301964327
vehicle_lon
:
-71.0570772306528
stops
:
Array[5]
0
:
Array[6]
0
:
"130"
1
:
"70085"
2
:
124
3
:
Array1
4
:
124
5
:
0
var map = L.map('map').setView([42.365, -71.10], 12),
svg = d3.select(map.getPanes().overlayPane).append("svg"),
ashmontG = svg.append("g").attr("class", "leaflet-zoom-hide"),
inboundG = svg.append("g").attr("class", "leaflet-zoom-hide");
var transform = d3.geo.transform({point: projectPoint}),
path = d3.geo.path().projection(transform);
var track = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return applyLatLngToLayer(d).x
})
.y(function(d) {
return applyLatLngToLayer(d).y
});
var ashmontPath = ashmontG.selectAll("path")
.data([ashmont.features])
.enter()
.append("path")
.style("fill", "none")
.style("stroke", "black")
.style("stroke-width", 2)
.style("opacity", 0.1)
.attr("d", track)
var trains = inboundG.selectAll("circle")
.data(a_live_trains)
.enter()
.append("circle")
.attr("r", 6)
.style("fill", "blue")
.attr("class", "train");
d3.selectAll(".train").each(function(d) {
//the convertCoords function takes a lat/lng pair bound to the circle element and returns the coordinates in pixels using the leaflet latlngtolayerpoint function
var x = convertCoords(d).x,
y = convertCoords(d).y;
console.log(x, y);
for(j=0; j<d.stops.length; j++){
var matrix, xn, xy;
d3.select(this).transition()
.duration(d.stops[j][4]*50)
.delay(d.stops[j][5]*50)
.attrTween("transform", pathMove(d.stops[j][3]))
.each("end", ctm(this))
function ctm(x) {
console.log(x);
matrix = x.getCTM();
xn = matrix.e + x*matrix.a + y*matrix.c,
yn = matrix.f + x*matrix.b + y*matrix.d;
console.log(xn, yn)
}
}
})
function pathMove(path) {
return function (d, i, a) {
return function(t) {
var length = path.node().getTotalLength();
var p = path.node().getPointAtLength(t*length);
//var ptoPoint = map.layerPointToLatLng(new L.Point(p.x, p.y
return "translate(" + p.x + "," + p.y + ")";
}
}
}
moveTrains();
map.on("viewreset", reset);
reset();
function reset() {
svg.attr("width", bottomRight[0] - topLeft[0] + padding)
.attr("height", bottomRight[1] - topLeft[1] + padding)
.style("left", (topLeft[0]-(padding/2)) + "px")
.style("top", (topLeft[1]-(padding/2)) + "px");
ashmontG.attr("transform", "translate(" + (-topLeft[0] + (padding/2)) + ","
+ (-topLeft[1] + (padding/2)) + ")");
inboundG.attr("transform", "translate(" + (-topLeft[0] + (padding/2)) + ","
+ (-topLeft[1] + (padding/2)) + ")");
ashmontPath.attr("d", track);}
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x))
//var point = latLngToPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y)
}
Writing the code as shown immediately below returns the pre-transformation svg matrix. I believe this is happening because the 'this' keyword for each circle object was being selected pre-transform, and was passing the pre-transform SVG position into the ctm function. Additionally, the ctm function was executing before the pathMove function was even being called.
d3.select(this).transition()
.duration(d.stops[j][4]*50)
.delay(d.stops[j][5]*50)
.attrTween("transform", pathMove(d.stops[j][3]))
.each("end", ctm(this))
function ctm(x) {
console.log(x);
matrix = x.getCTM();
Slightly modifying the code using the example Mark provided above executes properly. I believe this is due to the fact that the 'this' keyword is being called a second time post-transform within the ctm function. Writing it this way grabs the SVGmatrix each time after the pathMove function has been called.
d3.select(this).transition()
.duration(d.stops[j][4]*50)
.delay(d.stops[j][5]*50)
.attrTween("transform", pathMove(d.stops[j][3]))
.each("end", ctm);
function ctm(x) {
console.log(this);
matrix = this.getCTM();

Curved labels on circular diagram (D3.js)

So, I'm basically trying to make a multilevel circular partition (aka sunburst diagram) with D3.js (v4) and a JSON data.
I placed some labels, which must have different angles depending of their levels (circles) on the partition :
- Level < 3 must be curved and "follow" the arc radius.
- level == 3 must be straight and perpendicular of the arc radius.
I didn't use textPath tags, because I'm not really experienced in SVG and it looks overly complicated to me, and I don't really know how to use it.
here's my code (without the JSON but this is a really classical one, I can add a part of it if it is needed):
var width = 800;
var height = 800;
var radius = 400;
var formatNumber = d3.format(",d");
var x = d3.scaleLinear().range([0, 2 * Math.PI]);
var y = d3.scaleSqrt().range([0, radius]);
var arc = d3.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); })
.innerRadius(function(d) { return setRadius("inner", d.data.level); })
.outerRadius(function(d) { return setRadius("outer", d.data.level); });
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + (height/2) + ")");
var hierarchy = d3.hierarchy(dataset)
.sum(function(d) { return d.size; });
var partition = d3.partition();
svg.selectAll("path")
.data(partition(hierarchy).descendants())
.enter().append("path")
.attr("id", function(d, i){ return "path" + i; })
.attr("d", arc)
.attr("stroke", "white")
.attr("stroke-width", "1px")
.style("fill", function(d) { return (d.data.color) ? d.data.color : 'black'; });
svg.selectAll("text")
.data(partition(hierarchy).descendants())
.enter().append("text")
.attr("transform", function(d){ return setLabelPosition(d); })
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-size", "18px")
.attr("fill", function(d){ return d.data.textcolor; })
.text(function(d){ if(parseInt(d.data.level) > 0 && parseInt(d.data.level) < 4){ return (d.data.name).toUpperCase(); }});
d3.select(self.frameElement)
.style("height", height + "px");
function setRadius(side, level){
var result = 0;
var innerValues = [0, 120, 180, 240, 365];
var outerValues = [0, 180, 240, 365, 400];
if(!side){
throw error;
}
if(side === "inner"){
result = innerValues[level];
}
if(side === "outer"){
result = outerValues[level];
}
return result;
};
function setLabelPosition(d){
var result = '';
var angle = 0;
var centroid = arc.centroid(d);
if(parseInt(d.data.level) === 3){
angle = (180/Math.PI * (arc.startAngle()(d) + arc.endAngle()(d))/2 - 90);
if(angle > 90){
angle = angle - 180;
}
result = "translate(" + centroid + ")rotate(" + angle + ")";
} else {
angle = (180/Math.PI * (arc.startAngle()(d) + arc.endAngle()(d))/2);
result = "translate(" + centroid + ")rotate(" + angle + ")";
}
return result;
};
And the result :
My problem is, how to curve these level 1 & 2 labels (like the one which have a red border), but keep my lvl 3 labels as they currently are.
It's really a pain in the head, and I did many search (on Google and SO) but I didn't find any satisfying answer.
A solution without using a textPath will be awesome if possible, but any advice is welcome.
Many thanks guys and sorry for my English (as you can probably see it's not my birth language).
PS : This is D3.js v4.

How to update a d3 visualization without redrawing every frame?

I am using d3 to create a waveform visualization. I have it looking great, but in my efforts to design this waveform I have paid no attention to doing it an optimized way; I just was trying to get it to work.
setInterval(function() {
if (refresh) waveform();
if (palette_refresh) {
palette_refresh = false;
updateColorPalette();
}
}, options.refresh_rate);
function waveform() {
analyser.getByteFrequencyData(fd);
document.getElementById('wf_box').innerHTML = "";
var data = ... irrelevant implementation details ...
var chart = d3.select("#wf_box").append("svg")
.attr("class", "chart")
.attr("width", "" + percent_offset + "%")
.attr("style", "padding-left:" + (100 - percent_offset) + "%;")
.attr("viewBox", "0 0 " + Math.max(w * data.length, 0) + " " + Math.max(h, 0));
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d, i) {
var x_offset = x(i) - Math.max(w * max / 900 - 0.25, 0.1) / 2;
return x_offset;
})
.attr("y", function(d, i) {
var height = y(d * options.bar_height) / h;
return h - Math.pow(Math.max(height, 0.01), 1.5);
})
.attr("width", function(d) {
return '1px';
})
.attr("height", function(d, i) {
var height = y(d * options.bar_height) / h;
return Math.pow(Math.max(height, 0.01), 1.5) + options.bar_y_offset;
});
}
As you can see, I am calling the waveform function repeatedly using options.refresh_rate as my speed. I would imagine that there is a more efficient way of updating the data used in my waveform than deleting the waveform from the DOM and redrawing it every frame. Does anybody have any insight to how I can accomplish this?
Thanks!
Take a look at the enter/exit/update-pattern in d3 https://bl.ocks.org/mbostock/3808218 and see if that can give you a hint.

D3pie.js labels.inner distance from the center

I use http://d3pie.org/#docs-settings
But there is no such parameter as the distance from the center to the internal labels.
Can someone tried to do it?
I want to move the internal labels closer to the outer edge of the circle.
Thank you so much.
now so:
need:
You can position the labels by defining a new arc as suggested in https://stackoverflow.com/a/8270668/2314737 and then applying the centroid function.
I defined a new arc newarc with an inner radius equal to 2/3 of the outer radius.
var newarc = d3.svg.arc()
.innerRadius(2 * radius / 3)
.outerRadius(radius);
Here's the JS code:
var width = 300;
var height = 300;
var svg = d3.select("body").append("svg");
svg.attr("width", width)
.attr("height", height);
var dataset = [11, 13, 18, 25, 31];
var radius = width / 2;
var innerRadius = 0;
var arc = d3.svg.arc()
.innerRadius(0)
.outerRadius(radius);
var pie = d3.layout.pie();
var arcs = svg.selectAll("g.arc")
.data(pie(dataset))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + radius + ", " + radius + ")");
//Draw arc paths
var color = d3.scale.category10();
arcs.append("path")
.attr("fill", function (d, i) {
console.log(d);
return color(i);
})
.attr("stroke", "white")
.attr("d", arc);
var newarc = d3.svg.arc()
.innerRadius(2 * radius / 3)
.outerRadius(radius);
// Place labels
arcs.append("text")
.attr("transform", function (d) {
return "translate(" + newarc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.attr("fill", "white")
.text(function (d) {
return d.value + "%";
});
Here is a working demo: http://jsfiddle.net/user2314737/kvz8uev8/2/
I decided to enroll in another way.
I added my property in the object and function of positioning inner labels in D3pie file d3pie.js
This function is located on the line - 996 d3pie.js
positionLabelGroups: function(pie, section) {
d3.selectAll("." + pie.cssPrefix + "labelGroup-" + section)
.style("opacity", 0)
.attr("transform", function(d, i) {
var x, y;
if (section === "outer") {
x = pie.outerLabelGroupData[i].x;
y = pie.outerLabelGroupData[i].y;
} else {
var pieCenterCopy = extend(true, {}, pie.pieCenter);
// now recompute the "center" based on the current _innerRadius
if (pie.innerRadius > 0) {
var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
var newCoords = math.translate(pie.pieCenter.x, pie.pieCenter.y, pie.innerRadius, angle);
pieCenterCopy.x = newCoords.x;
pieCenterCopy.y = newCoords.y;
//console.log('i ='+i , 'angle='+angle, 'pieCenterCopy.x='+pieCenterCopy.x, 'pieCenterCopy.y='+pieCenterCopy.y);
}
var dims = helpers.getDimensions(pie.cssPrefix + "labelGroup" + i + "-inner");
var xOffset = dims.w / 2;
var yOffset = dims.h / 4; // confusing! Why 4? should be 2, but it doesn't look right
// ADD VARAIBLE HERE !!! =)
var divisor = pie.options.labels.inner.pieDistanceOfEnd;
x = pieCenterCopy.x + (pie.lineCoordGroups[i][0].x - pieCenterCopy.x) / divisor;
y = pieCenterCopy.y + (pie.lineCoordGroups[i][0].y - pieCenterCopy.y) / divisor;
x = x - xOffset;
y = y + yOffset;
}
return "translate(" + x + "," + y + ")";
});
},
I add var divisor = pie.options.labels.inner.pieDistanceOfEnd;
Then I spotted this property devoltnyh the configuration file bhp and passed for plotting parameters.
inner: {
format: "percentage",
hideWhenLessThanPercentage: null,
pieDistanceOfEnd : 1.8
},
Meaning pieDistanceOfEnd: 1 hang tag on the outer radius of the chart
value pieDistanceOfEnd: 1.25 turn them slightly inward ....
You can play these parameters and to achieve the desired option.
In d3pie.js look for the function positionLabelGroups. In this function both labels (outer and inner) are positioned.
To modify the distance from the center you can play with the x,y here:
x = pieCenterCopy.x + (pie.lineCoordGroups[i][0].x - pieCenterCopy.x) / 1.8;
y = pieCenterCopy.y + (pie.lineCoordGroups[i][0].y - pieCenterCopy.y) / 1.8;
What I did was decreasing the 1.8 to 1.2 and obtained what youre looking for. Dont know what the other vars do, but you can study the code to figure it out

Categories

Resources