Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single stacked bar chart #884

Closed
subarroca opened this issue Apr 16, 2020 · 14 comments · Fixed by #988
Closed

Single stacked bar chart #884

subarroca opened this issue Apr 16, 2020 · 14 comments · Fixed by #988

Comments

@subarroca
Copy link
Contributor

Feature Request

Summary

Horizontal stacked bar chart to display multiple values of the same group

Feature Description

  • Show all the values in a stack filling the whole width
  • Show legend
  • Show separate overlay for each segment
  • Clicking on legend should disable one of the segments and hide it, making the rest of the chart to occupy 100%
  • Clicking on segment should select it, paint it in selection color and output the selection

Attachments

https://demo.dev.dynatracelabs.com/ui/user-sessions/-15832438029561I824BXYQV8TZOYSTZUSOXWK1WONTSJ6?gtf=2020-04%20to%202020-05&gf=all
image

@ffriedl89
Copy link
Collaborator

@subarroca I've talked to a couple of people in the past about such a component - so i feel this would be a good addition to the library.
Please create an API proposal first before starting with the implementation.

Initial thoughts & questions:

  • Legend needs to be optional
  • Maybe the bar icon in the legend is not the correct one here.
  • tooltip should work similar to other charts and get the context provided in the ng-template.
  • We need to make sure not all elements can be hidden - always at least one element needs to be present.
  • What is the selection color? The chart should be in themecolors as long as we don't have more than 3 metrics - the same as other charts. Where is the selection color coming from? we don't have that in the theme.

@ffriedl89 ffriedl89 added the help wanted Extra attention is needed label Apr 16, 2020
@tomheller
Copy link
Collaborator

The primary challenge that we have faced in the past with this one, is that this needs to work in a connected manner (in table columns, for example).
The challenge here is that a specific metric should get the same color in all connected instances, i.e.
Row 1 shows: metricA blue, metricB red, metricC yellow
Row2 shows: metricA blue, metricC yellow

This works a little bit against our current system of coloring a chart, that's why we are still having trouble with this one.

@ffriedl89
Copy link
Collaborator

Maybe we need to leave this up to the developer to colorize the same metric in rows the same way. Auto coloring for simple usecases and for connected ones, colors need to be set on the series.

@subarroca
Copy link
Contributor Author

subarroca commented Apr 16, 2020

<dt-single-stacked-bar
  [series]="series"
  [selected]="selected"
  (selectedChange)="onSelect($event)"
  valueDisplayMode="percent"
  [visibleLegend]="false"
  >
  <div dtSingleStackedBarOverlay>...</div>
</dt-single-stacked-bar>

inputs

  • series: DtSingleStackedBarNode[] Nodes of information to be displayed
  • selected: DtSingleStackedBarNode Node to be selected
  • valueDisplayMode: DtChartValueMode Format of display for values. Default DtChartValueMode.ABSOLUTE
  • visibleLegend: boolean Wether legend should be shown or not. Default: true

outputs

  • selectedChange: EventEmitter Selected DtSingleStackedBarNode

content projection

  • dtSingleStackedBarOverlay

models

interface DtSingleStackedBarNode {
  label: string;
  value: number;
  color?: DtColors | string;
}
  • label Label to be shown in the chart
  • value Absolute or relative number to be represented in the chart
  • color Optional. If color should override the assigned one, set it here
export enum DtChartValueMode {
  ABSOLUTE = 'absolute',
  PERCENT = 'percent',
}

colors

  • should follow theme if 3 or less
  • should follow generic chart palette if more than 3
  • color is overridden if user indicates one
  • selected color should be turquoise

legend

  • should display a common bullet
  • should display label and value

TBD

  • what happens when all nodes are disabled?

@tomheller
Copy link
Collaborator

Hi @subarroca!
The API proposal looks good so far. A couple of things I want to discuss.

Inputs

visibleLegend: boolean Whether legend should be shown or not. Default: true

I think we should consider connecting the SingleStackedBarChart to the dt-legend and let the consumer define where the legend is going to be rendered. I'm thinking about the usecase of a SingleStackedBarChart within a table-row, where at the end of the table itself, it shows the legend for the chart.
This however, results in a necessary communication between the charts, to sync up their series and colors, as mentioned in #884 (comment). A workaround would be to not have the series not automatically follow the chart colors, but let the user define a chart color.

Models

value Absolute or relative number to be represented in the chart

Please use a union type of 'percent' | 'absolute' as the value instead of the enum.

@tomheller tomheller added this to the Removing highcharts milestone Apr 28, 2020
@subarroca
Copy link
Contributor Author

Hi @tomheller! I made some changes to the proposal to make it fit better.
I'm using DtSingleStackedBarChartValueDisplayMode which is

export type DtSingleStackedBarChartValueDisplayMode =
  | 'none'
  | 'absolute'
  | 'percent';

Does this work for you or do you prefer not to have the type?

Also I've added selectable because not in every case you want to use it as a selectable chart.

Colors are automatically fetched with the getDtChartColorPalette but if any node has a color it just overrides the assigned one.

