D3 v4 quadtree root is returning undefined - d3.js

I'm using D3 v4 and can't get quadtree to work. It keeps returning undefined for the root. I don't think it likes the data I'm giving it.
const Nodes = [];
for (let i = 0; i < 10; i++) {
Nodes.push({
radius: 5,
x: Math.random() * 500,
y: Math.random() * 500,
velocityX: Math.random(),
velocityY: Math.random()
});
}
collide() {
var quadtree = d3.quadtree().extent([
[
0, 0
],
[1500, 1000]
]).addAll(Nodes);
console.log(quadtree);
}

Let's have a look at any given object in your Nodes array:
[{
radius: 5,
x: 301.25792388143293,
y: 35.626900264457696,
velocityX: 0.43542026096574715,
velocityY: 0.03662733324854717
}]
As you can see, the x and y coordinates are defined in properties with "x" and "y" as key.
However, this is the default function for the x coordinate in d3.quadtree:
function x(d) {
return d[0];
}
And for the y coordinate:
function y(d) {
return d[1];
}
As you can see, those functions won't work with your object structure.
Solution:
Set the x and y coordinates according to your object:
var quadtree = d3.quadtree()
.x(function(d) {
return d.x
})
.y(function(d) {
return d.y
})
Here is your code with that change, check the console:
const Nodes = [];
for (let i = 0; i < 10; i++) {
Nodes.push({
radius: 5,
x: Math.random() * 500,
y: Math.random() * 500,
velocityX: Math.random(),
velocityY: Math.random()
});
}
var quadtree = d3.quadtree().extent([
[
0, 0
],
[1500, 1000]
]).x(function(d) {
return d.x
})
.y(function(d) {
return d.y
})
.addAll(Nodes);
console.log(quadtree);
<script src="https://d3js.org/d3.v4.min.js"></script>

Related

How to make D3 gauge chart in Qlikview extension

