DiagrammeR: Force horizontal arrows to be straight rather than diagonal - r

I am attempting to recreate this example as a test for a flow diagram for a project I am working on: https://dannyjnwong.github.io/STROBE-CONSORT-Diagrams-in-R/
That page shows the code should result in a diagram that looks like this: https://dannyjnwong.github.io/figures/2018-02-12-STROBE-CONSORT-Diagrams-in-R/STROBE.png
However, when I try running the same exact code in RStudio I get this instead, the horizontal arrows do not render as horizontal, they instead curve downwards:
Is there any way to force these arrows to be straight and horizontal as they are in the github example? Could it perhaps be related to the version of DiagrammeR? That post uses DiagrammeR_0.9.2 while mine is using DiagrammeR_1.0.6.1 I would like to avoid having to roll back my version of the package if possible. Thanks!

I use ortho splines with DiagrammeR to get horizontal lines in my flowcharts. I tried using add_global_graph_attrs with create_graph in your example which produced horizontal lines but did not keep the architecture intact.
Here is how I have made similar graphs. I use glue for convenience to insert specific values and text in the flowchart. Perhaps this may be helpful for you.
library(DiagrammeR)
library(glue)
n <- 1000
exclude1 <- 100
exclude2 <- 50
include1 <- n - exclude1 - exclude2
grViz(
glue("digraph my_flowchart {{
graph[splines = ortho]
node [fontname = Helvetica, shape = box, width = 4, height = 1]
node1[label = <Total available patients<br/>(n = {n})>]
blank1[label = '', width = 0.01, height = 0.01]
excluded1[label = <Excluded because of<br/>exclusion criteria (n={exclude1})>]
node1 -> blank1[dir = none];
blank1 -> excluded1[minlen = 2];
{{ rank = same; blank1 excluded1 }}
blank2[label = '', width = 0.01, height = 0.01]
excluded2[label = <Excluded because of missing values (n={exclude2})>]
blank1 -> blank2[dir = none];
blank2 -> excluded2[minlen = 2];
{{ rank = same; blank2 excluded2 }}
node2[label = <Included for analysis<br/>(n={include1})>]
blank2 -> node2;
node3[label = <Data linked with<br/>external dataset>]
node2 -> node3;
}}")
)
Note: a couple of efforts have been made to construct CONSORT diagrams:
https://github.com/higgi13425/ggconsort
https://github.com/tgerke/ggconsort
Diagram

Related

How to put background image to the plot in Rust plotters lib

I'm trying to draw car trips on a plane. I'm using Plotters library.
Here is some code example of trips' drawing procedure:
pub fn save_trips_as_a_pic<'a>(trips: &CarTrips, resolution: (u32, u32))
{
// Some initializing stuff
/// <...>
let root_area =
BitMapBackend::new("result.png", (resolution.0, resolution.1)).into_drawing_area();
root_area.fill(&WHITE).unwrap();
let root_area =
root_area.margin(10,10,10,10).titled("TITLE",
("sans-serif", 20).into_font()).unwrap();
let drawing_areas =
root_area.split_evenly((cells.1 as usize, cells.0 as usize));
for (index, trip) in trips.get_trips().iter().enumerate(){
let mut chart =
ChartBuilder::on(drawing_areas.get(index).unwrap())
.margin(5)
.set_all_label_area_size(50)
.build_ranged(50.0f32..54.0f32, 50.0f32..54.0f32).unwrap();
chart.configure_mesh().x_labels(20).y_labels(10)
.disable_mesh()
.x_label_formatter(&|v| format!("{:.1}", v))
.y_label_formatter(&|v| format!("{:.1}", v))
.draw().unwrap();
let coors = trip.get_points();
{
let draw_result =
chart.draw_series(series_from_coors(&coors, &BLACK)).unwrap();
draw_result.label(format!("TRIP {}",index + 1)).legend(
move |(x, y)|
PathElement::new(vec![(x, y), (x + 20, y)], &random_color));
}
{
// Here I put red dots to see exact nodes
chart.draw_series(points_series_from_trip(&coors, &RED));
}
chart.configure_series_labels().border_style(&BLACK).draw().unwrap();
}
}
What I got now on Rust Plotters:
So, after drawing it in the 'result.png' image file, I struggle to understand these "lines", because I don't see the map itself. I suppose, there is some way in this library to put a map "map.png" in the background of the plot. If I would use Python, this problem will be solved like this:
# here we got a map image;
img: Image.Image = Image.open("map-image.jpg")
img.putalpha(64)
imgplot = plt.imshow(img)
# let's pretend that we got our map size in pixels and coordinates
# just in right relation to each other.
scale = 1000
x_shift = 48.0
y_shift = 50.0
coor_a = Coordinate(49.1, 50.4)
coor_b = Coordinate(48.9, 51.0)
x_axis = [coor_a.x, coor_b.x]
x_axis = [(element-x_shift) * scale for element in x_axis]
y_axis = [coor_a.y, coor_b.y]
y_axis = [(element-y_shift) * scale for element in y_axis]
plt.plot(x_axis, y_axis, marker='o')
plt.show()
Desired result on Python
Well, that's easy on Python, but I got no idea, how to do similar thing on Rust.

