Suppose I have a grid in javascript represented as such: ** Note this is just a tiny grid to serve as an example.
{
"width": 5,
"height": 5,
"nodes": [
[{
"x": 0,
"y": 0,
"walkable": true
}, {
"x": 1,
"y": 0,
"walkable": true
}, {
"x": 2,
"y": 0,
"walkable": true
}, {
"x": 3,
"y": 0,
"walkable": true
}, {
"x": 4,
"y": 0,
"walkable": true
}],
[{
"x": 0,
"y": 1,
"walkable": true
}, {
"x": 1,
"y": 1,
"walkable": true
}, {
"x": 2,
"y": 1,
"walkable": true
}, {
"x": 3,
"y": 1,
"walkable": true
}, {
"x": 4,
"y": 1,
"walkable": true
}],
[{
"x": 0,
"y": 2,
"walkable": true
}, {
"x": 1,
"y": 2,
"walkable": true
}, {
"x": 2,
"y": 2,
"walkable": true
}, {
"x": 3,
"y": 2,
"walkable": true
}, {
"x": 4,
"y": 2,
"walkable": true
}],
[{
"x": 0,
"y": 3,
"walkable": true
}, {
"x": 1,
"y": 3,
"walkable": true
}, {
"x": 2,
"y": 3,
"walkable": true
}, {
"x": 3,
"y": 3,
"walkable": true
}, {
"x": 4,
"y": 3,
"walkable": true
}],
[{
"x": 0,
"y": 4,
"walkable": true
}, {
"x": 1,
"y": 4,
"walkable": true
}, {
"x": 2,
"y": 4,
"walkable": true
}, {
"x": 3,
"y": 4,
"walkable": true
}, {
"x": 4,
"y": 4,
"walkable": true
}]
]
}
The "walkable" boolean will determine which areas are blocked off so to speak.
How would I flood this grid to mark the isolated areas? In the above example, a flood fill would fill the entire grid with a single color, because all areas are walkable. But supposing the grid had some areas which were unreachable from other areas (based on the walkable bool), how would I mark the different areas? I basically want to set a color property for each node. If the node isn't the same color as another node, then I know it can't be reached from that node.
EDIT:
Here's what I have so far. Can't run this on a node without getting a maximum call stack error:
function floodFill(node, grid) {
if (node.walkable == false) {
return;
}
if ((node.floodColor != undefined) && (node.floodColor == 'red')) {
return;
}
node.floodColor = 'red';
if ((grid.nodes[node.y + 1] != undefined) && (grid.nodes[node.y + 1][node.x] != undefined)) {
floodFill(grid.nodes[node.y + 1][node.x], grid);
}
if ((grid.nodes[node.y - 1] != undefined) && (grid.nodes[node.y - 1][node.x] != undefined)) {
floodFill(grid.nodes[node.y - 1][node.x], grid);
}
if ((grid.nodes[node.y] != undefined) && (grid.nodes[node.y][node.x + 1] != undefined)) {
floodFill(grid.nodes[node.y][node.x + 1], grid);
}
if ((grid.nodes[node.y] != undefined) && (grid.nodes[node.y][node.x - 1] != undefined)) {
floodFill(grid.nodes[node.y][node.x - 1], grid);
}
}
For those of you who did answer, the above is the sort of thing i am looking for. Descriptions of what to do don't help me, as I've already read quite a bit of descriptions of what to do. Explicit code please :p
Simply repeat flood-fill from many different locations. Just make sure to not fill the same area twice and your algorithm would still work in linear time, assuming reasonable representation of neighborhood (for example a matrix).
n_colors = 0;
for field in grid:
if field has no color assigned yet:
floodFill(fromField: field, color: n_colors)
n_colors = n_colors + 1
I will try this :
first create a resultMap which is a copy of your current grid with a color attribute by point, set all the walkable === false points to color: black
const floodMap = yourData.nodes
.map(n => n.map(p => {...p, {color: p.walkable ? 'black': undefined ));
create a function flood(x, y, color) which take a point in input. If this point is already colored, then do nothing (and return false), otherwise apply color take the (4 or 8 according to your rule) connected points if they aren't colored, and run flood(x', y', color) on them (and return true).
function flood(x, y, color) {
if(!floodMap[y][x].color) {
floodMap(x + 1, y, color);
floodMap(x - 1, y, color);
floodMap(x, y + 1, color);
floodMap(x, y - 1, color);
return true;
}
return false;
}
apply this function for each point of the matrix by changing the color each time the previous call have returned true.
const colors = ['red', 'blue', 'yellow', 'green', 'purple'];
const currentColor = 0;
floodMap.forEach(n => n.forEach(p => {
const flooded = flood(p.x, p.y, colors[currentColor]);
if(flooded) currentColor++;
}));
Related
I was trying to plot animated word cloud using plotly. When the output is plot (html) I can see all the word cloud in animated way (one by one using play botton). The problem is with the slider, which doesn't move at all.
The input data are 2 lists of 4 elements: the first list are 4 diferents wikipedia texts storaged in my_test = [text1, text2, text3, text4], and the second list is year = ["1952", "1962", "1967", "1972"].
The code is:
from wordcloud import WordCloud, STOPWORDS
import plotly.graph_objs as go
from plotly.offline import plot
import plotly.offline as py
py.init_notebook_mode()
def plotly_wordcloud(text):
wc = WordCloud(width = 500,
height = 300,
margin=0,
stopwords = set(STOPWORDS),
max_words = 2000,
max_font_size = 100,
min_font_size = 10)
wc.generate(text)
word_list=[]
freq_list=[]
fontsize_list=[]
position_list=[]
orientation_list=[]
color_list=[]
for (word, freq), fontsize, position, orientation, color in wc.layout_:
word_list.append(word)
freq_list.append(freq)
fontsize_list.append(fontsize)
position_list.append(position)
orientation_list.append(orientation)
color_list.append(color)
# get the positions
x=[]
y=[]
for i in position_list:
x.append(i[0])
y.append(i[1])
# get the relative occurence frequencies
new_freq_list = []
for i in freq_list:
new_freq_list.append(i*100)
new_freq_list
return x,y,word_list,freq_list,fontsize_list,color_list,new_freq_list
my_text = [text1,text2,text3,text4]
years = ["1952", "1962", "1967", "1972"]
fig_dict = {
"data": [],
"layout": {},
"layout.update": {},
"frames": []
}
sliders_dict = {
"active": 0,
"yanchor": "top",
"xanchor": "left",
"currentvalue": {
"font": {"size": 20},
"prefix": "Year:",
"visible": True,
"xanchor": "right"
},
"transition": {"duration": 300, "easing": "cubic-in-out"},
"pad": {"b": 10, "t": 50},
"len": 0.9,
"x": 0.1,
"y": 0,
"steps": []
}
fig_dict["layout"] = {"width":1000, "height":600}
fig_dict["layout"]["xaxis"] = {"showgrid":False, "showticklabels":False, "zeroline":False}
fig_dict["layout"]["yaxis"] = {"showgrid":False, "showticklabels":False, "zeroline":False}
fig_dict["layout"]["hovermode"] = "closest"
fig_dict["layout"]["title"] = "Issues by Month"
fig_dict["layout"]["sliders"] = {
"args": ["transition", {"duration": 300,
"easing": "cubic-in-out"}],
"initialValue": "1952",
"plotlycommand": "animate",
"values": years,
"visible": True
}
fig_dict["layout"]["updatemenus"] = [
{"buttons": [
{"args": [None, {"frame": {"duration": 500, "redraw": False},
"fromcurrent": True, "transition": {"duration": 300,
"easing": "quadratic-in-out"}}],
"label": "Play",
"method": "animate"
},
{
"args": [[None], {"frame": {"duration": 0, "redraw": False},
"mode": "immediate",
"transition": {"duration": 0}}],
"label": "Pause",
"method": "animate"
}
],
"direction": "left",
"pad": {"r": 10, "t": 87},
"showactive": False,
"type": "buttons",
"x": 0.1,
"xanchor": "right",
"y": 0,
"yanchor": "top"
}]
first_time = True
for texto,year in zip(my_text,years):
x,y,word_list,freq_list,fontsize_list,color_list,new_freq_list = plotly_wordcloud(str(texto))
if first_time == True:
first_time = False
data = go.Scatter(x=x,
y=x,
textfont = dict(size=new_freq_list,
color=color_list),
name=year,
mode='text',
text=word_list
)
fig_dict["data"].append(data)
fig_dict["frames"].append(go.Frame(data=[go.Scatter(
x=x,
y=y,
textfont = dict(size=new_freq_list,
color=color_list),
name=year,
mode='text',
text=word_list
)]))
slider_step = {"args": [[year],
{"frame": {"duration": 300, "redraw": False},
"mode": "immediate",
"transition": {"duration": 300}}
],
"label": year,
"method": "animate"}
sliders_dict["steps"].append(slider_step)
fig_dict["layout"]["sliders"] = [sliders_dict]
fig = go.Figure(fig_dict)
plot(fig)
I'm trying to display labels on the x axis on the bar chart as shown below but it only shows every other one. Does anyone know how to display all?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.7.0/nv.d3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.7.0/nv.d3.min.js"></script>
</head>
<body>
<h2>multiBarChart</h2>
<div id="multibarchart"><svg style="height:350px;width:800px;"></svg></div>
<script>
data_multibarchart = [{
"values": [{
"y": 4,
"x": 'a'
}, {
"y": 9,
"x": 'b'
}, {
"y": 9,
"x": 'c'
}, {
"y": 5,
"x": 'd'
}, {
"y": 6,
"x": 'e'
}, {
"y": 4,
"x": 'f'
}, {
"y": 1,
"x": 'g'
}, {
"y": 7,
"x": 'h'
}, {
"y": 6,
"x": 'i'
}, {
"y": 2,
"x": 'j'
}],
"key": "Count",
"yAxis": "1"
}, {
"values": [{
"y": 8,
"x": 'a'
}, {
"y": 18,
"x": 'b'
}, {
"y": 18,
"x": 'c'
}, {
"y": 10,
"x": 'd'
}, {
"y": 12,
"x": 'e'
}, {
"y": 8,
"x": 'f'
}, {
"y": 2,
"x": 'g'
}, {
"y": 14,
"x": 'h'
}, {
"y": 12,
"x": 'i'
}, {
"y": 4,
"x": 'j'
}],
"key": "Duration",
"yAxis": "1"
}];
nv.addGraph(function() {
var chart = nv.models.multiBarChart();
chart.margin({
top: 30,
right: 60,
bottom: 70,
left: 60
});
var datum = data_multibarchart;
chart.yAxis.tickFormat(d3.format(',.2f'));
chart.tooltipContent(function(key, y, e, graph) {
var x = String(graph.point.x);
var y = String(graph.point.y);
if (key == 'Count') {
var y = String(graph.point.y) + ' call';
}
if (key == 'Duration') {
var y = String(graph.point.y) + ' min';
}
tooltip_str = '<center><b>' + key + '</b></center>' + y + ' at ' + x;
return tooltip_str;
});
chart.showLegend(true);
d3.select('#multibarchart svg').datum(datum).transition().duration(500).attr('height', 350).call(chart);
});
</script>
</body>
</html>
I figured out. I just had to make them visible:
d3.selectAll('#multibarchart svg .tick text').style("opacity", 1);
Note that I also tried setting:
chart.reduceXTicks = false;
chart.staggerLabels = false;
chart.wrapLabels = false;
but this did NOT help.
My data array looks like this:
circleData = {
"x": [10,20,30,40], "r":[1,2,3,4], "y": [0,0,0,0]
},
{
"x": [15,25,35,45], "r":[5,6,7,8], "y": [20,20,20,20]
}
I want to create two rows of four circles with the above x, r, and y parameters. But how? The problem is, if the x values are bound in order to create the circles, then the circles have "access" to the x data, but no longer to the r or y data.
As mentioned in Lars Kotthoff's comment, the easiest way will probably be to restructure your data. Because D3's data binding relies on arrays, one approach would be something like this:
circleData = [{
"x": [10,20,30,40], "r":[1,2,3,4], "y": [0,0,0,0]
},
{
"x": [15,25,35,45], "r":[5,6,7,8], "y": [20,20,20,20]
}];
var circles = d3.merge(circleData.map(function(d) {
return d3.zip(d.x, d.y, d.r);
}));
This will give you a 2-dimensional array containing 8 arrays representing all 8 circles with their cx, cy, and r parameters.
You may then bind circles as your data and operate very D3-ish on it.
var circleData = [{
"x": [10, 20, 30, 40],
"r": [1, 2, 3, 4],
"y": [0, 0, 0, 0]
}, {
"x": [15, 25, 35, 45],
"r": [5, 6, 7, 8],
"y": [20, 20, 20, 20]
}];
var circles = d3.merge(circleData.map(function(d) {
return d3.zip(d.x, d.y, d.r);
}));
var svg = d3.select("body")
.append("svg").append("g")
.attr("transform", "translate(50,50)"); // Translate for the sake of visibility
svg.selectAll("circle")
.data(circles).enter()
.append("circle")
.attr({
"cx": function(d) {
return d[0];
},
"cy": function(d) {
return d[1];
},
"r": function(d) {
return d[2];
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
3 library for graph generation & i am facing problems while setting the X & Y axis for the intervals based upon values. I want to know whether NVD3's java script has inbuilt capabilities to adjust the X axis intervals based upon the number on records inside the data?
I am generating the graph & it possibly sets the y axis's first value to the lowest value of the sample data. If i need to start the y axis from 0 too then how should it be done?
sample data:
var data = [{
"key": "30 Day",
"values": [{
"x": 0,
"y": 18
}, {
"x": 1,
"y": 24
}, {
"x": 2,
"y": 23
}, {
"x": 3,
"y": 27
},{
"x": 4,
"y": 24
},{
"x": 5,
"y": 31
},{
"x": 6,
"y": 37
},{
"x": 7,
"y": 46
},{
"x": 8,
"y": 32
},{
"x": 9,
"y": 23
},{
"x": 10,
"y": 30
}]
}];
Several NVD3 charts support xDomain/yDomain options. For example:
var chart = nv.models.lineChart();
chart.xDomain( [ 0, 10 ] ).yDomain( [ 0, 50 ] );
I'm trying to create a stack bar graph using the stack layout.
I can make it work only if I pass it an array of x,y coordinates. But I want to be able to add meta data to it, such as series title.
I've read the docs (https://github.com/mbostock/d3/wiki/Stack-Layout), and seen how it's done on a steamgraph (Correct usage of stack.values([accessor]) in D3 Streamgraph?). The problem with these examples is that they don't take into account things like y scale, making it difficult to establish variables such as yStackMax.
I also need the data to be passed to the stack() function early on, because I'm planning to redraw this and other things when the data is refreshed. In short, instead of:
var data = [
[
{ "x": 0, "y": 91},
{ "x": 1, "y": 290}
],
[
{ "x": 0, "y": 9},
{ "x": 1, "y": 49}
],
[
{ "x": 0, "y": 10},
{ "x": 1, "y": 25}
]
];
var layers = d3.layout.stack()(data);
var yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
... which works, I want to be able to do:
var data = [
{
"name": "apples",
"values": [
{ "x": 0, "y": 91},
{ "x": 1, "y": 290}
]
},
{
"name": "oranges",
"values": [
{ "x": 0, "y": 9},
{ "x": 1, "y": 49}
]
},
{
"name": "potatoes",
"values": [
{ "x": 0, "y": 10},
{ "x": 1, "y": 25}
]
}
];
var layers = d3.layout.stack()(data).values(function(d) { return d.values; });
var yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
... which doesn't work.
This is the working code fiddle: http://jsfiddle.net/StephanTual/E6FeP/
This is the fiddle for the code that doesn't work: http://jsfiddle.net/StephanTual/Tnj8W/
Here's an updated fiddle.
The key part was:
// define the accessor before adding in the data
var layers = d3.layout.stack().values(function(d) { return d.values; })(data);
var yStackMax = d3.max(layers, function(layer) { return d3.max(layer.values, function(d) { return d.y0 + d.y; }); });
And then I made a couple other adjustments as necessary to access the .values.