Major and Minor Tickmarks with Plotly

Plotly: How to set values for major ticks / gridlines for timeseries on x-axis?

(updated answer for newer versions of plotly)

With newer versions of plotly, you can specify dtick = 'M1' to set gridlines at the beginning of each month. You can also format the display of the month through tickformat:

Snippet 1

fig.update_xaxes(dtick="M2",
tickformat="%b\n%Y"
)

Plot 1

Sample Image

And if you'd like to set the gridlines at every second month, just change "M1" to "M2"

Plot 2

Sample Image

Complete code:

# imports
import pandas as pd
import plotly.express as px

# data
df = px.data.stocks()
df = df.tail(40)
colors = px.colors.qualitative.T10

# plotly
fig = px.line(df,x = 'date',
y = [c for c in df.columns if c != 'date'],
template = 'plotly_dark',
color_discrete_sequence = colors,
title = 'Stocks',
)

fig.update_xaxes(dtick="M2",
tickformat="%b\n%Y"
)

fig.show()

Old Solution:

How to set the gridlines will depend entirely on what you'd like to display, and how the figure is built before you try to edit the settings. But to obtain the result specified in the question, you can do it like this.

Step1:

Edit fig['data'][series]['x'] for each series in fig['data'].

Step2:

set tickmode and ticktext in:

go.Layout(xaxis = go.layout.XAxis(tickvals = [some_values]
ticktext = [other_values])
)

Result:

Sample Image

Complete code for a Jupyter Notebook:

# imports
import plotly
import cufflinks as cf
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
import pandas as pd
import numpy as np
from IPython.display import HTML
from IPython.core.display import display, HTML
import copy
import plotly.graph_objs as go

# setup
init_notebook_mode(connected=True)
np.random.seed(123)
cf.set_config_file(theme='pearl')
#%qtconsole --style vim

# Random data using cufflinks
df = cf.datagen.lines()

# create figure setup
fig = df.iplot(asFigure=True, kind='scatter',
xTitle='Dates',yTitle='Returns',title='Returns')

# create df1 to mess around with while
# keeping the source intact in df
df1 = df.copy(deep = True)
df1['idx'] = range(0, len(df))

# time variable operations and formatting
df1['yr'] = df1.index.year
df1['mth'] = df1.index.month_name()

# function to replace month name with
# abbreviated month name AND year
# if the month is january
def mthFormat(month):
dDict = {'January':'jan','February':'feb', 'March':'mar',
'April':'apr', 'May':'may','June':'jun', 'July':'jul',
'August':'aug','September':'sep', 'October':'oct',
'November':'nov', 'December':'dec'}
mth = dDict[month]
return(mth)

# replace month name with abbreviated month name
df1['mth'] = [mthFormat(m) for m in df1['mth']]

# remove adjacent duplicates for year and month
df1['yr'][df1['yr'].shift() == df1['yr']] = ''
df1['mth'][df1['mth'].shift() == df1['mth']] = ''

# select and format values to be displayed
df1['idx'][df1['mth']!='']
df1['display'] = df1['idx'][df1['mth']!='']
display = df1['display'].dropna()
displayVal = display.values.astype('int')
df_display = df1.iloc[displayVal]
df_display['display'] = df_display['display'].astype('int')
df_display['yrmth'] = df_display['mth'] + '<br>' + df_display['yr'].astype(str)

# set properties for each trace
for ser in range(0,len(fig['data'])):

fig['data'][ser]['x'] = df1['idx'].values.tolist()
fig['data'][ser]['text'] = df1['mth'].values.tolist()
fig['data'][ser]['hoverinfo']='all'

# layout for entire figure
f2Data = fig['data']
f2Layout = go.Layout(
xaxis = go.layout.XAxis(
tickmode = 'array',
tickvals = df_display['display'].values.tolist(),
ticktext = df_display['yrmth'].values.tolist(),
zeroline = False)#,
)

# plot figure with specified major ticks and gridlines
fig2 = go.Figure(data=f2Data, layout=f2Layout)
iplot(fig2)

Some important details:


1. Flexibility and limitations with iplot():

This approach with iplot() and editing all those settings is a bit clunky, but it's very flexible with regards to the number of columns / variables in the dataset, and arguably preferable to building each trace manually like trace1 = go.Scatter() for each and every column in the df.

2. Why do you have to edit each series / trace?

If you try to skip the middle part with

for ser in range(0,len(fig['data'])):

fig['data'][ser]['x'] = df1['idx'].values.tolist()
fig['data'][ser]['text'] = df1['mth'].values.tolist()
fig['data'][ser]['hoverinfo']='all'

and try to set tickvals and ticktext directly on the entire plot, it will have no effect:

Sample Image

I think that's a bit weird, but I think it's caused by some underlying settings initiated by iplot().

3. One thing is still missing:

In order fot thie setup to work, the structure of ticvals and ticktext is [0, 31, 59, 90] and ['jan<br>2015', 'feb<br>', 'mar<br>', 'apr<br>'], respectively. This causes the xaxis line hovertext show the position of the data where ticvals and ticktext are empty:

Sample Image

Any suggestions on how to improve the whole thing is highly appreciated. Better solutions than my own will instantly receive Accepted Answer status!

Set major tick labels to be displayed as scientific notation in a Plotly plot in R