I have made a gauge chart using D3.js and it is working perfectly. However, when I am trying to add the same javascript code to render the same chart in a Qlikview extension, it is not working.
Can anyone please help me to find out where I am going wrong? I am attaching the code of the Script.js file below:
Qva.LoadScript("QlikView/Examples/Gauge/d3.min.js", function () {
/*
function loadGoogleCoreChart() {
google.load('visualization', '1', {
packages: ['corechart'],
callback: googleCoreChartLoaded
});
}
function googleCoreChartLoaded() {
*/
Qva.AddExtension('QlikView/Examples/Gauge', function () {
//this.Element.innerHTML = "<script type=\"text/javascript\" src=\"http://d3js.org/d3.v2.min.js\"></script>"
//this.Element.innerHTML = "";
var gauge = function(container, configuration) {
var that = {};
var config = {
size : 200,
clipWidth : 200,
clipHeight : 110,
ringInset : 20,
ringWidth : 20,
pointerWidth : 10,
pointerTailLength : 5,
pointerHeadLengthPercent : 0.9,
minValue : 0,
maxValue : 10,
minAngle : -90,
maxAngle : 90,
transitionMs : 750,
majorTicks : 5,
labelFormat : d3.format(',g'),
labelInset : 10,
arcColorFn : d3.interpolateHsl(d3.rgb('#e8e2ca'), d3.rgb('#3e6c0a'))
};
var range = undefined;
var r = undefined;
var pointerHeadLength = undefined;
var value = 0;
var svg = undefined;
var arc = undefined;
var scale = undefined;
var ticks = undefined;
var tickData = undefined;
var pointer = undefined;
var donut = d3.layout.pie();
function deg2rad(deg) {
return deg * Math.PI / 180;
}
function newAngle(d) {
var ratio = scale(d);
var newAngle = config.minAngle + (ratio * range);
return newAngle;
}
function configure(configuration) {
var prop = undefined;
for ( prop in configuration ) {
config[prop] = configuration[prop];
}
range = config.maxAngle - config.minAngle;
r = config.size / 2;
pointerHeadLength = Math.round(r * config.pointerHeadLengthPercent);
// a linear scale that maps domain values to a percent from 0..1
scale = d3.scale.linear()
.range([0,1])
.domain([config.minValue, config.maxValue]);
ticks = scale.ticks(config.majorTicks);
tickData = d3.range(config.majorTicks).map(function() {return 1/config.majorTicks;});
arc = d3.svg.arc()
.innerRadius(r - config.ringWidth - config.ringInset)
.outerRadius(r - config.ringInset)
.startAngle(function(d, i) {
var ratio = d * i;
return deg2rad(config.minAngle + (ratio * range));
})
.endAngle(function(d, i) {
var ratio = d * (i+1);
return deg2rad(config.minAngle + (ratio * range));
});
}
that.configure = configure;
function centerTranslation() {
return 'translate('+r +','+ r +')';
}
function isRendered() {
return (svg !== undefined);
}
that.isRendered = isRendered;
function render(newValue) {
svg = d3.select(container)
.append('svg:svg')
.attr('class', 'gauge')
.attr('width', config.clipWidth)
.attr('height', config.clipHeight);
var centerTx = centerTranslation();
var arcs = svg.append('g')
.attr('class', 'arc')
.attr('transform', centerTx);
arcs.selectAll('path')
.data(tickData)
.enter().append('path')
.attr('fill', function(d, i) {
return config.arcColorFn(d * i);
})
.attr('d', arc);
var lg = svg.append('g')
.attr('class', 'label')
.attr('transform', centerTx);
lg.selectAll('text')
.data(ticks)
.enter().append('text')
.attr('transform', function(d) {
var ratio = scale(d);
var newAngle = config.minAngle + (ratio * range);
return 'rotate(' +newAngle +') translate(0,' +(config.labelInset - r) +')';
})
.text(config.labelFormat);
var lineData = [ [config.pointerWidth / 2, 0],
[0, -pointerHeadLength],
[-(config.pointerWidth / 2), 0],
[0, config.pointerTailLength],
[config.pointerWidth / 2, 0] ];
var pointerLine = d3.svg.line().interpolate('monotone');
var pg = svg.append('g').data([lineData])
.attr('class', 'pointer')
.attr('transform', centerTx);
pointer = pg.append('path')
.attr('d', pointerLine/*function(d) { return pointerLine(d) +'Z';}*/ )
.attr('transform', 'rotate(' +config.minAngle +')');
update(newValue === undefined ? 0 : newValue);
}
that.render = render;
function update(newValue, newConfiguration) {
if ( newConfiguration !== undefined) {
configure(newConfiguration);
}
var ratio = scale(newValue);
var newAngle = config.minAngle + (ratio * range);
pointer.transition()
.duration(config.transitionMs)
.ease('elastic')
.attr('transform', 'rotate(' +newAngle +')');
}
that.update = update;
configure(configuration);
return that;
};
function onDocumentReady() {
var powerGauge = gauge(this.Element, {
size: 300,
clipWidth: 300,
clipHeight: 300,
ringWidth: 60,
maxValue: 10,
transitionMs: 4000,
});
alert("Some Text");
powerGauge.render();
function updateReadings() {
// just pump in random data here...
powerGauge.update(Math.random() * 10);
}
// every few seconds update reading values
updateReadings();
setInterval(function() {
updateReadings();
}, 5 * 1000);
}
if ( !window.isLoaded ) {
window.addEventListener("load", function() {
onDocumentReady();
}, false);
} else {
onDocumentReady();
}
});
});
Please help me with this as I am unable to find out the glitch.

d3.js: legends with dynamic element width

