D3 Tooltip for updated pie chart - javascript

So I've created a pie chart that takes in some JSON data. Upon clicking a radio button, the pie chart's data will update depending on what option is chosen. However, I'm running into an issue because I'd like to put a D3 tooltip on the pie chart and have its values update whenever the pie chart updates... anyone know the best way to do that?
Here's what my pie chart looks like so far:
<form>
<label><input type="radio" name="dataset" value="females" checked> Females </label>
<label><input type="radio" name="dataset" value="males"> Males </label>
<label><input type="radio" name="dataset" value="totalNumber"> Both </label>
</form>
<script>
var width = 760, height = 300, radius = Math.min(width, height) / 2;
var legendRectSize = 18;
var legendSpacing = 4;
var color = d3.scale.ordinal()
.domain(["7-15", "16-24", "25-33", "34-42", "43-51", "52-60", "61-70"])
.range(["#ff8b3d", "#d65050", "#ab0e4d", "#6f0049", "#4a004b", "#d0743c", "#BE2625"]);
var arc = d3.svg.arc()
.outerRadius(radius * 0.9)
.innerRadius(radius * 0.3);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.females; });
var svg = d3.select("#donut1").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 4 + "," + height / 2 + ")")
d3.json("graphdata.php", function(error, data) {
data.forEach(function(d) {
var path = svg.datum(data).selectAll("path")
.data(pie)
d.females = +d.females;
d.males = +d.males;
d.totalNumber = +d.totalNumber;
});
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([55, 0])
.html(function(d) {
return ("Victims:") + " " + d[value] + "<br>";
})
svg.call(tip);
var donut1 = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc")
.on("mouseover", tip.show)
.on("mouseout", tip.hide);
donut1.append("path")
.style("fill", function(d) { return color(d.data.age); })
.attr("d", arc)
.each(function(d) {this._current = d; }); //stores the initial angles
/*Control for changing the radio buttons & filtering the data*/
d3.selectAll("input")
.on("change", change);
var timeout = setTimeout(function() {
d3.select("input[value=\"females\"]").property("checked", true).each(change);}, 2000);
function change() {
var path = svg.datum(data).selectAll("path")
var value = this.value;
clearTimeout(timeout);
pie.value(function(d) { return d[value]; }); //change value function
tip.html(function(d) { return "Victims:" + " " + d[value]; }); //change the tooltip
path = path.data(pie); //compute new angles
path.transition().duration(1800).attrTween("d", arcTween); //redraw the arcs, please!
}
function type(d) {
d.females = +d.females;
d.males = +d.males;
d.totalNumber = +d.totalNumber;
return d;
}
//Store displayed angles in _current
//Interpolate them from _current to new angles
//_current is updated in-place by d3.interpolate during transition
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
I know that the key lies in the change() function and within this line:
tip.html(function(d) { return "Victims:" + " " + d[value]; }); //change the tooltip
But d[value] is undefined when I try to use it on that line. I'm just not sure what else it could be..
edit:
Here's the graphdata.php file contents:
<?php include("connectDB.php");
$query = "SELECT sum(sex ='M') males, sum(sex ='F') females, CASE
WHEN age BETWEEN 7 AND 15 THEN '7-15'
WHEN age <= 24 THEN '16-24'
WHEN age <= 33 THEN '25-33'
WHEN age <= 42 THEN '34-42'
WHEN age <= 51 THEN '43-51'
WHEN age <= 52 THEN '52-60'
WHEN age <= 70 THEN '61-70'
END AS age,
COUNT(*) AS totalNumber
FROM people
GROUP BY CASE
WHEN age <= 15 THEN '7-15'
WHEN age <= 24 THEN '16-24'
WHEN age <= 33 THEN '25-33'
WHEN age <= 42 THEN '34-42'
WHEN age <= 51 THEN '43-51'
WHEN age <= 52 THEN '52-60'
WHEN age <= 70 THEN '61-70'
END";
$data = array();
if($result = mysqli_query($db,$query)) {
while ($row = mysqli_fetch_assoc($result))
{
$data[] = $row;
}
}
echo json_encode($data, JSON_PRETTY_PRINT);
?>

I got a friend to help me out. What he ended up doing was this:
function change() {
var path = svg.datum(data).selectAll("path")
var value = this.value;
clearTimeout(timeout);
pie.value(function(d) { return d[value]; }); //change value function
tip.html(function(d) {
console.log($( "input:checked" ).val() );
if($( "input:checked" ).val() == "males") {
hoverValue = d.data.males;
}
if($( "input:checked" ).val() == "females") {
hoverValue = d.data.females;
}
if($( "input:checked" ).val() == "totalNumber") {
hoverValue = d.data.totalNumber;
}
return "Victims:" + " " +hoverValue;
}); //change the tooltip
path = path.data(pie); //compute new angles
path.transition().duration(1800).attrTween("d", arcTween); //redraw the arcs, please!
}
In particular, what has changed is the tip.html function. We take the value of whatever radio value input is checked and use it to determine what data to display. However, my data was located further down into the object, so we had to navigate like so: d.data.males, for example. We set that value to a variable and call it in the return so that it displays in the tooltip.

Related

d3js display animated text into pie charts

I have 3 pie charts that get loaded with an animation.
Each of this chart, has a text inside, the percent number, that one chart is loaded follow the animation and start the counting from 0% to tot%
I was able to show the text in one pie chart, but when i adapt the code to be used from 3 pie charts, i cannot find the way to display 3 text.
I loop trought the 3 div and trhough the 3 percent number, i can see the correct percent number in the console, but then nothing get displayed inside the pie chart :/
I'm new to d3 so it might be much easier then what i can see.
app.numbers = {
calcPerc : function(percent) {
return [percent, 100-percent];
},
drawDonutChart : function(el, percent, width, height, text_y) {
width = typeof width !== undefined ? width : 290; //width
height = typeof height !== undefined ? height : 290; //height
text_y = typeof text_y !== undefined ? text_y : "-.10em";
var radius = Math.min(width, height) / 2;
var pie = d3.pie().sort(null);
var dataset = {
lower: this.calcPerc(0),
upper: this.calcPerc(percent)
}
//this.percents = percent;
this.arc = d3.arc()
.innerRadius(radius - 20)
.outerRadius(radius);
var svg = d3.select(el).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset.lower))
.enter().append("path")
.attr("class", function(d, i) { return "color" + i })
.attr("d", this.arc)
.each(function(d) { this._currentArc = d; }); // store the initial values
// add text in the center of the donut
this.text = svg.append('text')
.attr("text-anchor", "middle")
.attr("dy", text_y)
.attr("d", percent)
if (typeof(percent) === "string") {
this.text.text(percent);
} else {
var self = this;
var timeout = setTimeout( function () {
clearTimeout(timeout);
path = path.data(pie(dataset.upper)); // update the data
path.transition().ease(d3.easeExp).duration('500').attrTween("d", function(a){
var progress = 0;
var format = d3.format(".0%");
// Store the displayed angles in _currentArc.
// Then, interpolate from _currentArc to the new angles.
// During the transition, _currentArc is updated in-place by d3.interpolate.
var i = d3.interpolate(this._currentArc, a);
var i2 = d3.interpolate(progress, percent);
this._currentArc = i(0);
return function(t) {
$(self.text).each(function(){
$(this).text( format(i2(t) / 100) );
});
return self.arc(i(t));
};
}); // redraw the arcs
}, 200);
}
},
init : function(){
$('.donut').each( function() {
var percent = $(this).data('donut');
app.numbers.drawDonutChart(
this,
percent,
190,
190,
".35em"
);
})
}
}
}
// initialize pie on scroll
window.onscroll = function() {
app.numbers.init();
}
Html:
<section class="row numbers section-bkg section-bkg--blue">
<div class="container">
<div class="col-xs-12 col-sm-4">
<div class="donut" data-donut="42"></div>
</div>
<div class="col-xs-12 col-sm-4">
<div class="donut" data-donut="12"></div>
</div>
<div class="col-xs-12 col-sm-4">
<div class="donut" data-donut="86"></div>
</div>
</div>
</section>
Any idea how should i display the text?
i'm pretty sure the problem is here:
$(self.text).each(function(){
$(this).text( format(i2(t) / 100) );
});
Despite mixing jQuery and D3 never being a good idea (try to avoid it), it seems that that was not the problem. The problem seems to be the fact that you're changing the text inside the factory, using a previously assigned self.
One solution (without changing your code too much, certainly there are better ways to refactor it) is getting the texts based on the current <path>:
var self2 = this;//this is the transitioning path
return function(t) {
d3.select(self2.parentNode).select("text").text(format(i2(t) / 100));
return self.arc(i(t));
};
Here is your code (without jQuery):
var app = {};
var color = d3.scaleOrdinal(d3.schemeCategory10)
app.numbers = {
calcPerc: function(percent) {
return [percent, 100 - percent];
},
drawDonutChart: function(el, percent, width, height, text_y) {
width = typeof width !== undefined ? width : 290; //width
height = typeof height !== undefined ? height : 290; //height
text_y = typeof text_y !== undefined ? text_y : "-.10em";
var radius = Math.min(width, height) / 2;
var pie = d3.pie().sort(null);
var dataset = {
lower: this.calcPerc(0),
upper: this.calcPerc(percent)
}
//this.percents = percent;
this.arc = d3.arc()
.innerRadius(radius - 20)
.outerRadius(radius);
var svg = d3.select(el).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset.lower))
.enter().append("path")
.attr("fill", function(d, i) {
return color(i)
})
.attr("d", this.arc)
.each(function(d) {
this._currentArc = d;
}); // store the initial values
// add text in the center of the donut
this.text = svg.append('text')
.attr("text-anchor", "middle")
.attr("dy", text_y)
.attr("d", percent)
this.text.text(percent);
var self = this;
path = path.data(pie(dataset.upper)); // update the data
path.transition().ease(d3.easeExp).duration(1000).attrTween("d", function(a) {
var progress = 0;
var format = d3.format(".0%");
// Store the displayed angles in _currentArc.
// Then, interpolate from _currentArc to the new angles.
// During the transition, _currentArc is updated in-place by d3.interpolate.
var i = d3.interpolate(this._currentArc, a);
var i2 = d3.interpolate(progress, percent);
this._currentArc = i(0);
var self2 = this;
return function(t) {
d3.select(self2.parentNode).select("text").text(format(i2(t) / 100));
return self.arc(i(t));
};
}); // redraw the arcs
},
init: function() {
d3.selectAll('.donut').each(function() {
var percent = d3.select(this).node().dataset.donut;
app.numbers.drawDonutChart(
this,
percent,
190,
190,
".35em"
);
})
}
}
app.numbers.init();
<script src="https://d3js.org/d3.v4.min.js"></script>
<section class="row numbers section-bkg section-bkg--blue">
<div class="container">
<div class="col-xs-12 col-sm-4">
<div class="donut" data-donut="42"></div>
</div>
<div class="col-xs-12 col-sm-4">
<div class="donut" data-donut="12"></div>
</div>
<div class="col-xs-12 col-sm-4">
<div class="donut" data-donut="86"></div>
</div>
</div>
</section>