About the use of the legend. I've put that in the template. Do I need to connect it or expose the legend somehow?

@tomheller
Copy link
Collaborator

tomheller commented Apr 28, 2020

Does this work for you or do you prefer not to have the type?

This works perfectly for me, 👍

Also I've added selectable because not in every case you want to use it as a selectable chart.

This works for me as well, can you reflect the changes in your comment as well, so we have an up to date and single source here #884 (comment)

About the use of the legend. I've put that in the template. Do I need to connect it or expose the legend somehow?

The point i was trying to make is, that multiple charts can share a single legend. That's why I don't think we can embed the legend within the chart itself, but need to do something like this. Imagine a table or any other sort of repeating data-representation, where the rows are semantically connected.

<dt-dummy-table>
  <dt-dummy-row *ngFor="let row of rows">
     <dt-single-stacked-bar [series]="row.series" legend="dummyTableLegend">
     </dt-single-stacked-bar>
  </dt-dummy-row>
</dt-dummy-table>
<dt-single-stacked-bar-legend #dummyTableLegend>
  <!-- ... ? -->
</dt-single-stacked-bar-legend>

This way you would not want to render the legend within every row, but at the end of the table, collecting all series points and displaying their icons and labels.
And this is the reason, why it might not be possible to pull the chart-colors automatically by chart, as they need to have the same colors throughout all charts within the table for the same series. Even if, for example in row 7, one series is missing.

I hope this is understandable?

@subarroca
Copy link
Contributor Author

Yes, totally understandable. I thought you wanted to interact with the legend and hide or show the elements in the chart.

I'll update the definition in a while

@subarroca
Copy link
Contributor Author

<dt-single-stacked-bar
  [series]="series"
  [selected]="selected"
  (selectedChange)="onSelect($event)"
  valueDisplayMode="percent"
  [visibleLegend]="false"
  >
  <div dtSingleStackedBarOverlay>...</div>
</dt-single-stacked-bar>

inputs

  • series: DtSingleStackedBarChartNode[] Nodes of information to be displayed
  • selected: DtSingleStackedBarChartNode Node to be selected
  • selectable: boolean Allow selections to be made on chart. Default: false
  • valueDisplayMode: DtSingleStackedBarChartValueDisplayMode Format of display for values. Default: none
  • visibleLegend: boolean Wether legend should be shown or not. Default: true

outputs

  • selectedChange: EventEmitter Selected DtSingleStackedBarChartNode

content projection

  • dtSingleStackedBarOverlay

models

interface DtSingleStackedBarChartNode {
  label: string;
  value: number;
  color?: DtColors | string;
}
  • label Label to be shown in the chart
  • value Absolute or relative number to be represented in the chart
  • color Optional. If color should override the assigned one, set it here
export type DtSingleStackedBarChartValueDisplayMode =
  | 'none'
  | 'absolute'
  | 'percent';

colors

  • should follow theme if 3 or less
  • should follow generic chart palette if more than 3
  • color is overridden if user indicates one
  • selected color should be turquoise

legend

  • should display a common bullet
  • should display label and value
  • on click it should hide the slice
  • last visible node cannot be hidden

@tomheller
Copy link
Collaborator

Maybe, if we think about connecting multiple ones of the single-stacked-bar-charts, something like a max input would be helpful. If none is given, you can calculate the max value as the sum of the passes series, but if you have multiple ones, you may want to have them in the same scale to be comparable.

This and the external legend is probably not something we need to tackle right away, because I think this can be extended as an option afterwards without a breaking change.

@subarroca
Copy link
Contributor Author

subarroca commented May 4, 2020

After the suggestions and considering that this component could be reusable for multiple series I decided to upgrade it and not call it single anymore. Single would be just a case.

<dt-stacked-bar
  [series]="series"
  [selectable]="selectable"
  [(selected)]="selected"
  [valueDisplayMode]="valueDisplayMode"
  [direction]="direction"
  [labelPosition]="labelPosition"
  [legendPosition]="legendPosition"
  [legends]="legends"
  [fillMode]="fillMode"
  [maxBarSize]="maxBarSize"
  [max]="max"
  >
  <ng-template dtStackedBarChartOverlay let-tooltip>
    <strong>{{ tooltip.seriesOrigin.label }}</strong>
    <div>{{ tooltip.origin.label }}: {{ tooltip.origin.value }}</div>
  </ng-template>
</dt-stacked-bar>

inputs

  • series: DtStackedBarChartSeries[] Series of information to be displayed
  • selectable: boolean Allow selections to be made on chart. Default: false
  • selected: [DtStackedBarChartSeries, DtStackedBarChartNode] Series and node to be selected.
  • valueDisplayMode: DtStackedBarChartValueDisplayMode Format of display for values when only one series provided. Default: none
  • direction: DtStackedBarChartDirection Direction of the bars in the chart. Default: horizontal
  • labelPosition: DtStackedBarChartElementPosition Position of the label related to the bar. Default: top
  • legendPosition: DtStackedBarChartElementPosition Where and if legend should be displayed. Default: bottom
  • legends: DtStackedBarChartLegend[] Array of legends that can be used to toggle bar nodes. Useful when the legend used is outside this component
  • fillMode: DtStackedBarChartFillMode Whether each bar should be filled completely or should take into account their siblings and max
  • maxBarSize: number Maximum size of the bar. Default: 16
  • max: number Max value in the chart

