LinearRegionItem in Pyqtgraph - pyqtgraph

I have 4 PlotWidgets (using pyqtgraph as pg) created as:
for i in range(4):
myPlot[i] = pg.PlotWidget(myWindow);
In each PlotWidget, I want to divide the x-axis to several regions with equal width (RegionWidth). Therfore, I have M = x-axis Range/RegionWidth. So I initialize region selection lines as:
for i in range(4):
for j in range(M):
ROI[i][j] = pg.LinearRegionItem()
ROI[i][j].setZValue(-10)
myPlot[i].addItem(ROI[i][j])
ROI[i][j].setRegion([xmin + j*RegionWidth, xmin +((j+1)*RegionWidth)-1])
The problem:
When I want to remove and clear these regions, I can't! I tried:
for i in range(4):
for j in range(M):
myPlot[i].removeItem(ROI[i][j])
It only clears the regions in the fourth PlotWidget. I want to remove/clear the four plots from these regions without affecting other items in the PlotWidget.

This code works good. It was an error of bad indenting.

Related

Legend outside plot does not work with plotyy in Octave

I am trying to create a plot in Octave (using v4.4.1 on Windows) using plotyy and putting the legend outside the plot (because the data covers all the usable space inside the graph). The following MVCE should reproduce the issue fairly well:
% Generate some random data to reproduce the issue
data = rand(1000,10);
data(:,1:8) = data(:,1:8)-0.5;
data(:,9:10) = data(:,9:10)+30;
timedate = linspace(737310,737313,size(data,1));
data_labels={'1';'2';'3';'4';'5';'6';'7';'8';'9';'10'};
% Plot the data
figure('Name','MVCE','Position',[300 200 1000 600])
[ax,h1,h2] = plotyy(timedate,data(:,1:8),timedate,data(:,9:10));
set(h2,'Visible','on');
datetick(ax(1),'x','HH:MM:SS')
datetick(ax(2),'x','HH:MM:SS')
ylim(ax(1),[-1 1])
ylim(ax(2),[20 50])
xlabel('Date & time')
ylabel(ax(1),'Something')
ylabel(ax(2),'Something else')
title('plotyy graph with legend problem')
[hl,hlo] = legend([h1;h2],data_labels,'location','eastoutside');
grid on
This the output of the code using the gnuplot graphics toolkit:
As you can see, the legend does not go outside the plot, and the second y axis is not visible (it looks like part of the plot is actually truncated).
I have tried using the qt and fltk graphics toolkits, which give issues of their own:
With qt graphics toolkit
With fltk graphics toolkit
Can anoybody suggest a fix or at least workaround? Does the same issue also happen in MATLAB or is it Octave-specific?
EDIT
Using the suggestion in Tasos' answer, I managed to almost make it work with gnuplot:
% Plot the data
figure('Name','MVCE','Position',[300 200 1000 600])
[ax,h1,h2] = plotyy(timedate,data(:,1:8),timedate,data(:,9:10));
set(h2,'Visible','on');
datetick(ax(1),'x','HH:MM:SS')
datetick(ax(2),'x','HH:MM:SS')
ylim(ax(1),[-1 1])
ylim(ax(2),[20 50])
ax1Pos = get(ax(1), 'position');
ax2Pos = get(ax(2), 'position');
ax1Pos(3) = ax1Pos(3) * 0.73;
ax2Pos(3) = ax2Pos(3) * 0.73;
set(ax(1), 'position', ax2Pos);
set(ax(2), 'position', ax2Pos);
xlabel('Date & time')
ylabel(ax(1),'Something')
ylabel(ax(2),'Something else')
title('plotyy graph with legend problem')
[hl,hlo] = legend([h1;h2],data_labels,'location','eastoutside');
pos = get(hl,'Position');
pos(1) = 0.9;
set(hl,'Position',pos)
grid on
Which produces:
Apart from the fact that the legend overlays with the second y axis label (which it doesn't on my screen, only when printing to jpg), the problem is that Octave appears to plot two legends on top of each other for some reason: one with the first set of data attached to the first set of axes, and one with the complete set of data, for both axes right on top of the first legend. This is obviously wrong, and trying to set the Visible property of hl to off deletes both legends, not just the one.
UPDATED: deals with both legend placement and OpenGL precision affecting graph.
Regarding the problem of the legend not appearing exactly in the position you want it to, you can manually adjust the position of all axes involved in a figure, to place them exactly where you want.
Regarding the problem of OpenGL being unable to deal with the precision involved when adding small numbers to a large number, plot the graph with only the small numbers involved, and then simply adjust the xticklabels to correspond to the numbers you desire.
Full code below:
% Generate some random data to reproduce the issue
data = rand(1000,10);
data(:,1:8) = data(:,1:8)-0.5;
data(:,9:10) = data(:,9:10)+30;
t_offset = 737310;
timedate = linspace(0,3,size(data,1));
data_labels={'1';'2';'3';'4';'5';'6';'7';'8';'9';'10'};
% Plot the data
figure('Name','MVCE','Position',[300 200 1000 600])
[ax,h1,h2] = plotyy(timedate,data(:,1:8),timedate,data(:,9:10));
set(h2,'Visible','on');
ylim(ax(1),[-1 1])
ylim(ax(2),[20 50])
ylabel(ax(1),'Something')
ylabel(ax(2),'Something else')
title('plotyy graph with legend problem')
[hl,hlo] = legend([h1;h2],data_labels,'location','eastoutside');
set(hl, 'position', get(hl, 'position') .* [0.975, 1, 0.975, 1] )
grid on
ax1Pos = get(ax(1), 'position'); ax2Pos = get(ax(2), 'position');
ax1Pos(3) = ax1Pos(3) * 0.95; ax2Pos(3) = ax2Pos(3) * 0.95;
set(ax(1), 'position', ax2Pos); set(ax(2), 'position', ax2Pos);
XTicks = get(ax(1), 'xtick');
set(ax(1), 'xticklabel', datestr(XTicks + t_offset, 'HH:MM:SS'))
xlabel('Date & time')
set(ax(2), 'xtick', []);
Output:

Custom legend labels - geopandas.plot()

A colleague and I have been trying to set custom legend labels, but so far have failed. Code and details below - any ideas much appreciated!
Notebook: toy example uploaded here
Goal: change default rate values used in the legend to corresponding percentage values
Problem: cannot figure out how to access the legend object or pass legend_kwds to geopandas.GeoDataFrame.plot()
Data: KCMO metro area counties
Excerpts from toy example
Step 1: read data
# imports
import geopandas as gpd
import matplotlib.pyplot as plt
%matplotlib inline
# read data
gdf = gpd.read_file('kcmo_counties.geojson')
Option 1 - get legend from ax as suggested here:
ax = gdf.plot('val', legend=True)
leg = ax.get_legend()
print('legend object type: ' + str(type(leg))) # <class NoneType>
plt.show()
Option 2: pass legend_kwds dictionary - I assume I'm doing something wrong here (and clearly don't fully understand the underlying details), but the _doc_ from Geopandas's plotting.py - for which GeoDataFrame.plot() is simply a wrapper - does not appear to come through...
# create number of tick marks in legend and set location to display them
import numpy as np
numpoints = 5
leg_ticks = np.linspace(-1,1,numpoints)
# create labels based on number of tickmarks
leg_min = gdf['val'].min()
leg_max = gdf['val'].max()
leg_tick_labels = [str(round(x*100,1))+'%' for x in np.linspace(leg_min,leg_max,numpoints)]
leg_kwds_dict = {'numpoints': numpoints, 'labels': leg_tick_labels}
# error "Unknown property legend_kwds" when attempting it:
f, ax = plt.subplots(1, figsize=(6,6))
gdf.plot('val', legend=True, ax=ax, legend_kwds=leg_kwds_dict)
UPDATE
Just came across this conversation on adding in legend_kwds - and this other bug? which clearly states legend_kwds was not in most recent release of GeoPandas (v0.3.0). Presumably, that means we'll need to compile from the GitHub master source rather than installing with pip/conda...
I've just come across this issue myself. After following your link to the Geopandas source code, it appears that the colourbar is added as a second axis to the figure. so you have to do something like this to access the colourbar labels (assuming you have plotted a chloropleth with legend=True):
# Get colourbar from second axis
colourbar = ax.get_figure().get_axes()[1]
Having done this, you can manipulate the labels like this:
# Get numerical values of yticks, assuming a linear range between vmin and vmax:
yticks = np.interp(colourbar.get_yticks(), [0,1], [vmin, vmax])
# Apply some function f to each tick, where f can be your percentage conversion
colourbar.set_yticklabels(['{0:.2f}%'.format(ytick*100) for ytick in yticks])
This can be done by passing key-value pairs to dictionary argument legend_kwds:
gdf.plot(column='col1', cmap='Blues', alpha=0.5, legend=True, legend_kwds={'label': 'FOO', 'shrink': 0.5}, ax=ax)

