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

[Lens] Chart level categorical color palettes #69800

Closed
wants to merge 47 commits into from

Conversation

flash1293
Copy link
Contributor

@flash1293 flash1293 commented Jun 24, 2020

Fixes #68404

This PR lays the groundwork to use categorical color palettes in Lens.

TODOs:

  • Use EUI component for palette picker
  • Remove redundant code in palette definition
  • Make Kibana palette more predictable (memoize and don't use the mapped keys in the preview)
  • Finalize offered palettes
  • Make sure layered lightening of color doesn't overlap and stays within bounds (see considerations)
  • Find and implement design for picker
  • Unify naming (currently it's a mixture of "colorfunction" and "palette")
  • Make "palette" arg one expression optional and fall back to eui standard palette for existing saved objects (to avoid migration)
  • Fix existing tests
  • Write additional tests
    • lightening function
    • frame state handling
    • SeriesLayer building in xy and pie color accessor functions
  • Document interfaces
  • Remove opacity from vislib bar charts
  • Respect advanced setting color map in all palettes

Architecture

As colors are persisted when switching between visualizations, the state for these is not part of the visualization state but is managed by the frame itself. It's part of the root state reducer in x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_management.ts. It is exposed to visualizations via the frame public api:

  globalPalette: {
    /** the currently active color function **/
    colorFunction: ColorFunctionDefinition;
    /** the current state of the color function, if it has state **/
    state: unknown;
    /** change the color function (passing in an id of another color function) **/  
    setColorFunction: (id: string) => void;
    /** changing the state of the current color function **/  
    setState: (updater: (state: unknown) => unknown) => void;
    /** all available color functions **/  
    availableColorFunctions: Record<string, ColorFunctionDefinition>;
  };

Color functions are like indexpattern operations - they are statically defined, but all other components always interact with an abstract interface so it's fairly straightforward to make it a dynamic registry later on. It is stateful already (to sync colors with other charts on a dashboard when using the "Kibana" palette).

A color function looks like this:

export interface ColorFunctionDefinition<T = unknown> {
  // stored id
  id: string;
  // human readable name
  title: string;

  // serialize into expression to pass to render expression function
  toExpression: (state?: T) => Ast;
  // actual color function to use in editor if necessary
  getColor: (series: SeriesLayer[], state?: T) => string | null;

  // rendering UI for editing the state of the color definition
  renderEditor?: (
    domElement: Element,
    props: { state?: T; setState: (updater: (oldState: T) => T) => void }
  ) => void;
  // rendering a preview of the color function (used in the dropdown of the palette picker)
  renderPreview: (domElement: Element, props: { state?: T }) => void;
}

As all of this is exposed via the frame, visualizations have full control over these (and could overwrite them theoretically). However in most cases it is enough to render the standard component to allow editing the palette x-pack/plugins/lens/public/editor_frame_service/palettes/palette_picker.tsx

Expression integration

The palette service owning the palette definitions in x-pack/plugins/lens/public/editor_frame_service/palettes will register an expression for each palette (lens_palette_eui, lens_palette_eui_pastel and so on) These functions take a serialized form of their internal state as argument (or nothing if there is no internal state) and return a lens_palette data structure which just holds a single getColor function which can be called by the renderer to determine the color. The toExpression functions of the visualizations supporting the palette (xy and pie) use the toExpression function of the color function exposed on the frame api to put it as a separate arg of the render function on the expression:

xy_chart legend={...} palette={lens_palette_eui}

When the expression runs, the expression registered by the palette service will pass the right getColor function to the renderer where it can be used.

elastic-charts integration

Within the renderer the getColor function needs to get called with some information about the current series and its position in the hierarchy of series (also see #68330). In both pie and xy chart there is a "color accessor" function in which the getColor function can be called. To do so the information available in there has to be mapped to the generic arguments of getColor. This is very different for xy and pie chart.

User facing changes

Pie coloring changes

  • Use the regular eui palette for charts instead of the slightly lighter eui palette "behind text for pie charts"
  • Changing the logic for lightening colors of the outer rings of the pie to make sure it's not getting too close to pure white

before
Screenshot 2020-06-30 at 14 45 42

after
Screenshot 2020-06-30 at 14 45 36

Respect color map in advanced settings in all palettes

If a color is specified for a term in the color map in advanced settings, it will be applied. There are multiple ways to do this (currently only relevant for pie charts - all examples set CN to red):

  • Only pick up color map color when the term of the inner most ring matches

Screenshot 2020-06-30 at 15 09 06

* Pick up color map color in outer rings as well, but don't inherit this overwritten colors to child slices

Screenshot 2020-06-30 at 15 08 35

* Pick up color map color in outer rings as well, and inherit this color to child-slices

Screenshot 2020-06-30 at 15 07 14

I'm not sure which behavior is the best one.

Legacy color palette syncs with rest of dashboard

When the legacy color palette is used, colors for specific terms are synchronized across all visualizations - the first color assigned from the palette to a term is repeated if the term appears again.

Screenshot 2020-06-30 at 17 53 16

Remove opacity from vislib bar charts

This was changed on accident - it's fixed on this PR so the colors are aligned with Lens visualizations with "legacy palette":

before
Screenshot 2020-07-01 at 17 23 08

after
Screenshot 2020-07-01 at 17 23 13

Palettes

Besides default and legacy palette, these are the supported options:
Screenshot 2020-06-30 at 17 54 30

This is a deviation from the original list on the linked issue:

  • Current default for XY charts, EUI with round-robin
  • Current default for pie charts, EUI with round-robin
  • Use 7-color Visualize palette with hue-shifting, but no shared state with dashboards
  • Pick colors using Dashboard color function
  • A grayscale/light blue function
  • A function that assigns by name

As the eui palette and eui colorblind palette are really close, I don't think we should offer both. Also, I don't think it makes much sense to offer two separate "Visualize" palettes with shared dashboard state and without - let's just share the state whenever possible.

Also, I didn't implement the "assign by name" overwrite palette because I think we have to consider this one separately (out of scope for this PR)

Palette settings

Having a custom state per palette is implemented, but I'm not sure whether we actually need it right now. We could offer a "reverse" checkbox for the gradient palettes, but this isn't helping much as the assignment is dynamic and pretty much random anyway. It will come in handy for manual overrides in the future.

A semi-useful feature to put the API to use right from the start is a setting to shift the color palette (like this):
darken_palette

Another option would be a "custom" palette in which the user chooses either a set of colors using the picker to build their own palette or the EuiColorStops component to define a range to pick from. For both of these I'm not sure whether a specific visualization is actually the right place to keep this state - I suggest we extend this functionality in a second iteration.

@flash1293
Copy link
Contributor Author

@cchaos I started work on providing a palette picker and laying some groundwork for coloring in Lens in general. Working on those some things popped up (the "Considerations" section). Could you have a look? Interested in your opinion.

@cchaos
Copy link
Contributor

cchaos commented Jun 26, 2020

In master, xy chart uses a different palette than pie chart (regular eui palette vs color blind eui palette). After this change, the colors are necessarily aligned. This also affects existing charts. Is this something we are OK with? Which one is the right scheme?

This should not have been the case. All charts should be using euiPaletteColorBlind(). I think the original thought with user the lighter palette for pie charts was because there's text on top of the colors. The lighter palette helps increase the contrast between background and foreground. However, the more this comes up the more it's better to stick with the base euiPaletteColorBlind(). This palette is optimized for graphics. The contrast levels between the colors and the "typical" background color are optimized.

I've expressed this before, but my worry with using two different, yet same, color palettes for different chart types is going to hurt the user's connection of values when placed on a dashboard. There's been talk about evening the color palettes in all the charts in dashboards, how would you accomplish this with different palettes based on chart type?

We can't use the lighter version for bar charts, or any other shape-based chart, because it doesn't provide enough contrast.

This PR introduces a "Kibana" palette that uses the same colors as existing "Visualize" charts.

Do we really need to keep supporting this "palette"? I can understand Lens being able to support this when equalizing colors on a dashboard but do we have to make it an option in the select? If we must, can we call it "Legacy" not "Kibana Default" because "Default" is not longer true with this palette (at least not for Lens).

This is happening because there is a css rule setting the opacity to 0.8 for vislib xy charts:

That shouldn't have ever been the case for bar charts, only area charts. Had a hard time tracking how far back that one goes but I'm 100% ok changing it for non-area charts.

Design for palette picker

I'll help with this when it's closer to done.

Supported palettes

I'm guessing these were taken from Canvas, but EUI has a lovely set of hand-crafted palettes that compliment each other. Can we start with this set?

Screen Shot 2020-06-26 at 14 40 16 PM

We also have an EuiColorPalettePicker component that can be used now.

As the eui palette and eui colorblind palette are really close

WHAT are these two palettes? EUI only exports a single euiPaletteColorBlind(). There is no simple euiPalette for charts.

@timroes
Copy link
Contributor

timroes commented Jun 29, 2020

Kibana has an advanced setting visualizations:colorMapping. This is used often to assign specific values specific colors (like warning, error, or open/closed states, etc.). We should make sure we respect that setting in Lens too.

screenshot-20200629-153605

It's default value just has a mapping for "Count" with the "old" teal. Since Lens uses "Count of Records" as a label for the same, I think we should add that to the default value and give both the same color. I would suggest we're using the new color palette teal-like color for that. This would nevertheless change the color of all existing charts on "Count" slightly towards the new color palette. In my opinion the old teal and the new are close enough together to do that in a minor version, but would welcome other opinions on that.

@flash1293
Copy link
Contributor Author

flash1293 commented Jun 29, 2020

@cchaos

WHAT are these two palettes? EUI only exports a single euiPaletteColorBlind(). There is no simple euiPalette for charts.

You are right, I confused something here, my bad. On master xy chart is currently using the regular euiPaletteColorBlind palette while pie is using euiPaletteColorBlindBehindText. In this PR both would use the same color scheme (because there is just one palette). I guess there are three options:

  • Use euiPaletteColorBlind for every chart
  • Differentiate the two cases and use euiPaletteColorBlind for xy and euiPaletteColorBlindBehindText for pie (while still calling it the same palette in the UI).

I'll go with option 1 for now, if it doesn't work out we can reconsider.

I'm guessing these were taken from Canvas, but EUI has a lovely set of hand-crafted palettes that compliment each other. Can we start with this set?

This sounds good to me, I will change the implementation

@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-app (Team:KibanaApp)

@wylieconlon
Copy link
Contributor

Haven't tested this code yet, but here's my initial feedback:

  1. The goal of these palettes is to provide visual separation between categories, and I don't think the diverging color palettes are meeting that goal when used in pie charts or bar charts. Diverging scales have implicit meaning, such as for showing temperature or positive/negative values, and as implemented here we aren't able to take advantage of the meaning. I would like to remove diverging scales, and this is why I didn't put them in my original list.

  2. When using shared colors with dashboard, the pie chart should only color the innermost slice. This prevents pie charts from showing too many colors, which is a problem with the previous implementation that we should not copy.

  3. I would like to support a stateful option for the EUI palette which switches between round-robin and hue-shifting as a method of extending the palette beyond 10 colors. Here's an example from April when I last worked on this:

Screenshot 2020-04-28 11 48 01

  1. I don't understand the "darken" feature you're showing. Is it the same as "reverse"?

  2. It looks like this change has the potential to introduce low-contrast text in pie charts. Now that the chart library supports the correct color calculation, I'd like to try again with the automatic text color calculation. If that's not the best UX, then we need to have a font color option for pie charts.

  3. Gradients don't look good with bar charts unless the chart has very few categories, like 3-5, but no more. I don't think we can prevent users from using more series, but should we attempt to?

  4. Gradients look totally different against dark mode backgrounds. I think we have to live with this.

@flash1293
Copy link
Contributor Author

flash1293 commented Jul 2, 2020

@wylieconlon Thanks for your feedback

I can definitely see your point here, and I don't have a strong feeling about removing it. However, I can still see these palettes being used the right way in some situations (e.g. when ordering terms alphabetically and this alphabetical order corresponds with a good<->bad scale) and besides that IMHO they are nice to look at also without explicit meaning. I slightly lean on leaving them out for now - removing them later on because we don't like the idea anymore is much harder than not introducing in the first place and gather feedback for a while @cchaos would that be OK for you?

Agreed, that's also what I implemented. I wanted to highlight multiple possible approaches here, though. We can still introduce this using a stateful flag later on.

I don't really see a big user benefit of exposing this as a setting - the difference is subtle and doesn't matter in a lot of cases (more than 10 colors is already an exotic case). What about just using a hue-shifted palette extension as a default?

It's similar, I just wanted to show what can be done. It's simply reducing the lightness of each color by 20%

Good point, I will look into that.

Seems like something we should leave up the users, but I'm open to ideas how to make it easier to get to a good looking chart. I would like to avoid hiding options just because we think it won't look good though

We could make current dark mode an input to the palette and change the colors. I will look into that as well.

@flash1293
Copy link
Contributor Author

flash1293 commented Jul 2, 2020

Changes:

  • Using the label coloring logic from elastic-charts: Screenshot 2020-07-02 at 16 33 58
  • Removed status and complimentary palettes for now

I played around with reversing the lightness of the colors (e.g. 70% lightness becomes 20% lightness) when in dark mode. I decided the original palettes look much much better in dark mode than "inversed palettes". Removed that part, sorry for the ping Caroline

@cchaos
Copy link
Contributor

cchaos commented Jul 2, 2020

All I'll ask is please don't make any changes/manipulate the palettes coming from EUI (unless it's to support more than 10 series/colors). If there needs to be a discussion about the palettes themselves, lets do that in EUI.

I don't see the downside of giving users all options to colorize their chart. We can't know for sure what their data is representing or what trends they're trying to visualize. Eventually, we'll even support custom themes which would be even harder to try to manage color to data theory. We could give "suggestions" or hints, but if we have the palettes, lets let them use them. But I'll agree that removing them later is harder, so leaving them out for now is fine.

@flash1293
Copy link
Contributor Author

@elasticmachine merge upstream

@kibanamachine
Copy link
Contributor

💛 Build succeeded, but was flaky


Test Failures

Kibana Pipeline / kibana-oss-agent / Chrome UI Functional Tests.test/functional/apps/saved_objects_management/edit_saved_object·ts.saved objects management saved objects edition page allows to delete a saved object

Link to Jenkins

Standard Out

Failed Tests Reporter:
  - Test has failed 12 times on tracked branches: https://github.com/elastic/kibana/issues/68400

[00:00:00]       │
[00:11:52]         └-: saved objects management
[00:11:52]           └-> "before all" hook
[00:11:52]           └-: saved objects edition page
[00:11:52]             └-> "before all" hook
[00:11:52]             └-> allows to update the saved object when submitting
[00:11:52]               └-> "before each" hook: global before each
[00:11:52]               └-> "before each" hook
[00:11:52]                 │ info [saved_objects_management/edit_saved_object] Loading "mappings.json"
[00:11:52]                 │ info [saved_objects_management/edit_saved_object] Loading "data.json"
[00:11:52]                 │ info [o.e.c.m.MetadataCreateIndexService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana] creating index, cause [api], templates [], shards [1]/[0]
[00:11:52]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] current.health="GREEN" message="Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.kibana][0]]])." previous.health="YELLOW" reason="shards started [[.kibana][0]]"
[00:11:52]                 │ info [saved_objects_management/edit_saved_object] Created index ".kibana"
[00:11:52]                 │ debg [saved_objects_management/edit_saved_object] ".kibana" settings {"index":{"number_of_shards":"1","auto_expand_replicas":"0-1","number_of_replicas":"0"}}
[00:11:52]                 │ info [saved_objects_management/edit_saved_object] Indexed 4 docs into ".kibana"
[00:11:52]                 │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana/hkulE6NOSYSCb-yLW21e_Q] update_mapping [_doc]
[00:11:52]                 │ debg Migrating saved objects
[00:11:52]                 │ proc [kibana]   log   [12:49:02.134] [info][savedobjects-service] Creating index .kibana_2.
[00:11:52]                 │ info [o.e.c.m.MetadataCreateIndexService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2] creating index, cause [api], templates [], shards [1]/[1]
[00:11:52]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] updating number_of_replicas to [0] for indices [.kibana_2]
[00:11:52]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] current.health="GREEN" message="Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.kibana_2][0]]])." previous.health="YELLOW" reason="shards started [[.kibana_2][0]]"
[00:11:52]                 │ proc [kibana]   log   [12:49:02.203] [info][savedobjects-service] Reindexing .kibana to .kibana_1
[00:11:52]                 │ info [o.e.c.m.MetadataCreateIndexService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_1] creating index, cause [api], templates [], shards [1]/[1]
[00:11:52]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] updating number_of_replicas to [0] for indices [.kibana_1]
[00:11:52]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] current.health="GREEN" message="Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.kibana_1][0]]])." previous.health="YELLOW" reason="shards started [[.kibana_1][0]]"
[00:11:52]                 │ info [o.e.t.LoggingTaskListener] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] 3656 finished with response BulkByScrollResponse[took=25.7ms,timed_out=false,sliceId=null,updated=0,created=4,deleted=0,batches=1,versionConflicts=0,noops=0,retries=0,throttledUntil=0s,bulk_failures=[],search_failures=[]]
[00:11:52]                 │ info [o.e.c.m.MetadataDeleteIndexService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana/hkulE6NOSYSCb-yLW21e_Q] deleting index
[00:11:52]                 │ proc [kibana]   log   [12:49:02.552] [info][savedobjects-service] Migrating .kibana_1 saved objects to .kibana_2
[00:11:53]                 │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2/Su4m1MqHTQysZeUkTGHcig] update_mapping [_doc]
[00:11:53]                 │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2/Su4m1MqHTQysZeUkTGHcig] update_mapping [_doc]
[00:11:53]                 │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2/Su4m1MqHTQysZeUkTGHcig] update_mapping [_doc]
[00:11:53]                 │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2/Su4m1MqHTQysZeUkTGHcig] update_mapping [_doc]
[00:11:53]                 │ proc [kibana]   log   [12:49:02.689] [info][savedobjects-service] Pointing alias .kibana to .kibana_2.
[00:11:53]                 │ proc [kibana]   log   [12:49:02.727] [info][savedobjects-service] Finished in 595ms.
[00:11:53]                 │ debg applying update to kibana config: {"accessibility:disableAnimations":true,"dateFormat:tz":"UTC"}
[00:11:54]               │ debg navigating to settings url: http://localhost:6181/app/management
[00:11:54]               │ debg navigate to: http://localhost:6181/app/management
[00:11:54]               │ debg browser[INFO] http://localhost:6181/app/management?_t=1594039744196 341 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-P5polb1UreUSOe5V/Pv7tc+yeZuJXiOi/3fqhGsU7BE='), or a nonce ('nonce-...') is required to enable inline execution.
[00:11:54]               │
[00:11:54]               │ debg browser[INFO] http://localhost:6181/bundles/app/core/bootstrap.js 42:19 "^ A single error about an inline script not firing due to content security policy is expected!"
[00:11:54]               │ debg ... sleep(700) start
[00:11:55]               │ debg ... sleep(700) end
[00:11:55]               │ debg returned from get, calling refresh
[00:11:56]               │ debg browser[INFO] http://localhost:6181/34376/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.js 452:106112 "INFO: 2020-07-06T12:49:05Z
[00:11:56]               │        Adding connection to http://localhost:6181/elasticsearch
[00:11:56]               │
[00:11:56]               │      "
[00:11:56]               │ERROR browser[SEVERE] http://localhost:6181/34376/bundles/core/core.entry.js 83:261771 TypeError: Failed to fetch
[00:11:56]               │          at Fetch._callee3$ (http://localhost:6181/34376/bundles/core/core.entry.js:34:105174)
[00:11:56]               │          at l (http://localhost:6181/34376/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.js:368:144815)
[00:11:56]               │          at Generator._invoke (http://localhost:6181/34376/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.js:368:144568)
[00:11:56]               │          at Generator.forEach.e.<computed> [as throw] (http://localhost:6181/34376/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.js:368:145172)
[00:11:56]               │          at fetch_asyncGeneratorStep (http://localhost:6181/34376/bundles/core/core.entry.js:34:99267)
[00:11:56]               │          at _throw (http://localhost:6181/34376/bundles/core/core.entry.js:34:99675)
[00:11:56]               │ debg browser[INFO] http://localhost:6181/app/management?_t=1594039744196 341 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-P5polb1UreUSOe5V/Pv7tc+yeZuJXiOi/3fqhGsU7BE='), or a nonce ('nonce-...') is required to enable inline execution.
[00:11:56]               │
[00:11:56]               │ debg browser[INFO] http://localhost:6181/bundles/app/core/bootstrap.js 42:19 "^ A single error about an inline script not firing due to content security policy is expected!"
[00:11:56]               │ debg currentUrl = http://localhost:6181/app/management
[00:11:56]               │          appUrl = http://localhost:6181/app/management
[00:11:56]               │ debg TestSubjects.find(kibanaChrome)
[00:11:56]               │ debg Find.findByCssSelector('[data-test-subj="kibanaChrome"]') with timeout=60000
[00:11:57]               │ debg browser[INFO] http://localhost:6181/34376/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.js 452:106112 "INFO: 2020-07-06T12:49:07Z
[00:11:57]               │        Adding connection to http://localhost:6181/elasticsearch
[00:11:57]               │
[00:11:57]               │      "
[00:11:57]               │ debg ... sleep(501) start
[00:11:58]               │ debg ... sleep(501) end
[00:11:58]               │ debg in navigateTo url = http://localhost:6181/app/management
[00:11:58]               │ debg TestSubjects.exists(statusPageContainer)
[00:11:58]               │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="statusPageContainer"]') with timeout=2500
[00:12:00]               │ debg --- retry.tryForTime error: [data-test-subj="statusPageContainer"] is not displayed
[00:12:01]               │ debg TestSubjects.click(objects)
[00:12:01]               │ debg Find.clickByCssSelector('[data-test-subj="objects"]') with timeout=10000
[00:12:01]               │ debg Find.findByCssSelector('[data-test-subj="objects"]') with timeout=10000
[00:12:01]               │ debg Find.existsByDisplayedByCssSelector('*[data-test-subj="savedObjectsTable"] .euiBasicTable-loading') with timeout=2500
[00:12:01]               │ debg --- retry.try error: Waiting
[00:12:02]               │ debg Find.existsByDisplayedByCssSelector('*[data-test-subj="savedObjectsTable"] .euiBasicTable-loading') with timeout=2500
[00:12:04]               │ debg --- retry.tryForTime error: *[data-test-subj="savedObjectsTable"] .euiBasicTable-loading is not displayed
[00:12:05]               │ debg Find.existsByDisplayedByCssSelector('*[data-test-subj="savedObjectsTable"] .euiBasicTable-loading') with timeout=2500
[00:12:07]               │ debg --- retry.tryForTime error: *[data-test-subj="savedObjectsTable"] .euiBasicTable-loading is not displayed
[00:12:08]               │ debg TestSubjects.find(savedObjectsTable)
[00:12:08]               │ debg Find.findByCssSelector('[data-test-subj="savedObjectsTable"]') with timeout=10000
[00:12:08]               │ debg navigateToUrl http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist
[00:12:08]               │ debg browser[INFO] http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist?_t=1594039757960 341 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-P5polb1UreUSOe5V/Pv7tc+yeZuJXiOi/3fqhGsU7BE='), or a nonce ('nonce-...') is required to enable inline execution.
[00:12:08]               │
[00:12:08]               │ debg browser[INFO] http://localhost:6181/bundles/app/core/bootstrap.js 42:19 "^ A single error about an inline script not firing due to content security policy is expected!"
[00:12:08]               │ debg currentUrl = http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist
[00:12:08]               │          appUrl = http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist
[00:12:08]               │ debg TestSubjects.find(kibanaChrome)
[00:12:08]               │ debg Find.findByCssSelector('[data-test-subj="kibanaChrome"]') with timeout=60000
[00:12:09]               │ debg TestSubjects.exists(savedObjectEditSave)
[00:12:09]               │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="savedObjectEditSave"]') with timeout=120000
[00:12:09]               │ debg browser[INFO] http://localhost:6181/34376/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.js 452:106112 "INFO: 2020-07-06T12:49:19Z
[00:12:09]               │        Adding connection to http://localhost:6181/elasticsearch
[00:12:09]               │
[00:12:09]               │      "
[00:12:10]               │ debg TestSubjects.getAttribute(savedObjects-editField-title, value)
[00:12:10]               │ debg TestSubjects.find(savedObjects-editField-title)
[00:12:10]               │ debg Find.findByCssSelector('[data-test-subj="savedObjects-editField-title"]') with timeout=10000
[00:12:10]               │ debg TestSubjects.setValue(savedObjects-editField-title, Edited Dashboard)
[00:12:10]               │ debg TestSubjects.click(savedObjects-editField-title)
[00:12:10]               │ debg Find.clickByCssSelector('[data-test-subj="savedObjects-editField-title"]') with timeout=10000
[00:12:10]               │ debg Find.findByCssSelector('[data-test-subj="savedObjects-editField-title"]') with timeout=10000
[00:12:10]               │ debg TestSubjects.setValue(savedObjects-editField-description, Some description)
[00:12:10]               │ debg TestSubjects.click(savedObjects-editField-description)
[00:12:10]               │ debg Find.clickByCssSelector('[data-test-subj="savedObjects-editField-description"]') with timeout=10000
[00:12:10]               │ debg Find.findByCssSelector('[data-test-subj="savedObjects-editField-description"]') with timeout=10000
[00:12:10]               │ debg TestSubjects.find(savedObjectEditSave)
[00:12:10]               │ debg Find.findByCssSelector('[data-test-subj="savedObjectEditSave"]') with timeout=10000
[00:12:10]               │ debg Find.existsByDisplayedByCssSelector('*[data-test-subj="savedObjectsTable"] .euiBasicTable-loading') with timeout=2500
[00:12:11]               │ debg --- retry.try error: Waiting
[00:12:12]               │ debg Find.existsByDisplayedByCssSelector('*[data-test-subj="savedObjectsTable"] .euiBasicTable-loading') with timeout=2500
[00:12:14]               │ debg --- retry.tryForTime error: *[data-test-subj="savedObjectsTable"] .euiBasicTable-loading is not displayed
[00:12:15]               │ debg TestSubjects.find(savedObjectsTable)
[00:12:15]               │ debg Find.findByCssSelector('[data-test-subj="savedObjectsTable"]') with timeout=10000
[00:12:15]               │ debg navigateToUrl http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist
[00:12:15]               │ debg browser[INFO] http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist?_t=1594039764982 341 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-P5polb1UreUSOe5V/Pv7tc+yeZuJXiOi/3fqhGsU7BE='), or a nonce ('nonce-...') is required to enable inline execution.
[00:12:15]               │
[00:12:15]               │ debg browser[INFO] http://localhost:6181/bundles/app/core/bootstrap.js 42:19 "^ A single error about an inline script not firing due to content security policy is expected!"
[00:12:15]               │ debg currentUrl = http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist
[00:12:15]               │          appUrl = http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist
[00:12:15]               │ debg TestSubjects.find(kibanaChrome)
[00:12:15]               │ debg Find.findByCssSelector('[data-test-subj="kibanaChrome"]') with timeout=60000
[00:12:16]               │ debg TestSubjects.getAttribute(savedObjects-editField-title, value)
[00:12:16]               │ debg TestSubjects.find(savedObjects-editField-title)
[00:12:16]               │ debg Find.findByCssSelector('[data-test-subj="savedObjects-editField-title"]') with timeout=10000
[00:12:16]               │ debg browser[INFO] http://localhost:6181/34376/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.js 452:106112 "INFO: 2020-07-06T12:49:26Z
[00:12:16]               │        Adding connection to http://localhost:6181/elasticsearch
[00:12:16]               │
[00:12:16]               │      "
[00:12:17]               │ debg TestSubjects.getAttribute(savedObjects-editField-description, value)
[00:12:17]               │ debg TestSubjects.find(savedObjects-editField-description)
[00:12:17]               │ debg Find.findByCssSelector('[data-test-subj="savedObjects-editField-description"]') with timeout=10000
[00:12:17]               └- ✓ pass  (22.5s) "saved objects management saved objects edition page allows to update the saved object when submitting"
[00:12:17]             └-> "after each" hook
[00:12:17]               │ info [saved_objects_management/edit_saved_object] Unloading indices from "mappings.json"
[00:12:17]               │ info [o.e.c.m.MetadataDeleteIndexService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2/Su4m1MqHTQysZeUkTGHcig] deleting index
[00:12:17]               │ info [o.e.c.m.MetadataDeleteIndexService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_1/dCmW8W1UThWfIY8k5qsfXA] deleting index
[00:12:17]               │ info [saved_objects_management/edit_saved_object] Deleted existing index [".kibana_2",".kibana_1"]
[00:12:17]               │ info [saved_objects_management/edit_saved_object] Unloading indices from "data.json"
[00:12:17]             └-> allows to delete a saved object
[00:12:17]               └-> "before each" hook: global before each
[00:12:17]               └-> "before each" hook
[00:12:17]                 │ info [saved_objects_management/edit_saved_object] Loading "mappings.json"
[00:12:17]                 │ info [saved_objects_management/edit_saved_object] Loading "data.json"
[00:12:17]                 │ info [o.e.c.m.MetadataCreateIndexService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana] creating index, cause [api], templates [], shards [1]/[0]
[00:12:17]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] current.health="GREEN" message="Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.kibana][0]]])." previous.health="YELLOW" reason="shards started [[.kibana][0]]"
[00:12:17]                 │ info [saved_objects_management/edit_saved_object] Created index ".kibana"
[00:12:17]                 │ debg [saved_objects_management/edit_saved_object] ".kibana" settings {"index":{"number_of_shards":"1","auto_expand_replicas":"0-1","number_of_replicas":"0"}}
[00:12:17]                 │ info [saved_objects_management/edit_saved_object] Indexed 4 docs into ".kibana"
[00:12:17]                 │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana/kswL8pLPQBeXR9i3uLecBw] update_mapping [_doc]
[00:12:17]                 │ debg Migrating saved objects
[00:12:17]                 │ proc [kibana]   log   [12:49:26.937] [info][savedobjects-service] Creating index .kibana_2.
[00:12:17]                 │ info [o.e.c.m.MetadataCreateIndexService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2] creating index, cause [api], templates [], shards [1]/[1]
[00:12:17]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] updating number_of_replicas to [0] for indices [.kibana_2]
[00:12:17]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] current.health="GREEN" message="Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.kibana_2][0]]])." previous.health="YELLOW" reason="shards started [[.kibana_2][0]]"
[00:12:17]                 │ proc [kibana]   log   [12:49:26.993] [info][savedobjects-service] Reindexing .kibana to .kibana_1
[00:12:17]                 │ info [o.e.c.m.MetadataCreateIndexService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_1] creating index, cause [api], templates [], shards [1]/[1]
[00:12:17]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] updating number_of_replicas to [0] for indices [.kibana_1]
[00:12:17]                 │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] current.health="GREEN" message="Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.kibana_1][0]]])." previous.health="YELLOW" reason="shards started [[.kibana_1][0]]"
[00:12:17]                 │ info [o.e.t.LoggingTaskListener] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] 3901 finished with response BulkByScrollResponse[took=27.9ms,timed_out=false,sliceId=null,updated=0,created=4,deleted=0,batches=1,versionConflicts=0,noops=0,retries=0,throttledUntil=0s,bulk_failures=[],search_failures=[]]
[00:12:17]                 │ info [o.e.c.m.MetadataDeleteIndexService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana/kswL8pLPQBeXR9i3uLecBw] deleting index
[00:12:17]                 │ proc [kibana]   log   [12:49:27.332] [info][savedobjects-service] Migrating .kibana_1 saved objects to .kibana_2
[00:12:17]                 │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2/7ptel3fYS4SHLqLEos-BLg] update_mapping [_doc]
[00:12:17]                 │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2/7ptel3fYS4SHLqLEos-BLg] update_mapping [_doc]
[00:12:17]                 │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2/7ptel3fYS4SHLqLEos-BLg] update_mapping [_doc]
[00:12:17]                 │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-centos-tests-xl-1594038012226229576] [.kibana_2/7ptel3fYS4SHLqLEos-BLg] update_mapping [_doc]
[00:12:17]                 │ proc [kibana]   log   [12:49:27.467] [info][savedobjects-service] Pointing alias .kibana to .kibana_2.
[00:12:17]                 │ proc [kibana]   log   [12:49:27.515] [info][savedobjects-service] Finished in 580ms.
[00:12:17]                 │ debg applying update to kibana config: {"accessibility:disableAnimations":true,"dateFormat:tz":"UTC"}
[00:12:19]               │ debg navigateToUrl http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist
[00:12:19]               │ debg browser[INFO] http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist?_t=1594039768996 341 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-P5polb1UreUSOe5V/Pv7tc+yeZuJXiOi/3fqhGsU7BE='), or a nonce ('nonce-...') is required to enable inline execution.
[00:12:19]               │
[00:12:19]               │ debg browser[INFO] http://localhost:6181/bundles/app/core/bootstrap.js 42:19 "^ A single error about an inline script not firing due to content security policy is expected!"
[00:12:19]               │ debg currentUrl = http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist
[00:12:19]               │          appUrl = http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist
[00:12:19]               │ debg TestSubjects.find(kibanaChrome)
[00:12:19]               │ debg Find.findByCssSelector('[data-test-subj="kibanaChrome"]') with timeout=60000
[00:12:20]               │ debg TestSubjects.find(savedObjectEditDelete)
[00:12:20]               │ debg Find.findByCssSelector('[data-test-subj="savedObjectEditDelete"]') with timeout=10000
[00:12:20]               │ debg browser[INFO] http://localhost:6181/34376/bundles/kbn-ui-shared-deps/kbn-ui-shared-deps.js 452:106112 "INFO: 2020-07-06T12:49:30Z
[00:12:20]               │        Adding connection to http://localhost:6181/elasticsearch
[00:12:20]               │
[00:12:20]               │      "
[00:12:21]               │ debg Clicking modal confirm
[00:12:21]               │ debg TestSubjects.exists(confirmModalTitleText)
[00:12:21]               │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="confirmModalTitleText"]') with timeout=2500
[00:12:21]               │ debg TestSubjects.click(confirmModalConfirmButton)
[00:12:21]               │ debg Find.clickByCssSelector('[data-test-subj="confirmModalConfirmButton"]') with timeout=10000
[00:12:21]               │ debg Find.findByCssSelector('[data-test-subj="confirmModalConfirmButton"]') with timeout=10000
[00:12:21]               │ debg TestSubjects.exists(confirmModalTitleText)
[00:12:21]               │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="confirmModalTitleText"]') with timeout=2500
[00:12:23]               │ debg browser[INFO] http://localhost:6181/34376/bundles/core/core.entry.js 83:262850 "Detected an unhandled Promise rejection.
[00:12:23]               │      TypeError: Cannot read property 'attributes' of undefined"
[00:12:23]               │ERROR browser[SEVERE] http://localhost:6181/34376/bundles/plugin/savedObjectsManagement/1.plugin.js 5:34045 Uncaught TypeError: Cannot read property 'attributes' of undefined
[00:12:23]               │ debg --- retry.tryForTime error: [data-test-subj="confirmModalTitleText"] is not displayed
[00:12:24]               │ debg Find.existsByDisplayedByCssSelector('*[data-test-subj="savedObjectsTable"] .euiBasicTable-loading') with timeout=2500
[00:12:26]               │ debg --- retry.tryForTime error: *[data-test-subj="savedObjectsTable"] .euiBasicTable-loading is not displayed
[00:12:27]               │ debg TestSubjects.find(savedObjectsTable)
[00:12:27]               │ debg Find.findByCssSelector('[data-test-subj="savedObjectsTable"]') with timeout=10000
[00:12:37]               │ info Taking screenshot "/dev/shm/workspace/kibana/test/functional/screenshots/failure/saved objects management saved objects edition page allows to delete a saved object.png"
[00:12:37]               │ info Current URL is: http://localhost:6181/app/management/kibana/objects/savedDashboards/i-exist
[00:12:37]               │ info Saving page source to: /dev/shm/workspace/kibana/test/functional/failure_debug/html/saved objects management saved objects edition page allows to delete a saved object.html
[00:12:37]               └- ✖ fail: "saved objects management saved objects edition page allows to delete a saved object"
[00:12:37]               │