diagrammeR/GraphViz - justify node text if node text is multi-line substitution label

I am using substitution labels (##) with diagrammeR and Graphviz syntax. I have seen previous questions about justification of node labels such as this one when the labels are in-line text, but I am wondering how to justify node text generated from a multi-row substitution label. More specifically, for the label in the reproducible example below, I want the ‘main’ column, meaning the first and third rectangle labels, to remain centered, but multi-line node labels such as the rightmost rectangle to be left justified (the value as well as the subvalues). Since I specify line breaks in the substitution labels, I tried using double backslash \l instead of \n without success.
Additionally, I would like to bold the headers (in the reproducible example, the first value, second value, and third value rows), but not bold any subvalues.
Any help would be greatly appreciated. Thank you!
library(DiagrammeR)
library(DiagrammeRsvg)
a <- 100
x <- 50
b <- 30
d <- 20
grViz("
digraph a_nice_graph {
node[fontname = Helvetica, shape = box, width = 4, fontcolor = darkslategray]
firstvalue[label = '##1']
secondvalue[label = '##2']
thirdvalue[label = '##3']
blank[label = '', width = 0.01, height = 0.01]
{ rank = same; blank secondvalue }
firstvalue -> blank [dir = none]
blank -> secondvalue[minlen = 9]
blank -> thirdvalue
}
[1]: paste0('First value (n = ', a, ')')
[2]: paste0('Second value (n = ', a-x, ')\\nSubvalue = ', b, '\\nSubvalue = ', d, '')
[3]: paste0('Third value (n = ', x, ')')
")

Control edge with neato layout with processmapR

I'm using the bupaR process mining suite and processmapR to plot my log as a process map but when I try to set a custom position (which force the graph to use a neato layout) the edge become almost staight and the edge value hard to read:
Default graph with no custom position:
With custom position:
I tried to use
positions <- data.frame(act = c("node1","node2","node 3","node 4","node 5","Start", "End"),
y = c(5,4,3,2,1,6,0),
x = c(1,2,3,4,5,0,6),
stringsAsFactors = F)
graph = process_map(log, fixed_node_pos = positions, render = F)
map = add_global_graph_attrs(graph,
attr = "splines",
value = "true",
attr_type = "graph")
render_graph(map)
But I could not find any attribute to change the way edge are displayed, like adding more curve to them
How can I fix this problem ?
Thanks
Try the following:
map = add_global_graph_attrs(graph,
attr = "splines",
value = "curved",
attr_type = "graph")

XPlot trying to visualize data

I am having a really hard time trying to visualize some data using f#. I am trying to achieve this on Linux environment using jupyter notebooks that I am running on localhost. I am following this article.
Everything seems to be fine, I managed to load all the needed script files, such as MathNet.Numerics and XPlot. I don't get any errors, my terminal is fine as well, kernel is in place. I wonder why am I not getting any graph reprisentation after I run my code?
It only says that I get back Xplot.Plotly.PlotlyChart, what about the actual graph? I am not sure if this would be enough to help me out, if not, let me know and will fill in other information. I tried different browsers as well, didn't help.
Actual code:
#load #"<project-root>/.paket/load/net45/MathNet.Numerics.fsx"
#load #"<project-root>/.paket/load/net45/MathNet.Numerics.FSharp.fsx"
#load #"<project-root>/.paket/load/net45/XPlot.Plotly.fsx"
open System
open System.Linq
open MathNet.Numerics.Distributions
open MathNet.Numerics.LinearAlgebra
open XPlot.Plotly
let n = 40
let nbsim = 1000
let lambda = 0.2
let randomSeed = 1111
let exponential = Exponential.Samples(new Random(randomSeed), lambda) |> Seq.take (n* nbsim) |> List.ofSeq
let m = Matrix<float>.Build.DenseOfRowMajor(nbsim, n, exponential)
let means = m.RowSums() / (float n)
means.Average()
let historyTrace =
Histogram(
x = means,
xbins =
Xbins(
start = 2.8,
``end`` = 7.75,
size = 0.08
),
marker =
Marker(
color = "yellow",
line =
Line(
color = "grey",
width = 1
)
),
opacity = 0.75,
name = "Exponental distribution"
) :> Trace
let meanTrace =
Scatter(
x = [5; 5],
y = [0; 60],
name = "Theorical mean"
) :> Trace
// Or plain historyTrace below
[historyTrace; meanTrace]
|> Chart.Plot
|> Chart.WithXTitle("Means")
|> Chart.WithYTitle("Frequency")
|> Chart.WithTitle("Distribution of 1000 means of exponential distribution")
Please note that #load statements include <project-root> placeholder. I am using Paket to generate scripts for #load.
This worked for me in the F# Azure Notebook.
Make sure to include this in a cell before you invoke the chart
#load "XPlot.Plotly.Paket.fsx"
#load "XPlot.Plotly.fsx"
open XPlot.Plotly
This is a quote from FSharp for Azure Notebooks:
Note that we had to #load two helper scripts in order to load the
assemblies we need and to enable Display to show our charts. The first
downloads and installs the required Paket packages, and the second
sets up Display support.
The key line for you is: #load "XPlot.Plotly.fsx"
That is the one that lets you display the chart in the notebook.
This is my code in the Azure notebook:
// cell 1
#load "XPlot.Plotly.Paket.fsx"
#load "XPlot.Plotly.fsx"
// cell 2
Paket.Package [ "MathNet.Numerics"
"MathNet.Numerics.FSharp" ]
#load "Paket.Generated.Refs.fsx"
// cell 3
open System
open System.Linq
open MathNet.Numerics.Distributions
open MathNet.Numerics.LinearAlgebra
open XPlot.Plotly
let n = 40
let nbsim = 1000
let lambda = 0.2
let randomSeed = 1111
let exponential = Exponential.Samples(new Random(randomSeed), lambda) |> Seq.take (n* nbsim) |> List.ofSeq
let m = Matrix<float>.Build.DenseOfRowMajor(nbsim, n, exponential)
...

Bokeh Colorbar Vertical title to right of colorbar?

I'm trying to do something that I'd normally consider trivial but seems to be very difficult in bokeh: Adding a vertical colorbar to a plot and then having the title of the colorbar (a.k.a. the variable behind the colormapping) appear to one side of the colorbar but rotated 90 degrees clockwise from horizontal.
From what I can tell of the bokeh ColorBar() interface (looking at both documentation and using the python interpreter's help() function for this element), this is not possible. In desperation I have added my own Label()-based annotation. This works but is klunky and displays odd behavior when deployed in a bokeh serve situation--that the width of the data window on the plot varies inversely with the length of the title colorbar's title string.
Below I've included a modified version of the bokeh server mpg example. Apologies for its complexity, but I felt this was the best way to illustrate the problem using infrastructure/data that ships with bokeh. For those unfamiliar with bokeh serve, this code snippet needs to saved to a file named main.py that resides in a directory--for the sake of argument let's say CrossFilter2--and in the parent directory of CrossFilter2 one needs to invoke the command
bokeh serve --show CrossFilter2
this will then display in a browser window (localhost:5006/CrossFilter2) and if you play with the color selection widget you will see what I mean, namely that short variable names such as 'hp' or 'mpg' result in a wider data display windows than longer variable names such as 'accel' or 'weight'. I suspect that there may be a bug in how label elements are sized--that their x and y dimensions are swapped--and that bokeh has not understood that the label element has been rotated.
My questions are:
Must I really have to go to this kind of trouble to get a simple colorbar label feature that I can get with little-to-no trouble in matplotlib/plotly?
If I must go through the hassle you can see in my sample code, is there some other way I can do this that avoids the data window width problem?
import numpy as np
import pandas as pd
from bokeh.layouts import row, widgetbox
from bokeh.models import Select
from bokeh.models import HoverTool, ColorBar, LinearColorMapper, Label
from bokeh.palettes import Spectral5
from bokeh.plotting import curdoc, figure, ColumnDataSource
from bokeh.sampledata.autompg import autompg_clean as df
df = df.copy()
SIZES = list(range(6, 22, 3))
COLORS = Spectral5
# data cleanup
df.cyl = df.cyl.astype(str)
df.yr = df.yr.astype(str)
columns = sorted(df.columns)
discrete = [x for x in columns if df[x].dtype == object]
continuous = [x for x in columns if x not in discrete]
quantileable = [x for x in continuous if len(df[x].unique()) > 20]
def create_figure():
xs = df[x.value].tolist()
ys = df[y.value].tolist()
x_title = x.value.title()
y_title = y.value.title()
name = df['name'].tolist()
kw = dict()
if x.value in discrete:
kw['x_range'] = sorted(set(xs))
if y.value in discrete:
kw['y_range'] = sorted(set(ys))
kw['title'] = "%s vs %s" % (y_title, x_title)
p = figure(plot_height=600, plot_width=800,
tools='pan,box_zoom,wheel_zoom,lasso_select,reset,save',
toolbar_location='above', **kw)
p.xaxis.axis_label = x_title
p.yaxis.axis_label = y_title
if x.value in discrete:
p.xaxis.major_label_orientation = pd.np.pi / 4
if size.value != 'None':
groups = pd.qcut(df[size.value].values, len(SIZES))
sz = [SIZES[xx] for xx in groups.codes]
else:
sz = [9] * len(xs)
if color.value != 'None':
coloring = df[color.value].tolist()
cv_95 = np.percentile(np.asarray(coloring), 95)
mapper = LinearColorMapper(palette=Spectral5,
low=cv_min, high=cv_95)
mapper.low_color = 'blue'
mapper.high_color = 'red'
add_color_bar = True
ninety_degrees = pd.np.pi / 2.
color_bar = ColorBar(color_mapper=mapper, title='',
#title=color.value.title(),
title_text_font_style='bold',
title_text_font_size='20px',
title_text_align='center',
orientation='vertical',
major_label_text_font_size='16px',
major_label_text_font_style='bold',
label_standoff=8,
major_tick_line_color='black',
major_tick_line_width=3,
major_tick_in=12,
location=(0,0))
else:
c = ['#31AADE'] * len(xs)
add_color_bar = False
if add_color_bar:
source = ColumnDataSource(data=dict(x=xs, y=ys,
c=coloring, size=sz, name=name))
else:
source = ColumnDataSource(data=dict(x=xs, y=ys, color=c,
size=sz, name=name))
if add_color_bar:
p.circle('x', 'y', fill_color={'field': 'c',
'transform': mapper},
line_color=None, size='size', source=source)
else:
p.circle('x', 'y', color='color', size='size', source=source)
p.add_tools(HoverTool(tooltips=[('x', '#x'), ('y', '#y'),
('desc', '#name')]))
if add_color_bar:
color_bar_label = Label(text=color.value.title(),
angle=ninety_degrees,
text_color='black',
text_font_style='bold',
text_font_size='20px',
x=25, y=300,
x_units='screen', y_units='screen')
p.add_layout(color_bar, 'right')
p.add_layout(color_bar_label, 'right')
return p
def update(attr, old, new):
layout.children[1] = create_figure()
x = Select(title='X-Axis', value='mpg', options=columns)
x.on_change('value', update)
y = Select(title='Y-Axis', value='hp', options=columns)
y.on_change('value', update)
size = Select(title='Size', value='None',
options=['None'] + quantileable)
size.on_change('value', update)
color = Select(title='Color', value='None',
options=['None'] + quantileable)
color.on_change('value', update)
controls = widgetbox([x, y, color, size], width=200)
layout = row(controls, create_figure())
curdoc().add_root(layout)
curdoc().title = "Crossfilter"
You can add a vertical label to the Colorbar by plotting it on a separate axis and adding a title to this axis. To illustrate this, here's a modified version of Bokeh's standard Colorbar example (found here):
import numpy as np
from bokeh.plotting import figure, output_file, show
from bokeh.models import LogColorMapper, LogTicker, ColorBar
from bokeh.layouts import row
plot_height = 500
plot_width = 500
color_bar_height = plot_height + 11
color_bar_width = 180
output_file('color_bar.html')
def normal2d(X, Y, sigx=1.0, sigy=1.0, mux=0.0, muy=0.0):
z = (X-mux)**2 / sigx**2 + (Y-muy)**2 / sigy**2
return np.exp(-z/2) / (2 * np.pi * sigx * sigy)
X, Y = np.mgrid[-3:3:100j, -2:2:100j]
Z = normal2d(X, Y, 0.1, 0.2, 1.0, 1.0) + 0.1*normal2d(X, Y, 1.0, 1.0)
image = Z * 1e6
color_mapper = LogColorMapper(palette="Viridis256", low=1, high=1e7)
plot = figure(x_range=(0,1), y_range=(0,1), toolbar_location=None,
width=plot_width, height=plot_height)
plot.image(image=[image], color_mapper=color_mapper,
dh=[1.0], dw=[1.0], x=[0], y=[0])
Now, to make the Colorbar, create a separate dummy plot, add the Colorbar to the dummy plot and place it next to the main plot. Add the Colorbar label as the title of the dummy plot and center it appropriately.
color_bar = ColorBar(color_mapper=color_mapper, ticker=LogTicker(),
label_standoff=12, border_line_color=None, location=(0,0))
color_bar_plot = figure(title="My color bar title", title_location="right",
height=color_bar_height, width=color_bar_width,
toolbar_location=None, min_border=0,
outline_line_color=None)
color_bar_plot.add_layout(color_bar, 'right')
color_bar_plot.title.align="center"
color_bar_plot.title.text_font_size = '12pt'
layout = row(plot, color_bar_plot)
show(layout)
This gives the following output image:
One thing to look out for is that color_bar_width is set wide enough to incorporate both the Colorbar, its axes labels and the Colorbar label. If the width is set too small, you will get an error and the plot won't render.
As of Bokeh 0.12.10 there is no built in label available for colorbars. In addition to your approach or something like it, another possibility would be a custom extension, though that is similarly not trivial.
Offhand, a colobar label certainly seems like a reasonable thing to consider. Regarding the notion that it ought to be trivially available, if you polled all users about what they consider should be trivially available, there will be thousands of different suggestions for what to prioritize. As is very often the case in the OSS world, there are far more possible things to do, than there are people to do them (less than 3 in this case). So, would first suggest a GitHub Issue to request the feature, and second, if you have the ability, volunteering to help implement it. Your contribution would be valuable and appreciated by many.

Resources