I am returning data via SQL to an asp.net label as comma-delimited. I am pulling x, y, and c data. I am using the third variable to tell me what set the data belong to (I have multiple sets). Now, these sets have different domains, so let's use a toy example:
X = [1,0.5,0.75,0.8; 10,5,7.5,8; 100, 50, 75, 80]
Y = [2,3,4,5; 20,30,40,50; 200, 300, 400, 500]
C = [1,1,1,1; 2,2,2,2; 3,3,3,3]
I have it set up so when the user selects a set from the dropdown, I show only that set by running
.attr("r", function (d) { if(d.c == $('#pick').val()) return 1; else return 0})
But of course the appropriate scales to show these are quite different! The way I determine domain is always returning the max value (for X that would be 100)
var x = d3.scale.linear().domain([0, d3.max(datafiltered, function (d) { return parseInt(d.x) }) * 1.1]).range([0 + margin, w - margin]),
Is there a way to reset this in the onclick event?
Replace the scale (x in your case) with the new one (basically repeat the line that instantiates d3.scale) in the onclick handler and then redraw the visualisation. You shouldn't need to change any of the actual visualisation code if you overwrite the scale.
Related
I have a project going where i use a vue-slider for adjusting volume, and at the same time i also use vue-slider`s process to show current level on the actual output, however i have a mathematical problem thats above my head of thinking.
Lets say i have read a level of -15db, now my scale is going from -40db to +20db, however vue-slider`s process does not care about this value, it value is based on percentage, from 0 - 100.
So the question is, what equation can i use to get percentage(between 0-100%) of value(-15) within min(-40) and max(20)?
Example of the slider:
Solution i used are following:
let percent = 0
if (level > chGainMin) {
const translator = Math.abs(chGainMin)
const newMin = chGainMin + translator
const newMax = chGainMax + translator
const newLevel = level + translator
percent = ((newLevel-newMin)*100) / (newMax-newMin)
}
Using abs to turn negative minimum to a positive number, and append this number to min, max and value. Then calculate the percentage as usual.
the sankeyNetwork from networkD3 package is pretty clever most of the times at positioning the nodes, but there are occasions that I want to place the node to another location horizontally.
There are also other occasion that I want to vertically move the start point of an edge. Take the following screen shot for example:
For the node, I want to move the B3 on the 2nd column from the right to the 4th column from the left. Similarly, I want to move the B4 from the 2nd column from the right to the 5th column from the left.
For the edge, I want to move the start point of the very first edge (B1->B11) to the low end of B1.
My guess is that I need to make some changes to the source code and manually put in the specific positions. I saw this post for js How to tune horizontal node position in d3 sankeyjs, but I'm not sure what to do in R, beside I did not find any post talking about changing the edge positions.
Here is the replicable data and code:
load(url("https://github.com/bossaround/question/raw/master/sankeyexample.RData"))
# nn is the node, ee is the edge, now create the link (ll)
ll <- inner_join(ee, nn, by = c("N1"="name")) %>%
rename(source_ID = ID) %>%
inner_join(nn, by = c("N2"="name")) %>%
rename(target_ID = ID)
# Create Sankey Plot
sankeyNetwork(
Links = ll,
Nodes = nn,
Source = "source_ID",
Target = "target_ID",
Value = "Value",
NodeID = "newname",
fontSize = 12,
nodeWidth = 40,
nodePadding = 20,
NodeGroup = "newname"
)
Thank you in advance!
You can manually adjust the vertical position of nodes, so if you drag the top-right B11 node to the bottom, the link line from the top-left B1 node will automatically adjust to the bottom of that B1 node.
To achieve what's described in the answer to How to tune horizontal node position in d3 sankey.js?, all you have to do is add the sinksRight = FALSE parameter to your sankeyNetwork() call, though I don't think that achieves what you actually want.
Manually adjusting the horizontal position of nodes is not currently possible. Adjusting them individually in code would be very tedious, but you could add additional JS to run after it loads to individually adjust the positioning of specific nodes with some convoluted JS code like d3.selectAll(".node").filter(function(d) { return d.name == "B3" }).attr("transform", function(d) { return "translate(" + (d.x - 300) + "," + d.y + ")"; })
Is it possible to enable hover tool on the image (the glyph created by image(), image_rgba() or image_url()) so that it will display some context data when hovering on points of the image. In the documentation I found only references and examples for the hover tool for glyphs like lines or markers.
Possible workaround solution:
I think it's possible to convert the 2d signal data into a columnar Dataframe format with columns for x,y and value. And use rect glyph instead of image. But this will also require proper handling of color mapping. Particularly, handling the case when the values are real numbers instead of integers that you can pass to some color palette.
Update for bokeh version 0.12.16
Bokeh version 0.12.16 supports HoverTool for image glyphs. See:
bokeh release 0.12.16
for erlier bokeh versions:
Here is the approach I've been using for Hovering over images using bokeh.plotting.image and adding in top of it an invisible (alpha=0) bokeh.plotting.quad that has Hovering capabilities for the data coordinates. And I'm using it for images with approximately 1500 rows and 40000 columns.
# This is used for hover and taptool
imquad = p.quad(top=[y1], bottom=[y0], left=[x0], right=[x1],alpha=0)
A complete example of and image with capabilities of selecting the minimum and maximum values of the colorbar, also selecting the color_mapper is presented here: Utilities for interactive scientific plots using python, bokeh and javascript. Update: Latest bokeh already support matplotlib cmap palettes, but when I created this code, I needed to generate them from matplotlib.cm.get_cmap
In the examples shown there I decided not to show the tooltip on the image with tooltips=None inside the bokeh.models.HoverTool function. Instead I display them in a separate bokeh.models.Div glyph.
Okay, after digging more deeply into docs and examples, I'll probably answer this question by myself.
The hover effect on image (2d signal) data makes no sense in the way how this functionality is designed in Bokeh. If one needs to add some extra information attached to the data point it needs to put the data into the proper data model - the flat one.
tidying the data
Basically, one needs to tidy his data into a tabular format with x,y and value columns (see Tidy Data article by H.Wickham). Now every row represents a data point, and one can naturally add any contextual information as additional columns.
For example, the following code will do the work:
def flatten(matrix: np.ndarray,
extent: Optional[Tuple[float, float, float, float]] = None,
round_digits: Optional[int] = 0) -> pd.DataFrame:
if extent is None:
extent = (0, matrix.shape[1], 0, matrix.shape[0])
x_min, x_max, y_min, y_max = extent
df = pd.DataFrame(data=matrix)\
.stack()\
.reset_index()\
.rename(columns={'level_0': 'y', 'level_1': 'x', 0: 'value'})
df.x = df.x / df.x.max() * (x_max - x_min) + x_min
df.y = df.y / df.y.max() * (y_max - y_min) + y_min
if round_digits is not None:
df = df.round({'x': round_digits, 'y': round_digits})
return df
rect glyph and ColumnDataSource
Then, use rect glyph instead of image with x,y mapped accordingly and the value column color-mapped properly to the color aesthetics of the glyph.
color mapping for values
here you can use a min-max normalization with the following multiplication by the number of colors you want to use and the round
use bokeh builtin palettes to map from computed integer value to a particular color value.
With all being said, here's an example chart function:
def InteractiveImage(img: pd.DataFrame,
x: str,
y: str,
value: str,
width: Optional[int] = None,
height: Optional[int] = None,
color_pallete: Optional[List[str]] = None,
tooltips: Optional[List[Tuple[str]]] = None) -> Figure:
"""
Notes
-----
both x and y should be sampled with a constant rate
Parameters
----------
img
x
Column name to map on x axis coordinates
y
Column name to map on y axis coordinates
value
Column name to map color on
width
Image width
height
Image height
color_pallete
Optional. Color map to use for values
tooltips
Optional.
Returns
-------
bokeh figure
"""
if tooltips is None:
tooltips = [
(value, '#' + value),
(x, '#' + x),
(y, '#' + y)
]
if color_pallete is None:
color_pallete = bokeh.palettes.viridis(50)
x_min, x_max = img[x].min(), img[x].max()
y_min, y_max = img[y].min(), img[y].max()
if width is None:
width = 500 if height is None else int(round((x_max - x_min) / (y_max - y_min) * height))
if height is None:
height = int(round((y_max - y_min) / (x_max - x_min) * width))
img['color'] = (img[value] - img[value].min()) / (img[value].max() - img[value].min()) * (len(color_pallete) - 1)
img['color'] = img['color'].round().map(lambda x: color_pallete[int(x)])
source = ColumnDataSource(data={col: img[col] for col in img.columns})
fig = figure(width=width,
height=height,
x_range=(x_min, x_max),
y_range=(y_min, y_max),
tools='pan,wheel_zoom,box_zoom,reset,hover,save')
def sampling_period(values: pd.Series) -> float:
# #TODO think about more clever way
return next(filter(lambda x: not pd.isnull(x) and 0 < x, values.diff().round(2).unique()))
x_unit = sampling_period(img[x])
y_unit = sampling_period(img[y])
fig.rect(x=x, y=y, width=x_unit, height=y_unit, color='color', line_color='color', source=source)
fig.select_one(HoverTool).tooltips = tooltips
return fig
#### Note: however this comes with a quite high computational price
Building off of Alexander Reshytko's self-answer above, I've implemented a version that's mostly ready to go off the shelf, with some examples. It should be a bit more straightforward to modify to suit your own application, and doesn't rely on Pandas dataframes, which I don't really use or understand. Code and examples at Github: Bokeh - Image with HoverTool
In a web app, I would like to let the user select a region of interest in a plotted image using the nice box/lasso selection tools of bokeh. I would the like to receive the selected pixels for further operations in python.
For scatter plots, this is easy to do in analogy with the gallery,
import bokeh.plotting
import numpy as np
# data
X = np.linspace(0, 10, 20)
def f(x): return np.random.random(len(x))
# plot and add to document
fig = bokeh.plotting.figure(x_range=(0, 10), y_range=(0, 10),
tools="pan,wheel_zoom,box_select,lasso_select,reset")
plot = fig.scatter(X, f(X))
#plot = fig.image([np.random.random((10,10))*255], dw=[10], dh=[10])
bokeh.plotting.curdoc().add_root(fig)
# callback
def callback(attr, old, new):
# easily access selected points:
print sorted(new['1d']['indices'])
print sorted(plot.data_source.selected['1d']['indices'])
plot.data_source.data = {'x':X, 'y':f(X)}
plot.data_source.on_change('selected', callback)
however if I replace the scatter plot with
plot = fig.image([np.random.random((10,10))*255], dw=[10], dh=[10])
then using the selection tools on the image does not change anything in plot.data_source.selected.
I'm sure this is the intended behavior (and it makes sense too), but what if I want to select pixels of an image? I could of course put a grid of invisible scatter points on top of the image, but is there some more elegant way to accomplish this?
It sounds like the tool you're looking for is actually the BoxEditTool. Note that the BoxEditTool requires a list of glyphs (normally these will be Rect instances) that will render the ROIs, and that listening to changes should be set using:
rect_glyph_source.on_change('data', callback)
This will trigger the callback function any time you make any changes to your ROIs.
The relevant ColumnDataSource instance (rect_glyph_source in this example) will be updated so that the 'x' and 'y' keys list the center of each ROI in the image's coordinates space, and of course 'width' and 'height' describe its size. As far as I know there isn't currently a built-in method for extracting the data itself, so you will have to do something like:
rois = rect_glyph_source.data
roi_index = 0 # x, y, width and height are lists, and each ROI has its own index
x_center = rois['x'][roi_index]
width = rois['width'][roi_index]
y_center = rois['y'][roi_index]
height = rois['height'][roi_index]
x_start = int(x_center - 0.5 * width)
x_end = int(x_center + 0.5 * width)
y_start = int(y_center - 0.5 * height)
y_end = int(y_center + 0.5 * height)
roi_data = image_plot.source.data['image'][0][y_start:y_end, x_start:x_end]
IMPORTANT: In the current version of Bokeh (0.13.0) there is a problem with the synchronization of the BoxEditTool at the server and it isn't functional. This should be fixed in the next official Bokeh release. For more information and a temporary solution see this answer or this discussion.
I am using a Flot graph and I am setting up various interactive elements.
One of these elements is one in which the user inputs any x value (It really could be either an x or y value depending on the situation, but for simplicity, let's assume it is always an x-axis value) and I need to output the corresponding y coordinate on the line I have graphed. I feel like this should be kind of simple, so I apologize if the answer is an obvious one. Note that the input value is probably not going to be a "point" in the array which flot is using to create the line (although it could).
You could also imagine a vertical line at x = [user input, not necessarily a whole number] intersecting another line series at some point. I would need to find the point of intersection. I tried uploading a photo, but I don't have enough reputation points.
How's your algebra?
There's actually an example of this buried in flot's examples here. If you view the source to that page you'll see this (I've added explanation comments):
// Find the nearest points, x-wise
// loop the series data until you find the point
// immediately after your x value of interest (pos.x in this code)
for (j = 0; j < series.data.length; ++j) {
if (series.data[j][0] > pos.x) {
break;
}
}
// Now Interpolate
// Here's the algebra fun!
var y,
p1 = series.data[j - 1], // point before your x
p2 = series.data[j]; // point after your x
if (p1 == null) {
y = p2[1]; // if no point before just get y-value
} else if (p2 == null) {
y = p1[1]; // if no point after just get y-value
} else {
y = p1[1] + (p2[1] - p1[1]) * (pos.x - p1[0]) / (p2[0] - p1[0]);
// here's the algebra bit, see below
}
In that final else the equation used is this interpolation between two points. Ain't math grand?