On Tue, May 13, 2014 at 3:36 AM, Ümit Seren <[email protected]> wrote:
> Hi everyone, > > WARNING: long email. > > tl;tr version: > I ported the Markerclusterer-Plus library > (<http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/examples/advanced_example.html> > http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/examples/advanced_example.html) > for google maps to Polymer. > The source can be found here: > <https://github.com/timeu/google-map-markerclusterer> > https://github.com/timeu/google-map-markerclusterer > The component page + demo can be found here: > <http://timeu.github.io/google-map-markerclusterer/components/google-map-markerclusterer/> > http://timeu.github.io/google-map-markerclusterer/components/google-map-markerclusterer/ > > *Background:* > I wanted to play around with polymer and web-components for some time now > but there was always other work I had to do. > Recently I had to work on a visualization to display quantitive values on > a pie chart on a google-map (output of a clustering analysis). > <http://jsfiddle.net/timeu/kxMRQ/embedded/result/> > http://jsfiddle.net/timeu/kxMRQ/embedded/result/ > The problem was, that we had data for 1000 markers and displaying that > much pie charts was really slow. > So after some research I stumbled over Markerclusterer-Plus and after some > tweaking it worked fine > (Demo:<http://jsfiddle.net/timeu/gr6qJ/embedded/result/> > http://jsfiddle.net/timeu/gr6qJ/embedded/result/) > > However we wanted to replace the default cluster icons with custom ones > (cluster icon should be a pie chart with an average of all the embedded pie > charts). Markerclusterer-Plus didn’t provide a way to specify custom > cluster icons so I was planning to extend the library and I thought instead > of just extending the library I could also port it to Polymer. > > This is my first polymer-element and although I tried to stick the > official Polymer docs and best practices as closely as possible the code > might not be perfect. > > In the course of developing this Polymer element I ran into some questions > and issues. Maybe some Polymer Devs could provide some feedback and maybe > comment if I chose the right approaches: > > *Basic structure:* > I decided to not just write a simple wrapper for the markerclusterer-plus > js library. Instead I tried to map the markerclusterer-plus classes to > corresponding polymer-elements. > The google-map-markerclusterer consists of 5 polymer-elements: > > - <google-map-markerclsuterer>: This is the main element that the user > should use. It depends on the google-apis and google-map elements. > - <google-map-clustericon>: This is an internal polymer-element that > handles some of the clustering functionality. > - <google-map-defaulticon>: This is an internal polymer-element that > displays the default cluster icon > - <google-map-overlayview-marker>: This is an element that allows to > use any overlay as a marker. > - <google-map-overlayview>: This is a polymer-element that wraps an > OnverlayView of google maps (similar to the <google-map-marker>). > <google-map-markerclusterer>, <google-map-clustericon> and > <google-map-overlayview-marker> inherit from it. > > The original Markerclusterer-Plus library only supported simples markers > but the polymer version should also support custom markers that are > implemented via an OverlayView. > > Here are some things I couldn’t get my head around: > > *1. To embed or not to embed the element inside of <google-map> ?* > > <google-map-markerclusterer> depends on the <google-map> element and > specifically on the map attribute. > Currently the <google-map> element defines a content insertion point for > markers so that you can write > > <google-map><google-map-marker></google-map> > > I could now extend google-map and change the template to allow to embed my > <google-map-markerclusterer> but I wanted to avoid using inheritence too > much. AFAIK best practice is to use composition instead of inheritence. > So I decided to put the <google-map-markerclusterer> as sibling to a > <google-map>. Inside a parent polymer-element I could use data-binding to > bind the map attribute (like I did in the demo): > > <polymer-element name="my-app"> > <template> > <google-map map="{{map}}" /> > <google-map-markerclusterer map="{{map}}" /> > <template> > </polymer-element> > > *2.) Best practice when dealing with multiple elements that depend on a > third party library ?* > > google-map-markerclusterer as well as google-map depend on the google-api. > Therefore I put the <google-api> element into the template of > <google-map-markerclusterer> to make sure that google-api is loaded. I > also have to wait for the googleApiLoaded event to be fired to initialize > the OverlayView. > > However google-map-markercluster can pratically only be used together with > the <google-map> element. So does it really make sense to add the > dependency to google-api to google-map-markerclusterer? I could use the > approach to send events to sibling elements (described here: > <http://www.polymer-project.org/articles/communication.html#events> > http://www.polymer-project.org/articles/communication.html#events) but > that puts the burdon on the client of my element to make sure that the > event is propogated to the google-map-markerclusterer. > > Furthermore the base class of google-map-markerclusterer - > google-map-overlayview - also depends on the the google-map-api to be > loaded. Should I put the gogole-api element in both classes or is it enough > to put it only intot he google-map-overlayview and use event bubling to > handle the event in google-map-markerclusterer ? > In general if some internal elements depend on the external 3rd party > library should there templates contain it or not ? > Yes. If the element depends on that functionality to be useful, it should include it. The loader elements in google-apis only load the library once. That means if you have multiple instances of <google-maps-api>, it will only load the maps library once. > *3.) How to programatically add child elements to an element?* > > Currently the google-map-markerclusterer defines a published markers > attribute that has a Changewatcher attached (markersChanged()) which > generates the clusters and adds them to the map. I create an array of > markers or overlayview elements and then set the markers attribute. > > However that doesn’t seem completely right to me because a marker is not a > domain model but a custom/polymer-element. For example <google-map> uses > a content insertion point for gogole-map-markers and a MutationObserver to > handle the changes to the LightDOM. > Should I take the same approach? My feeling says yes, but with > google-map-markerclusterer I don’t add one or two markers but potentially > thousands. I could make a loop over my marker elements array and add them > one by one to the LightDOM. Will my MutationObserver callback be executed > on each iteration? That would kill performance (I could probably use > this.async to delay the costly clustering function call). > > Another alternative might be to use the repeat-binding together with a > content insertion point inside of the template to add multiple markers (is > that actually possible)? > Yes. But it might be just as easy to expose a .markers property on your element and have folks set the array of marker data in JS. Inside the element, you can use a template create to stamp out the necessarily markup. > What is common best practice for dynamically adding polymer-elements to > other polymer-elements ? Should I allow both cases (attribute and LightDOM) > or only one ? How to deal with adding multiple elements at once ? > *4.) How to deal with configurable child elements ?* > > The default behavior of google-map-markerclusterer is to use the > google-map-defaulticon element to display the cluster icon. > However I wanted to give the user the possibility to change this to a > custom icon. > So I decided to add an insertion point and a default icon to the template. > > <polymer-element name="google-map-markerclusterer"> > <template> > <google-maps-api></google-maps-api> > <google-map-clustericon id="defaultclustericon"><google-map-defaulticon > defaultStyles="{{styles}}" /></google-map-clustericon> > <content id="clustericontemplate" > select="google-map-clustericon"></content> > <!-- markers is not used currently --> > <content id="markers"></content> > </template> > </polymer-element> > > And in the attached function I do following: > > attached: function() { > var clusterIconTemplateContent = > this.$.clustericontemplate.getDistributedNodes(); > if (clusterIconTemplateContent.length > 0) { > this.clusterIconTemplate_ = clusterIconTemplateContent[0]; > } > else { > this.clusterIconTemplate_ = this.$.defaultclustericon; > } > }, > > This way if I only define the google-map-markerclusterer element it uses > the defaultclustericon element in template but alternatively I can do > something like this: > > <google-map-markerclusterer> > <google-map-clustericon> > <my-custom-clustericon></my-custom-clustericon> > </google-map-clustericon> > </google-map-markerclusterer> > > Is that the right approach? And what happens if have two insertion points > (i.e. markers and a clustericontemplate) ? > google-map uses an icon attribute for this: https://github.com/PolymerLabs/google-map/blob/master/google-map.html#L91:L95 It defaults to the normal pin, but allows users to override with an image. > *5.) Issues with microtasks* > > The google-map-markerclusterers listens to the *idle* event of > google-maps which is called whenever the map becomes idle after *zooming*or > *panning*. > When the even is fired the google-map-markerclusterer will re-run the > clustering, remove existing clusters and add the new clusters. To remove a > marker or overlayview from a gogole-map, setMap(null) has to be called on > the overlayview/marker. > Initially I used the changeWatcher (mapChanged) to handle this. > However I ran into a weird problem that if I use the mousewheel to quickly > zoom in and out it can happen that the clusters are not properly removed. > This is probably due to the fact that the markers are removed from the > array before the changeWatcher had time to call setMap(null). I tried to > use this.async and Platform.flush() but nothing helped. > I ended up creating setters (setMap()) on my elements that directly set > the map on the overlay/marker instead of using the changeWatchers. > > Are there any best practices to cope with that case ? > > *Some additional remarks: * > > *Data-binding in styles (Polyfill): * > > Data-binding inside of styles using the Polyfills doesn’t work (I think > this is a known issue). Probably this won’t be fixed. > So far I have to define style changes twice: > > :host { > position:absolute; > left:-{{iconOffset[1]}}; > top:-{{iconOffset[0]}}; > } > > and then in the code to get to work with the polyfills. > > this.style.left = "-"+this.iconOffset[1];this.style.top = > "-"+this.iconOffset[0]; > > There was also a weird issue that Chrome somehow didn’t properly display > one element that was styled, although the styles were correct. When I > manually unchecked one of the styles of that element in Chrome Dev Tools > and then checked it again, the element was displayed correctly. As a > workaround I added just set a constant style programtaically ( > this.$.text.style.position="absolute";) in my code. > Not sure if this is a bug > Correct. If you want to bind to styles, you should do it in a style attribute on the element. <span style="color: {{color}}"> > *Binding and insertion points:* > > > <http://stackoverflow.com/questions/23522037/data-binding-between-nested-polymer-elements> > http://stackoverflow.com/questions/23522037/data-binding-between-nested-polymer-elements > > Because I chose the approach to extract a configurable polymer-element > (clustericon) from the insertion point and add it to > google-map-markerclusterer I can’t relay on data-binding to pass in some > settings (gridSize,etc). > As a workaround I set the attributes programatically (element.gridSize = > this.gridSize). > Is there a better way ? > > *document.importNode(element,true) doesn’t copy instance variables ?* > > Could it be that document.importNode() doesn’t copy the instance > variables ? > > For example in the default setup the google-map-defaulticon is displayed > with a standard style (defind as a static variables). > However the user can change pass a different style array to the > google-map-markerclsuterer. When the user changes the style array it gets > propogated to the <google-map-defaulticon> (placeholder) that is included > in the template of <google-map-markerclusterer>. But when I then use > document.importNode to clone this element the styles variable is null and > the default style is applied again. > The current workaround is to use a static variable to solve this. > > In general I must say I really enjoyed working with Polymer and I can see > the huge potential (great work on that note!). > I hope I could convey the issues, I had clearly enough. > I also plan to write a blog-post on my experience porting > markerclusterer-plus to polymer. > Awesome! Looking forward to that. > cheers > Uemit > > Follow Polymer on Google+: plus.google.com/107187849809354688692 > --- > You received this message because you are subscribed to the Google Groups > "Polymer" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To view this discussion on the web visit > https://groups.google.com/d/msgid/polymer-dev/64c5fa95-90c1-43c1-af15-49323d29e6ae%40googlegroups.com<https://groups.google.com/d/msgid/polymer-dev/64c5fa95-90c1-43c1-af15-49323d29e6ae%40googlegroups.com?utm_medium=email&utm_source=footer> > . > For more options, visit https://groups.google.com/d/optout. > Follow Polymer on Google+: plus.google.com/107187849809354688692 --- You received this message because you are subscribed to the Google Groups "Polymer" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/polymer-dev/CACGqRCAaQzQxgfmAODkiydyEJSxQcXJ-T5g3RdmWWQzQKWA_PQ%40mail.gmail.com. For more options, visit https://groups.google.com/d/optout.
