-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into a11y1stBatchTests
- Loading branch information
Showing
10 changed files
with
500 additions
and
66 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
...gacy/plugins/ml/public/application/components/color_range_legend/_color_range_legend.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* Overrides for d3/svg default styles */ | ||
.mlColorRangeLegend { | ||
text { | ||
@include fontSize($euiFontSizeXS - 2px); | ||
fill: $euiColorDarkShade; | ||
} | ||
|
||
.axis path { | ||
fill: none; | ||
stroke: none; | ||
} | ||
|
||
.axis line { | ||
fill: none; | ||
stroke: $euiColorMediumShade; | ||
shape-rendering: crispEdges; | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
x-pack/legacy/plugins/ml/public/application/components/color_range_legend/_index.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
@import 'color_range_legend'; |
153 changes: 153 additions & 0 deletions
153
...legacy/plugins/ml/public/application/components/color_range_legend/color_range_legend.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import React, { useEffect, useRef, FC } from 'react'; | ||
import d3 from 'd3'; | ||
|
||
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; | ||
|
||
const COLOR_RANGE_RESOLUTION = 10; | ||
|
||
interface ColorRangeLegendProps { | ||
colorRange: (d: number) => string; | ||
justifyTicks?: boolean; | ||
showTicks?: boolean; | ||
title?: string; | ||
width?: number; | ||
} | ||
|
||
/** | ||
* Component to render a legend for color ranges to be used for color coding | ||
* table cells and visualizations. | ||
* | ||
* This current version supports normalized value ranges (0-1) only. | ||
* | ||
* @param props ColorRangeLegendProps | ||
*/ | ||
export const ColorRangeLegend: FC<ColorRangeLegendProps> = ({ | ||
colorRange, | ||
justifyTicks = false, | ||
showTicks = true, | ||
title, | ||
width = 250, | ||
}) => { | ||
const d3Container = useRef<null | SVGSVGElement>(null); | ||
|
||
const scale = d3.range(COLOR_RANGE_RESOLUTION + 1).map(d => ({ | ||
offset: (d / COLOR_RANGE_RESOLUTION) * 100, | ||
stopColor: colorRange(d / COLOR_RANGE_RESOLUTION), | ||
})); | ||
|
||
useEffect(() => { | ||
if (d3Container.current === null) { | ||
return; | ||
} | ||
|
||
const wrapperHeight = 32; | ||
const wrapperWidth = width; | ||
|
||
// top: 2 — adjust vertical alignment with title text | ||
// bottom: 20 — room for axis ticks and labels | ||
// left/right: 1 — room for first and last axis tick | ||
// when justifyTicks is enabled, the left margin is increased to not cut off the first tick label | ||
const margin = { top: 2, bottom: 20, left: justifyTicks || !showTicks ? 1 : 4, right: 1 }; | ||
|
||
const legendWidth = wrapperWidth - margin.left - margin.right; | ||
const legendHeight = wrapperHeight - margin.top - margin.bottom; | ||
|
||
// remove, then redraw the legend | ||
d3.select(d3Container.current) | ||
.selectAll('*') | ||
.remove(); | ||
|
||
const wrapper = d3 | ||
.select(d3Container.current) | ||
.classed('mlColorRangeLegend', true) | ||
.attr('width', wrapperWidth) | ||
.attr('height', wrapperHeight) | ||
.append('g') | ||
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | ||
|
||
// append gradient bar | ||
const gradient = wrapper | ||
.append('defs') | ||
.append('linearGradient') | ||
.attr('id', 'mlColorRangeGradient') | ||
.attr('x1', '0%') | ||
.attr('y1', '0%') | ||
.attr('x2', '100%') | ||
.attr('y2', '0%') | ||
.attr('spreadMethod', 'pad'); | ||
|
||
scale.forEach(function(d) { | ||
gradient | ||
.append('stop') | ||
.attr('offset', `${d.offset}%`) | ||
.attr('stop-color', d.stopColor) | ||
.attr('stop-opacity', 1); | ||
}); | ||
|
||
wrapper | ||
.append('rect') | ||
.attr('x1', 0) | ||
.attr('y1', 0) | ||
.attr('width', legendWidth) | ||
.attr('height', legendHeight) | ||
.style('fill', 'url(#mlColorRangeGradient)'); | ||
|
||
const axisScale = d3.scale | ||
.linear() | ||
.domain([0, 1]) | ||
.range([0, legendWidth]); | ||
|
||
// Using this formatter ensures we get e.g. `0` and not `0.0`, but still `0.1`, `0.2` etc. | ||
const tickFormat = d3.format(''); | ||
const legendAxis = d3.svg | ||
.axis() | ||
.scale(axisScale) | ||
.orient('bottom') | ||
.tickFormat(tickFormat) | ||
.tickSize(legendHeight + 4) | ||
.ticks(legendWidth / 40); | ||
|
||
wrapper | ||
.append('g') | ||
.attr('class', 'legend axis') | ||
.attr('transform', 'translate(0, 0)') | ||
.call(legendAxis); | ||
|
||
// Adjust the alignment of the first and last tick text | ||
// so that the tick labels don't overflow the color range. | ||
if (justifyTicks || !showTicks) { | ||
const text = wrapper.selectAll('text')[0]; | ||
if (text.length > 1) { | ||
d3.select(text[0]).style('text-anchor', 'start'); | ||
d3.select(text[text.length - 1]).style('text-anchor', 'end'); | ||
} | ||
} | ||
|
||
if (!showTicks) { | ||
wrapper.selectAll('.axis line').style('display', 'none'); | ||
} | ||
}, [JSON.stringify(scale), d3Container.current]); | ||
|
||
if (title === undefined) { | ||
return <svg ref={d3Container} />; | ||
} | ||
|
||
return ( | ||
<EuiFlexGroup gutterSize="s"> | ||
<EuiFlexItem grow={false}> | ||
<EuiText size="xs"> | ||
<strong>{title}</strong> | ||
</EuiText> | ||
</EuiFlexItem> | ||
<EuiFlexItem grow={false}> | ||
<svg ref={d3Container} /> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
); | ||
}; |
14 changes: 14 additions & 0 deletions
14
x-pack/legacy/plugins/ml/public/application/components/color_range_legend/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
export { ColorRangeLegend } from './color_range_legend'; | ||
export { | ||
colorRangeOptions, | ||
colorRangeScaleOptions, | ||
useColorRange, | ||
COLOR_RANGE, | ||
COLOR_RANGE_SCALE, | ||
} from './use_color_range'; |
59 changes: 59 additions & 0 deletions
59
...egacy/plugins/ml/public/application/components/color_range_legend/use_color_range.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { influencerColorScaleFactory } from './use_color_range'; | ||
|
||
jest.mock('../../contexts/ui/use_ui_chrome_context'); | ||
|
||
describe('useColorRange', () => { | ||
test('influencerColorScaleFactory(1)', () => { | ||
const influencerColorScale = influencerColorScaleFactory(1); | ||
|
||
expect(influencerColorScale(0)).toBe(0); | ||
expect(influencerColorScale(0.1)).toBe(0.1); | ||
expect(influencerColorScale(0.2)).toBe(0.2); | ||
expect(influencerColorScale(0.3)).toBe(0.3); | ||
expect(influencerColorScale(0.4)).toBe(0.4); | ||
expect(influencerColorScale(0.5)).toBe(0.5); | ||
expect(influencerColorScale(0.6)).toBe(0.6); | ||
expect(influencerColorScale(0.7)).toBe(0.7); | ||
expect(influencerColorScale(0.8)).toBe(0.8); | ||
expect(influencerColorScale(0.9)).toBe(0.9); | ||
expect(influencerColorScale(1)).toBe(1); | ||
}); | ||
|
||
test('influencerColorScaleFactory(2)', () => { | ||
const influencerColorScale = influencerColorScaleFactory(2); | ||
|
||
expect(influencerColorScale(0)).toBe(0); | ||
expect(influencerColorScale(0.1)).toBe(0); | ||
expect(influencerColorScale(0.2)).toBe(0); | ||
expect(influencerColorScale(0.3)).toBe(0); | ||
expect(influencerColorScale(0.4)).toBe(0); | ||
expect(influencerColorScale(0.5)).toBe(0); | ||
expect(influencerColorScale(0.6)).toBe(0.04999999999999999); | ||
expect(influencerColorScale(0.7)).toBe(0.09999999999999998); | ||
expect(influencerColorScale(0.8)).toBe(0.15000000000000002); | ||
expect(influencerColorScale(0.9)).toBe(0.2); | ||
expect(influencerColorScale(1)).toBe(0.25); | ||
}); | ||
|
||
test('influencerColorScaleFactory(3)', () => { | ||
const influencerColorScale = influencerColorScaleFactory(3); | ||
|
||
expect(influencerColorScale(0)).toBe(0); | ||
expect(influencerColorScale(0.1)).toBe(0); | ||
expect(influencerColorScale(0.2)).toBe(0); | ||
expect(influencerColorScale(0.3)).toBe(0); | ||
expect(influencerColorScale(0.4)).toBe(0.05000000000000003); | ||
expect(influencerColorScale(0.5)).toBe(0.125); | ||
expect(influencerColorScale(0.6)).toBe(0.2); | ||
expect(influencerColorScale(0.7)).toBe(0.27499999999999997); | ||
expect(influencerColorScale(0.8)).toBe(0.35000000000000003); | ||
expect(influencerColorScale(0.9)).toBe(0.425); | ||
expect(influencerColorScale(1)).toBe(0.5); | ||
}); | ||
}); |
Oops, something went wrong.