[Maps] Enable borders for icon symbols (#43066)

* [Maps] Enable borders on icons

* [Maps] Enable borders on icons

* Review feedback

* Remove unnecessary Fragment
This commit is contained in:
Nick Peihl 2019-08-12 10:51:07 -07:00 committed by GitHub
parent c29a17ce3c
commit 3503aa16ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 61 additions and 30 deletions

View file

@ -24,6 +24,10 @@ Use *Icon* to symbolize Points as icons.
*Fill color*:: The fill color of the point features.
*Border color*:: The border color of the point features.
*Border width*:: The border width of the point features.
*Symbol orientation*:: The symbol orientation rotating the icon clockwise.
*Symbol size*:: The radius of the symbol size, in pixels.

View file

@ -38,6 +38,8 @@ exports[`Renders PolygonIcon with correct styles when not line only or not point
exports[`Renders SymbolIcon with correct styles when isPointOnly and symbolId provided 1`] = `
<SymbolIcon
fill="#ff0000"
stroke="rgb(106,173,213)"
strokeWidth="1px"
symbolId="airfield-15"
/>
`;

View file

@ -15,31 +15,35 @@ export class SymbolIcon extends Component {
imgDataUrl: undefined,
prevSymbolId: undefined,
prevFill: undefined,
prevStroke: undefined,
prevStrokeWidth: undefined,
}
componentDidMount() {
this._isMounted = true;
this._loadSymbol(this.props.symbolId, this.props.fill);
this._loadSymbol(this.props.symbolId, this.props.fill, this.props.stroke, this.props.strokeWidth);
}
componentDidUpdate() {
this._loadSymbol(this.props.symbolId, this.props.fill);
this._loadSymbol(this.props.symbolId, this.props.fill, this.props.stroke, this.props.strokeWidth);
}
componentWillUnmount() {
this._isMounted = false;
}
async _loadSymbol(nextSymbolId, nextFill) {
async _loadSymbol(nextSymbolId, nextFill, nextStroke, nextStrokeWidth) {
if (nextSymbolId === this.state.prevSymbolId
&& nextFill === this.state.prevFill) {
&& nextFill === this.state.prevFill
&& nextStroke === this.state.prevStroke
&& nextStrokeWidth === this.state.prevStrokeWidth) {
return;
}
let imgDataUrl;
try {
const svg = getMakiSymbolSvg(nextSymbolId);
const styledSvg = await styleSvg(svg, nextFill);
const styledSvg = await styleSvg(svg, nextFill, nextStroke, nextStrokeWidth);
imgDataUrl = buildSrcUrl(styledSvg);
} catch (error) {
// ignore failures - component will just not display an icon
@ -49,7 +53,9 @@ export class SymbolIcon extends Component {
this.setState({
imgDataUrl,
prevSymbolId: nextSymbolId,
prevFill: nextFill
prevFill: nextFill,
prevStroke: nextStroke,
prevStrokeWidth: nextStrokeWidth
});
}
}
@ -68,4 +74,6 @@ export class SymbolIcon extends Component {
SymbolIcon.propTypes = {
symbolId: PropTypes.string.isRequired,
fill: PropTypes.string.isRequired,
stroke: PropTypes.string.isRequired,
strokeWidth: PropTypes.string.isRequired
};

View file

@ -75,6 +75,8 @@ export class VectorIcon extends Component {
<SymbolIcon
symbolId={this.props.symbolId}
fill={style.fill}
stroke={style.stroke}
strokeWidth={style.strokeWidth}
/>
);
}

View file

@ -14,7 +14,7 @@ import { VectorStyleSymbolEditor } from './vector_style_symbol_editor';
import { OrientationEditor } from './orientation/orientation_editor';
import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../../vector_style_defaults';
import { VECTOR_SHAPE_TYPES } from '../../../sources/vector_feature_types';
import { SYMBOLIZE_AS_CIRCLE } from '../../vector_constants';
import { SYMBOLIZE_AS_ICON } from '../../vector_constants';
import { i18n } from '@kbn/i18n';
import { SYMBOL_OPTIONS } from '../../symbol_utils';
@ -140,23 +140,8 @@ export class VectorStyleEditor extends Component {
}
_renderPointProperties() {
let lineColor;
let lineWidth;
let iconOrientation;
if (this.props.styleProperties.symbol.options.symbolizeAs === SYMBOLIZE_AS_CIRCLE) {
lineColor = (
<Fragment>
{this._renderLineColor()}
<EuiSpacer size="m" />
</Fragment>
);
lineWidth = (
<Fragment>
{this._renderLineWidth()}
<EuiSpacer size="m" />
</Fragment>
);
} else {
if (this.props.styleProperties.symbol.options.symbolizeAs === SYMBOLIZE_AS_ICON) {
iconOrientation = (
<Fragment>
<OrientationEditor
@ -185,9 +170,11 @@ export class VectorStyleEditor extends Component {
{this._renderFillColor()}
<EuiSpacer size="m" />
{lineColor}
{this._renderLineColor()}
<EuiSpacer size="m" />
{lineWidth}
{this._renderLineWidth()}
<EuiSpacer size="m" />
{iconOrientation}

View file

@ -85,6 +85,8 @@ export function VectorStyleSymbolEditor({ styleOptions, handlePropertyChange, sy
<SymbolIcon
symbolId={value}
fill={isDarkMode ? 'rgb(223, 229, 239)' : 'rgb(52, 55, 65)'}
stroke={isDarkMode ? 'rgb(255, 255, 255)' : 'rgb(0, 0, 0)'}
strokeWidth={'1px'}
/>
</EuiFlexItem>
<EuiFlexItem>

View file

@ -62,11 +62,19 @@ export function buildSrcUrl(svgString) {
return domUrl.createObjectURL(svg);
}
export async function styleSvg(svgString, fill) {
export async function styleSvg(svgString, fill, stroke, strokeWidth) {
const svgXml = await parseXmlString(svgString);
let style = '';
if (fill) {
svgXml.svg.$.style = `fill: ${fill};`;
style += `fill:${fill};`;
}
if (stroke) {
style += `stroke:${stroke};`;
}
if (strokeWidth) {
style += `stroke-width:${strokeWidth};`;
}
if (style) svgXml.svg.$.style = style;
const builder = new xml2js.Builder();
return builder.buildObject(svgXml);
}

View file

@ -14,16 +14,30 @@ describe('getMakiSymbolSvg', () => {
});
describe('styleSvg', () => {
it('Should not add style property when fill not provided', async () => {
it('Should not add style property when style not provided', async () => {
const unstyledSvgString = '<svg version="1.1" width="11px" height="11px" viewBox="0 0 11 11"><path/></svg>';
const styledSvg = await styleSvg(unstyledSvgString);
expect(styledSvg.split('\n')[1]).toBe('<svg version=\"1.1\" width=\"11px\" height=\"11px\" viewBox=\"0 0 11 11\">');
});
it('Should add style property to svg element', async () => {
it('Should add fill style property to svg element', async () => {
const unstyledSvgString = '<svg version="1.1" width="11px" height="11px" viewBox="0 0 11 11"><path/></svg>';
const styledSvg = await styleSvg(unstyledSvgString, 'red');
// eslint-disable-next-line max-len
expect(styledSvg.split('\n')[1]).toBe('<svg version=\"1.1\" width=\"11px\" height=\"11px\" viewBox=\"0 0 11 11\" style=\"fill: red;\">');
expect(styledSvg.split('\n')[1]).toBe('<svg version=\"1.1\" width=\"11px\" height=\"11px\" viewBox=\"0 0 11 11\" style=\"fill:red;\">');
});
it('Should add stroke style property to svg element', async () => {
const unstyledSvgString = '<svg version="1.1" width="11px" height="11px" viewBox="0 0 11 11"><path/></svg>';
const styledSvg = await styleSvg(unstyledSvgString, 'red', 'white');
// eslint-disable-next-line max-len
expect(styledSvg.split('\n')[1]).toBe('<svg version=\"1.1\" width=\"11px\" height=\"11px\" viewBox=\"0 0 11 11\" style=\"fill:red;stroke:white;\">');
});
it('Should add stroke-width style property to svg element', async () => {
const unstyledSvgString = '<svg version="1.1" width="11px" height="11px" viewBox="0 0 11 11"><path/></svg>';
const styledSvg = await styleSvg(unstyledSvgString, 'red', 'white', '2px');
// eslint-disable-next-line max-len
expect(styledSvg.split('\n')[1]).toBe('<svg version=\"1.1\" width=\"11px\" height=\"11px\" viewBox=\"0 0 11 11\" style=\"fill:red;stroke:white;stroke-width:2px;\">');
});
});

View file

@ -546,8 +546,12 @@ export class VectorStyle extends AbstractStyle {
const symbolId = this._descriptor.properties.symbol.options.symbolId;
mbMap.setLayoutProperty(symbolLayerId, 'icon-anchor', getMakiSymbolAnchor(symbolId));
const color = this._getMBColor(this._descriptor.properties.fillColor);
const haloColor = this._getMBColor(this._descriptor.properties.lineColor);
const haloWidth = this._getMbSize(this._descriptor.properties.lineWidth);
// icon-color is only supported on SDF icons.
mbMap.setPaintProperty(symbolLayerId, 'icon-color', color);
mbMap.setPaintProperty(symbolLayerId, 'icon-halo-color', haloColor);
mbMap.setPaintProperty(symbolLayerId, 'icon-halo-width', haloWidth);
mbMap.setPaintProperty(symbolLayerId, 'icon-opacity', alpha);
// circle sizing is by radius