I am trying to replace a d3 legend drawing function which used manual creation and placement of the legend elements to one using .data().
I was able to get everything working, except one thing: with manually looping through the data array, I was able to ensure equal spacing between the legend elements by using a currentX tracker variable. With .data() however, I can't reference the other elements, and have to use fixed-width boxes, which the designer hates (and me too).
Given the code below, how do I make the new code (top row) behave exactly like the old code (bottom row)?
(Please do not suggest that I use a legend library, there is lots of event-handling and more stuff in the "real" code upon which this testcase was extracted)
var config = {
circleYCenter: 170,
circleXCenter: 150,
legendMarginTop: 52,
legendMarginInner: 18,
legendDotHeight: 8,
legendDotWidth: 16,
};
var colors = {
'lightyellow': '#FEE284',
'darkblue': '#2872A3',
'dirtyorange': '#E68406',
};
function drawLegend(container, map) {
var legendGroup = container.append("g").attr({
"transform": "translate(0,50)",
"class": "legendGroup"
});
//New code
var elGroup = legendGroup.selectAll("g").data(map).enter().append("g").attr({
transform: function(d, i) {
return "translate(" + (i * 100) + ",0)"
}
});
elGroup.append("text").text(function(d) {
return d.label;
}).attr({
x: config.legendDotWidth + config.legendMarginInner
});
elGroup.append("rect").attr({
x: 0,
width: config.legendDotWidth,
y: -5,
height: config.legendDotHeight,
"fill": function(d) {
return d.color;
}
});
//Old code
var currentX = 0;
map.forEach(function(el, key) {
var elGroup = legendGroup.append("g").attr("transform", "translate(" + currentX + ",100)");
elGroup.append("rect").attr({
x: 0,
width: config.legendDotWidth,
y: -5,
height: config.legendDotHeight,
"fill": el.color
});
elGroup.append("text").attr({
"x": (config.legendDotWidth + 10),
"y": 0,
"alignment-baseline": "middle"
}).text(el.label);
currentX += elGroup.node().getBBox().width + config.legendMarginInner;
});
}
drawLegend(d3.select("svg"), [{
label: "foo",
color: colors.dirtyorange
}, {
label: "Banana",
color: colors.darkblue
}, {
label: "baz",
color: colors.lightyellow
}]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width=600 height=300></svg>
First make the group:
var elGroup = legendGroup.selectAll("g").data(map).enter().append("g");
Append the text and rect DOM as you are doing:
elGroup.append("text").text(function(d) {
return d.label;
}).attr({
x: config.legendDotWidth + config.legendMarginInner
});
elGroup.append("rect").attr({
x: 0,
width: config.legendDotWidth,
y: -5,
height: config.legendDotHeight,
"fill": function(d) {
return d.color;
}
});
Now add the translate to the group using the bbox of the group.
var currentX = 0;
elGroup.attr({
transform: function(d, i) {
var ret = "translate(" + currentX + ",0)"
currentX += this.getBBox().width + config.legendMarginInner;
return ret;
}
});
working code here

understanding functions in d3js

Can some one explain me this function:
var transitions = function ()
{
return states.reduce(function (initial, state) {
return initial.concat(
state.transitions.map(function (transition) {
return {source: state, transition: transition};
})
);
}, []);
};
and this line: var gTransitions = svg.append('g').selectAll("path.transition"); - how path.transition is getting selected?
I am new d3 and javascript and I am really stuck at this point in my project.
The above snippet is taken out of below code. I have put comments saying "QUESTION1" and "QUESTION2" to find it.
window.onload = function ()
{
var radius = 40;
window.states = [
{x: 43, y: 67, label: "first", transitions: []},
{x: 340, y: 150, label: "second", transitions: []},
{x: 200, y: 250, label: "third", transitions: []}
];
window.svg = d3.select('body')
.append("svg")
.attr("width", "960px")
.attr("height", "500px");
// define arrow markers for graph links
svg.append('svg:defs').append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 4)
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('class', 'end-arrow')
;
// line displayed when dragging new nodes
var drag_line = svg.append('svg:path')
.attr({
'class': 'dragline hidden',
'd': 'M0,0L0,0'
})
;
//QUESTION1
var gTransitions = svg.append('g').selectAll("path.transition");
var gStates = svg.append("g").selectAll("g.state");
//QUESTION2
var transitions = function ()
{
return states.reduce(function (initial, state) {
return initial.concat(
state.transitions.map(function (transition) {
return {source: state, transition: transition};
})
);
}, []);
};
var transformTransitionEndpoints = function (d, i) {
var endPoints = d.endPoints();
var point = [
d.type == 'start' ? endPoints[0].x : endPoints[1].x,
d.type == 'start' ? endPoints[0].y : endPoints[1].y
];
return "translate(" + point + ")";
}
var transformTransitionPoints = function (d, i) {
return "translate(" + [d.x, d.y] + ")";
}
var computeTransitionPath = (function () {
var line = d3.svg.line()
.x(function (d, i) {
return d.x;
})
.y(function (d, i) {
return d.y;
})
.interpolate("cardinal");
return function (d) {
var source = d.source,
target = d.transition.points.length && d.transition.points[0] || d.transition.target,
deltaX = target.x - source.x,
deltaY = target.y - source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourcePadding = radius + 4, //d.left ? 17 : 12,
sourceX = source.x + (sourcePadding * normX),
sourceY = source.y + (sourcePadding * normY);
source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
target = d.transition.target;
deltaX = target.x - source.x;
deltaY = target.y - source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
normX = deltaX / dist;
normY = deltaY / dist;
targetPadding = radius + 8;//d.right ? 17 : 12,
targetX = target.x - (targetPadding * normX);
targetY = target.y - (targetPadding * normY);
var points =
[{x: sourceX, y: sourceY}].concat(
d.transition.points,
[{x: targetX, y: targetY}]
)
;
var l = line(points);
return l;
};
})();
var dragPoint = d3.behavior.drag()
.on("drag", function (d, i) {
console.log("transitionmidpoint drag");
var gTransitionPoint = d3.select(this);
gTransitionPoint.attr("transform", function (d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [d.x, d.y] + ")"
});
// refresh transition path
gTransitions.selectAll("path").attr('d', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll("circle.endpoint").attr({
transform: transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll("circle.point").attr({
transform: transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
});
var renderTransitionMidPoints = function (gTransition) {
gTransition.each(function (transition) {
var transitionPoints = d3.select(this).selectAll('circle.point').data(transition.transition.points, function (d) {
return transition.transition.points.indexOf(d);
});
transitionPoints.enter().append("circle")
.attr({
'class': 'point',
r: 4,
transform: transformTransitionPoints
})
.call(dragPoint);
transitionPoints.exit().remove();
});
};
var renderTransitionPoints = function (gTransition) {
gTransition.each(function (d) {
var endPoints = function () {
var source = d.source,
target = d.transition.points.length && d.transition.points[0] || d.transition.target,
deltaX = target.x - source.x,
deltaY = target.y - source.y,
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
normX = deltaX / dist,
normY = deltaY / dist,
sourceX = source.x + (radius * normX),
sourceY = source.y + (radius * normY);
source = d.transition.points.length && d.transition.points[ d.transition.points.length - 1] || d.source;
target = d.transition.target;
deltaX = target.x - source.x;
deltaY = target.y - source.y;
dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
normX = deltaX / dist;
normY = deltaY / dist;
targetPadding = radius + 8;//d.right ? 17 : 12,
targetX = target.x - (radius * normX);
targetY = target.y - (radius * normY);
return [{x: sourceX, y: sourceY}, {x: targetX, y: targetY}];
};
var transitionEndpoints = d3.select(this).selectAll('circle.endpoint').data([
{endPoints: endPoints, type: 'start'},
{endPoints: endPoints, type: 'end'}
]);
transitionEndpoints.enter().append("circle")
.attr({
'class': function (d) {
return 'endpoint ' + d.type;
},
r: 4,
transform: transformTransitionEndpoints
})
;
transitionEndpoints.exit().remove();
});
};
var renderTransitions = function () {
gTransition = gTransitions.enter().append('g')
.attr({
'class': 'transition'
})
gTransition.append('path')
.attr({
d: computeTransitionPath,
class: 'background'
})
.on({
dblclick: function (d, i) {
gTransition = d3.select(d3.event.target.parentElement);
if (d3.event.ctrlKey) {
var p = d3.mouse(this);
gTransition.classed('selected', true);
d.transition.points.push({x: p[0], y: p[1]});
renderTransitionMidPoints(gTransition, d);
gTransition.selectAll('path').attr({
d: computeTransitionPath
});
} else {
var gTransition = d3.select(d3.event.target.parentElement),
transition = gTransition.datum(),
index = transition.source.transitions.indexOf(transition.transition);
transition.source.transitions.splice(index, 1)
gTransition.remove();
d3.event.stopPropagation();
}
}
});
gTransition.append('path')
.attr({
d: computeTransitionPath,
class: 'foreground'
});
renderTransitionPoints(gTransition);
renderTransitionMidPoints(gTransition);
gTransitions.exit().remove();
};
var renderStates = function () {
var gState = gStates.enter()
.append("g")
.attr({
"transform": function (d) {
return "translate(" + [d.x, d.y] + ")";
},
'class': 'state'
})
.call(drag);
gState.append("circle")
.attr({
r: radius + 4,
class: 'outer'
})
.on({
mousedown: function (d) {
console.log("state circle outer mousedown");
startState = d, endState = undefined;
// reposition drag line
drag_line
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', false)
.attr('d', 'M' + d.x + ',' + d.y + 'L' + d.x + ',' + d.y);
// force element to be an top
this.parentNode.parentNode.appendChild(this.parentNode);
//d3.event.stopPropagation();
},
mouseover: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
},
mouseout: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
//$( this).popover( "hide");
}
});
gState.append("circle")
.attr({
r: radius,
class: 'inner'
})
.on({
mouseover: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", true);
},
mouseout: function () {
svg.select("rect.selection").empty() && d3.select(this).classed("hover", false);
},
});
};
var startState, endState;
var drag = d3.behavior.drag()
.on("drag", function (d, i) {
console.log("drag");
if (startState) {
return;
}
var selection = d3.selectAll('.selected');
// if dragged state is not in current selection
// mark it selected and deselect all others
if (selection[0].indexOf(this) == -1) {
selection.classed("selected", false);
selection = d3.select(this);
selection.classed("selected", true);
}
// move states
selection.attr("transform", function (d, i) {
d.x += d3.event.dx;
d.y += d3.event.dy;
return "translate(" + [d.x, d.y] + ")"
});
// move transistion points of each transition
// where transition target is also in selection
var selectedStates = d3.selectAll('g.state.selected').data();
var affectedTransitions = selectedStates.reduce(function (array, state) {
return array.concat(state.transitions);
}, [])
.filter(function (transition) {
return selectedStates.indexOf(transition.target) != -1;
});
affectedTransitions.forEach(function (transition) {
for (var i = transition.points.length - 1; i >= 0; i--) {
var point = transition.points[i];
point.x += d3.event.dx;
point.y += d3.event.dy;
}
});
// reappend dragged element as last
// so that its stays on top
selection.each(function () {
this.parentNode.appendChild(this);
});
// refresh transition path
gTransitions.selectAll("path").attr('d', computeTransitionPath);
// refresh transition endpoints
gTransitions.selectAll("circle.endpoint").attr({
transform: transformTransitionEndpoints
});
// refresh transition points
gTransitions.selectAll("circle.point").attr({
transform: transformTransitionPoints
});
d3.event.sourceEvent.stopPropagation();
})
.on("dragend", function (d) {
console.log("dragend");
// needed by FF
drag_line.classed('hidden', true)
.style('marker-end', '');
if (startState && endState) {
startState.transitions.push({label: "transition label 1", points: [], target: endState});
update();
}
startState = undefined;
d3.event.sourceEvent.stopPropagation();
});
svg.on({
mousedown: function () {
console.log("mousedown", d3.event.target);
if (d3.event.target.tagName == 'svg') {
if (!d3.event.ctrlKey) {
d3.selectAll('g.selected').classed("selected", false);
}
var p = d3.mouse(this);
}
},
mousemove: function () {
var p = d3.mouse(this);
// update drag line
drag_line.attr('d', 'M' + startState.x + ',' + startState.y + 'L' + p[0] + ',' + p[1]);
var state = d3.select('g.state .inner.hover');
endState = (!state.empty() && state.data()[0]) || undefined;
},
mouseup: function () {
console.log("mouseup");
// remove temporary selection marker class
d3.selectAll('g.state.selection').classed("selection", false);
},
mouseout: function ()
{
if (!d3.event.relatedTarget || d3.event.relatedTarget.tagName == 'HTML') {
// remove temporary selection marker class
d3.selectAll('g.state.selection').classed("selection", false);
}
}
});
update();
function update() {
gStates = gStates.data(states, function (d) {
return states.indexOf(d);
});
renderStates();
var _transitions = transitions();
gTransitions = gTransitions.data(_transitions, function (d) {
return _transitions.indexOf(d);
});
renderTransitions();
}
;
};
I assume this is from http://bl.ocks.org/lgersman/5370827.
Background
states (=window.states) is an array of state objects (3 in your case). Each state object has a property transitions (which represents possible changes to other states from this state), which is an array.
Question 1
This uses the reduce, concat and map method of the Array prototype to build a function that returns an array of objects of the form { source: state, transition: transition } using the transition arrays inside the state array.
The 1st layer is pretty simple - just a function definition. You call it eventually using var _transitions = transition();
var transitions = function () {
return ...
};
Note that each call returns the list based on the states / transitions that exist at the time the function is called.
The 2nd layer builds an array by concatenating array fragments from the 3rd layer. From the documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce), reduce effectively gets a single value from an array.
In our case, that single value is a larger array built by concatenating array fragments. The 2nd parameter to the reduce function is the intial value (in this case an empty array)
return states.reduce(function (initial, state) {
return initial.concat(
...
);
}, []);
So we first pass in an empty array. The output of the 3rd layer (... in the section above) using the 1st element of states (i.e. states[0]) is concatenated to it to build a new array. This new array is then concatenated with the 2nd output of the 3rd layer (i.e. using states[1]) and so on
The 3rd layer is a simple map (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). For each transition array entry in the state, it returns an object of the form { source: state, transition: transition }, using this to build an array (which is used by the 2nd layer as we saw above)
state.transitions.map(function (transition) {
return { source: state, transition: transition };
})
So, if we were to trace this for the "first" state and assuming you had 2 transition entries (your code has an empty array, but the original example inserts a couple of transitions), you'd get something like
[
{
source: <<"first" state object>>
transition: <<transition1a of "first" state - from it's transition array, 1st element>>
},
{
source: <<"first" state object>>
transition: <<transition1b of "first" state - from it's transition array, 2nd element>>
},
]
Carrying this up to the 2nd layer, you'd get something like this (assuming state "second" had 3 transitions emanating from it)
[
{
source: <<"first" state object>>
transition: <<transition1a of "first" state - from it's transition array, 1st element>>
},
{
source: <<"first" state object>>
transition: <<transition1b of "first" state - from it's transition array, 2nd element>>
},
{
source: <<"second" state object>>
transition: <<transition2a of "second" state - from it's transition array, 1st element>>
},
{
source: <<"second" state object>>
transition: <<transition2b of "second" state - from it's transition array, 2nd element>>
},
{
source: <<"second" state object>>
transition: <<transition2c of "second" state - from it's transition array, 3rd element>>
},
...
... and so on for all the states
]
And the 1st layer is effectively a function which does all the steps above when called.
Question 2
This effectively builds a d3 selection (see https://github.com/mbostock/d3/wiki/Selections) - the selection's d3 data comes from the output of the 1st question. The very end of your code has this link
gTransitions = gTransitions.data(_transitions, function (d) {
return _transitions.indexOf(d);
});
_transitions being set by a call to transitions(); in the line just above that.
This d3 selection is then used as d3 selections normally are (with an enter() / exit()), to update the svg element DOM. If you search for gTransitions.enter() and gTransitions.exit() you can find the related bits of code that keep your svg DOM updated. Note that the enter() involves a number of steps (append a g, set it's class, attach behaviour, append a path to the g...)
The first time, the update() function is called takes care of syncing the DOM to the initial data (in your case since your transition properties are empty arrays, nothing is created).
Subsequently, DOM event handlers update the respective states's transition arrays and the update() function is called at the end of the handler to reattach updated data (i.e. output of the transition() call) and hence drive the creation / removal of DOM elements for the transitions (via a call to renderTransitions()) - these are effectively the svg paths between (state) svg circles

d3.js Pie Chart With label

I started working with this d3.js Donut Chart: JSFiddleI am trying to change it into a Pie Chart without the circle in the middle. I am new to d3.js. I have tried several different ideas but have been unable to get this to remove the circle in the middle of the chart. Any and all help is appreciated
Here is my code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<style>
.label-text {
alignment-baseline : middle;
font-size: 12px;
font-family: arial,helvetica,"sans-serif";
fill: #393939;
}
.label-line {
stroke-width: 1;
stroke: #393939;
}
.label-circle {
fill: #393939;
}
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg>
<g id="canvas">
<g id="art" />
<g id="labels" /></g>
</svg>
<script>
var data = [{
label: 'Star Wars',
instances: 207
}, {
label: 'Lost In Space',
instances: 3
}, {
label: 'the Boston Pops',
instances: 20
}, {
label: 'Indiana Jones',
instances: 150
}, {
label: 'Harry Potter',
instances: 75
}, {
label: 'Jaws',
instances: 5
}, {
label: 'Lincoln',
instances: 1
}];
svg = d3.select("svg");
canvas = d3.select("#canvas");
art = d3.select("#art");
labels = d3.select("#labels");
// Create the pie layout function.
// This function will add convenience
// data to our existing data, like
// the start angle and end angle
// for each data element.
jhw_pie = d3.layout.pie();
jhw_pie.sort(null);
jhw_pie.value(function (d) {
// Tells the layout function what
// property of our data object to
// use as the value.
return d.instances;
});
// Store our chart dimensions
cDim = {
height: 500,
width: 500,
innerRadius: 50,
outerRadius: 150,
labelRadius: 175
}
// Set the size of our SVG element
svg.attr({
height: cDim.height,
width: cDim.width
});
// This translate property moves the origin of the group's coordinate
// space to the center of the SVG element, saving us translating every
// coordinate individually.
canvas.attr("transform", "translate(" + (cDim.width / 2) + "," + (cDim.height / 2) + ")");
pied_data = jhw_pie(data);
// The pied_arc function we make here will calculate the path
// information for each wedge based on the data set. This is
// used in the "d" attribute.
pied_arc = d3.svg.arc()
.innerRadius(50)
.outerRadius(150);
// This is an ordinal scale that returns 10 predefined colors.
// It is part of d3 core.
pied_colors = d3.scale.ordinal()
.range(["#04B486", "#F2F2F2", "#F5F6CE", "#00BFFF","orange","purple","pink"]);
// Let's start drawing the arcs.
enteringArcs = art.selectAll(".wedge").data(pied_data)
.enter();
enteringArcs
.append("g")
.attr("class", "wedge")
.append("path")
.attr("d", pied_arc)
.style("fill", function (d, i) {
return pied_colors(i);
});
// Now we'll draw our label lines, etc.
enteringLabels = labels.selectAll(".label").data(pied_data).enter();
labelGroups = enteringLabels.append("g").attr("class", "label");
labelGroups.append("circle").attr({
x: 0,
y: 0,
r: 2,
fill: "#000",
transform: function (d, i) {
centroid = pied_arc.centroid(d);
return "translate(" + pied_arc.centroid(d) + ")";
},
'class': "label-circle"
});
// "When am I ever going to use this?" I said in
// 10th grade trig.
textLines = labelGroups.append("line").attr({
x1: function (d, i) {
return pied_arc.centroid(d)[0];
},
y1: function (d, i) {
return pied_arc.centroid(d)[1];
},
x2: function (d, i) {
centroid = pied_arc.centroid(d);
midAngle = Math.atan2(centroid[1], centroid[0]);
x = Math.cos(midAngle) * cDim.labelRadius;
return x;
},
y2: function (d, i) {
centroid = pied_arc.centroid(d);
midAngle = Math.atan2(centroid[1], centroid[0]);
y = Math.sin(midAngle) * cDim.labelRadius;
return y;
},
'class': "label-line"
});
textLabels = labelGroups.append("text").attr({
x: function (d, i) {
centroid = pied_arc.centroid(d);
midAngle = Math.atan2(centroid[1], centroid[0]);
x = Math.cos(midAngle) * cDim.labelRadius;
sign = (x > 0) ? 1 : -1
labelX = x + (5 * sign)
return labelX;
},
y: function (d, i) {
centroid = pied_arc.centroid(d);
midAngle = Math.atan2(centroid[1], centroid[0]);
y = Math.sin(midAngle) * cDim.labelRadius;
return y;
},
'text-anchor': function (d, i) {
centroid = pied_arc.centroid(d);
midAngle = Math.atan2(centroid[1], centroid[0]);
x = Math.cos(midAngle) * cDim.labelRadius;
return (x > 0) ? "start" : "end";
},
'class': 'label-text'
}).text(function (d) {
return d.data.label
});
alpha = 0.5;
spacing = 12;
function relax() {
again = false;
textLabels.each(function (d, i) {
a = this;
da = d3.select(a);
y1 = da.attr("y");
textLabels.each(function (d, j) {
b = this;
// a & b are the same element and don't collide.
if (a == b) return;
db = d3.select(b);
// a & b are on opposite sides of the chart and
// don't collide
if (da.attr("text-anchor") != db.attr("text-anchor")) return;
// Now let's calculate the distance between
// these elements.
y2 = db.attr("y");
deltaY = y1 - y2;
// Our spacing is greater than our specified spacing,
// so they don't collide.
if (Math.abs(deltaY) > spacing) return;
// If the labels collide, we'll push each
// of the two labels up and down a little bit.
again = true;
sign = deltaY > 0 ? 1 : -1;
adjust = sign * alpha;
da.attr("y", +y1 + adjust);
db.attr("y", +y2 - adjust);
});
});
// Adjust our line leaders here
// so that they follow the labels.
if (again) {
labelElements = textLabels[0];
textLines.attr("y2", function (d, i) {
labelForLine = d3.select(labelElements[i]);
return labelForLine.attr("y");
});
setTimeout(relax, 20)
}
}
relax();
</script>
</body>
</html>
Thanks
See this updated fiddle.
The code contained the following lines, of which the innerRadious was changed to 0.
pied_arc = d3.svg.arc()
.innerRadius(00) // <- this
.outerRadius(150);
It's a bit misleading, as there's an innerRadius variable somewhere before that, but it's not used at this point. While you're at it, you might want to align all of that stuff.

d3.js gauge-like arc initialization and transition

I'd like your assistance in building a kind of 'gauge', an 'arc' based circle that would populate a value from 0 to 100 dynamically (8 gauges).
I was able to get this working (based on Arc Clock gist https://gist.github.com/mbostock/1098617), but now I'm trying to force a transition on every update and on the start.
I'm trying to implement the following flow:
1. arch loaded - goes from 0 to 100 -> from 100 to initial value
2. arch updated - goes from previous value to 0 -> from 0 to new value
Can't seem to find the right way to implement this...
The values are currently being inserted at random (10 increments)
var w = 1500,
h = 300,
x = d3.scale.ordinal().domain(d3.range(8)).rangePoints([0, w], 2);
var fields = [
{ name: "A", value: 100, previous: 0, size: 100 },
{ name: "B", value: 100, previous: 0, size: 100 },
{ name: "C", value: 100, previous: 0, size: 100 },
{ name: "D", value: 100, previous: 0, size: 100 },
{ name: "E", value: 100, previous: 0, size: 100 },
{ name: "F", value: 100, previous: 0, size: 100 },
{ name: "G", value: 100, previous: 0, size: 100 },
{ name: "H", value: 100, previous: 0, size: 100 }
];
var arc = d3.svg.arc()
.innerRadius(40)
.outerRadius(60)
.startAngle(0)
.endAngle(function (d) { return (d.value / d.size) * 2 * Math.PI; });
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(0," + (h / 2) + ")");
var path = svg.selectAll("path")
.data(fields.filter(function (d) { return d.value; }), function (d) { return d.name; })
.enter().append("svg:path")
.attr("transform", function (d, i) { return "translate(" + x(i) + ",0)"; })
.transition()
.ease("liniar")
.duration(750)
.attrTween("d", arcTween);
setTimeout(function () { services() }, 750);
setInterval(function () { services(); }, 5000);
function services() {
for (var i = 0; i < fields.length; i++) {
fields[i].previous = fields[i].value;
fields[i].value = Math.floor((Math.random() * 100) + 1);
}
path = svg.selectAll("path").data(fields.filter(function (d) { return d.value; }), function (d) { return d.name; });
path.transition()
.ease("linear")
.duration(1600)
.attrTween("d", arcTweenReversed);
}
function arcTween(b) {
var i = d3.interpolate({ value: b.previous }, b);
return function (t) {
return arc(i(t));
};
}
Here is JSFiddle to see it live: http://jsfiddle.net/p5xWZ/2/
Thanks in advance!
Something like the following chain transition could complete the arc and then go back to the next value:
path.transition()
.ease("linear")
.duration(function(d, i) { return 1600 * ((d.size-d.value)/d.size); })
.delay(function(d, i) { return i * 10; })
.attrTween("d", completeArc)
.transition()
.ease("linear")
.duration(function(d, i) { return 1600 * (d.value/d.size); })
.attrTween("d", resetArc)
.style("fill", function (d) { if (d.value < 100) { return "green"; } else { return "red" } });
Where completing the arc goes to 100, and resetting the arc goes from 0 to the next value:
function completeArc(b) {
// clone the data for the purposes of interpolation
var newb = $.extend({}, b);
// Set to 100
newb.value = newb.size;
var i = d3.interpolate({value: newb.previous}, newb);
return function(t) {
return arc(i(t));
};
}
function resetArc(b) {
var i = d3.interpolate({value: 0}, b);
return function(t) {
return arc(i(t));
};
}
Fiddle here also with fill color added.

Resources