bokeh axis limits fail when mixing x_range with y_range across multiple plots

I'm trying to visualize a high-dim point set x (here of dim (6 x 42)) in a series of 2D scatter plots (x[1] vs x[2] etc.) using bokeh. [edit2] See this nice example from scikit-opt as a reference. When x[1] occurs in two plots it should interact with the same range and the plots should rescale simultaneously. I have accomplished this, but I don't get it to scale correctly. Here's a minimal example: [edit2]
import bokeh
import bokeh.io
import numpy as np
import bokeh.plotting
bokeh.io.output_notebook()
# That's my fictional dataset
x = np.random.randn(6, 42)
x[2] *= 10
# Build the pairwise scatter plots
kw = dict(plot_width=165, plot_height=165)
# `ranges` stores the range in each dimension,
# used as both, x- and y-range depending on
# where the variable is.
figs, ranges = {}, {}
for r, row in enumerate(x):
for c, col in enumerate(x):
if r is not c:
fig = bokeh.plotting.figure(
x_range=ranges.get(c, None), y_range=ranges.get(r, None),
**kw)
fig.scatter(x=col, y=row)
fig.xaxis.axis_label = f'Dim {c}'
fig.yaxis.axis_label = f'Dim {r}'
if c not in ranges:
ranges[c] = fig.x_range
if r not in ranges:
ranges[r] = fig.y_range
figs[f'{r}_{c}'] = fig
else:
break
# Setup the plotting layout
plots = [[]]
for r, row in enumerate(x):
for c, col in enumerate(x):
if r is not c:
plots[-1].append(figs[f'{r}_{c}'])
else:
plots.append([])
break
staircase = bokeh.layouts.gridplot(plots, **kw)
bokeh.plotting.show(staircase)
.. into an ipython notebook (>=py3.6), bokeh sets the scale for dim 1, and 2 correctly. Then, it starts to set the scale for the following dimensions as in dim 2. Notice that I scaled dim 2 10-fold to make this point.
Interactively, I can rescale the plot back to optimal settings. However, I'd like to do that by default. What options do I have inside bokeh to rescale? I played a bit with fig.xaxis.bounds, but unsuccessfully. Thanks for your help!
Epilogue:
Following #bigreddot's answer, I added the lines:
for i, X in enumerate(x):
ranges[i].start = X.min()
ranges[i].end = X.max()
to fix the starting ranges. I still think that the behaviour is a bug.
From your code and description I still can't quite tell what you are hoping to accomplish. [1] But I will state that the default DataRange1d ranges that plot's use automatically make space for all renderers, across all plots they are shared by. In this sense, I see exactly what I would expect when I run your code. If you want something different, there are two things you could control:
DataRange1d has a .renderers property. If you only want the "auto" ranging to be over a subset of the renderers, then you can explicitly set this property to the list you want. Renderers are returned by the glyph functions, e.g. fig.scatter
Don't use the "auto" ranges. You can also set the x_range and y_range yourself to be Range1d objects. These have start and end properties that you can set, and these will be the definite bounds of the range, e.g. x-range=Range1d(0, 10)
[1] The ranges are linked in what I would consider an odd way, and I can't tell if that is intended. But that is a result of your looping/python code and not Bokeh.

