Skip to content

Commit

Permalink
Vector layer legend includes conditional styling
Browse files Browse the repository at this point in the history
This updates legend code to include conditional styling.

* A "Label" attribute is added to conditional styling configuration for use in legends
* The documentation for vector layers is updated to include "label"
* Legend code includes conditional styling, indented below the layer name and its default style image
  • Loading branch information
michaelpnelson committed Mar 20, 2023
1 parent 7a89c5c commit b028aec
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 42 deletions.
8 changes: 6 additions & 2 deletions docs/configuration/layers/vector.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Style attributes applied to features based on feature attribute values. These st
Each conditional style has a `property`, which is the name of a feature attribute, and `conditions`, an array of objects containing:

- `value`: the value of the feature's `property` attribute
- `label`: an optional description of features matching this condition, for use in legends; if omitted; value will be used
- `style`: an object of style properties and values
- `operator`: an optional operator to use to compare the condition value to the feature's property value. Operators are:
- `>` greater than, for number values
Expand All @@ -96,13 +97,15 @@ In this sample configuration, features having a `Station_Type` value of `Public`
"conditions": [
{
"value": "Public",
"label": "Public Stations",
"style": {
"strokeColor": "#0000ff",
"fillColor": "#0000ff"
}
},
{
"value": "Private",
"label": "Private Stations",
"style": {
"strokeColor": "#00ff00",
"fillColor": "#00ff00"
Expand All @@ -113,16 +116,17 @@ In this sample configuration, features having a `Station_Type` value of `Public`
]
```

This sample configuration styles features having a `Charging_Level` value of 2 or higher with a white stroke color:
This sample configuration styles features having a `Charging_Level` value of 3 or higher with a white stroke color:

```
"conditionalStyles": [
{
"property": "Charging_Level",
"conditions": [
{
"label": "Fast Charging",
"operator": ">=",
"value": 2,
"value": 3,
"style": {
"strokeColor": "#ffffff"
}
Expand Down
105 changes: 65 additions & 40 deletions src/smk/layer/layer-vector.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,67 +21,94 @@ include.module( 'layer.layer-vector-js', [ 'layer.layer-js' ], function () {
var cv = $( '<canvas width="' + width * mult + '" height="' + height + '">' ).get( 0 )
var ctx = cv.getContext( '2d' )

var styles = [].concat( self.config.style )
const legendData = [];
legendData.push({
title: self.config.legend && self.config.legend.title || self.config.title,
style: self.config.style
});

if (self.config.conditionalStyles) {
const defaultStyle = self.config.style ? self.config.style : {};
self.config.conditionalStyles.forEach(conditionalStyle => {
conditionalStyle.conditions.forEach(condition => {
const combinedStyle = Object.assign({}, defaultStyle);
Object.assign(combinedStyle, condition.style);
legendData.push({
title: condition.label || condition.value,
style: combinedStyle,
indent: true
});
});
});
}

return SMK.UTIL.resolved( 0 )
.then( drawPoint )
.then( drawLine )
.then( drawFill )
.then( function () {
return [ {
url: cv.toDataURL( 'image/png' ),
title: self.config.legend.title || self.config.title
} ]
return legendData;
} )

function drawPoint( offset ) {
if ( !self.config.legend.point ) return offset

return SMK.UTIL.makePromise( function ( res, rej ) {
if ( styles[ 0 ].markerUrl ) {
var img = $( '<img>' )
.on( 'load', function () {
var r = img.width / img.height
if ( r > 1 ) r = 1 / r
ctx.drawImage( img, offset, 0, height * r, height )
res( offset + width )
} )
.on( 'error', res )
.attr( 'src', viewer.resolveAttachmentUrl( styles[ 0 ].markerUrl, null, 'png' ) )
.get( 0 )
}
else {
if (legendData.length === 1 && legendData[0].style.markerUrl) {
loadMarkerImage(offset)
.then((offset) => {
legendData[0].url = cv.toDataURL( 'image/png' );
return offset;
});
} else {
legendData.forEach(legendItem => {
const legendItemStyle = legendItem.style;
ctx.beginPath()
ctx.arc( offset + width / 2, height / 2, styles[ 0 ].strokeWidth / 2, 0, 2 * Math.PI )
ctx.arc( offset + width / 2, height / 2, legendItemStyle.strokeWidth / 2, 0, 2 * Math.PI )
ctx.lineWidth = 2
ctx.strokeStyle = cssColorAsRGBA( styles[ 0 ].strokeColor, styles[ 0 ].strokeOpacity )
ctx.fillStyle = cssColorAsRGBA( styles[ 0 ].fillColor, styles[ 0 ].fillOpacity )
ctx.strokeStyle = cssColorAsRGBA( legendItemStyle.strokeColor, legendItemStyle.strokeOpacity )
ctx.fillStyle = cssColorAsRGBA( legendItemStyle.fillColor, legendItemStyle.fillOpacity )
ctx.fill()
ctx.stroke()

res( offset + width )
}
} )
legendItem.url = cv.toDataURL( 'image/png' );
});
}
}

function loadMarkerImage(offset) {
return SMK.UTIL.makePromise( function ( res, rej ) {
var img = $( '<img>' )
.on( 'load', function () {
var r = img.width / img.height
if ( r > 1 ) r = 1 / r
ctx.drawImage( img, offset, 0, height * r, height )
res( offset + width )
} )
.on( 'error', res )
.attr( 'src', viewer.resolveAttachmentUrl( legendData[0].style.markerUrl, null, 'png' ) )
.get( 0 );
});
}

function drawLine( offset ) {
if ( !self.config.legend.line ) return offset

styles.forEach( function ( st ) {
ctx.lineWidth = st.strokeWidth
ctx.strokeStyle = cssColorAsRGBA( st.strokeColor, st.strokeOpacity )
ctx.lineCap = st.strokeCap
if ( st.strokeDashes ) {
ctx.setLineDash( st.strokeDashes.split( ',' ) )
if ( parseFloat( st.strokeDashOffset ) )
ctx.lineDashOffset = parseFloat( st.strokeDashOffset )
legendData.forEach( function ( legendItem ) {
const legendItemStyle = legendItem.style;
ctx.lineWidth = legendItemStyle.strokeWidth
ctx.strokeStyle = cssColorAsRGBA( legendItemStyle.strokeColor, legendItemStyle.strokeOpacity )
ctx.lineCap = legendItemStyle.strokeCap
if ( legendItemStyle.strokeDashes ) {
ctx.setLineDash( legendItemStyle.strokeDashes.split( ',' ) )
if ( parseFloat( legendItemStyle.strokeDashOffset ) )
ctx.lineDashOffset = parseFloat( legendItemStyle.strokeDashOffset )
}

var hw = st.strokeWidth / 2
var hw = legendItemStyle.strokeWidth / 2
ctx.moveTo( offset, height / 2 )
ctx.quadraticCurveTo( offset + width - hw, 0, offset + width - hw, height )
ctx.stroke()
legendItem.url = cv.toDataURL( 'image/png' );
} )

return offset + width
Expand All @@ -90,14 +117,12 @@ include.module( 'layer.layer-vector-js', [ 'layer.layer-js' ], function () {
function drawFill( offset ) {
if ( !self.config.legend.fill ) return offset

styles.forEach( function ( st ) {
// var w = self.config.style.strokeWidth
// ctx.lineWidth = w
// ctx.strokeStyle = self.config.style.strokeColor + alpha( self.config.style.strokeOpacity )
ctx.fillStyle = cssColorAsRGBA( st.fillColor, st.fillOpacity )
legendData.forEach( function ( legendItem ) {
const legendItemStyle = legendItem.style;
ctx.fillStyle = cssColorAsRGBA( legendItemStyle.fillColor, legendItemStyle.fillOpacity )

ctx.fillRect( 0, 0, width, height )
// ctx.strokeRect( w / 2, w / 2, width - w , height - w )
legendItem.url = cv.toDataURL( 'image/png' );
} )

return offset + width
Expand Down
1 change: 1 addition & 0 deletions src/smk/tool/layers/layer-display.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<div class="smk-legend-item"
v-for="legend in display.legends"
>
<span v-if="legend.indent" class="smk-legend-item-indent"></span>
<img v-bind:src="legend.url" v-bind:style="{ width: legend.width + 'px', height: legend.height + 'px' }">
<span class="smk-legend-title">{{ legend.title }}</span>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/smk/tool/layers/tool-layers.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@
font-size: 11px;
}

.smk-layers-panel .smk-legend .smk-legend-item .smk-legend-item-indent {
width: 10px;
}

.smk-layers-panel .smk-display .smk-folder-expand:hover {
border: 1px solid transparent;
}
Expand Down
1 change: 1 addition & 0 deletions src/smk/tool/legend/legend-display.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<div class="smk-legend-item"
v-for="legend in display.legends"
>
<span v-if="legend.indent" class="smk-legend-item-indent"></span>
<img v-bind:src="legend.url" v-bind:style="{ width: legend.width + 'px', height: legend.height + 'px' }">
<span class="smk-legend-title">{{ legend.title }}</span>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/smk/tool/legend/tool-legend.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
font-size: 11px;
}

.smk-legend-status .smk-display .smk-legend-item .smk-legend-item-indent {
width: 10px;
}

.smk-legend-status .smk-display .smk-item {
display: flex;
align-items: center;
Expand Down

0 comments on commit b028aec

Please sign in to comment.