How to show full text when zoom in & truncate it when zoom out

I am creating a tree chart with d3.js, it works fine... but I want text to react to zooming, Here is the JSFiddle.
Please look at first node... it has lots of characters (in my case max will be 255)
When zoomed in or out, my text remains same, but I want to see all on zoom in.
var json = {
"name": "Maude Charlotte Licia Fernandez Maude Charlotte Licia Fernandez Maude Charlotte Licia Fernandez Maude Charlotte Licia FernandezMaude Charlotte Licia Fernandez Maude Charlotte Licia Fernandez Maude Charlotte Licia Fernandez Maude asdlkhkjh asd asdsd",
"id": "06ada7cd-3078-54bc-bb87-72e9d6f38abf",
"_parents": [{
"name": "Janie Clayton Norton",
"id": "a39bfa73-6617-5e8e-9470-d26b68787e52",
"_parents": [{
"name": "Pearl Cannon",
"id": "fc956046-a5c3-502f-b853-d669804d428f",
"_parents": [{
"name": "Augusta Miller",
"id": "fa5b0c07-9000-5475-a90e-b76af7693a57"
}, {
"name": "Clayton Welch",
"id": "3194517d-1151-502e-a3b6-d1ae8234c647"
}]
}, {
"name": "Nell Morton",
"id": "06c7b0cb-cd21-53be-81bd-9b088af96904",
"_parents": [{
"name": "Lelia Alexa Hernandez",
"id": "667d2bb6-c26e-5881-9bdc-7ac9805f96c2"
}, {
"name": "Randy Welch",
"id": "104039bb-d353-54a9-a4f2-09fda08b58bb"
}]
}]
}, {
"name": "Helen Donald Alvarado",
"id": "522266d2-f01a-5ec0-9977-622e4cb054c0",
"_parents": [{
"name": "Gussie Glover",
"id": "da430aa2-f438-51ed-ae47-2d9f76f8d831",
"_parents": [{
"name": "Mina Freeman",
"id": "d384197e-2e1e-5fb2-987b-d90a5cdc3c15"
}, {
"name": "Charlotte Ahelandro Martin",
"id": "ea01728f-e542-53a6-acd0-6f43805c31a3"
}]
}, {
"name": "Jesus Christ Pierce",
"id": "bfd1612c-b90d-5975-824c-49ecf62b3d5f",
"_parents": [{
"name": "Donald Freeman Cox",
"id": "4f910be4-b827-50be-b783-6ba3249f6ebc"
}, {
"name": "Alex Fernandez Gonzales",
"id": "efb2396d-478a-5cbc-b168-52e028452f3b"
}]
}]
}]
};
var boxWidth = 250,
boxHeight = 100;
// Setup zoom and pan
var zoom = d3.behavior.zoom()
.scaleExtent([.1, 1])
.on('zoom', function() {
svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
})
// Offset so that first pan and zoom does not jump back to the origin
.translate([600, 600]);
var svg = d3.select("body").append("svg")
.attr('width', 1000)
.attr('height', 500)
.call(zoom)
.append('g')
// Left padding of tree so that the whole root node is on the screen.
// TODO: find a better way
.attr("transform", "translate(150,200)");
var tree = d3.layout.tree()
// Using nodeSize we are able to control
// the separation between nodes. If we used
// the size parameter instead then d3 would
// calculate the separation dynamically to fill
// the available space.
.nodeSize([100, 200])
// By default, cousins are drawn further apart than siblings.
// By returning the same value in all cases, we draw cousins
// the same distance apart as siblings.
.separation(function() {
return .9;
})
// Tell d3 what the child nodes are. Remember, we're drawing
// a tree so the ancestors are child nodes.
.children(function(person) {
return person._parents;
});
var nodes = tree.nodes(json),
links = tree.links(nodes);
// Style links (edges)
svg.selectAll("path.link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", elbow);
// Style nodes
var node = svg.selectAll("g.person")
.data(nodes)
.enter().append("g")
.attr("class", "person")
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Draw the rectangle person boxes
node.append("rect")
.attr({
x: -(boxWidth / 2),
y: -(boxHeight / 2),
width: boxWidth,
height: boxHeight
});
// Draw the person's name and position it inside the box
node.append("text")
.attr("text-anchor", "start")
.attr('class', 'name')
.text(function(d) {
return d.name;
});
// Text wrap on all nodes using d3plus. By default there is not any left or
// right padding. To add padding we would need to draw another rectangle,
// inside of the rectangle with the border, that represents the area we would
// like the text to be contained in.
d3.selectAll("text").each(function(d, i) {
d3plus.textwrap()
.container(d3.select(this))
.valign("middle")
.draw();
});
/**
* Custom path function that creates straight connecting lines.
*/
function elbow(d) {
return "M" + d.source.y + "," + d.source.x + "H" + (d.source.y + (d.target.y - d.source.y) / 2) + "V" + d.target.x + "H" + d.target.y;
}
body {
text-align: center;
}
svg {
margin-top: 32px;
border: 1px solid #aaa;
}
.person rect {
fill: #fff;
stroke: steelblue;
stroke-width: 1px;
}
.person {
font: 14px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3plus/1.8.0/d3plus.min.js"></script>
I made a sample of your requirement in this fiddle
It may need some more tweaking to position the text vertical middle; but this can be the base for you to work on. Calculations are done in the function wrap() and call on page load and zooming.
function wrap() {
var texts = d3.selectAll("text"),
lineHeight = 1.1, // ems
padding = 2, // px
fSize = scale > 1 ? fontSize / scale : fontSize,
// find how many lines can be included
lines = Math.floor((boxHeight - (2 * padding)) / (lineHeight * fSize)) || 1;
texts.each(function(d, i) {
var text = d3.select(this),
words = d.name.split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
tspan = text.text(null).append("tspan").attr("dy", "-0.5em").style("font-size", fSize + "px");
while ((word = words.pop())) {
line.push(word);
tspan.text(line.join(" "));
// check if the added word can fit in the box
if ((tspan.node().getComputedTextLength() + (2 * padding)) > boxWidth) {
// remove current word from line
line.pop();
tspan.text(line.join(" "));
lineNumber++;
// check if a new line can be placed
if (lineNumber > lines) {
// left align text of last line
tspan.attr("x", (tspan.node().getComputedTextLength() - boxWidth) / 2 + padding);
--lineNumber;
break;
}
// create new line
tspan.text(line.join(" "));
line = [word]; // place the current word in new line
tspan = text.append("tspan")
.style("font-size", fSize + "px")
.attr("dy", "1em")
.text(word);
}
// left align text
tspan.attr("x", (tspan.node().getComputedTextLength() - boxWidth) / 2 + padding);
}
// align vertically inside the box
text.attr("text-anchor", "middle").attr("y", padding - (lineHeight * fSize * lineNumber) / 2);
});
}
Also note that I've added the style dominant-baseline: hanging; to .person class
The code in this jsfiddle is an attempt to address the performance issues that you have with very large tree charts. A delay is set with setTimeout in the zoom event handler to allow zooming at "full speed", without text resizing. Once the zooming stops for a short time, the text is rearranged according to the new scaling:
var scaleValue = 1;
var refreshTimeout;
var refreshDelay = 0;
var zoom = d3.behavior.zoom()
.scaleExtent([.1, 1.5])
.on('zoom', function () {
svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
scaleValue = d3.event.scale;
if (refreshTimeout) {
clearTimeout(refreshTimeout);
}
refreshTimeout = setTimeout(function () {
wrapText();
}, refreshDelay);
})
The delay (in milliseconds) depends on the number of nodes in the tree. You can experiment with the mathematical expression to find the best parameters for the wide range of node counts that you expect in your tree.
// Calculate the refresh delay
refreshDelay = Math.pow(node.size(), 0.5) * 2.0;
You can also set the parameters in calcFontSize to fit your needs:
// Calculate the font size for the current scaling
var calcFontSize = function () {
return Math.min(24, 10 * Math.pow(scaleValue, -0.25))
}
The initialization of the nodes has been slightly modified:
node.append("rect")
.attr({
x: 0,
y: -(boxHeight / 2),
width: boxWidth,
height: boxHeight
});
node.append("text")
.attr("text-anchor", "start")
.attr("dominant-baseline", "middle")
.attr('class', 'name')
.text(function (d) {
return d.name;
});
And the text is processed in wrapText:
// Adjust the font size to the zoom level and wrap the text in the container
var wrapText = function () {
d3.selectAll("text").each(function (d, i) {
var $text = d3.select(this);
if (!$text.attr("data-original-text")) {
// Save original text in custom attribute
$text.attr("data-original-text", $text.text());
}
var content = $text.attr("data-original-text");
var tokens = content.split(/(\s)/g);
var strCurrent = "";
var strToken = "";
var box;
var lineHeight;
var padding = 4;
$text.text("").attr("font-size", calcFontSize());
var $tspan = $text.append("tspan").attr("x", padding).attr("dy", 0);
while (tokens.length > 0) {
strToken = tokens.shift();
$tspan.text((strCurrent + strToken).trim());
box = $text.node().getBBox();
if (!lineHeight) {
lineHeight = box.height;
}
if (box.width > boxWidth - 2 * padding) {
$tspan.text(strCurrent.trim());
if (box.height + lineHeight < boxHeight) {
strCurrent = strToken;
$tspan = $text.append("tspan").attr("x", padding).attr("dy", lineHeight).text(strCurrent.trim());
} else {
break;
}
}
else {
strCurrent += strToken;
}
}
$text.attr("y", -(box.height - lineHeight) / 2);
});
}
Text wrapping can be process intensive if we have a lot of text. To address those issues, present in my first answer, this new version has improved performance, thanks to pre-rendering.
This script creates an element outside of the DOM, and stores all nodes and edges into it. Then it checks which elements would be visible, removing them from the DOM, and adding them back when appropriate.
I'm making use of jQuery for data(), and for selecting elements. In my example on the fiddle, there are 120 nodes. But it should work similarly for much more, as the only nodes rendered are the ones on screen.
I changed the zoom behaviour, so that the zoom is centered on the mouse cursor, and was surprised to see that the pan / zoom works on iOS as well.
See it in action.
UPDATE
I applied the timeout (ConnorsFan's solution), as it makes a big difference. In addition, I added a minimum scale for which text should be re-wrapped.
$(function() {
var viewport_width = $(window).width(),
viewport_height = $(window).height(),
node_width = 120,
node_height = 60,
separation_width = 100,
separation_height = 55,
node_separation = 0.78,
font_size = 20,
refresh_delay = 200,
refresh_timeout,
zoom_extent = [0.5, 1.15],
// Element outside DOM, to calculate pre-render
buffer = $("<div>");
// Parse "transform" attribute
function parse_transform(input_string) {
var transformations = {},
matches, seek;
for (matches in input_string = input_string.match(/(\w+)\(([^,)]+),?([^)]+)?\)/gi)) {
seek = input_string[matches].match(/[\w.\-]+/g), transformations[seek.shift()] = seek;
}
return transformations;
}
// Adapted from ConnorsFan's answer
function get_font_size(scale) {
fs = ~~Math.min(font_size, 15 * Math.pow(scale, -0.25));
fs = ~~(((font_size / scale) + fs) / 2)
return [fs, fs]
}
// Use d3plus to wrap the text
function wrap_text(scale) {
if (scale > 0.75) {
$("svg > g > g").each(function(a, b) {
f = $(b);
$("text", f)
.text(f.data("text"));
});
d3.selectAll("text").each(function(a, b) {
d3_el = d3.select(this);
d3plus.textwrap()
.container(d3_el)
.align("center")
.valign("middle")
.width(node_width)
.height(node_height)
.valign("middle")
.resize(!0)
.size(get_font_size(scale))
.draw();
});
}
}
// Handle pre-render (remove elements that leave viewport, add them back when appropriate)
function pre_render() {
buffer.children("*")
.each(function(i, el) {
d3.transform(d3.select(el).attr("transform"));
var el_path = $(el)[0],
svg_wrapper = $("svg"),
t = parse_transform($("svg > g")[0].getAttribute("transform")),
element_data = $(el_path).data("coords"),
element_min_x = ~~element_data.min_x,
element_max_x = ~~element_data.max_x,
element_min_y = ~~element_data.min_y,
element_max_y = ~~element_data.max_y,
svg_wrapper_width = svg_wrapper.width(),
svg_wrapper_height = svg_wrapper.height(),
s = parseFloat(t.scale),
x = ~~t.translate[0],
y = ~~t.translate[1];
if (element_min_x * s + x <= svg_wrapper_width &&
element_min_y * s + y <= svg_wrapper_height &&
0 <= element_max_x * s + x &&
0 <= element_max_y * s + y) {
if (0 == $("#" + $(el).prop("id")).length) {
if (("n" == $(el).prop("id").charAt(0))) {
// insert nodes above edges
$(el).clone(1).appendTo($("svg > g"));
wrap_text(scale = t.scale);
} else {
// insert edges
$(el).clone(1).prependTo($("svg > g"));
}
}
} else {
id = $(el).prop("id");
$("#" + id).remove();
}
});
}
d3.scale.category20();
var link = d3.select("body")
.append("svg")
.attr("width", viewport_width)
.attr("height", viewport_height)
.attr("pointer-events", "all")
.append("svg:g")
.call(d3.behavior.zoom().scaleExtent(zoom_extent)),
layout_tree = d3.layout.tree()
.nodeSize([separation_height * 2, separation_width * 2])
.separation(function() {
return node_separation;
})
.children(function(a) {
return a._parents;
}),
nodes = layout_tree.nodes(json),
edges = layout_tree.links(nodes);
// Style links (edges)
link.selectAll("path.link")
.data(edges)
.enter()
.append("path")
.attr("class", "link")
.attr("d", function(a) {
return "M" + a.source.y + "," + a.source.x + "H" + ~~(a.source.y + (a.target.y - a.source.y) / 2) + "V" + a.target.x + "H" + a.target.y;
});
// Style nodes
var node = link.selectAll("g.person")
.data(nodes)
.enter()
.append("g")
.attr("transform", function(a) {
return "translate(" + a.y + "," + a.x + ")";
})
.attr("class", "person");
// Draw the rectangle person boxes
node.append("rect")
.attr({
x: -(node_width / 2),
y: -(node_height / 2),
width: node_width,
height: node_height
});
// Draw the person's name and position it inside the box
node_text = node.append("text")
.attr("text-anchor", "start")
.text(function(a) {
return a.name;
});
// Text wrap on all nodes using d3plus. By default there is not any left or
// right padding. To add padding we would need to draw another rectangle,
// inside of the rectangle with the border, that represents the area we would
// like the text to be contained in.
d3.selectAll("text")
.each(function(a, b) {
d3plus.textwrap()
.container(d3.select(this))
.valign("middle")
.resize(!0)
.size(get_font_size(1))
.draw();
});
// START Create off-screen render
// Append node edges to memory, to allow pre-rendering
$("svg > g > path")
.each(function(a, b) {
el = $(b)[0];
if (d = $(el)
.attr("d")) {
// Parse d parameter from rect, in the format found in the d3 tree dom: M0,0H0V0V0
for (var g = d.match(/([MLQTCSAZVH])([^MLQTCSAZVH]*)/gi), c = g.length, h, k, f, l, m = [], e = [], n = 0; n < c; n++) {
command = g[n], void 0 !== command && ("M" == command.charAt(0) ? (coords = command.substring(1, command.length), m.push(~~coords.split(",")[0]), e.push(~~coords.split(",")[1])) : "V" == command.charAt(0) ? e.push(~~command.substring(1, command.length)) : "H" == command.charAt(0) && m.push(~~command.substring(1, command.length)));
}
0 < m.length && (h = Math.min.apply(this, m), f = Math.max.apply(this, m));
0 < e.length && (k = Math.min.apply(this, e), l = Math.max.apply(this, e));
$(el).data("position", a);
$(el).prop("id", "e" + a);
$(el).data("coords", {
min_x: h,
min_y: k,
max_x: f,
max_y: l
});
// Store element coords in memory
hidden_element = $(el).clone(1);
buffer.append(hidden_element);
}
});
// Append node elements to memory
$("svg > g > g").each(function(a, b) {
el = $("rect", b);
transform = b.getAttribute("transform");
null !== transform && void 0 !== transform ? (t = parse_transform(transform), tx = ~~t.translate[0], ty = ~~t.translate[1]) : ty = tx = 0;
// Calculate element area
el_min_x = ~~el.attr("x");
el_min_y = ~~el.attr("y");
el_max_x = ~~el.attr("x") + ~~el.attr("width");
el_max_y = ~~el.attr("y") + ~~el.attr("height");
$(b).data("position", a);
$(b).prop("id", "n" + a);
$(b).data("coords", {
min_x: el_min_x + tx,
min_y: el_min_y + ty,
max_x: el_max_x + tx,
max_y: el_max_y + ty
});
text_el = $("text", $(b));
0 < text_el.length && $(b).data("text", d3.select(text_el[0])[0][0].__data__.name);
// Store element coords in memory
hidden_element = $(b).clone(1);
// store node in memory
buffer.append(hidden_element);
});
// END Create off-screen render
d3_svg = d3.select("svg");
svg_group = d3.select("svg > g");
// Setup zoom and pan
zoom = d3.behavior.zoom()
.on("zoom", function() {
previous_transform = $("svg > g")[0].getAttribute("transform");
svg_group.style("stroke-width", 1.5 / d3.event.scale + "px");
svg_group.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
pre_render();
if (previous_transform !== null) {
previous_transform = parse_transform(previous_transform);
if (previous_transform.scale != d3.event.scale) {
// ConnorsFan's solution
if (refresh_timeout) {
clearTimeout(refresh_timeout);
}
scale = d3.event.scale;
refresh_timeout = setTimeout(function() {
wrap_text(scale = scale);
}, refresh_delay, scale);
}
}
});
// Apply initial zoom / pan
d3_svg.call(zoom);
});

d3.js reverse transition does not work

I am working on horizontal segment bar chart. I want to make it so that the bar chart will animate the colour transition between individual segments depending on the value that is generated randomly every few seconds.
I also have a text box that at the moment says "hello" and it is moving simultaneously with the transition.It works only in one direction from left to right.
I cannot make the colour transition between segments and translating the text box from left to right. From left to right I mean that the number generated last time is greater than the currently generated number. The bar chart should turn off the segments from right to left. But it does it from left to right also the text box is acting weirdly when it comes to reverse translation.
I am also getting this error: g attribute transform: Expected transform function, "null". In my code I want to make a transition on my valueLabel and I think the way I am using is not correct. Despite this error the code gets executed.
My fiddle code is here
Many thanks for suggestions
var configObject = {
svgWidth : 1000,
svgHeight : 1000,
minValue : 1,
maxValue : 100,
midRange : 50,
highRange : 75,
numberOfSegments : 50
};
//define variables
var newValue;
var gaugeValue = configObject.minValue - 1;
var mySegmentMappingScale;
var rectArray=[];
//define svg
var svg = d3.select("body").append("svg")
.attr("width", configObject.svgWidth)
.attr("height", configObject.svgHeight)
.append("g")
.attr("transform", 'translate(' + configObject.svgWidth/2 + ',' + configObject.svgHeight/2 + ')');
//var myG=svg.append('g');
var valueLabel= svg.append("text")
.attr('x',0)
.attr('y', (configObject.svgHeight/13)+15)
.text("hello");
var backgroundRect=svg.append("rect")
.attr("fill", "black")
.attr("x",0)
.attr("y", 0)
.attr("width", (configObject.svgWidth/3))
.attr("height", configObject.svgHeight/13);
for(i = 0; i <= configObject.numberOfSegments; i++){
var myRect=svg.append("rect")
.attr("fill", "#2D2D2D")
.attr("x",i * ((configObject.svgWidth/3)/configObject.numberOfSegments))
.attr("y", 0)
.attr("id","rect"+i)
.attr("width", ((configObject.svgWidth/3)/configObject.numberOfSegments)-3)
.attr("height", configObject.svgHeight/13);
rectArray.push(myRect);
}
//define scale
function setmySegmentMappingScale(){
var domainArray = [];
var x=0;
for(i = configObject.minValue; i <= configObject.maxValue+1; i = i + (configObject.maxValue - configObject.minValue)/configObject.numberOfSegments){
if(Math.floor(i) != domainArray[x-1]){
var temp=Math.floor(i);
domainArray.push(Math.floor(i));
x++;
}
}
var rangeArray = [];
for(i = 0; i <= configObject.numberOfSegments+1; i++){// <=
rangeArray.push(i);
}
mySegmentMappingScale = d3.scale.threshold().domain(domainArray).range(rangeArray);
}
//generate random number
function generate(){
var randomNumber = Math.random() * (configObject.maxValue - configObject.minValue) + configObject.minValue;
newValue = Math.floor(randomNumber);
setmySegmentMappingScale();
animateSVG();
}
function animateSVG(){
var previousSegment = mySegmentMappingScale(gaugeValue) -1;
var newSegment = mySegmentMappingScale(newValue) -1;
if(previousSegment <= -1 && newSegment > -1){
for(i = 0; i <= newSegment; i++){
rectArray[i].transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.styleTween("fill", function() { return d3.interpolate( "#2D2D2D","red"); });
valueLabel
.transition().ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.attr("transform","translate(" + (i * ((configObject.svgWidth/3)/configObject.numberOfSegments)+((configObject.svgWidth/3)/configObject.numberOfSegments)) + "," + 0 + ")")
}
}
else if(newSegment > previousSegment){
for(i = previousSegment; i <= newSegment; i++){
rectArray[i].transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.styleTween("fill", function() { return d3.interpolate( "#2D2D2D","red"); });
//console.log(temp);
valueLabel
.transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.attr("transform","translate(" + (i * ((configObject.svgWidth/3)/configObject.numberOfSegments)+((configObject.svgWidth/3)/configObject.numberOfSegments)) + "," + 0 + ")")
}
}
else if(newSegment < previousSegment){
for(i = previousSegment; i > newSegment; i--){
rectArray[i].transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.styleTween("fill", function() { return d3.interpolate( "red","#2D2D2D"); });
valueLabel
.transition()
.ease("linear")
.duration(50)
.delay(function(d){return i * 90})
.attr("transform","translate(" + (i * ((configObject.svgWidth/3)/configObject.numberOfSegments)-((configObject.svgWidth/3)/configObject.numberOfSegments)) + "," + 0 + ")")
}
}
gaugeValue = newValue;
}
setInterval(function() {
generate()
}, 8000);
The problem is just the delay.
When newSegment > previousSegment, you set the delay like this:
.delay(function(d){return i * 90})
Which makes sense, because i is an increasing variable. But, when newSegment < previousSegment the same math doesn't work anymore: i is a decreasing variable, and the delay has to increase as i decreases, not the other way around.
This is what you need:
.delay(function(d){return Math.abs(i -previousSegment)*90})
Here is your updated fiddle: https://jsfiddle.net/b7usw2nr/

