Changing style of map elements dynamically - javascript

I am attempting to change the style of a number of map elements on the fly, without success. By on the fly I mean changing the color of a map element, the water mass for instance, dynamically from a computed color. Using Tangram but open to other engines if there is a better solution. Since Tangram uses a YAML file to style the various elements, I am using inline Javascript on the scene (YAML) file. In other words, instead of giving it a fixed color, I want to compute the color (from an image). Instead of this:
water:
draw:
polygons:
color: "blue"
I am doing something along these lines (further complicated because I am using Vue Router):
water:
draw:
polygons:
color: function() { return this.colors[0]; }
computedColors is computed in a mixing, then broadcasted to the appropriate route:
var colorize = {
data: function () {
return {
computedColors: []
};
},
created: function () {
this.getColors();
},
methods: {
getColors: function () {
...
self.computedColors = [
...
];
self.$broadcast('parent-colors', self.computedColors);
...
};
};
}
Here is the route:
router.map({
'/map': {
name: 'map',
component: Vue.extend({
template: '#map',
mixins: [colorize],
data: function () {
return {
colors: []
};
},
events: {
'parent-colors': function (computedColors) {
this.colors = computedColors;
}
},
ready: {
var map = L.map('map');
var layer = Tangram.leafletLayer({
scene: './tiles/scene.yaml'
});
layer.addTo(map);
map.setView([40.70531887544228, -74.00976419448853], 15);
}
});
Any hints on what I may be doing wrong appreciated.
UPDATE
I am getting an error of sorts. It is related with Tangram but not quite able to figure out what it is exactly. Seems to be an issue with the parsing of the YAML file. If I change this in my scene.yaml:
water:
draw:
polygons:
color: function() { return this.colors[0]; }
With this:
water:
draw:
polygons:
color: function() { return this.color1; }
I get no errors but unfortunately the water mass still isn't assigned any color either.
Of course, I had to change these lines in the map route instance too:
data: function () {
return {
color1: ''
};
},
...
events: {
'parent-colors': function (computedColors) {
this.color1 = computedColors[0];
}
}

The following doesn't offer a solution to the specific problem of styling a Tangram map on the fly (which Yaml doesn't seem to easily allow), but it partially answers the question of how to style map vectors dynamically. It implements the plugin Leaflet.VectorGrid and assigns properties to layers programmatically through the vectorTileLayerStyles method (accomplished in the example below with color: self.colors[6]).
L.vectorGrid.slicer(countries, {
rendererFactory: L.svg.tile,
vectorTileLayerStyles: {
sliced: function() {
return {
stroke: true,
color: self.colors[6],
weight: 0.5,
};
}
},
}).addTo(map);
The variable countries is really just a GeoJson with var countries = added to it, something along these lines:
var countries = {
"type": "FeatureCollection",
"features": [
{ "type": "Feature"
(...)
}
]
};
It's a straightforward solution and it works perfectly with small amounts of data, but since it's a client-side solution it's heavy on the browser when dealing with bigger data sets. Still, it may be useful for those who may be looking for a simple way to style a simplified world map or a limited map area on the fly.
UPDATE
A more performant solution is to use a tile server. The example below implements t-rex, with canvas renderer L.canvas.tile in the rendererFactory option instead of L.svg.tile and protobuf:
var lines = "http://127.0.0.1:6767/lines/{z}/{x}/{y}.pbf";
var multilinestrings = "http://127.0.0.1:6767/multilinestrings/{z}/{x}/{y}.pbf";
var multipolygons = "http://127.0.0.1:6767/multipolygons/{z}/{x}/{y}.pbf";
var vectorTileStyling = {
lines: {
weight: .8,
fillColor: this.colors[1],
color: this.colors[1],
fillOpacity: .8,
opacity: .8
},
multilinestrings: {
weight: .8,
fillColor: this.colors[2],
color: this.colors[2],
fillOpacity: .8,
opacity: .8
},
multipolygons: {
fill: true,
weight: .8,
fillColor: this.colors[3],
color: this.colors[3],
fillOpacity: .8,
opacity: .8,
}
};
var externalVectorTileOptions = {
rendererFactory: L.canvas.tile,
vectorTileLayerStyles: vectorTileStyling,
maxZoom: 16,
minZoom: 14
};
L.vectorGrid.protobuf(lines, externalVectorTileOptions).addTo(map);
L.vectorGrid.protobuf(multilinestrings, externalVectorTileOptions).addTo(map);
L.vectorGrid.protobuf(multipolygons, externalVectorTileOptions).addTo(map);

Related

JVectorMap Multimap drill down has different style when drilled down, how can I change this style as well?

This question was asked here as well maybe a year ago, but no answer was provided. I'd like to know how I can make the "drilled-down" map have the same style and background as my main map.
var regionStyling9 = { // This style is set to the main map's regionStyle
initial: {
fill: '#E9E9E9',
stroke: '#505050',
"fill-opacity": 1,
"stroke-width": 1,
"stroke-opacity": 1
},
hover: { fill: "#4DB870" }
};
$(function() {
new jvm.MultiMap({
container: $('#Chart9Map'),
maxLevel: 1,
main: {
map: 'us_aea_en',
backgroundColor: '#fff',
regionStyle: regionStyling9, // Here I set the top level style to the main map
series: {
regions: [{ values: stateMapData, scale: ['#cce2ec', '#006593'], normalizeFunction: 'polynomial' }]
}
},
mapUrlByCode: function(code, multiMap) {
return '../Scripts/JVectorMap/jquery-jvectormap-data-' +
code.toLowerCase() +
'-' +
multiMap.defaultProjection +
'-en.js';
}
});
});
How can I affect the style of the secondary chart like I did for the main chart?
According to the jVectorMap MultiMap Documentation, there is only config for the main map, no other object for the drill down maps...
I know what you mean, I also could not find this info. It is very strange the library doesnt handle this. Its such a well produced package, yet seems to me missing some basics.
Assuming you arent using the free version, the license says you are allowed to modify the code.
Open up the jquery-jvectormap.js file (prettify it is needed).
At the bottom you'll see the lines:
currentMap.params.container.hide(), that.maps[name] ? that.maps[name].params.container.show() : that.addMap(name, {
map: name,
multiMapLevel: currentMap.params.multiMapLevel + 1
}), that.history.push(that.maps[name]), that.backButton.show()
Change this so settings from the main map are passed down to the children (so just add the 3 extra lines):
currentMap.params.container.hide(), that.maps[name] ? that.maps[name].params.container.show() : that.addMap(name, {
backgroundColor: that.params.main.backgroundColor,
series: that.params.main.series,
onRegionTipShow: that.params.main.onRegionTipShow,
map: name,
multiMapLevel: currentMap.params.multiMapLevel + 1

Stop vis.js physics after nodes load but allow drag-able nodes

I am trying to draw a vis.js network diagram and have vis load and position the nodes. I then want the physics to be disabled so the nodes can be moved by the user. I have tried this but it is not working.
var options = {
nodes: {
borderWidth:4,
size:60,
color: {
border: '#222222',
background: 'grey'
},
font:{color:'black'}
},
edges: {
arrows: {
to: {enabled: false, scaleFactor:1},
middle: {enabled: false, scaleFactor:1},
from: {enabled: false, scaleFactor:1}
},
color: 'black'
},
{ physics: enabled: false; };
Has anyone done this? if so can you provide an example or advice on best way to accomplish this. I have also read the explanation located here, but not being too familiar with java I can't figure the steps out.
Thanks
After some more work and help from the vis.js developer here is the completed code, minus the json data and some options. The trick is to use the "stabilizationIterationsDone" event and disable physics:
// create a network
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var options = {
nodes: ...,
edges: ...,
physics: {
forceAtlas2Based: {
gravitationalConstant: -26,
centralGravity: 0.005,
springLength: 230,
springConstant: 0.18,
avoidOverlap: 1.5
},
maxVelocity: 146,
solver: 'forceAtlas2Based',
timestep: 0.35,
stabilization: {
enabled: true,
iterations: 1000,
updateInterval: 25
}
}
};
network = new vis.Network(container, data, options);
network.on("stabilizationIterationsDone", function () {
network.setOptions( { physics: false } );
});
I suppose you first want to let the network stabilize and only then disable physics? In that case you can load the Network with physics and stabilization enabled. Once the nodes are stabilized, the stabilized event is fired. Then you can disable the physics via network.setOptions
I was able to figure this out and the code now looks like this
// create a network
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var options = {
nodes: {
borderWidth:1,
size:45,
color: {
border: '#222222',
background: 'grey'
},
font:{color:'black',
size: 11,
face :'arial',
},
},
edges: {
arrows: {
to: {enabled: false, scaleFactor:1},
middle: {enabled: false, scaleFactor:1},
from: {enabled: false, scaleFactor:1}
},
color: {
color:'#848484',
highlight:'#848484',
hover: '#848484',
},
font: {
color: '#343434',
size: 11, // px
face: 'arial',
background: 'none',
strokeWidth: 5, // px
strokeColor: '#ffffff',
align:'vertical'
},
smooth: {
enabled: false, //setting to true enables curved lines
//type: "dynamic",
//roundness: 0.5
},
}
};
network = new vis.Network(container, data, options);
network.setOptions({
physics: {enabled:false}
});
}
I had to move the network.setOptions() as shown in the new code and it is now working as desired.

Getting JSON from Google for Highmaps

I'm pretty rusty with JavaScript, so I'm hoping someone can help me out. I'm working with Highmaps and would like to link the map to data in a Google Spreadsheet. (It's a U.S. map of counties which will be updated regularly, so having it all in the script itself is a little unwieldy.)
This is what my code looks like now:
<script type="text/javascript">
var example = 'us-counties',
theme = 'default';
(function($) { // encapsulate jQuery
$(function() {
var data = [{
"code": "us-al-001",
"name": "Autauga County, AL",
"value": 0
},
…
{
"code": "us-pr-153",
"name": "Yauco Municipio, PR",
"value": 0
}],
countiesMap = Highcharts.geojson(Highcharts.maps['countries/us/us-all-all']),
lines = Highcharts.geojson(Highcharts.maps['countries/us/us-all-all'], 'mapline'),
options;
// Add state acronym for tooltip
Highcharts.each(countiesMap, function(mapPoint) {
mapPoint.name = mapPoint.name + ' County, ' + mapPoint.properties['hc-key'].substr(3, 2);
});
series: [{
name: 'County',
mapData: countiesMap,
data: data,
joinBy: ['hc-key', 'code'],
tooltip: {
enabled: true,
positioner: function () {
return { x: 0, y: 250 };
},
pointFormat: '{point.name}',
borders: 0.5
},
borderWidth: 0.5
}, {
type: 'mapline',
name: 'State borders',
data: [lines[0]],
color: 'white'
}, {
type: 'mapline',
name: 'Separator',
data: [lines[1]],
color: 'gray'
}]
};
// Instanciate the map
$('#container').highcharts('Map', options);
});
$(document).ready(function() {
$("#view-menu").click(function(e) {
$("#wrap").toggleClass("toggled");
});
$("#sidebar-close").click(function(e) {
$("#wrap").removeClass("toggled");
});
});
})(jQuery);
</script>
Of course, since there's over 3,200 counties, I'd rather store that data elsewhere and pull it into the var data = [] string, but I'm not sure how to do that.
Any help would be appreciated.
Although this isn't something I've done, it looks like this should be relatively straightforward to do (although nothing is as simple as it looks of course).
There is an API for Google Sheets (https://developers.google.com/google-apps/spreadsheets/) and you can Google examples of how to retrieve data - this one looks clear (https://developers.google.com/gdata/docs/json) although it does point out that there are newer versions of some of the relevant APIs.
If you pull in the JSON data from the Google Sheet you then just need to put the values into the 'value' element of your data variable. You could do all of that within your main function or do it separately and pass it to your function as a parameter.

Way to shorten / refactor multiple plugin values

I have some 15-20 highcharts on a single page (using a slider, 1-2 charts per slide), some bar charts, some column charts, some pie charts, with different display options. What I was using was having multiple methods inside my closure where I had methods like self.drawColumnChart(xPlotColor, yPlotColor, xPlotLabelSize, yPlotLabelSize, ...10 more arguments). Inside the same object I have methods like 'drawRevenueChart(), drawLossChart()' etc. drawRevenueChart() was calling self.drawColumnChart( with 15 arguments. As the number of charts grew, I ended up passing more and more arguments to self.drawColumnChart( so I thought I could refactor this by changing the drawRevenueChart() as
("$id").highcharts(
{chart: {
plotOptions: {
labelSize: '2em'
},
xAxis:{
labelSize: '1.3em',
formatter: function(){
return '% ' + this.value;
}
...and so on
}
})'
I don't need the self.drawColumnChart(xPlotColor, yPlotColor, xPlotLabelSize, yPlotLabelSize, ...10 more arguments) any more but I just passed that complexity to drawRevenueChart(). drawRevenueChart() used to be 2 lines long, but now it's 25 lines long. Same with drawLossChart(), it used to be 3 lines long, just calling self.drawColumnChart(, but it turned into a 15 line long method after refactor.
Can you guys think of any other way how I can refactor/shorten this? Maybe drawRevenueChart() calls self.drawChart("column", [plotOptions.labelSize: '2em', xAxis: {labelSize: '1.e em'}...
It just seems that I have to keep repeating
plotOptions: {
labelSize: '2em'
},
xAxis:{
labelSize: '1.3em',
all over my closure for each chart with different options. Is there a way to shorten this? I'm already using jQuery extend() to extend default chart options with custom options. It's all inside a closure. But regardless of how I refactor this, I find myself repeating the same lines with different options. Any ideas are welcome.
Update:
As requested by TrueBlueAussie:
it used to be:
myClosure{
var self = this;
self.drawColumnChart = function(selector, xPlotColour, yPlotColour, xAxisName, yAxisName, xPlotOptionsSize....10 more arguments)
{
$(selector).highcharts({
chart: {
type: 'column'
},
xPlot:{
style:{
color: xPlotColour
}
},
yPlot: {
labels: {
style:{
color: yPlotColour
}
}
},
xAxis:{
labels: {
name: xAxisName,
}
}
})
}
drawRevenueChart: function(data){
self.drawColumnChart("#chartid1", 'blue', 'red', 'profit', 'month', '1.2em', null, false, null....);
}
drawLossChart: function(data){
self.drawColumnChart("#chartid2", 'orange', 'yellow, 'loss', 'month', '2em' ...);
}
}
After refactor
drawRevenueChart: function(data){
$("#chartid1").highcharts({
chart: {
type: 'column'
},
xPlot:{
style:{
color: 'blue'
}
},
yPlot: {
labels: {
style:{
color: 'red'
}
}
},
xAxis:{
labels: {
name: 'profit',
}
}
});
}
drawLossChart: function(data){
$("chartid2").highcharts({
xplot:{
style:{
color: 'orange'
}
},
xAxis:{
labels:{
name: 'loss',
color: 'puke'
}
}
}
};
So I just moved the 3 level deep object setting from one method to another, no real gain.
Okay, that is clearer now. This is not really a code refactoring problem, so much as a data-refactoring problem. The only solution I can suggest is to find common data in the structures, store those branches as vars within your scope and $.extend() them together to build the final options structure.
e.g.
myClosure {
// Put any common settings in shared vars
var columnChart = {
chart: {
type: 'column'
}
// any other common properties for a column chart
};
var barChart = {
chart: {
type: 'barchart'
}
// any other common properties for a bar chart
}
var self = this;
self.drawColumnChart = function (selector, data) {
$(selector).highcharts($.extend({}, columnChart, data));
}
drawRevenueChart: function (data) {
self.drawColumnChart("#chartid1", {
xPlot: {
style: {
color: 'blue'
}
},
yPlot: {
labels: {
style: {
color: 'red'
}
}
},
xAxis: {
labels: {
name: 'month name',
}
}
});
}
drawLossChart: function (data) {
self.drawColumnChart("#chartid2", {
xPlot: {
style: {
color: 'orange'
}
},
yPlot: {
labels: {
style: {
color: 'yellow'
}
}
},
xAxis: {
labels: {
name: 'loss',
}
});
}
}
}
This has the advantage that each call is still readable, if you know the HighCharts option structure, and is therefore easier to maintain.
Unless you use a strongly typed JS language (like TypeScript), using functions with loads of parameters is human-error-prone so best avoided.
What I ended up doing is I created an object inside the plugin which handles all the highcharts options with a single drawChart function which takes an object. i.e.
var plugin = function(){
helper.drawChart({
legendEnabled: true,
selector: '#divID',
backgroundColor: '#FF0000', //etc all other options
});
var chartHelper = function(){
var defaults = {
_common:{
//common highcharts options
{
legend: false //etc
}
},
_bar:{
chart: 'bar', //all common bar options
},
get bar(){ return $.extend({}, _common, _bar); }
}
this.drawChart = function(options){
var default = {};
if (options.legendEnabled){
default.legend = true;
}
if (options.yLabelText){
default.yTitle = { text = options.yLabelText }
}//and other options here
$(options.selector).highchart($.extend({}, defaults.bar, default);
}
}
var helper = new chartHelper();
}
I have some 20 charts inside the plugin and this saved me some 600 lines. All the chart logic is inside the helper, it doesn't clutter the plugin code, I don't need to keep repeating .highcharts({ 20 different options }) inside each draw function, it's just once I need to do that now.

Leaflet: Pass an extra argument to L.geoJson options

I'm working on choropleth maps using Leaflet framework. I'd like to have several separate layers for several years, so i' ve written this code (note that only names of 'style2002' and 'style2010' should be passed, without any arguments):
population2002 = L.geoJson(regionData, {
style: style2002
});
population2010 = L.geoJson(regionData, {
style: style2010
});
, there "style" functions which are colouring my vector polygons depening on their attributes (which names are prefix 'Pop_' plus year) are:
function style2002(feature)
{
return {
fillColor: getColor(feature.properties.Pop_2002),
weight: 2,
opacity: 1,
color: 'white',
dashArray: '',
fillOpacity: 0.7
};
}
function style2010(feature)
{
return {
fillColor: getColor(feature.properties.Pop_2010),
weight: 2,
opacity: 1,
color: 'white',
dashArray: '',
fillOpacity: 0.7
};
};
As you can guess, i want to use one "style" function instead of separate functions for each year i need. Something like:
function styleByYear(feature, year)
{
var property = 'feature.properties.Pop_';
property += year;
return {
fillColor: getColor(property),
weight: 2,
opacity: 1,
color: 'white',
dashArray: '',
fillOpacity: 0.7
};
}
But how to pass the second argument to style function? In L.geoJson constructor i write only the name of function, without any arguments, as you can see from the first piece of code!
What should i do?
And one more question: how the first argument ('feature') is passed into layer constructor..?
What if you create a global variable:
var property = 'Pop_' + year
and the edit your function to the following(You should use brackets instead of dot notation):
function styleByYear(feature)
{
return {
fillColor: getColor(feature['properties'][property]),
weight: 2,
opacity: 1,
color: 'white',
dashArray: '',
fillOpacity: 0.7
};
}
I have done something similar to what you're asking based on choropleth tutorial like you. I have multiple buttons that changes map style for different dates.
You could try what is in the Leaflet tutorial on GeoJSON. Look for the second code section in the "Options" section. You would just add the normal styling first (i.e. the stuff that is the same for both years). Example taken from that site adding your particular code:
geojsonlayer = L.geoJson(regionData, {
style: function(feature) {
var finalStyleObject = normalStyling(feature); //return the normal style object
switch (feature.properties) { //switch through the various years
case 'Pop_2002': {finalStyleObject.fillColor: "#ff0000"};
case 'Pop_2010': {finalStyleObject.fillColor: "#0000ff"};
}
return finalStyleObject;
}
});
function normalStyling(feature){
return {
weight: 2,
opacity: 1,
color: 'white',
dashArray: '',
fillOpacity: 0.7
};
}

Categories

Resources