Let's just do it ourselves in JavaScript, if Plotly doesn't provide the needed functionality.

  • let's grab all ticks on the y-axis using d3

    ticks = Plotly.d3.selectAll('g.ytick');
  • the raw data is stored in data.x

  • then change the representation of each one to scientific notation

    Plotly.d3
    .selectAll('g.ytick')
    .each(function(data, i)
    {
    Plotly.d3.select(this)
    .select('text')
    .html(formatNumber(data.x, 2));
    })
    • finally inject all the code using htmlwidgets in our graph

      p <- onRender(p, javascript)

  • now it would be one-time only change, every time a user zooms or modifies the plot the changes would be lost. In order to make sure that changes are applied every time the code is wrapped in a function fix_ticks() and added to Plotly's plotly_afterplot event (el is the htmlwidget element)

    el.on('plotly_afterplot', fix_ticks);

Update

If you want to change the format of the scientific notation, you could write your function, e.g.

function formatNumber(num, desiredLength)
{
num = num.toExponential().toUpperCase();
var r = /(\\d*)([E][-+])(\\d*)/;
var fields = r.exec(num);
if (fields !== null && fields.length > 3)
{
return fields[1] + fields[2] + fields[3].padStart(desiredLength, '0');
}
else
{
return num;
}
}

and then call it for each tick

ticks.forEach(function(tick) 
{
var num = parseInt(tick[0].innerHTML);
tick[0].innerHTML = formatNumber(num, 2);
})

Note: this might not work in RStudio but shows up correctly in your browser after saving the output.


Complete code

library(plotly)
library(htmlwidgets)

p <- plot_ly(x = mtcars$mpg , y = mtcars$disp) %>%
add_lines()

javascript <- "
function(el, x)
{
function fixTicks()
{

Plotly.d3
.selectAll('g.ytick')
.each(function(data, i)
{
Plotly.d3.select(this)
.select('text')
.html(formatNumber(data.x, 2));
})
}

function formatNumber(num, desiredLength)
{
num = num.toExponential().toUpperCase();
var r = /(\\d*)([E][-+])(\\d*)/;
var fields = r.exec(num);
if (fields !== null && fields.length > 3)
{
return fields[1] + fields[2] + fields[3].padStart(desiredLength, '0');
}
else
{
return num;
}
}

el.on('plotly_afterplot', fixTicks);
}"

p <- onRender(p, javascript)
p

How to set minor ticks in plotly

Please see below code for an example. You will need to use the minor attribute to set this. The length is controlled by ticklen, color by tickcolor, number of ticks by nticks, position (inside/outside) by minor_ticks and so on. More information is available here.

Code

import plotly.express as px
import pandas as pd

df = px.data.tips()
fig = px.scatter(df, x="total_bill", y="tip", color="sex")
fig.update_xaxes(minor=dict(ticklen=6, tickcolor="black", tickmode='auto', nticks=10, showgrid=True))
fig.update_yaxes(minor_ticks="inside")
fig.show()

Sample Image

How to set minor ticks in plotly

Please see below code for an example. You will need to use the minor attribute to set this. The length is controlled by ticklen, color by tickcolor, number of ticks by nticks, position (inside/outside) by minor_ticks and so on. More information is available here.

Code

import plotly.express as px
import pandas as pd

df = px.data.tips()
fig = px.scatter(df, x="total_bill", y="tip", color="sex")
fig.update_xaxes(minor=dict(ticklen=6, tickcolor="black", tickmode='auto', nticks=10, showgrid=True))
fig.update_yaxes(minor_ticks="inside")
fig.show()

Sample Image

How to remove the axes tick marks in plots from plotly?

  • you have not indicated how you are building the sub-plots
  • you can make all axis invisible with fig.update_layout({ax:{"visible":False, "matches":None} for ax in fig.to_dict()["layout"] if "axis" in ax})
  • full example code:
import numpy as np
import pandas as pd
import plotly.express as px

L = 100
df = pd.DataFrame(
{
"lin": np.linspace(0, 100, L),
"cos": np.cos(np.linspace(0, np.pi, L)),
"sin": np.sin(np.linspace(0, np.pi, L)),
"tan": np.tan(np.linspace(0, np.pi, L)),
"char": np.random.choice(list("ABCDEF"), L),
"rand": np.random.uniform(1,50, L)
}
)

fig = px.scatter(
df.stack().reset_index(), x="level_0", y=0, facet_col="level_1", facet_col_wrap=3
)
fig.update_layout({ax:{"visible":False, "matches":None} for ax in fig.to_dict()["layout"] if "axis" in ax})
  • matrix of all columns against all columns. Hide tick marks
px.scatter_matrix(df).update_layout({ax:{"tickmode":"array","tickvals":[]} for ax in fig.to_dict()["layout"] if "axis" in ax})

Logarithmic axis with modified ticks

There is no need to start with ggplot2. You can do this directly in plotly:

library(dplyr)
library(tidyr)
library(plotly)

dat %>%
filter(size >= 0.2) %>%
pivot_longer(-size) %>%
plot_ly(x = ~size, y = ~value, color = ~name, type = 'scatter', mode = 'lines') %>%
layout(xaxis = list(type = "log",
tickvals = as.list(c(seq(0.2,1,0.2), seq(2,10,2)))))

Sample Image

Change tick text in Plotly Surface Plot

I was missing an outer dictionary containing xaxis and yaxis, solution below:

data = [go.Surface(z=df.values.tolist(), colorscale='Blackbody')]
layout = go.Layout(
scene=dict(
xaxis=dict(
tickmode = "Array",
ticktext = Hours,
tickvals = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]
),
yaxis=dict(
tickmode = "Array",
ticktext = Weekdays,
tickvals = [0,1,2,3,4,5,6]
)
)
)
fig = go.Figure(data=data, layout=layout)
plotly.offline.plot(fig, filename='test.html')


Related Topics



Leave a reply



Submit