2

I am trying to create a graph with 10 different lines with different colours and markers using Plotly express. Something similar to this:

enter image description here

I can create a nice looking graph with different colours using the px.line function as the documentation suggests. My code looks like this:

import plotly.express as px
import numpy as np
import pandas as pd

rand_elems = []
for i in range(10):
    rand_elems.append(np.random.randn(25))

data = pd.DataFrame(rand_elems)

px.line(data_frame=data.T)

and my line graph looks like this:

enter image description here

where each variable is a (25,) numpy array with random values from the standard normal distribution (created with np.random.randn(25)).

Is there a way I can add different styles to each line? Other plotting libraries are also welcome as I couldn't find a solution for this in Plotly's documentation.

I understand there is a limit of line styles I could use. Maybe I could cycle through them and the colours? What would be a good solution for this?

EDIT: The graph purpose is solely to show that the signals are random and within the standard normal distribution limits.

2
  • @Mr.T Thanks for the matplotlib resource! I have added an edit explicating what I want to achieve with this graph. Commented Nov 17, 2020 at 19:11
  • Have a look at this page of the Plotly docs regarding line graphs. There are a few different example here. Commented Nov 17, 2020 at 19:15

3 Answers 3

5

I have taken @vestland 's code and adapted and fixed to a simpler version that achieves exactly what I needed. The idea is to use SymbolValidator() from plotly.validators.scatter.marker as noted in @vestland 's answer. One could also add a random factor to this list for more distinct results.

sample running code:

import plotly.express as px
from itertools import cycle
from plotly.validators.scatter.marker import SymbolValidator

# data
df = px.data.gapminder()
df = df[df['country'].isin(['Canada', 'USA', 'Norway', 'Sweden', 'Germany'])]

# plotly
fig = px.line(df, x='year', y='lifeExp',
              color='country',
              line_dash = 'continent')

raw_symbols = SymbolValidator().values
# Take only the string values which are in this order.
symbols_names = raw_symbols[::-2]
markers = cycle(symbols_names)

# set unique marker style for different countries
fig.update_traces(mode='lines+markers')
for d in fig.data:
    d.marker.symbol = next(markers)
    d.marker.size = 10
fig.show()

This code produces the following graph: enter image description here Thanks a lot for the insights @vestland I wonder if Plotly could make this a parameter option in the next versions.

Update:

If your DataFrame does not have an aggregation column to use for the line_dash parameter like mine, you can also cycle through a list with line styles and overwrite them using `fig.data.line["dash"] as presented below:

  • Code to generate data and import packages.
import numpy as np
import random
import plotly.express as px
import numpy as np
import pandas as pd
from itertools import cycle
from random import shuffle
from plotly.validators.scatter.marker import SymbolValidator
from plotly.validators.scatter.line import DashValidator

rand_elems = []
for i in range(10):
    rand_elems.append(np.random.randn(25))

data = pd.DataFrame(rand_elems)
data.index = [f"Signal {i}" for i in range(10)]
  • Code to generate different marker and line styles for each column in the DataFrame.
line_styles_names = ['solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot']
line_styles = cycle(line_styles_names)

fig = (px.line(data_frame=data.T, 
              color_discrete_sequence=px.colors.qualitative.Bold_r,
              title="First 10 Signals Visualization",
             ))

symbols_names = list(set([i.replace("-open", "").replace("-dot", "") for i in SymbolValidator().values[::-2]]))
shuffle(symbols_names)
markers = cycle(symbols_names)

_ = fig.update_traces(mode='lines+markers')
for d in fig.data:
    d.line["dash"] = next(line_styles)
    d.marker.symbol = next(markers)
    d.marker.size = 10
fig.show()

with this, the result should look similar to this, which is exactly what I was looking for. enter image description here

Sign up to request clarification or add additional context in comments.

Comments

3

px.line is perfect for datasets of high diversity, like different categories for an array of countries accross several continents because you can distinguish the catgories using arguments like color = 'country, line_dash = 'continent to assign colors and shapes to them. Here's an example using a subset the built-in dataset px.data.gapminder()

plot 1

enter image description here

code 1

import plotly.express as px
from plotly.validators.scatter.marker import SymbolValidator

# data
df = px.data.gapminder()
df = df[df['country'].isin(['Canada', 'USA', 'Norway', 'Sweden', 'Germany'])]

# plotly
fig = px.line(df, x='year', y='lifeExp',
              color='country',
              line_dash = 'continent')
fig.show()

But you seem to be interested in different shapes of the markers as well, and there does not seem to be a built-in functionality for those that are as easy to use as color and line_shape. So what follows is a way to cycle through available marker shapes and apply those to, for example, the different countries. You could of course define your own sequence by picking shapes from marker styles, like:

['arrow-bar-left', 'asterisk', 'arrow-right', 'line-ne', 'circle-cross', 'y-left']

But you can also grab a bunch of styles based on raw_symbols = SymbolValidator().values, refine those findings a bit and add those to, for example, country names.

Here's the result

enter image description here

And here's how you do it:

import plotly.express as px
from itertools import cycle

# data
df = px.data.gapminder()
df = df[df['country'].isin(['Canada', 'USA', 'Norway', 'Sweden', 'Germany'])]

# plotly
fig = px.line(df, x='year', y='lifeExp',
              color='country',
              line_dash = 'continent')

# retrieve a bunch of markers
raw_symbols = SymbolValidator().values
namestems = []
namevariants = []
symbols = []
for i in range(0,len(raw_symbols),3):
    name = raw_symbols[i+2]
    symbols.append(raw_symbols[i])
    namestems.append(name.replace("-open", "").replace("-dot", ""))
    namevariants.append(name[len(namestems[-1]):])
markers = cycle(list(set(namestems)))

# set unique marker style for different countries
fig.update_traces(mode='lines+markers')
for d in fig.data:
    d.marker.symbol = next(markers)
    d.marker.size = 10
fig.show()

3 Comments

Your solution looks great to me! Just what I was looking for! I will soon give it a try with my data (no need for you to do it) and accept your answer! Thank you very much @vestland
I'm sorry @vesland your code has some errors to it. I have fixed it, but I have found an easier way to achieve what I was looking for. Your code was very intuitive and led me to a solution. Thanks again! I'll be posting my version on an answer to this post.
@GabrielZiegler You're right, from plotly.validators.scatter.marker import SymbolValidator was missing. Runs fine now.
-1

The way I know is to create individual graph objects for each line you want to plot (each having it's own style). Then create a list of all the graph objects and pass it to the data argument of the go.Figure() function.

See this blog for an example.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.