outputs

  • selectedChange: EventEmitter<[DtStackedBarChartSeries, DtStackedBarChartNode]> Selected DtStackedBarChartSeries and DtStackedBarChartNode

content projection

  • dtStackedBarOverlay

models

interface DtStackedBarChartSeries {
  label: string;
  nodes: DtStackedBarChartNode[];
}
  • label Label to be shown in the chart
  • nodes Every segment to be displayed in the chart
interface DtStackedBarChartNode {
  label: string;
  value: number;
  color?: DtColors | string;
}
  • label Label to be shown in the chart
  • value Absolute or relative number to be represented in the chart
  • color Optional. If color should override the assigned one, set it here
interface DtStackedBarChartTooltipData {
  origin: DtStackedBarChartNode;
  seriesOrigin: DtStackedBarChartSeries;

  valueRelative: number;
  color: DtColors | string;
  visible: boolean;
  selected: boolean;
  length?: string;
}
  • origin Node passed by user in series array
  • seriesOrigin Original parent series
  • valueRelative Numeric percentage value based on this node vs sum of top level
  • color Color for this node in this state
  • visible If node is visible
  • selected If node is currently selected
  • legend Current length in percentage given only the visible nodes
type DtStackedBarChartValueDisplayMode = 'none' | 'absolute' | 'percent';
type DtStackedBarChartElementPosition =
  | 'none'
  | 'top'
  | 'right'
  | 'bottom'
  | 'left';
type DtStackedBarChartFillMode = 'full' | 'relative';
type DtStackedBarChartDirection = 'horizontal' | 'vertical';

colors

  • should follow theme if 3 or less
  • should follow generic chart palette if more than 3
  • color is overridden if user indicates one
  • selected segment should show a turquoise halo

legend

  • should display a common bullet
  • should display label and value (if only one series)
  • on click it should hide the nodes corresponding to that label
  • last visible node cannot be hidden
  • could be external and should allow to toggle elements

@tomheller
Copy link
Collaborator

Hi @subarroca!
This is a great effort to extend the bar chart. A couple of things to consider:

Bar horizontal / vertical vs Bar chart & column chart
In charting, vertical bar charts are usually referred to as column charts. I'm not sure if we want to diverge from this standard. I would opt to create two components dt-stacked-column-chart and dt-stacked-bar-chart, they can share most of their internal logic through a base class, i.e. DtStackedBarColumnChartBase.

selector
Please include the word "chart" within the selector, to make it clear what the component will represent.

direction Input
Will probably no longer be necessary if we split them into two components.

labelPosition: DtStackedBarChartElementPosition
Maybe we can find a type that will suit both and will be equally applicable for column and bar charts. As left/right will not make a lot of sense on column charts and top/bottom with bar charts, maybe we can go with something like start | end?

legendPosition: DtStackedBarChartElementPosition
I would opt to remove this at the current level and provide a fixed position for the legend. I don't see any benefit or use case at this time to define a position. As of right now, all our legends for bar/column charts are below the chart.

maxBarSize: number
I"m guessing this refers to the maxWidth of a single column/bar. Maybe we can find a way to share this between column and bar with another name, i.e maxTrackSize.

Things to consider in the future

The API proposal does not include anything about axis, axis labels, ticks and sizes. I guess that we can do that in an additional step later on.

Considerations

As this seems like a very large component, I guess it would be feasible to split this up and define a minimum viable product to get started with. Otherwise I see this one taking a fairly long time. What are your thoughts on this @ffriedl89 @subarroca ?

@subarroca
Copy link
Contributor Author

subarroca commented May 5, 2020

Hi!

First of all everything I suggested is already done so it would not take much time to do this except for testing

Selector
It already has 'chart' in the name but I forgot it in here, thanks for noticing though

About the bar / column chart
As a matter of fact it's already done in one component with one single template and using grid layout and element positioning so I would not go for a separate components approach. I could change the direction naming to type: 'bar' | 'column'

Label position
When showing some trials to UX they suggested that label could go on the left for the bar chart, so I had top, bottom and left, and right only seemed to complete the picture though I don't thing it will be much useful. I'll talk to them to see if we can get rid of right and left

Legend position
Easily removable

maxBarSize
It's exactly what you described, if we change the naming to bar|column it would make sense to rename it as you proposed

Axis
I have a working x-axis line for column chart. I thought we should add a 'valueAxis' somehow but I was thinking that for a following phase as this has grown far beyond its original limits and it would be an improvement to add easily

@subarroca
Copy link
Contributor Author

subarroca commented May 12, 2020

closed by #988

@tomheller tomheller linked a pull request May 20, 2020 that will close this issue
4 tasks
@tomheller tomheller added has-pr and removed needs discussion help wanted Extra attention is needed labels Jun 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants