this.set() not a function when clicking D3 Pie Chart Segments - javascript

I am trying to set a 'selectedLabel' when I click on my pie chart segments for use further in my component but I running into a 'this.set() is not a function' error that I have not seen before.
This answer suggests that the object I am trying to set the property for is not an Ember object and therefore needs to be changed with Ember.set(object, 'property' value) but this returns a null error.
I am using both this.get and this.set methods elsewhere in the same component so I'm not sure if there is a problem with his specific object or if I have a scoping issue.
Code:
let g = svg.selectAll("arc")
.data(mpie)
.enter().append("g")
.attr("class", "arc")
g
.on("mousemove", function(d) {
var mouseVal = mouse(this);
div
.html("Items: " + d.data.count + "</br>" + "Date: " + d.data.label)
.style("left", (event.pageX + 12) + "px")
.style("top", (event.pageY - 10) + "px")
.style("opacity", 1)
.style("display", "block");
})
.on("mouseout", function() {
div.html(" ").style("display", "none");
})
//attempt to set 'selectedLabel' property
.on("click", function(d) {
this.set('selectedLabel', d.data.label)
})
Console logging 'this' in the click function gives me the correct segment of the pie chart.

Wrong context. If you want to preserve the context in a function use an arrow function and this will be the outer object.
```
g.on("click", (d)=> {
this.set('selectedLabel', d.data.label)
})
```

Related

How to display most recent value in line chart

