mirror of
https://github.com/umap-project/umap.git
synced 2025-04-30 12:12:36 +02:00
Merge pull request #1776 from umap-project/conditional-rules
feat: very minimal experimental conditional style rules
This commit is contained in:
commit
88b7e401c0
8 changed files with 481 additions and 4 deletions
|
@ -1,6 +1,6 @@
|
||||||
# Questions Fréquemment Posées (FAQ)
|
# Questions Fréquemment Posées (FAQ)
|
||||||
|
|
||||||
## Quelle syntaxe est acceptée dans les champs de description ?
|
## Quelle syntaxe est acceptée dans les champs de description ? {: #text-formatting }
|
||||||
|
|
||||||
* `*simple astérisque pour italique*` → *simple astérisque pour italique*
|
* `*simple astérisque pour italique*` → *simple astérisque pour italique*
|
||||||
* `**double astérisque pour gras**` → **double astérisque pour gras**
|
* `**double astérisque pour gras**` → **double astérisque pour gras**
|
||||||
|
@ -37,3 +37,13 @@ Sur MacOS, utliser `Cmd` à la place de `Ctrl`.
|
||||||
* `Ctrl+-` → dézoome
|
* `Ctrl+-` → dézoome
|
||||||
* `Shift+click` sur un élément → ouvre le panneau d'édition de cet élément
|
* `Shift+click` sur un élément → ouvre le panneau d'édition de cet élément
|
||||||
* `Ctrl+Shift+click` sur un élément → ouvre le panneau d'édition du calque de cet élément
|
* `Ctrl+Shift+click` sur un élément → ouvre le panneau d'édition du calque de cet élément
|
||||||
|
|
||||||
|
## Quelle syntaxe est acceptée dans les règles de formattage conditionnel ? {: #conditional-rules }
|
||||||
|
|
||||||
|
* `macolonne=impair` → cible les éléments dont la colonne `macolonne` vaut `impair`
|
||||||
|
* `macolonne!=impair` → cible les éléments dont la colonne `macolonne` est absente ou dont la valeur est différente de `impair`
|
||||||
|
* `macolonne>12` → cible les éléments dont la colonne `macolonne` est supérieur au nombre `12`
|
||||||
|
* `macolonne<12.34` → cible les éléments dont la colonne `macolonne` est inférieure au nombre `12.34`
|
||||||
|
|
||||||
|
Quand la condition est vraie pour un élément donné, le style associé sera appliqué.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Frequently Asked Questions (FAQ)
|
# Frequently Asked Questions (FAQ)
|
||||||
|
|
||||||
## Which syntax is allowed in description fields?
|
## Which syntax is allowed in description fields? {: #text-formatting }
|
||||||
|
|
||||||
* `*single star for italic*` → *single star for italic*
|
* `*single star for italic*` → *single star for italic*
|
||||||
* `**double star for bold**` → **double star for bold**
|
* `**double star for bold**` → **double star for bold**
|
||||||
|
@ -38,3 +38,11 @@ In MacOS, `Ctrl` is replaced by `Cmd`.
|
||||||
* `Shift+click` on a feature → edit this feature
|
* `Shift+click` on a feature → edit this feature
|
||||||
* `Ctrl+Shift+click` on a feature → edit this feature layer
|
* `Ctrl+Shift+click` on a feature → edit this feature layer
|
||||||
|
|
||||||
|
## Which syntax is allowed in conditional rules? {: #conditional-rules }
|
||||||
|
|
||||||
|
* `mycolumn=odd` → will match features whose column `mycolumn` equal `odd`
|
||||||
|
* `mycolumn!=odd` → will match features whose column `mycolumn` is missing or different from `odd`
|
||||||
|
* `mycolumn>12` → will match features whose column `mycolumn` is greater than `12` (as number)
|
||||||
|
* `mycolumn<12.34` → will match features whose column `mycolumn` is lower than `12.34` (as number)
|
||||||
|
|
||||||
|
When the condition match, the associated style will be applied to corresponding feature.
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Caption from './caption.js'
|
||||||
import { Panel, EditPanel, FullPanel } from './ui/panel.js'
|
import { Panel, EditPanel, FullPanel } from './ui/panel.js'
|
||||||
import Dialog from './ui/dialog.js'
|
import Dialog from './ui/dialog.js'
|
||||||
import Tooltip from './ui/tooltip.js'
|
import Tooltip from './ui/tooltip.js'
|
||||||
|
import Rules from './rules.js'
|
||||||
import * as Utils from './utils.js'
|
import * as Utils from './utils.js'
|
||||||
import { SCHEMA } from './schema.js'
|
import { SCHEMA } from './schema.js'
|
||||||
import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js'
|
import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js'
|
||||||
|
@ -43,6 +44,7 @@ window.U = {
|
||||||
Panel,
|
Panel,
|
||||||
Request,
|
Request,
|
||||||
RequestError,
|
RequestError,
|
||||||
|
Rules,
|
||||||
SCHEMA,
|
SCHEMA,
|
||||||
ServerRequest,
|
ServerRequest,
|
||||||
SyncEngine,
|
SyncEngine,
|
||||||
|
|
241
umap/static/umap/js/modules/rules.js
Normal file
241
umap/static/umap/js/modules/rules.js
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
import { DomUtil, DomEvent, stamp } from '../../vendors/leaflet/leaflet-src.esm.js'
|
||||||
|
import * as Utils from './utils.js'
|
||||||
|
import { translate } from './i18n.js'
|
||||||
|
|
||||||
|
class Rule {
|
||||||
|
|
||||||
|
get condition() {
|
||||||
|
return this._condition
|
||||||
|
}
|
||||||
|
|
||||||
|
set condition(value) {
|
||||||
|
this._condition = value
|
||||||
|
this.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get isDirty() {
|
||||||
|
return this._isDirty
|
||||||
|
}
|
||||||
|
|
||||||
|
set isDirty(status) {
|
||||||
|
this._isDirty = status
|
||||||
|
if (status) this.map.isDirty = status
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(map, condition = '', options = {}) {
|
||||||
|
// TODO make this public properties when browser coverage is ok
|
||||||
|
// cf https://caniuse.com/?search=public%20class%20field
|
||||||
|
this._condition = null
|
||||||
|
this._isDirty = false
|
||||||
|
this.OPERATORS = [
|
||||||
|
['>', this.gt],
|
||||||
|
['<', this.lt],
|
||||||
|
// When sent by Django
|
||||||
|
['<', this.lt],
|
||||||
|
['!=', this.not_equal],
|
||||||
|
['=', this.equal],
|
||||||
|
]
|
||||||
|
this.map = map
|
||||||
|
this.active = true
|
||||||
|
this.options = options
|
||||||
|
this.condition = condition
|
||||||
|
}
|
||||||
|
|
||||||
|
render(fields) {
|
||||||
|
this.map.render(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
equal(other) {
|
||||||
|
return this.expected === other
|
||||||
|
}
|
||||||
|
|
||||||
|
not_equal(other) {
|
||||||
|
return this.expected != other
|
||||||
|
}
|
||||||
|
|
||||||
|
gt(other) {
|
||||||
|
return other > this.expected
|
||||||
|
}
|
||||||
|
|
||||||
|
lt(other) {
|
||||||
|
return other < this.expected
|
||||||
|
}
|
||||||
|
|
||||||
|
parse() {
|
||||||
|
let vars = []
|
||||||
|
this.cast = (v) => v
|
||||||
|
this.operator = undefined
|
||||||
|
for (const [sign, func] of this.OPERATORS) {
|
||||||
|
if (this.condition.includes(sign)) {
|
||||||
|
this.operator = func
|
||||||
|
vars = this.condition.split(sign)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (vars.length != 2) return
|
||||||
|
this.key = vars[0]
|
||||||
|
this.expected = vars[1]
|
||||||
|
if (!isNaN(this.expected)) this.cast = parseFloat
|
||||||
|
else if (['true', 'false'].includes(this.expected)) this.cast = (v) => !!v
|
||||||
|
this.expected = this.cast(this.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
match(props) {
|
||||||
|
if (!this.operator || !this.active) return false
|
||||||
|
return this.operator(this.cast(props[this.key]))
|
||||||
|
}
|
||||||
|
|
||||||
|
getMap() {
|
||||||
|
return this.map
|
||||||
|
}
|
||||||
|
|
||||||
|
getOption(option) {
|
||||||
|
return this.options[option]
|
||||||
|
}
|
||||||
|
|
||||||
|
edit() {
|
||||||
|
const options = [
|
||||||
|
[
|
||||||
|
'condition',
|
||||||
|
{
|
||||||
|
handler: 'BlurInput',
|
||||||
|
label: translate('Condition'),
|
||||||
|
placeholder: translate('key=value or key!=value'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'options.color',
|
||||||
|
'options.iconClass',
|
||||||
|
'options.iconUrl',
|
||||||
|
'options.iconOpacity',
|
||||||
|
'options.opacity',
|
||||||
|
'options.weight',
|
||||||
|
'options.fill',
|
||||||
|
'options.fillColor',
|
||||||
|
'options.fillOpacity',
|
||||||
|
'options.smoothFactor',
|
||||||
|
'options.dashArray',
|
||||||
|
]
|
||||||
|
const container = DomUtil.create('div')
|
||||||
|
const builder = new U.FormBuilder(this, options)
|
||||||
|
const defaultShapeProperties = DomUtil.add('div', '', container)
|
||||||
|
defaultShapeProperties.appendChild(builder.build())
|
||||||
|
|
||||||
|
this.map.editPanel.open({ content: container })
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToolbox(row) {
|
||||||
|
row.classList.toggle('off', !this.active)
|
||||||
|
const toggle = DomUtil.createButtonIcon(
|
||||||
|
row,
|
||||||
|
'icon-eye',
|
||||||
|
translate('Show/hide layer')
|
||||||
|
)
|
||||||
|
const edit = DomUtil.createButtonIcon(
|
||||||
|
row,
|
||||||
|
'icon-edit show-on-edit',
|
||||||
|
translate('Edit')
|
||||||
|
)
|
||||||
|
const remove = DomUtil.createButtonIcon(
|
||||||
|
row,
|
||||||
|
'icon-delete show-on-edit',
|
||||||
|
translate('Delete layer')
|
||||||
|
)
|
||||||
|
DomEvent.on(edit, 'click', this.edit, this)
|
||||||
|
DomEvent.on(
|
||||||
|
remove,
|
||||||
|
'click',
|
||||||
|
function () {
|
||||||
|
if (!confirm(translate('Are you sure you want to delete this rule?'))) return
|
||||||
|
this._delete()
|
||||||
|
this.map.editPanel.close()
|
||||||
|
},
|
||||||
|
this
|
||||||
|
)
|
||||||
|
DomUtil.add('span', '', row, this.condition || translate('empty rule'))
|
||||||
|
DomUtil.createIcon(row, 'icon-drag', translate('Drag to reorder'))
|
||||||
|
row.dataset.id = stamp(this)
|
||||||
|
DomEvent.on(toggle, 'click', () => {
|
||||||
|
this.active = !this.active
|
||||||
|
row.classList.toggle('off', !this.active)
|
||||||
|
this.map.render(['rules'])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_delete() {
|
||||||
|
this.map.rules.rules = this.map.rules.rules.filter((rule) => rule != this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Rules {
|
||||||
|
constructor(map) {
|
||||||
|
this.map = map
|
||||||
|
this.rules = []
|
||||||
|
this.loadRules()
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRules() {
|
||||||
|
if (!this.map.options.rules?.length) return
|
||||||
|
for (const { condition, options } of this.map.options.rules) {
|
||||||
|
if (!condition) continue
|
||||||
|
this.rules.push(new Rule(this.map, condition, options))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReorder(src, dst, initialIndex, finalIndex) {
|
||||||
|
const moved = this.rules.find((rule) => stamp(rule) == src.dataset.id)
|
||||||
|
const reference = this.rules.find((rule) => stamp(rule) == dst.dataset.id)
|
||||||
|
const movedIdx = this.rules.indexOf(moved)
|
||||||
|
let referenceIdx = this.rules.indexOf(reference)
|
||||||
|
const minIndex = Math.min(movedIdx, referenceIdx)
|
||||||
|
const maxIndex = Math.max(movedIdx, referenceIdx)
|
||||||
|
moved._delete() // Remove from array
|
||||||
|
referenceIdx = this.rules.indexOf(reference)
|
||||||
|
let newIdx
|
||||||
|
if (finalIndex === 0) newIdx = 0
|
||||||
|
else if (finalIndex > initialIndex) newIdx = referenceIdx
|
||||||
|
else newIdx = referenceIdx + 1
|
||||||
|
this.rules.splice(newIdx, 0, moved)
|
||||||
|
moved.isDirty = true
|
||||||
|
this.map.render(['rules'])
|
||||||
|
}
|
||||||
|
|
||||||
|
edit(container) {
|
||||||
|
const body = DomUtil.createFieldset(container, translate('Conditional style rules'))
|
||||||
|
if (this.rules.length) {
|
||||||
|
const ul = DomUtil.create('ul', '', body)
|
||||||
|
for (const rule of this.rules) {
|
||||||
|
rule.renderToolbox(DomUtil.create('li', 'orderable', ul))
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderable = new U.Orderable(ul, this.onReorder.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
DomUtil.createButton('umap-add', body, translate('Add rule'), this.addRule, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
addRule() {
|
||||||
|
const rule = new Rule(this.map)
|
||||||
|
rule.isDirty = true
|
||||||
|
this.rules.push(rule)
|
||||||
|
rule.edit(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
commit() {
|
||||||
|
this.map.options.rules = this.rules.map((rule) => {
|
||||||
|
return {
|
||||||
|
condition: rule.condition,
|
||||||
|
options: rule.options,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getOption(option, feature) {
|
||||||
|
for (const rule of this.rules) {
|
||||||
|
if (rule.match(feature.properties)) {
|
||||||
|
if (Utils.usableOption(rule.options, option)) return rule.options[option]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,6 +57,10 @@ export const SCHEMA = {
|
||||||
type: Object,
|
type: Object,
|
||||||
impacts: ['data'],
|
impacts: ['data'],
|
||||||
},
|
},
|
||||||
|
condition: {
|
||||||
|
type: String,
|
||||||
|
impacts: ['data'],
|
||||||
|
},
|
||||||
dashArray: {
|
dashArray: {
|
||||||
type: String,
|
type: String,
|
||||||
impacts: ['data'],
|
impacts: ['data'],
|
||||||
|
@ -386,6 +390,10 @@ export const SCHEMA = {
|
||||||
type: Object,
|
type: Object,
|
||||||
impacts: ['remote-data'],
|
impacts: ['remote-data'],
|
||||||
},
|
},
|
||||||
|
rules: {
|
||||||
|
type: Object,
|
||||||
|
impacts: ['data'],
|
||||||
|
},
|
||||||
scaleControl: {
|
scaleControl: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
impacts: ['ui'],
|
impacts: ['ui'],
|
||||||
|
|
|
@ -419,6 +419,7 @@ U.Map = L.Map.extend({
|
||||||
this.importer = new U.Importer(this)
|
this.importer = new U.Importer(this)
|
||||||
this.drop = new U.DropControl(this)
|
this.drop = new U.DropControl(this)
|
||||||
this.share = new U.Share(this)
|
this.share = new U.Share(this)
|
||||||
|
this.rules = new U.Rules(this)
|
||||||
this._controls.tilelayers = new U.TileLayerControl(this)
|
this._controls.tilelayers = new U.TileLayerControl(this)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -823,7 +824,11 @@ U.Map = L.Map.extend({
|
||||||
return U.SCHEMA[option] && U.SCHEMA[option].default
|
return U.SCHEMA[option] && U.SCHEMA[option].default
|
||||||
},
|
},
|
||||||
|
|
||||||
getOption: function (option) {
|
getOption: function (option, feature) {
|
||||||
|
if (feature) {
|
||||||
|
const value = this.rules.getOption(option, feature)
|
||||||
|
if (value !== undefined) return value
|
||||||
|
}
|
||||||
if (U.Utils.usableOption(this.options, option)) return this.options[option]
|
if (U.Utils.usableOption(this.options, option)) return this.options[option]
|
||||||
return this.getDefaultOption(option)
|
return this.getDefaultOption(option)
|
||||||
},
|
},
|
||||||
|
@ -1031,6 +1036,7 @@ U.Map = L.Map.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
saveSelf: async function () {
|
saveSelf: async function () {
|
||||||
|
this.rules.commit()
|
||||||
const geojson = {
|
const geojson = {
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
geometry: this.geometry(),
|
geometry: this.geometry(),
|
||||||
|
@ -1558,6 +1564,7 @@ U.Map = L.Map.extend({
|
||||||
this._editShapeProperties(container)
|
this._editShapeProperties(container)
|
||||||
this._editDefaultProperties(container)
|
this._editDefaultProperties(container)
|
||||||
this._editInteractionsProperties(container)
|
this._editInteractionsProperties(container)
|
||||||
|
this.rules.edit(container)
|
||||||
this._editTilelayer(container)
|
this._editTilelayer(container)
|
||||||
this._editOverlay(container)
|
this._editOverlay(container)
|
||||||
this._editBounds(container)
|
this._editBounds(container)
|
||||||
|
|
|
@ -1496,7 +1496,7 @@ U.DataLayer = L.Evented.extend({
|
||||||
} else if (this.layer && this.layer.defaults && this.layer.defaults[option]) {
|
} else if (this.layer && this.layer.defaults && this.layer.defaults[option]) {
|
||||||
return this.layer.defaults[option]
|
return this.layer.defaults[option]
|
||||||
} else {
|
} else {
|
||||||
return this.map.getOption(option)
|
return this.map.getOption(option, feature)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
201
umap/tests/integration/test_conditional_rules.py
Normal file
201
umap/tests/integration/test_conditional_rules.py
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
import pytest
|
||||||
|
from playwright.sync_api import expect
|
||||||
|
|
||||||
|
from ..base import DataLayerFactory
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def getColors(elements):
|
||||||
|
return [
|
||||||
|
el.evaluate("e => window.getComputedStyle(e).backgroundColor")
|
||||||
|
for el in elements.all()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
DATALAYER_DATA1 = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {
|
||||||
|
"mytype": "even",
|
||||||
|
"name": "Point 2",
|
||||||
|
"mynumber": 10,
|
||||||
|
"myboolean": True,
|
||||||
|
"mydate": "2024/04/14 12:19:17",
|
||||||
|
},
|
||||||
|
"geometry": {"type": "Point", "coordinates": [0.065918, 48.385442]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {
|
||||||
|
"mytype": "odd",
|
||||||
|
"name": "Point 1",
|
||||||
|
"mynumber": 12,
|
||||||
|
"myboolean": False,
|
||||||
|
"mydate": "2024/03/13 12:20:20",
|
||||||
|
},
|
||||||
|
"geometry": {"type": "Point", "coordinates": [3.55957, 49.767074]},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"_umap_options": {
|
||||||
|
"name": "Calque 1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DATALAYER_DATA2 = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {
|
||||||
|
"mytype": "even",
|
||||||
|
"name": "Point 4",
|
||||||
|
"mynumber": 10,
|
||||||
|
"myboolean": "true",
|
||||||
|
"mydate": "2024/08/18 13:14:15",
|
||||||
|
},
|
||||||
|
"geometry": {"type": "Point", "coordinates": [0.856934, 45.290347]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {
|
||||||
|
"mytype": "odd",
|
||||||
|
"name": "Point 3",
|
||||||
|
"mynumber": 14,
|
||||||
|
"mydate": "2024-04-14T10:19:17.000Z",
|
||||||
|
},
|
||||||
|
"geometry": {"type": "Point", "coordinates": [4.372559, 47.945786]},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"_umap_options": {
|
||||||
|
"name": "Calque 2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_simple_equal_rule_at_load(live_server, page, map):
|
||||||
|
map.settings["properties"]["rules"] = [
|
||||||
|
{"condition": "mytype=odd", "options": {"color": "aliceblue"}}
|
||||||
|
]
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
|
||||||
|
markers = page.locator(".leaflet-marker-icon .icon_container")
|
||||||
|
expect(markers).to_have_count(4)
|
||||||
|
colors = getColors(markers)
|
||||||
|
assert colors.count("rgb(240, 248, 255)") == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_simple_not_equal_rule_at_load(live_server, page, map):
|
||||||
|
map.settings["properties"]["rules"] = [
|
||||||
|
{"condition": "mytype!=even", "options": {"color": "aliceblue"}}
|
||||||
|
]
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
|
||||||
|
markers = page.locator(".leaflet-marker-icon .icon_container")
|
||||||
|
expect(markers).to_have_count(4)
|
||||||
|
colors = getColors(markers)
|
||||||
|
assert colors.count("rgb(240, 248, 255)") == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_gt_rule_with_number_at_load(live_server, page, map):
|
||||||
|
map.settings["properties"]["rules"] = [
|
||||||
|
{"condition": "mynumber>10", "options": {"color": "aliceblue"}}
|
||||||
|
]
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
|
||||||
|
markers = page.locator(".leaflet-marker-icon .icon_container")
|
||||||
|
expect(markers).to_have_count(4)
|
||||||
|
colors = getColors(markers)
|
||||||
|
assert colors.count("rgb(240, 248, 255)") == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_lt_rule_with_number_at_load(live_server, page, map):
|
||||||
|
map.settings["properties"]["rules"] = [
|
||||||
|
{"condition": "mynumber<14", "options": {"color": "aliceblue"}}
|
||||||
|
]
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
|
||||||
|
markers = page.locator(".leaflet-marker-icon .icon_container")
|
||||||
|
expect(markers).to_have_count(4)
|
||||||
|
colors = getColors(markers)
|
||||||
|
assert colors.count("rgb(240, 248, 255)") == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_lt_rule_with_float_at_load(live_server, page, map):
|
||||||
|
map.settings["properties"]["rules"] = [
|
||||||
|
{"condition": "mynumber<12.3", "options": {"color": "aliceblue"}}
|
||||||
|
]
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
|
||||||
|
markers = page.locator(".leaflet-marker-icon .icon_container")
|
||||||
|
expect(markers).to_have_count(4)
|
||||||
|
colors = getColors(markers)
|
||||||
|
assert colors.count("rgb(240, 248, 255)") == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_equal_rule_with_boolean_at_load(live_server, page, map):
|
||||||
|
map.settings["properties"]["rules"] = [
|
||||||
|
{"condition": "myboolean=true", "options": {"color": "aliceblue"}}
|
||||||
|
]
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
|
||||||
|
markers = page.locator(".leaflet-marker-icon .icon_container")
|
||||||
|
expect(markers).to_have_count(4)
|
||||||
|
colors = getColors(markers)
|
||||||
|
assert colors.count("rgb(240, 248, 255)") == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_create_new_rule(live_server, page, openmap):
|
||||||
|
DataLayerFactory(map=openmap, data=DATALAYER_DATA1)
|
||||||
|
DataLayerFactory(map=openmap, data=DATALAYER_DATA2)
|
||||||
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}#6/48.948/1.670")
|
||||||
|
markers = page.locator(".leaflet-marker-icon .icon_container")
|
||||||
|
expect(markers).to_have_count(4)
|
||||||
|
page.get_by_role("button", name="Edit").click()
|
||||||
|
page.get_by_role("link", name="Map advanced properties").click()
|
||||||
|
page.get_by_text("Conditional style rules").click()
|
||||||
|
page.get_by_role("button", name="Add rule").click()
|
||||||
|
page.locator("input[name=condition]").click()
|
||||||
|
page.locator("input[name=condition]").fill("mytype=odd")
|
||||||
|
page.locator(".umap-field-color .define").first.click()
|
||||||
|
page.get_by_title("AliceBlue").first.click()
|
||||||
|
colors = getColors(markers)
|
||||||
|
assert colors.count("rgb(240, 248, 255)") == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_deactive_rule_from_list(live_server, page, openmap):
|
||||||
|
openmap.settings["properties"]["rules"] = [
|
||||||
|
{"condition": "mytype=odd", "options": {"color": "aliceblue"}}
|
||||||
|
]
|
||||||
|
openmap.save()
|
||||||
|
DataLayerFactory(map=openmap, data=DATALAYER_DATA1)
|
||||||
|
DataLayerFactory(map=openmap, data=DATALAYER_DATA2)
|
||||||
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}#6/48.948/1.670")
|
||||||
|
markers = page.locator(".leaflet-marker-icon .icon_container")
|
||||||
|
expect(markers).to_have_count(4)
|
||||||
|
colors = getColors(markers)
|
||||||
|
assert colors.count("rgb(240, 248, 255)") == 2
|
||||||
|
page.get_by_role("button", name="Edit").click()
|
||||||
|
page.get_by_role("link", name="Map advanced properties").click()
|
||||||
|
page.get_by_text("Conditional style rules").click()
|
||||||
|
page.get_by_role("button", name="Show/hide layer").click()
|
||||||
|
colors = getColors(markers)
|
||||||
|
assert colors.count("rgb(240, 248, 255)") == 0
|
||||||
|
page.get_by_role("button", name="Show/hide layer").click()
|
||||||
|
colors = getColors(markers)
|
||||||
|
assert colors.count("rgb(240, 248, 255)") == 2
|
Loading…
Reference in a new issue