plotly.js icon indicating copy to clipboard operation
plotly.js copied to clipboard

add stacked grouped bars

Open nicolaskruchten opened this issue 5 years ago • 16 comments
trafficstars

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 avatar Jun 10 '20 13:06 nicolaskruchten

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.

nicolaskruchten avatar Jun 10 '20 13:06 nicolaskruchten

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.

baumstan avatar Jul 30 '20 13:07 baumstan

This issue is actually still open :)

nicolaskruchten avatar Jul 30 '20 17:07 nicolaskruchten

Is there any update on this issue?

valentinstn avatar Dec 11 '20 12:12 valentinstn

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

nicolaskruchten avatar Dec 11 '20 13:12 nicolaskruchten

Thanks @nicolaskruchten

valentinstn avatar Dec 11 '20 14:12 valentinstn

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

anyadmitrieva avatar Mar 15 '21 18:03 anyadmitrieva

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

nicolaskruchten avatar Mar 15 '21 18:03 nicolaskruchten

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?

tanya-pix avatar Jun 17 '21 06:06 tanya-pix

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

RenaudLN avatar Jul 13 '21 06:07 RenaudLN

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

araichev avatar Oct 17 '21 01:10 araichev

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

Alexander-Serov avatar Feb 07 '22 18:02 Alexander-Serov

@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

RenaudLN avatar Feb 07 '22 23:02 RenaudLN

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

lyndon-bird avatar Apr 26 '23 22:04 lyndon-bird

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

gusblack-tt avatar Apr 23 '24 14:04 gusblack-tt

@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

clarson0 avatar May 07 '24 21:05 clarson0

Resolved in #7009.

archmoj avatar Nov 08 '24 20:11 archmoj