I have trouble accessing the last value (row) of my CSV to display it on hover in my line chart. I need the value to be displayed as text, but also to be used as Y coordinate to line up with the end of the line.
This is what I have. The first part works, but not the second (in between ///):
function mouseover(d) {
d3.select(d.corporation.line).classed("corporation--hover", true);
d.corporation.line.parentNode.appendChild(d.corporation.line);
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
focus.select(".corpname").text(d.corporation.name);
focus.select(".ranking").text(d.value);
/////
focus.append("text")
.datum(function(d) { return {name: d.corporation.name, value: d.corporation.value[d.corporation.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.values) + ")"; })
.text(function(d) { return d.value; });
/////
}
I tried few different variations without success. Would love if someone could take a peak.
PLUNK is here: http://plnkr.co/edit/1Nf992jYjSGyKhLhaij5?p=preview
Thanks!
Here is how you access the data bound to your line element: this.__data__
First, let's create a variable to access your data, specifically the array of values:
var that = this.__data__.corporation.values;
Then, we can get the date and the value of the end of the line:
var thatLength = that.length;
var thatValue = that[thatLength - 1].value;
var thatDate = that[thatLength - 1].date;
And display the text:
someLegend
.attr("x", (x(yearFormat(thatDate))*-1)-90)
.attr("y", y(thatValue))
.text(thatValue);
Here is the plunker: http://plnkr.co/edit/4HquzuJ6FJeZvEVWuUS5?p=preview
PS: I created this variable someLegend because I didn't have time to understand the translate logic of your focus.

d3 remove text from svg

I have sliders that modify the S command of a path. I want the source name to appear on the path which it does; however how do I remove the previously created text element? I have tried to remove it (see code below) but it doesn't work. The dom just fills up with extra text elements and the text on the path gets darker and darker as they start to pile up on each other. I have even tried to check for the text element by id as shown but no go. Hope you can shed any light on how to remove the text element so there is just one as each S command is modified.
I have added a fiddle here (append text at very bottom of code window):
fiddle...
graph.links.forEach(function (d, i) {
//console.log(obj[0].text, graph.links[i].source.name, graph.links[i].linkid);
if (graph.links[i].source.name == obj[0].text) {
var linkid = graph.links[i].linkid;
var the_path = $("#" + linkid).attr("d");
var arr = $("#" + linkid).attr("d").split("S");
//update S command
$("#" + linkid).attr("d", arr[0] + " S" + scommand_x2 + "," + scommand_y2 + " " + scommand_x + "," + scommand_y);
svg.select("#txt_" + linkid).remove();
svg.selectAll("#" + linkid).data(graph.links).enter()
.append("text")
.attr("id", "txt_" + linkid)
.append("textPath")
.attr("xlink:href", function (d) {
return "#" + linkid;
})
.style("font-size", fontSize + "px")
.attr("startOffset", "50%")
.text("")
.text(graph.links[i].source.name);
}
});
Here is a solution:
https://jsfiddle.net/kx3u23oe/
I did a couple of things. First, you don't need to bind this text to data the way you did. Second, I move the variable outside the update function, with all the append:
var someText = svg.append("text").append("textPath");
Then I kept only this inside update function:
someText.attr("xlink:href", "#L0")
.style("font-size", "12px")
.attr("startOffset", "50%")
.text("some text");
You can remove a text element using 'remove' function. Here is a working code snippet for the same.
var text = d3.select("body")
.append("text")
.text("Hello...");
setTimeout(function() {
text.remove();
}, 800);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
By the way, the problem in your code is, you are iterating over each link (graph.links.forEach(function (d, i) {) and creates a text element for all links(.data(graph.links).enter()) in each iteration. This creates n*n number of text labels; where n is the number of links. So I assume your code should be as follows.
svg.select("#txt_" + linkid).remove();
svg.selectAll("#" + linkid)
.append("text")
.attr("id", "txt_" + linkid)
.append("textPath")
.attr("xlink:href", function (d) {
return "#" + linkid;
})
.style("font-size", fontSize + "px")
.attr("startOffset", "50%")
.text(graph.links[i].source.name);

D3.js Dynamically change the color of arrows in Force Directed Graph [duplicate]

I am trying to do the obvious thing of getting the arrowhead colors of my directed graph's links to match the edge colors. Surprisingly I have not found a complete solution for doing this, although this older post seems like an excellent starting point. I would be fine with adapting that solution to work as outlined below, or if there is a superior method for creating arrowheads that achieves this effect I would be most thankful.
First, I have a linear gradient color function to color my edges by property like this:
var gradientColor = d3.scale.linear().domain([0,1]).range(["#08519c","#bdd7e7"]);
Then, like that previous post I have a function for adding markers:
function marker (color) {
var reference;
svg.append("svg:defs").selectAll("marker")
.data([reference])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15) // This sets how far back it sits, kinda
.attr("refY", 0)
.attr("markerWidth", 9)
.attr("markerHeight", 9)
.attr("orient", "auto")
.attr("markerUnits", "userSpaceOnUse")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("fill", color);
return "url(#" + reference + ")"; };
And then the links definition I have is this one based on the Curved Links example.
var link = svg.selectAll(".link")
.data(bilinks)
.enter().append("path")
.attr("class", "link")
.style("fill", "none")
.style("opacity", "0.5")
.style("stroke-width", "2")
.style("stroke", function(d) { return gradientColor(d[3]); } )
.attr("marker-end", marker( "#FFCC33" ) );
This DOES NOT work as written; the browser gives me an "Uncaught TypeError: Cannot read property '5' of undefined" (where 'd[5]' refers to the fifth property in a list of properties that the links have). The problem is clearly passing the data function to the marker function in this case. If I feed in a static color like "#FFCC33" then the arrowheads DO change color (now). Unfortunately the person who posted this "marker function" solution 1.5 years ago didn't include the bit about passing the color to the marker function at all.
I don't know how to feed in the link's color properly. Ideally I would be able to use a reference to the color of the link that the arrowhead is attached to rather than inputting the same color function (because eventually I'm going to be coloring the links via different schemes based on button presses).
I've created a JS Fiddle that includes all the necessary bits to see and solve the problem. Currently I'm passing a static color to the markers, but it should be whatever is the color of the link it is attached to. I've also included features for another question on properly positioning the arrowheads and edge tails.
I don't believe you're able to define a single SVG marker and change it's colour. Instead you need to define the marker many times (1 for each colour that you need to use). There's a nice example that recently popped up onto the D3 website.
The way this works, is by having lots if different markers, each defining the colour of the marker. Here's a screenshot of all the markers that are defined:
Then this particular example, cycles the CSS classes on the paths. The particular colored marker that each path is using is defined within the CSS class that's being applied to a path at any given time.
I've modified your example to add a new marker per path (and changed the colors slightly in the gradient to prove that it's working). Here's what I've got:
var width = 960,
height = 500;
var color = d3.scale.category20();
var gradientColor = d3.scale.linear().domain([0, 15]).range(["#ff0000", "#0000ff"]);
var force = d3.layout.force()
.linkDistance(10)
.linkStrength(2)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var defs = svg.append("svg:defs");
d3.json("http://bost.ocks.org/mike/miserables/miserables.json", function (error, graph) {
if (error) throw error;
function marker(color) {
defs.append("svg:marker")
.attr("id", color.replace("#", ""))
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15) // This sets how far back it sits, kinda
.attr("refY", 0)
.attr("markerWidth", 9)
.attr("markerHeight", 9)
.attr("orient", "auto")
.attr("markerUnits", "userSpaceOnUse")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.style("fill", color);
return "url(" + color + ")";
};
var nodes = graph.nodes.slice(),
links = [],
bilinks = [];
graph.links.forEach(function (link) {
var s = nodes[link.source],
t = nodes[link.target],
i = {}, // intermediate node
linkValue = link.value // for transfering value from the links to the bilinks
;
nodes.push(i);
links.push({
source: s,
target: i
}, {
source: i,
target: t
});
bilinks.push([s, i, t, linkValue]);
});
force.nodes(nodes)
.links(links)
.start();
var link = svg.selectAll(".link")
.data(bilinks).enter().append("path")
.attr("class", "link")
.style("fill", "none")
.style("opacity", "0.5")
.style("stroke-width", "2")
.each(function(d) {
var color = gradientColor(d[3]);
console.log(d[3]);
d3.select(this).style("stroke", color)
.attr("marker-end", marker(color));
});
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", function (d) {
return 2 + d.group;
})
.style("opacity", 0.5)
.style("fill", function (d) {
return color(d.group);
});
node.append("title")
.text(function (d) {
return d.name;
});
force.on("tick", function () {
link.attr("d", function (d) {
return "M" + d[0].x + "," + d[0].y + "S" + d[1].x + "," + d[1].y + " " + d[2].x + "," + d[2].y;
});
node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

All elements take the same values

I'm trying to assign different elements different attribtsty. But everyone is given the same (last) attribute. What's the matter?
for (var i = 1; i < 12; i++) {
d3.select("#id_" + i)
.text(parseFloat(data[i - 1] / 1000000).toFixed(2))
.on("mouseover", function (d) {
d3.select("#tooltip")
.style("left", "200px")
.style("top", d3.event.pageY - 30 + "px")
.select("#info")
.html("<b>" + keys[i - 2] + "</b>");
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function () {
d3.select("#tooltip").classed("hidden", true);
});
}
Link: JSFIDDLE
Try this:
for (var i = 1; i < 12; i++) {
(function (i) {
d3.select("#id_" + i)
.text(parseFloat(data[i - 1] / 1000000).toFixed(2))
.on("mouseover", function (d) {
d3.select("#tooltip")
.style("left", "200px")
.style("top", d3.event.pageY - 30 + "px")
.select("#info")
.html("<b>" + keys[i - 2] + "</b>");
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function () {
d3.select("#tooltip").classed("hidden", true);
});
})(i)
}
The code that you have written is an example of imperative programming, where you tell the computer how to do something.
D3 is an example of declarative programming, where you tell the computer what to do, and let the computer decide how to do it.
D3 was not designed to loop through to assign values like in your question. Instead of loops and conditionals and other "hows", you should focus on what you want to happen.
To do this, you should use what is called "data binding", where you bind your dataset to the svg to draw your text (here is a beginner tutorial/simple explanation http://bost.ocks.org/mike/circles/)
In relation to your problem, you should put your data in one object like this:
var data = [
["key": 1, "value": 0.0, "x": 84, "y": 310],
...
];
You then bind this data to the svg element, where you can then tell D3 to draw your text with those attributes without specifiying the exact implementation.
svg.selectAll("text")
.data(data)
.enter()
.attr("x", function(d, i) {
return d.x;
})
.attr("y", function(d, i) {
return d.y;
})
...
.on("mouseover", function(d, i) {
d3.select("#tooltip")
.style("left", "200px")
.style("top", d3.event.pageY - 30 + "px")
.select("#info")
.html("<b>" + d.key + "</b>");
d3.select("#tooltip").classed("hidden", false);
})
...
The function function(d, i) gives the actual object d as an argument, and i as the object's location in the array.
Your issue with the tooltips is that i is the same value (12) no mater which element is moused over. To see this, you can log i in your mouseover handler. There are a few ways you could recover the index of the element that is moused over. One of the easiest (though perhaps not cleanest) would be to recover it from the element ID.
for (var i = 1; i < 12; i++) {
d3.select("#id_" + i)
.text(parseFloat(data[i - 1] / 1000000).toFixed(2))
.on("mouseover", function (d) {
var index = this.id.slice(3,9); //ADDED THIS LINE
d3.select("#tooltip")
.style("left", "200px")
.style("top", d3.event.pageY - 30 + "px")
.select("#info")
.html("<b>" + keys[index - 2] + "</b>"); //USE THE INDEX
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function () {
d3.select("#tooltip").classed("hidden", true);
});
}
I also suspect that you mean keys[index-1] not keys[index-2]. The latter goes out of bounds of the array.

vertical bar showing values in javascript

I have an issue and I really need your help.
I have a realtime graph with a vertical bar that moves with cursor and i want it to show the value of the graph (d.time and d.value) when the cursor points to. http://jsfiddle.net/QBDGB/54/ i have two series of data (data1s and data2s) that is generated randomly and I put the time in which the data is generated in "time" variable as you can see:
now = new Date(Date.now() - duration);
var data1 = initialise();
var data2 = initialise();
//Make stacked data
var data1s = data1;
var data2s = [];
for(var i = 0; i < data1s.length; i++){
data2s.push({
value: data1s[i].value + data2[i].value,
time: data2[i].time
}
)};
function initialise() {
var arr = [];
for (var i = 0; i < n; i++) {
var obj = {
time: Date.now(),
value: Math.floor(Math.random() * 100)
};
arr.push(obj);
}
return arr;
}
When I hover around the graph I want the tooltip show the time and value but it does not recognize it and show "undefined" since I do not know how to pass my datasets (data1s and data2s) so "mouseover function can recognize which data to show! This is how the tooltip functions are made and call from "path1" and "path2".
function mouseover() {
div.transition()
.duration(500)
.style("opacity", 1);
}
function mousemove(d) {
div
.text( d.time+ ", " + d.value)
.style("left", (d3.event.pageX ) + "px")
.style("top", (d3.event.pageY ) + "px");
}
function mouseout() {
div.transition()
.duration(500)
.style("opacity", 1e-6);
}
var path1 = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data1s])
.attr("class", "line1")
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseout", mouseout);
var path2 =svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data2s])
.attr("class", "line2")
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseout", mouseout);
Do you have any idea of what to do? i think i need to add
svg.selectAll("path1")
.attr("opacity", 1)
or svg.selectAll("datas1")
.attr("opacity", 1)
Somewhere! but i do not know how..
Thank you,
Update your mouseover function as:
function mousemove(d) {
div
.text( d[0].time+ ", " + d[0].value)
.style("left", (d3.event.pageX ) + "px")
.style("top", (d3.event.pageY ) + "px");
}
Include the index to the object 'd'.
Hope that helps.

Categories

Resources