plotly / plotly.js

Open-source JavaScript charting library behind Plotly and Dash
https://plotly.com/javascript/
MIT License
17.07k stars 1.87k forks source link

add stacked grouped bars #4914

Closed nicolaskruchten closed 2 days ago

nicolaskruchten commented 4 years ago

We need to permit mixed stacked and grouped bars.

We will implement this with a new bar.stackgroup attribute as spec'ed here: https://github.com/plotly/plotly.js/issues/3402

nicolaskruchten commented 4 years ago

See also related https://github.com/plotly/plotly.js/issues/3614, https://github.com/plotly/plotly.js/issues/1835, https://github.com/plotly/plotly.js/issues/1015. I'd like to keep the current issue as the master tracking one for this work, so we can close out others and refer to this one as needed.

baumstan commented 4 years ago

I see this issue is marked as closed but I'm having trouble tracking resolution. Could you point me in the right direction, please?

See also related #3614, #1835, #1015. I'd like to keep the current issue as the master tracking one for this work, so we can close out others and refer to this one as needed.

nicolaskruchten commented 4 years ago

This issue is actually still open :)

valentinstn commented 3 years ago

Is there any update on this issue?

nicolaskruchten commented 3 years ago

This issue is going to be worked on in the next couple of months.

valentinstn commented 3 years ago

Thanks @nicolaskruchten

anyadmitrieva commented 3 years ago

Hello, could you please tell me if this feature is still expected to be available soon?

nicolaskruchten commented 3 years ago

We will be working on it in the near future but I can't publicly commit to a timeline at this time.

tanya-pix commented 3 years ago

We will be working on it in the near future but I can't publicly commit to a timeline at this time.

Are there any updates on this?

RenaudLN commented 3 years ago

For anyone trying to do this, whilst waiting for the official feature you can get the same outcome by creating the second stackgroup on another yaxis and playing with the traces offsets. image

araichev commented 3 years ago

@RenaudLN or anyone else, can you show us code to produce the figure above or similar?

Alexander-Serov commented 2 years ago

@RenaudLN Yes, could you please share the code for your plot?

RenaudLN commented 2 years ago

@araichev @Alexander-Serov Here is a minimum reproducible example with Python. Note that the offsets are suited to this monthly data and will have to be adjusted with other inputs.

import numpy as np
import pandas as pd
import plotly.graph_objects as go

# Create dummy data indexed by month and with multi-columns [product, revenue]
index = pd.date_range("2020", "2021", freq="MS", closed="left")
df = pd.concat(
    [
        pd.DataFrame(
            np.random.rand(12, 3) * 1.25 + 0.25,
            index=index,
            columns=["Revenue1", "Revenue2", "Revenue3"]
        ),
        pd.DataFrame(
            np.random.rand(12, 3) + 0.5,
            index=index,
            columns=["Revenue1", "Revenue2", "Revenue3"]
        ),
    ],
    axis=1,
    keys=["Product1", "Product2"]
)

# Create a figure with the right layout
fig = go.Figure(
    layout=go.Layout(
        height=600,
        width=1000,
        barmode="relative",
        yaxis_showticklabels=False,
        yaxis_showgrid=False,
        yaxis_range=[0, df.groupby(axis=1, level=0).sum().max().max() * 1.5],
       # Secondary y-axis overlayed on the primary one and not visible
        yaxis2=go.layout.YAxis(
            visible=False,
            matches="y",
            overlaying="y",
            anchor="x",
        ),
        font=dict(size=24),
        legend_x=0,
        legend_y=1,
        legend_orientation="h",
        hovermode="x",
        margin=dict(b=0,t=10,l=0,r=10)
    )
)

# Define some colors for the product, revenue pairs
colors = {
    "Product1": {
        "Revenue1": "#F28F1D",
        "Revenue2": "#F6C619",
        "Revenue3": "#FADD75",
    },
    "Product2": {
        "Revenue1": "#2B6045",
        "Revenue2": "#5EB88A",
        "Revenue3": "#9ED4B9",
    }
}

# Add the traces
for i, t in enumerate(colors):
    for j, col in enumerate(df[t].columns):
        if (df[t][col] == 0).all():
            continue
        fig.add_bar(
            x=df.index,
            y=df[t][col],
            # Set the right yaxis depending on the selected product (from enumerate)
            yaxis=f"y{i + 1}",
            # Offset the bar trace, offset needs to match the width
            # The values here are in milliseconds, 1billion ms is ~1/3 month
            offsetgroup=str(i),
            offset=(i - 1) * 1000000000,
            width=1000000000,
            legendgroup=t,
            legendgrouptitle_text=t,
            name=col,
            marker_color=colors[t][col],
            marker_line=dict(width=2, color="#333"),
            hovertemplate="%{y}<extra></extra>"
        )

fig.show()

image

lyndon-bird commented 1 year ago

Is there any update with this issue? Would be great to have implemented!

gusblack-tt commented 6 months ago

Is there any update on this feature? Looks like it has been asked for in one form or another for 8 years

clarson0 commented 6 months ago

@araichev Is this different than multicategory? I recently made a similar chart to the screenshot above

`function onlyUnique(value, index, array) { return array.indexOf(value) === index; }

// Example data for the x-axis, normally you would fetch this from your data set var categories = [ ["2023-12-01", "Dog"], ["2023-12-02", "Cat"], ["2023-12-03", "Mouse"], ["2023-12-04", "Dog"], ["2023-12-05", "Cat"], ["2023-12-06", "Mouse"], ["2023-12-07", "Dog"], ["2023-12-08", "Cat"], ["2023-12-09", "Mouse"] ];

let dates = categories.map(c => { return c[0]; })

let animals = categories.map(c => { return c[1] })

dates = dates.filter(onlyUnique); animals = animals.filter(onlyUnique);

let xDates = []; dates.forEach(d => { animals.forEach(c => { xDates.push(d); }) })

let xAnimals = []; dates.forEach(d => { animals.forEach(c => { xAnimals.push(c); }) })

var yValuesA = xAnimals.map(() => Math.random() 0.95 + 0.05); var yValuesB = xAnimals.map(() => Math.random() 0.95 + 0.05);

var data = [ { "marker": { "color": [ "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)", "rgb(25, 80, 22)", "rgb(15, 60, 90)", "rgb(113, 13, 14)", "rgb(127, 63, 0)" ], "opacity": 0.5 }, "name": "Test data A", "showlegend": false, "type": "bar", "x": [ xDates, xAnimals ], "y": yValuesA, }, { "marker": { "color": 'darkblue', "opacity": 0.5 }, "name": "Test data B", "showlegend": false, "type": "bar", "x": [ xDates, xAnimals ], "y": yValuesB, }, ];

var layout = { paper_bgcolor: "lightblue", plot_bgcolor: "lightgrey", margin: { "t": 20, "r": 20, "b": 120, "l": 20, // "pad": 0 }, barmode: "stack", xaxis: { "anchor": "y", "type": "multicategory", "tickangle": "auto", "autotickangles": [ 90 ], "tickfont": { "size": 9 }, "showdividers": true, "tickson": "boundaries", "ticklen": 14, "dividercolor": "grey", "dividerwidth": 1, "range": [ -0.5, 83.5 ], "autorange": true }, yaxis: { type: 'linear', range: [0,1], } }

// console.log('data', data); // console.log('layout', layout); // console.log('xDates', xDates); // console.log('xAnimals', xAnimals)

Plotly.newPlot('myDiv', data, layout);`

Screenshot 2024-05-07 at 2 15 39 PM
archmoj commented 2 days ago

Resolved in #7009.