Stack Trace

{ TimeoutError: Waiting for element to be located By(css selector, [data-test-subj="savedObjectsTable"])
Wait timed out after 10019ms
    at /dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/webdriver.js:842:17
    at process._tickCallback (internal/process/next_tick.js:68:7) name: 'TimeoutError', remoteStacktrace: '' }

Build metrics

✅ unchanged

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

Copy link
Contributor

@wylieconlon wylieconlon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating from my last feedback. I've now tested and reviewed the code, and here's my feedback about the behavior from most-to-least important:

  1. I see why you've chosen to put each palette into a separate function so that they can define their arguments, but I think this goes too far- especially since we haven't built any stateful palettes yet. The decision tree I would use here is:

    • Is it possible to just pass the paletteId in for the simple case, and palette state gets passed in via variable?
    • Is it possible to pass the paletteID and palette state as JSON?
    • Instead of defining a unique function for each, what's the fewest functions? I see three unique functions here: "round-robin categorical", "legacy" and "gradient".
  2. The color lightening logic needs a second look

  3. The forms to actually select a palette also need a second look

}}
>
<EuiIcon type="gear" />
</EuiButtonEmpty>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now inconsistent with the XY chart

@@ -119,6 +120,7 @@ export function XyToolbar(props: VisualizationToolbarProps<State>) {
}}
anchorPosition="downRight"
>
<PalettePicker frame={props.frame} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this need an EuiFormRow with columnCompressed:

