based on this example: http://bl.ocks.org/d3noob/10633704 i wish to make an input on my keyboard (a number) and make the circles disappear with the help of array.slice(). Unfortunally it did not worked well. In my code, i created some circles based on the values of the array days. With the HTML part i am able to create a button, where i can make a number input. With the last part days.slice(nValue) i want that the input number is the same like the number inside the brackets of the slice() function, so the array days is getting shorter and automatically let circles based on the value of the array disappear. But unfortunally there is a mistake i made in this code. Can someone maybe be so kind and help? I am using D3 to solve this problem.
Thanks
<!DOCTYPE html>
<meta charset="utf-8">
<title>Input (number) test</title>
<p>
<label for="nValue"
style="display: inline-block; width: 120px; text-align: right">
angle = <span id="nValue-value"></span>
</label>
<input type="number" min="0" max="360" step="4" value="0" id="nValue">
</p>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 600;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var days = [7, 12, 20, 31, 40, 50];
console.log(days);
var circle = svg.selectAll("circle")
.data(days)
.enter().append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 40; })
.attr("r", function(d) { return Math.sqrt(d); });
d3.select("#nValue").on("input", function() {
update(+this.value);
});
// Initial update value
update(0);
function update(nValue) {
days.slice(nValue);
}
It took me a while to see what you're after here, and I might still be off a bit in my understanding.
The Problem
As I see understand it, you are modifying an array of data (with a select menu in this case), but the modified array does not appear to modify your visualization. Essentially, as "the array days is getting shorter ... let circles based on the value[s] of the array disappear."
Updating the visualization
To update the visualization you need to bind the new data to your selection. After this you can remove unneeded elements in the visualization, add new ones (not relevant to this question), or modify existing elements. Changing the data array by itself will not update the visualization. To have the visualization utilize the new information you need to bind that data to the selection:
circle.data(data);
Then you can remove the old items:
circle.exit().remove();
Then you can modify properties of the old items:
circle.attr('cx',function(d,i) {...
Your update function needs to at least update the data and remove unneeded elements.
Changing the Array
In the following snippet I append both a select menu and the circles with d3 based on the data in the array. Selecting an item in the menu will remove a circle:
var data = [10,20,30,40,50,60,70,80,90,100];
var color = d3.schemeCategory10; // color array built in
//// Add the select and options:
var select = d3.select('body')
.append('select')
.on('change',function() { update(this.value) });
var start = select.append('option')
.html("select: ");
var options = select.selectAll('.option')
.data(data)
.enter()
.append('option')
.attr('class','option')
.attr('value',function(d,i) { return i; })
.html(function(d) { return d; });
//// Add the circles (and svg)
var svg = d3.selectAll('body')
.append('svg')
.attr('width',500)
.attr('height',200);
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',function(d,i) { return i * 30 + 50; })
.attr('cy',50)
.attr('r',10)
.attr('fill',function(d,i) { return color[i]; });
// Update everything:
function update(i) {
data.splice(i,1); // remove that element.
// Update and remove option from the select menu:
options.data(data).exit().remove();
// Remove that circle:
circles.data(data).exit().remove();
circles.attr('cx',function(d,i) { return i * 30 + 50; })
.attr('fill',function(d,i) { return color[i]; });
// reset the select menu:
start.property('selected','selected');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
There is a problem here, only the last circle and menu item is removed each time. Why? Imagine a four element array, if you remove the second item, d3 does not know that you removed the second item, you might have modified elements two and three and removed element four.
Since all your items are appended with their increment (which position they are in the array), and this doesn't account for holes that were created when other items were removed, you need to change the approach a little.
A solution
Instead of relying on the increment of an item in the array (as this will change every time an element that is before another element is removed from the array), you could use an id property in your data.
This would require restructuring you data a little. Something like:
var data = [ {id:1,value:1},{id2....
As the id property won't change, this makes a better property to set attributes. Take a look at the following snippet:
var data = [{id:0,value:10},{id:1,value:20},{id:2,value:23},{id:3,value:40},{id:4,value:50},{id:5,value:60},{id:6,value:70},{id:7,value:77},{id:8,value:86},{id:9,value:90}];
var color = d3.schemeCategory10; // color array built in
//// Add the select and options:
var select = d3.select('body')
.append('select')
.on('change',function() { update(this.value); } ); // add an event listener for changes
// append a default value:
var start = select.append('option')
.html("Select:");
var options = select.selectAll('.option')
.data(data)
.enter()
.append('option')
.attr('class','option')
.attr('value',function(d,i) { return i; })
.html(function(d) { return d.value; });
//// Add the circles (and svg)
var svg = d3.selectAll('body')
.append('svg')
.attr('width',500)
.attr('height',200);
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',function(d) { return d.id * 30 + 50; })
.attr('cy',50)
.attr('r',10)
.attr('fill',function(d) { return color[d.id]; });
// Update everything:
function update(i) {
data.splice(i,1); // remove the element selected
// Update and remove option from the select menu:
options.data(data).exit().remove();
// Remove that circle:
circles.data(data).exit().remove();
// update the options (make sure each option has the correct attributes
options.attr('value',function(d,i) { return i; })
.html(function(d) { return d.value; })
// Make sure circles are in the right place and have the right color:
circles.attr('cx',function(d) { return d.id * 30 + 50; })
.attr('fill',function(d) { return color[d.id]; });
// reset the default value so the change will work on all entries:
start.property('selected', 'selected');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
Try changing your update function to this:
function update(nValue) {
days = days.slice(nValue);
}
I am trying to create a semi circle donut chart to show some progress data. This I was able to complete. Further I needed to place text inside/ center of the donut chart which again I was able to do successfully. Now I have a new requirement where I need to show some text on the start and end positions of the graph(I need 0% and 100% to be shown on either axis). I tried several ways without any ado. Can you please help me with a possible solution.
Please find the dojo I created here:
http://dojo.telerik.com/Exike
This is roughly what I would like my end result to appear like:
Any suggestions for the same?
Thanks in advance.
You could add a couple of text boxes to your render function and use the bounding box to place them:
render: function (e) {
var draw = kendo.drawing;
var geom = kendo.geometry;
var chart = e.sender;
// The center and radius are populated by now.
// We can ask a circle geometry to calculate the bounding rectangle for us.
var circleGeometry = new geom.Circle(center, radius);
var bbox = circleGeometry.bbox();
// Render the text
var text = new draw.Text("33%", [0, 0], {
font: "18px Verdana,Arial,sans-serif"
});
// Align the text in the bounding box
draw.align([text], bbox, "center");
draw.vAlign([text], bbox, "center");
var text0 = new draw.Text("0%", [bbox.origin.x, bbox.origin.y + bbox.size.height / 2 ], {
font: "10px Verdana,Arial,sans-serif"
});
var text100 = new draw.Text("100%", [bbox.origin.x + bbox.size.width - 28, bbox.origin.y + bbox.size.height / 2 ], {
font: "10px Verdana,Arial,sans-serif"
});
// Draw it on the Chart drawing surface
e.sender.surface.draw(text);
e.sender.surface.draw(text0);
e.sender.surface.draw(text100);
}
Use DonutCenterTemplate for that:
<ng-template kendoChartDonutCenterTemplate>
<h3>99.5%</h3>
Accepted
</ng-template>
I am working with a scatterplot in d3 and each dot on the graph represents a paper.
All dots are blue on the graph but on click I want that selected paper to change color eg (yellow). But when another dot is clicked I want that first dot to go back to blue and the newly selected dot to turn yellow. How would I achieve this? I have a function called clickHandler which is called when the circles are created at the on click attribute.
Here is a similar question d3 javascript alternate colors on click
But instead of the circle turning back to the original color on the second click of that circle I want it to turn back on a click of another circle.
The following code is from the question linked above, I was wondering would I be on the right track thinking this is the method I should be following for what I want?:
function clickHandler (d, i) {
var toggleColor = (function() {
var currentColor = "blue";
return function() {
currentColor = currentColor == "blue" ? "yellow" : "blue";
d3.select(this).style("fill", currentColor)
.style("stroke", "black");
}
})
}
Any help is greatly appreciated! Thanks in advance!
This can be as simple as:
function clickHandler (d, i) {
// reset all circle to blue
d3.selectAll('circle') //<-- or slap a class name on your circles and use that
.style('fill', 'blue');
// set selected one to yellow
d3.select(this)
.style('fill', 'yellow');
}
I have tried replacing many pieces of the strings in the legend javascript to make the first color field the same color as the no value for the data. I am assuming I need to add an exception to the first rule but I can't figure it out. I started with the leaflet tuturial for choropleth maps and have gotten this far. This is the last real piece I need to get the map fully functional. Basically wanting the no data value, countries that are not colored or highlighted, and first field in legend to be grey like the map shows, not the green second value as is shown.
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 0.00001, 0.7300001, 2.9900001, 6.700001],
labels = [],
from, to;
for (var i = 0; i < grades.length; i++) {
from = grades[i];
to = grades[i + 1];
labels.push(
'<i style="background:' + getColor(from + '1' ) + '"></i> ' +
from + (to ? '–' + to : '–43.89303638'));
} //changed '1' to get right colors on the legend but first field is still second green, not sure why??
div.innerHTML = labels.join('<br>');
return div;
};
legend.addTo(map);
This is the piece that fills the colors of the countries by value as is set up now which is functioning correctly.
function getColor(d) {
return d > 6.700001 ? '#D7191C' :
d > 2.9900001 ? '#FDAE61' :
d > 0.7300001 ? '#A6D96A' :
d > 0.00001 ? '#1A9641' :
'#E9E9E9';
}
function style(feature) {
return {
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7,
fillColor: getColor(feature.properties.UNdata_CO2_20151106_191526521_Value)//changed value from value above in blue
};
}
Here is a link to my webpage!
I added a note regarding my last change, before that it was showing two grey fields then dark green, light green, and orange without showing red for highest values...
The for loop in the legend.onAdd function is generating the colors and labels automatically from the grades array, but you can simply add another label of your own before or after the loop. Assuming that #E9E9E9 is your undefined color, it would go like this:
labels.push('<i style="background: #E9E9E9"></i> ' + 'undefined');
Put this just before the loop if you want to add the undefined field at the top of the legend, or just after the loop if you want to add it to the end. Here is an example fiddle showing this method at work with some random circle markers:
http://fiddle.jshell.net/4aqfdz1d/
In creating a svg map using raphael js where I have hover states. How ever I am trying to write the country names onto the map. The problem I am facing is the names become their own object and block the country so I loose hover and click when the cursor is directly over the text. Is there a way where I can draw the text and not have it block the map. I've tried set() but with no sucess.
Thanks,
What I have below doesn't have the text() or print() included:
var r = Raphael('map', 1450, 2180);
arr = new Array();
for (var country in paths) {
var obj = r.path(paths[country].path);
countryName = paths[country].name;
color = paths[country].color;
scolor = paths[country].stroke;
obj.attr({fill:color,stroke:scolor,
'stroke-width': 1,
'stroke-linejoin': 'round'});
arr[obj.id] = country;
obj
.hover(function(){
console.log(arr[this.id]);
this.animate({
fill: paths[arr[this.id]].hover
}, 300);
}, function(){
this.animate({
fill: paths[arr[this.id]].color
}, 300);
})
});
Try setting the pointer-events attribute to none for the text elements.
Documentation:
http://www.w3.org/TR/SVG/interact.html#PointerEventsProperty