Multiple histograms in Julia using Plots.jl

I am working with a large number of observations and to really get to know it I want to do histograms using Plots.jl
My question is how I can do multiple histograms in one plot as this would be really handy. I have tried multiple things already, but I am a bit confused with the different plotting sources in julia (plots.jl, pyplot, gadfly,...).
I don't know if it would help for me to post some of my code, as this is a more general question. But I am happy to post it, if needed.
There is an example that does just this:
using Plots
pyplot()
n = 100
x1, x2 = rand(n), 3rand(n)
# see issue #186... this is the standard histogram call
# our goal is to use the same edges for both series
histogram(Any[x1, x2], line=(3,0.2,:green), fillcolor=[:red :black], fillalpha=0.2)
I looked for "histograms" in the Plots.jl repo, found this related issue and followed the links to the example.
With Plots, there are two possibilities to show multiple series in one plot:
First, you can use a matrix, where each column constitutes a separate series:
a, b, c = randn(100), randn(100), randn(100)
histogram([a b c])
Here, hcat is used to concatenate the vectors (note the spaces instead of commas).
This is equivalent to
histogram(randn(100,3))
You can apply options to the individual series using a row matrix:
histogram([a b c], label = ["a" "b" "c"])
(Again, note the spaces instead of commas)
Second, you can use plot! and its variants to update a previous plot:
histogram(a) # creates a new plot
histogram!(b) # updates the previous plot
histogram!(c) # updates the previous plot
Alternatively, you can specify which plot to update:
p = histogram(a) # creates a new plot p
histogram(b) # creates an independent new plot
histogram!(p, c) # updates plot p
This is useful if you have several subplots.
Edit:
Following Felipe Lema's links, you can implement a recipe for histograms that share the edges:
using StatsBase
using PlotRecipes
function calcbins(a, bins::Integer)
lo, hi = extrema(a)
StatsBase.histrange(lo, hi, bins) # nice edges
end
calcbins(a, bins::AbstractVector) = bins
#userplot GroupHist
#recipe function f(h::GroupHist; bins = 30)
args = h.args
length(args) == 1 || error("GroupHist should be given one argument")
bins = calcbins(args[1], bins)
seriestype := :bar
bins, mapslices(col -> fit(Histogram, col, bins).weights, args[1], 1)
end
grouphist(randn(100,3))
Edit 2:
Because it is faster, I changed the recipe to use StatsBase.fit for creating the histogram.

aligning patterns across panels with gridExtra and grid.pattern()

The gridExtra package adds a grob of class "pattern" that lets one fill rectangles with patterns. For example,
library(gridExtra)
grid.pattern(pattern = 1)
creates a box filled with diagonal lines. I want to create a stack of panels in which each panel is filled with these diagonal lines. This is easy:
library(lattice); library(gridExtra)
examplePlot <- xyplot(
1 ~ 1 | 1:2,
panel = function () grid.pattern(pattern = 1),
layout = c(1, 2),
# Remove distracting visual detail
scales = list(x=list(draw=FALSE), y=list(draw=FALSE)),
strip = FALSE, xlab = '', ylab = ''
)
print(examplePlot)
The problem is that the diagonal lines aren't aligned across panels. That is, there is a visual "break" where the bottom of the first panel meets the top of the second panel: at that point, the lines don't line up. This is the problem that I want to fix.
I can eliminate most of the visual break by adding the argument pattern.offset = c(.2005, 0) to the grid.pattern call, and making sure that it applies only to the bottom panel. But this solution doesn't generalize. For example, if I change the pattern (e.g., by using the granularity argument to grid.pattern), this solution won't work. Is there a more general fix?
To make this work, you'll have to take charge of setting the panel.height argument used by print.trellis. (To see why, try resizing your plotting device after running your example code: as the size of the device and the panels changes, so does the matching/mismatching of the lines):
## Calculate vertical distance (in mm) between 45 degree diagonal lines
## spaced 5mm apart (the default distance for grid.pattern).
vdist <- 5 * sqrt(2)
nLines <- 8L ## can be any integer
panelHeight <- list(x = nLines*vdist, units = "mm", data = NULL)
## Plot it
print(examplePlot, panel.height=panelHeight)

Resources