Screenshot 2020-07-06 11 54 21

})}
>
<ColorPicker {...props} />
</EuiFormRow>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you've added this EuiFormRow to the wrong component...
Screenshot 2020-07-06 11 58 09

stroke-opacity: 1;
stroke-width: 0;
}

.series > path {
fill-opacity: .8;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change doesn't belong in this PR, despite it being important. The main reason is that I think it needs to have a separate bullet point in the release notes.

}}
datasourceMap={{
testDatasource: mockDatasource,
testDatasource2: mockDatasource2,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
testDatasource2: mockDatasource2,

gray: {
title: i18n.translate('xpack.lens.palettes.grayLabel', { defaultMessage: 'gray' }),
...buildRoundRobinCategorical('gray', euiPaletteGray),
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest flipping the order of positive and negative palettes here, since the order is kept in objects.

return {
default: {
title: i18n.translate('xpack.lens.palettes.defaultPaletteLabel', {
defaultMessage: 'default',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use sentence case for these.

@@ -97,6 +92,12 @@ export function PieComponent(
fillLabel.valueFormatter = () => '';
}

const totalSeriesCount = uniq(
firstTable.rows.map((row) => {
return columnGroups.map(({ col: { id: columnId } }) => row[columnId]).join(',');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few edge cases here, and my preference is not to make changes to handle the edge cases but to at least note them in the code:

  • Falsy values will all get the same color
  • Dates will get different colors even if they are displayed the same as text

/**
* A map of all available palettes (keys being the ids).
*/
availablePalettes: Record<string, PaletteDefinition>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that objects inherently have sort order now, but an array instead of Record might make more sense here.

@@ -251,6 +258,7 @@ export function XYChart({
data.tables[layer.layerId].rows.some((row) => row[layer.xAccessor!] !== firstRowValue)
) {
return false;
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return false;

@flash1293
Copy link
Contributor Author

@wylieconlon I agree with almost all of your remarks and will fix them later this week or early next week, but I think we should discuss the first point:

I see why you've chosen to put each palette into a separate function so that they can define their arguments, but I think this goes too far- especially since we haven't built any stateful palettes yet. The decision tree I would use here is:

  • Is it possible to just pass the paletteId in for the simple case, and palette state gets passed in via variable?
  • Is it possible to pass the paletteID and palette state as JSON?
  • Instead of defining a unique function for each, what's the fewest functions? I see three unique functions here: "round-robin categorical", "legacy" and "gradient".

I don't really see a downside of defining separate functions for each palette - to me this seems like the best approach among the options you listed. As long as we don't consider exposing the lens expression to the user, all of these approaches seem very similar to me - almost to a degree where refactoring it in the way you suggest isn't worth the effort. If we consider how a human user would use these, I definitely prefer the individual functions:

  • Is it possible to just pass the paletteId in for the simple case, and palette state gets passed in via variable?

This is different than any other expression function so far and would introduce an implicit and easy to break interface. Where is it documented how this variable is called? Can we type check it? Provide help in form of suggestions etc. in the editor?

  • Is it possible to pass the paletteID and palette state as JSON?

I know we do this in some places, but it's definitely not how the expression is meant to be used - also we can't leverage the type checking capabilities of the expression and it's super annoying to work with it

  • Instead of defining a unique function for each, what's the fewest functions? I see three unique functions here: "round-robin categorical", "legacy" and "gradient".

This is better, but still harder to use than separate functions for each palette - a user has to remember which "type" of palette they want to use before they can actually select it - having a separate function for each collapses these steps into one (without losing type-safety and auto-complete for providing arguments). The user would type lens_palette_, then get a list of all available palettes - if they select one, they get a list of arguments for the currently selected palette. Whether it's a "legacy", a "categorical" or a "gradient" palette shouldn't matter to the user, they are just selecting a palette (similar to how it's presented in the form based UI).

My main point is that more functions don't hurt in any way, but other approaches do have downsides. I can think of one possible point here which is polluting the drop down of possible functions, but I don't really think this is a problem, because we are very generous with functions in other places as well to ensure type-safety - the editing experience can be fixed (e.g. only highlighting functions matching the current context type), the type safety with catch-all functions will be harder to work around.
Screenshot 2020-07-07 at 09 12 03

That said, I'm not at all convinced my opinion is the right one, it's just the conclusion I came to so far - very likely I forgot to consider something.

@ppisljar What do you think about the topic? Short summary: Lens palettes can have different arguments (even if they don't have arguments at the moment). Do you think we should go with a scheme like this:

{lens_palette_eui arg1=5 arg2=2}
{lens_palette_legacy arg2=5 arg3=2}
{lens_palette_cool}

or something like this

{lens_palette id="eui" state="{...myJsonState...}"} //option 1
{var_set name="euiPaletteState" val={...}} {lens_palette id="eui"} //option 2
{lens_palette_categorical id="eui" arg1=5 arg2=2} {lens_palette_gradient id="cool"} //option 3

More details in the rest of the comment.

@flash1293
Copy link
Contributor Author

Discussed with @ppisljar and @timroes and this PR will need some changes - we will move the actual palette implementation out of Lens into the charts plugin so it can be used from everywhere in Kibana.

@monfera
Copy link
Contributor

monfera commented Jul 8, 2020

There are lots of constraints so handle these comments on face value:

  1. I see the concern but this doesn't look like a net improvement, as the washed-out colors are not that bad, and/or could be made darker by smaller gradient steps, while this change shifts the inner colors toward a somewhat less readable and subjectively less pleasant aesthetic:
    image
    The inner slices are noticeably oversaturated for flooding data ink that characterizes pie/sunburst/treemap charts. It'd look even more off with a larger surface eg. treemap
    Though maybe WCAG compliant already, there'd be merit to lightening back the inner pie to increase the contrast of the overlaid text too.

  2. Probably these colors are tech examples, let's chat if it is something under consideration for users:
    image
    Bright red is rarely used unless with a very very strong, considered intent (highlight/danger/loss/...), and preferably in measured quantities, such as here or here

  3. Not the topic of this PR, but how could we retire the rust brown palette and avoid them even for illustration 😄

  4. Looks like the elastic-charts charts are now configured closer and closer to legacy charts, which is in my mind not even a non-goal, more of an anti-goal. If the future of analytics is overly informed by past charting tools then Lens will have the same issues wrt. dataviz compatible color palettes as those tools that came before

@monfera
Copy link
Contributor

monfera commented Jul 8, 2020

Re the good comments by @cchaos

I don't see the downside of giving users all options to colorize their chart
We can't know for sure what their data is representing or what trends they're trying to visualize. Eventually, we'll even support custom themes which would be even harder to try to manage color to data theory.

I agree with the layering(*) approach. The ease of use we give to the user to go with decent colors vs. shoot themselves in the foot color assignment wise matters a lot.

For example, TSVB, by its prominent, in-line placement of the color picker option, and through the legacy dialog for color picking, initially feels like liberating freedom, but unintentionally promotes arbitrary data and background color picking with a high probability for deeply problematic color assignments.

(*) There were actions (EUI dataviz docs and new EUI picker, @markov00's GAH presentation segments and earlier Product comments) about a layered approach where

  • in the first round, sensible, cohesive palettes and contrasts apply as default
  • alternative themes, dataviz palettes and eventually color assignment strategies (eg. focus+context here) could be picked by those users for whom it makes a difference
  • hex/colorwheel picking of arbitrary colors, esp. those that relate to one another (and which aren't? data ink overlaid with colored text; colors for categories in the data etc.) as a last resort (Inherent or acquired meaning in the same gh issue)

It doesn't mean that giving the lowest level, therefore least informed/guided color configurability should be obscured and put in a counterintuitive place; it can be made directly accessible to the power user by eg. a keyboard shortcut, but there should be an access gradient that realizes our layered approach.

We could give "suggestions" or hints, but if we have the palettes, lets let them use them

Nodding heavily, although mostly for the layering reason, rather than for the reason of palette existence. For example we really shouldn't offer those EUI palettes, if any, for dataviz, that haven't been designed or vetted for dataviz application. Maybe we could characterize them together if it's not done yet, they have various properties and dataviz applicability worthy of tabulating

But I'll agree that removing them later is harder, so leaving them out for now is fine.

++ for not rushing something to the user that may be a regret later

@flash1293
Copy link
Contributor Author

I'm closing this PR as we want to change a bunch of stuff. Will open a new updated PR shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Lens release_note:enhancement Team:Visualizations Visualization editors, elastic-charts and infrastructure v7.9.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Lens] Allow users to select chart level categorical color palette
7 participants