d3js Moving SVG elements inside a transforming group

I am working on a query builder project. I am trying to build a query generator using d3.js. I am stucked in a part where I want to move certain elements inside a transforming group. This is the repo and I am stucked in this function. I want to move the operators after connecting it and update the connected lines. Can anyone help me?
var circleDrag = d3.behavior.drag()
.on('dragstart', function () {
d3.event.sourceEvent.stopPropagation();
})
.on('drag', function () {
var parentQboxGroupId = d3.select(this).select(function () {
return this.parentNode;
});
var grandParent = parentQboxGroupId.select(function(){
return this.parentNode;
});
var drawingGroup = d3.select('#'+grandParent.attr('id'));
var currentC = d3.select(this);
dragging = true;
drawingGroup
.select('.lineInsideQbox')
.attr('x1', currentC.attr('cx'))
.attr('y1', currentC.attr('cy'))
.style('stroke','green')
.style('stroke-width','2px');
dummyLine.src = currentC.attr('id');
console.log('CIRCLE IS BEING DRAGGED' + JSON.stringify(dummyLine));
})
.on('dragend', function () {
console.log('drag circle end');
//if(!selectedCircle.id){
// dummyLine.target = selectedQbox.id;
//}
dummyLine.target = selectedCircle.id;
dragging = false;
console.log('DRAG END : SELCTED NODE : '+ JSON.stringify(selectedCircle));
console.log('DRAG END : DUMMY LINE : '+ JSON.stringify(dummyLine));
var targetNode = d3.select('#'+dummyLine.target);
var srcNode = d3.select('#'+dummyLine.src);
console.log('split : ' + dummyLine.src.split('--'));
var group = '#' + (dummyLine.src).split('--')[1];
console.log('G: ' + group);
d3.select(group).append('line')
.attr('id', function () {
var a = (dummyLine.src).split('--');
var b = (dummyLine.target).split('--');
if( a[0]== 'nodeRight'){
return dummyLine.src + '__' + dummyLine.target;
}else{
return dummyLine.target + '__' + dummyLine.src;
}
})
.attr('class', function () {
var a = (dummyLine.src).split('--');
var b = (dummyLine.target).split('--');
return 'line '+ a[1]+' '+b[1];
})
.attr('x1', srcNode.attr('cx'))
.attr('y1',srcNode.attr('cy'))
.attr('x2',targetNode.attr('cx'))
.attr('y2',targetNode.attr('cy'))
.style('stroke', 'black')
.style('stroke-width', '3px')
;
dummyLine.src = null;
dummyLine.target = null;
});
EDIT : When I try to drop a query Box. I can drop other operators inside it. Then I should be able to connect them inside. Here is the image showing what I am trying.
After the connections made, I try yo move large box & small operators individually. That's where the code break.
The main issue is that to move the operator, you use a translate to move the whole group ( tag) which includes the image, the two circles and the line. You then set the other end of the line using CX, CY values of the other operator it is connected to.
This wont work because the CX and CY values of the circles are not updated when you perform a translate, so on a second move, it would put the x, y values at the original point of the circles, not to the moved point.
To resolve, instead of translating the whole group, translate only the image, update the cx and cy values of the circles and then update the line x, y values with the new cx, cy of the circles:
All of the amendments needed are within your operatorDrag.js file.
First of all, when you append the circles, add an attribute which holds the original cx and cy values. We will need these when calculating the new cx, cy when dragging the operator:
change from this:
var op = currGroup
.append('image')
.attr('class', 'operator')
.attr('width', elem.attr('width') * 0.75)
.attr('height', elem.attr('height') * 0.75)
.attr('x', d3.mouse(this)[0])
.attr('y', d3.mouse(this)[1])
.attr('xlink:href', elem.attr('href'));
currGroup
.append('circle')
.attr('class', 'node nodeLeft')
.attr('id', function () {
return 'nodeLeft--' + currGroup.attr('id');
})
.attr('cx', op.attr('x'))
.attr('cy', op.attr('height') / 2 + Number(op.attr('y')))
.attr('r', 5)
.style('fill', 'red')
.on('mouseover', function () {
selectedCircle = {
id: d3.select(this).attr('id'),
cls: 'nodeLeft'
}
})
.call(circleDrag)
;
currGroup
.append('circle')
.attr('class', 'node nodeRight')
.attr('id', function () {
return 'nodeRight--' + currGroup.attr('id');
})
.attr('cx', Number(op.attr('x')) + Number(op.attr('width')))
.attr('cy', op.attr('height') / 2 + Number(op.attr('y')))
.attr('r', 5)
.style('fill', 'red')
.on('mouseover', function () {
selectedCircle = {
id: d3.select(this).attr('id'),
cls: 'nodeRight'
}
})
.call(circleDrag)
;
To this (the updated code is contained in comments starting with #SB):
var op = currGroup
.append('image')
.attr('class', 'operator')
.attr('width', elem.attr('width') * 0.75)
.attr('height', elem.attr('height') * 0.75)
.attr('x', d3.mouse(this)[0])
.attr('y', d3.mouse(this)[1])
.attr('xlink:href', elem.attr('href'));
currGroup
.append('circle')
.attr('class', 'node nodeLeft')
.attr('id', function () {
return 'nodeLeft--' + currGroup.attr('id');
})
.attr('cx', op.attr('x'))
.attr('cy', op.attr('height') / 2 + Number(op.attr('y')))
// #SB: add a reference to the original cx and cy position.
// we will need it to set new cx cy when moving operator
.attr('data-cx', op.attr('x'))
.attr('data-cy', op.attr('height') / 2 + Number(op.attr('y')))
//----------------------------------------------------------------------
.attr('r', 5)
.style('fill', 'red')
.on('mouseover', function () {
selectedCircle = {
id: d3.select(this).attr('id'),
cls: 'nodeLeft'
}
})
.call(circleDrag)
;
currGroup
.append('circle')
.attr('class', 'node nodeRight')
.attr('id', function () {
return 'nodeRight--' + currGroup.attr('id');
})
.attr('cx', Number(op.attr('x')) + Number(op.attr('width')))
.attr('cy', op.attr('height') / 2 + Number(op.attr('y')))
// #SB: add a reference to the original cx and cy position.
// we will need it to set new cx cy when moving operator
.attr('data-cx', Number(op.attr('x')) + Number(op.attr('width')))
.attr('data-cy', op.attr('height') / 2 + Number(op.attr('y')))
//----------------------------------------------------------------------
.attr('r', 5)
.style('fill', 'red')
.on('mouseover', function () {
selectedCircle = {
id: d3.select(this).attr('id'),
cls: 'nodeRight'
}
})
.call(circleDrag)
;
Once you have done this, go to your on drag method for the operators. This is the code we are going to change:
.on('drag', function () {
var g = d3.select(this);
var currentOp = g.select('.operator');
var parent = g.select(function () {
return this.parentNode;
}).select('.qbox');
var dx = d3.event.x;
var dy = d3.event.y;
var mouse = {dx: d3.event.x, dy: d3.event.y};
var currentObj = {
x: currentOp.attr('x'),
y: currentOp.attr('y'),
width: currentOp.attr('width'),
height: currentOp.attr('height')
};
var parentObj = {
x: parent.attr('x'),
y: parent.attr('y'),
width: parent.attr('width'),
height: parent.attr('height')
};
//console.log('parent width : ' + parent.attr('width'));
//console.log('parent width : ' + currentOp.attr('width'));
//g.attr('transform', 'translate(' + x + ',' + y + ')');
var loc = getXY(mouse, currentObj, parentObj);
g.attr('transform', 'translate(' + loc.x + ',' + loc.y + ')');
d3.select('#' + g.attr('id')).selectAll('.line')[0].forEach(function (e1) {
var line = d3.select(e1);
console.log('-------------------');
console.log('line : ' + line.attr('id'));
console.log('-------------------');
var split = line.attr('id').split('__');
if(g.attr('id') == split[0]){
//change x2, y2
var otherNode = d3.select('#'+split[1]);
line.attr('x2', otherNode.attr('cx'));
line.attr('y2', otherNode.attr('cy'));
}else{
var otherNode = d3.select('#'+split[0]);
line.attr('x1', otherNode.attr('cx'));
line.attr('y1', otherNode.attr('cy'));
}
})
}))
First thing is, do not translate the whole object, only the image:
var g = d3.select(this);
var currentOp = g.select('.operator');
var parent = g.select(function () {
return this.parentNode;
}).select('.qbox');
//#SB: added a reference to the parent id
var parent_id = g.select(function () {
return this.parentNode;
}).attr('id');
//---------------------------------------
var dx = d3.event.x;
var dy = d3.event.y;
var mouse = {dx: d3.event.x, dy: d3.event.y};
var currentObj = {
x: currentOp.attr('x'),
y: currentOp.attr('y'),
width: currentOp.attr('width'),
height: currentOp.attr('height')
};
var parentObj = {
x: parent.attr('x'),
y: parent.attr('y'),
width: parent.attr('width'),
height: parent.attr('height')
};
var loc = getXY(mouse, currentObj, parentObj);
//#SB: Do not translate everything, the cx, cy values of the circle are not updated
// when translating which will make future moves calculate incorrectly
g.selectAll('image').attr('transform', 'translate(' + loc.x + ',' + loc.y + ')');
Then, instead of translating the circles, change their cx, and cy values using the original cx, cy and translate values:
g.selectAll('circle')
.attr('cx', function () {
return parseFloat(d3.select(this).attr('data-cx')) + parseFloat(loc.x);
})
.attr('cy', function () {
return parseFloat(d3.select(this).attr('data-cy')) + parseFloat(loc.y);
});
The last thing is the update to the lines. In your original code, you were selecting all lines within the operator group but you will actually miss some lines by only selecting this group. Some lines can be a part of another operator group but be connected to the operator that is moving.
In this case we should select all lines within the parent group and check if the line is connected to the operator we are moving.
If it is connected then we update the x and y values:
//#SB: Select all the lines in the parent group instead of only group of the
// operator we are moving. There can be lines that exists on other groups that
// do not exist within the group that is being moved.
d3.select('#' + parent_id).selectAll('.line')[0].forEach(function (el) {
var parent_id = g.attr('id')
var line = d3.select(el)
var nodeType = line.attr('id').split("__"); // id tells us if the line is connected to the left or right node
var operators = line.attr('class').split(" "); // class holds info on what operators the line is connected to
var sourceCircleId = nodeType[0].split("--")[0] + '--' + operators[1];
var targetCircleId = nodeType[1].split("--")[0] + '--' + operators[2];
if (parent_id == operators[1] || parent_id == operators[2]) { // the line is connected to the operator we are moving
line.attr('x1', d3.select('#' + sourceCircleId).attr('cx'))
line.attr('y1', d3.select('#' + sourceCircleId).attr('cy'))
line.attr('x2', d3.select('#' + targetCircleId).attr('cx'))
line.attr('y2', d3.select('#' + targetCircleId).attr('cy'))
}
});
Complete OnDrag code:
.on('drag', function () {
var g = d3.select(this);
var currentOp = g.select('.operator');
var parent = g.select(function () {
return this.parentNode;
}).select('.qbox');
//#SB: added a reference to the parent id
var parent_id = g.select(function () {
return this.parentNode;
}).attr('id');
//---------------------------------------
var dx = d3.event.x;
var dy = d3.event.y;
var mouse = {dx: d3.event.x, dy: d3.event.y};
var currentObj = {
x: currentOp.attr('x'),
y: currentOp.attr('y'),
width: currentOp.attr('width'),
height: currentOp.attr('height')
};
var parentObj = {
x: parent.attr('x'),
y: parent.attr('y'),
width: parent.attr('width'),
height: parent.attr('height')
};
var loc = getXY(mouse, currentObj, parentObj);
//#SB: Do not translate everything, the cx, cy values of the circle are not updated
// when translating which will make future moves calculate incorrectly
g.selectAll('image').attr('transform', 'translate(' + loc.x + ',' + loc.y + ')');
g.selectAll('circle')
.attr('cx', function () {
return parseFloat(d3.select(this).attr('data-cx')) + parseFloat(loc.x);
})
.attr('cy', function () {
return parseFloat(d3.select(this).attr('data-cy')) + parseFloat(loc.y);
});
//#SB: Select all the lines in the parent group instead of only group of the
// operator we are moving. There can be lines that exists on other groups that
// do not exist within the group that is being moved.
d3.select('#' + parent_id).selectAll('.line')[0].forEach(function (el) {
var parent_id = g.attr('id')
var line = d3.select(el)
var nodeType = line.attr('id').split("__"); // id tells us if the line is connected to the left or right node
var operators = line.attr('class').split(" "); // class holds info on what operators the line is connected to
var sourceCircleId = nodeType[0].split("--")[0] + '--' + operators[1];
var targetCircleId = nodeType[1].split("--")[0] + '--' + operators[2];
if (parent_id == operators[1] || parent_id == operators[2]) { // the line is connected to the operator we are moving
line.attr('x1', d3.select('#' + sourceCircleId).attr('cx'))
line.attr('y1', d3.select('#' + sourceCircleId).attr('cy'))
line.attr('x2', d3.select('#' + targetCircleId).attr('cx'))
line.attr('y2', d3.select('#' + targetCircleId).attr('cy'))
}
});
}))

lat/lon positon on a D3.js map

I have this code, that basically creates a D3.js map and creates a 'circle' when someone 'hits' the page based on their IP location, this was originally done in Raphael and i am trying to just use the D3.js library, but I am stuck on how to place the 'circle' directly on the map with the right co-ordinates:-
function ZmgcClient() {
var personPath = "M255.968,166.154c34.206,0,61.936-27.727,61.936-61.934c0-34.208-27.729-61.936-61.936-61.936s-61.936,27.728-61.936,61.936 C194.032,138.428,221.762,166.154,255.968,166.154z M339.435,194.188c-13.082-13.088-28.625-20.924-84.83-20.924 c-56.214,0-71.38,8.505-83.796,20.924c-12.422,12.416-8.23,144.883-8.23,144.883l27.485-65.304l17.28,194.554l49.856-99.57 l46.521,99.57l16.456-194.554l27.487,65.304C347.664,339.07,352.521,207.271,339.435,194.188z";
if (! (this instanceof arguments.callee)) {
return new arguments.callee(arguments);
}
var self = this,
width = $('#map').width(),
mapCanvasHeight = (width * 0.45);
this.init = function() {
self.drawMap();
self.setupBayeuxHandlers();
};
this.setupBayeuxHandlers = function() {
$.getJSON("/config.json", function (config) {
self.client = new Faye.Client("http://" + window.location.hostname + ':' + config.port + '/faye', {
timeout: 120
});
self.client.subscribe('/stat', function (message) {
// console.log("MESSAGE", message);
self.drawMarker(message);
});
});
};
this.drawMap = function () {
var data;
// Most parts of D3 don't know anything about SVG—only DOM.
self.map = d3.geo.equirectangular().scale(width);
self.path = d3.geo.path().projection(self.map);
self.svg = d3.select('#map').append('svg:svg')
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", "0 0 " + width + " " + mapCanvasHeight);
self.countries = self.svg.append('svg:g').attr('id', 'countries');
// Load data from .json file
d3.json("/ui/data/world-countries.json", function(json) {
self.countries.selectAll("path") // select all the current path nodes
.data(json.features) // bind these to the features array in json
.enter().append("path") // if not enough elements create a new path
.attr("d", self.path) // transform the supplied jason geo path to svg
.on("mouseover", function(d) {
d3.select(this).style("fill","#6C0")
.append("svg:title")
.text(d.properties.name);})
.on("mouseout", function(d) {
d3.select(this).style("fill","#000000");})
});
}
this.geoCoordsToMapCoords = function (latitude, longitude) {
latitude = parseFloat(latitude);
longitude = parseFloat(longitude);
var mapWidth = width,
mapHeight = mapCanvasHeight,
x, y, mapOffsetX, mapOffsetY;
x = (mapWidth * (180 + longitude) / 360) % mapWidth;
latitude = latitude * Math.PI / 180;
y = Math.log(Math.tan((latitude / 2) + (Math.PI / 4)));
y = (mapHeight / 2) - (mapWidth * y / (2 * Math.PI));
mapOffsetX = mapWidth * 0.026;
mapOffsetY = mapHeight * 0.141;
return {
x: (x - mapOffsetX) * 0.97,
y: (y + mapOffsetY + 15),
xRaw: x,
yRaw: y
};
}
this.drawMarker = function (message) {
var lon = message.longitude,
lat = message.latitude,
city = message.city;
self.countries.append('svg:circle')
.attr("cy", lon, lat)
.attr("cx", lon, lat)
.attr('r', 5);
}
// Initialise
this.init();
};
var ZmgcClient;
jQuery(function() {
ZmgcClient = new ZmgcClient();
});
I am trying to pass lon/lat data and draw a circle at that point on the map, this code:
this.drawMarker = function (message) {
var latitude = message.latitude,
longitude = message.longitude,
text = message.title,
city = message.city,
x, y;
var mapCoords = this.geoCoordsToMapCoords(latitude, longitude);
x = mapCoords.x;
y = mapCoords.y;
console.log(x,y);
self.countries.append('svg:circle')
.attr("r", 40.5)
.attr("cx", longitude)
.attr("cy", latitude);
console.log(latitude, longitude);
The circle is created, but it is not in the correct position.
What am I missing?
Any advice much appreciated.
it must have been late, it should be
var mapCoords = this.geoCoordsToMapCoords(latitude, longitude);
x = mapCoords.x;
y = mapCoords.y;
self.countries.append('svg:circle')
.attr("r", 5)
.style("fill", "steelblue")
.attr("cx", x)
.attr("cy", y);
and not the latitude, longitude values.

Categories