techniq / layerchart

Composable Svelte chart components to build a wide range of visualizations
https://www.layerchart.com
MIT License
663 stars 12 forks source link

Stacked Barchart has single colour #279

Closed michaelrommel closed 2 weeks ago

michaelrommel commented 2 weeks ago

Hello,

I tried to use a stacked barchart in one of my projects. It works, but the stacked bars all have the same color, even though the tooltip correctly maps the colors. If I add a argument to the Bar component with class="fill-gruvgreen" the bars are all green. But shouldn't be the bars automatically have a different colour? They all are at the moment mapped to surface-content.

image
<script>
    import {
        Axis,
        Chart,
        Highlight,
        Svg,
        Bars,
        Tooltip,
        groupStackData,
    } from "layerchart";
    import { scaleOrdinal, scaleBand } from "d3-scale";
    import { sum } from "d3-array";

    const longData = [
        { year: 2019, basket: 1, fruit: "apples", value: 3840 },
        { year: 2019, basket: 1, fruit: "bananas", value: 1920 },
        { year: 2019, basket: 2, fruit: "cherries", value: 960 },
        { year: 2019, basket: 2, fruit: "grapes", value: 400 },

        { year: 2018, basket: 1, fruit: "apples", value: 1600 },
        { year: 2018, basket: 1, fruit: "bananas", value: 1440 },
        { year: 2018, basket: 2, fruit: "cherries", value: 960 },
        { year: 2018, basket: 2, fruit: "grapes", value: 400 },

        { year: 2017, basket: 1, fruit: "apples", value: 820 },
        { year: 2017, basket: 1, fruit: "bananas", value: 1000 },
        { year: 2017, basket: 2, fruit: "cherries", value: 640 },
        { year: 2017, basket: 2, fruit: "grapes", value: 400 },

        { year: 2016, basket: 1, fruit: "apples", value: 820 },
        { year: 2016, basket: 1, fruit: "bananas", value: 560 },
        { year: 2016, basket: 2, fruit: "cherries", value: 720 },
        { year: 2016, basket: 2, fruit: "grapes", value: 400 },
    ];
    const stackedData = groupStackData(longData, {
        xKey: "year",
        stackBy: "fruit",
    });
    const colorKeys = [...new Set(longData.map((x) => x.fruit))];
    const keyColors = [
        "hsl(var(--gruvred))",
        "hsl(var(--gruvgreen))",
        "hsl(var(--gruvblue))",
        "hsl(var(--gruvorange))",
    ];
    // const seriesColors = ["#00e047", "#7ceb68", "#b7f486", "#ecfda5"];
</script>

<div class="col-span-12 min-h-[300px] max-h-[300px] bg-gruvdbg0 block">
    <Chart
        data={stackedData}
        x="year"
        xScale={scaleBand().paddingInner(0.4).paddingOuter(0.1)}
        y="values"
        yNice={4}
        z="fruit"
        zScale={scaleOrdinal()}
        zDomain={colorKeys}
        zRange={keyColors}
        padding={{ left: 80, top: 80, bottom: 50, right: 80 }}
        tooltip={{ mode: "band" }}
        let:zScale
    >
        <Svg>
            <Axis placement="left" grid rule />
            <Axis placement="bottom" rule />
            <Bars radius={0} strokeWidth={1} />
            <Highlight area />
        </Svg>
        <Tooltip.Root let:data>
            <Tooltip.Header>{data.year}</Tooltip.Header>
            <Tooltip.List>
                {#each data.data as d}
                    <Tooltip.Item
                        label={d.fruit}
                        value={d.value}
                        color={zScale?.(d.fruit)}
                        format="integer"
                        valueAlign="right"
                    />
                {/each}

                <Tooltip.Separator />

                <Tooltip.Item
                    label="total"
                    value={sum([...data.data], (d) => d.value)}
                    format="integer"
                    valueAlign="right"
                />
            </Tooltip.List>
        </Tooltip.Root>
    </Chart>
</div>

I have now spent a couple of hours in investigating it, but I am at a loss here, no idea, where the colours would be mapped in the Bars component. It may have to do with the Context, but I am not sure. I also think the documentation and examples are outdated. I had to change c, cScale, cDomain and cRange to the z-equivalent, see above.

I would appreciate a pointer, what I am doing wrong.

Thanks,

Michael.

techniq commented 2 weeks ago

Hey @michaelrommel, thanks for checking out LayerChart.

I would recommend using the newer, simplified <BarChart> with series support, which greatly simplifies this use case. Take a look at this example, and other related examples

techniq commented 2 weeks ago

<BarChart> uses all those components under the hood (Chart, Tooltip, etc), but orchestrates them to simplify most use cases. It also provides many extension points includes props, named slots (marks, tooltip, etc) and default slot if you want to build most of the chart manually but with preconfigured defaults.

Feel free to hop on the Discord to troubleshoot as well

michaelrommel commented 2 weeks ago

It can be so easy, if you know, which component to take...

image

Thanks for the quick help. I was standing in my own way. Normally I like to figure things out myself, but this one question would have saved me a lot of time... I'll have a look at the discord as well. Thanks!!

michaelrommel commented 2 weeks ago

Still need to style that somehow, because I see the numbers on the Axis are now cut off...

techniq commented 2 weeks ago

You can add left (y) and bottom (x) padding to account for the Axis.

<BarChart padding={{ left: 16, bottom: 16 }}>

BarChart will setup some default padding for you (based on axis and legend), but it kind of looks like it's being overridden here, but could be wrong. It's not uncommon to set your own padding to, based on the length of axis label values.

michaelrommel commented 2 weeks ago

Yeah, I was just looking at that code... Good to know, how I can add that padding. Is there also a way to increase the font size?

techniq commented 2 weeks ago

The easiest way is to use props={{ xAxis: ..., yAxis: ... }} to pass props/classes to the underlying <Axis> instance

<BarChart
  data={dateSeriesData}
  x="date"
  y="value"
  props={{ xAxis: { tickLabelProps: { class: 'text-sm' } }, yAxis: ... }}
/>

You can also override the axis slot and pass your own Axis instance. See the Axis docs for examples of different props/use cases, including the rotated/styled example