While trying to add marker to mapbox using symbol layer, the marker was not visible.
Tried to add marker as shown below,
mapView.getMapAsync(m -> {
isMapReady = true;
mapboxMap = m;
mapboxMap.addOnMapClickListener(MapViewFragment.this);
addMarkerToSymbolLayer(45);
updateCameraPosition(location); });
private void updateCameraPosition(Location location){
if (mapboxMap != null) {
LatLng latLong = new LatLng();
if (location != null) {
latLong.setLatitude(location.getLatitude());
latLong.setLongitude(location.getLongitude());
}
final CameraPosition cameraPosition = new CameraPosition.Builder()
.target(latLong)
.build();
mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
mapView.postDelayed(() -> mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), AppConstants.MAP_CAMERA_ANIMATE_DURATION_MS_5000), 100);
private void addMarkerToSymbolLayer(float headDirection) {
GeoJsonSource geoJsonSource = new GeoJsonSource("geojson-source",
Feature.fromGeometry(Point.fromLngLat(77.6387, 12.9610)));
mapboxMap.addSource(geoJsonSource);
Bitmap compassNeedleSymbolLayerIcon = BitmapFactory.decodeResource(
getResources(), R.drawable.compass_needle);
mapboxMap.addImage("compass-needle-image-id", compassNeedleSymbolLayerIcon);
SymbolLayer aircraftLayer = new SymbolLayer("aircraft-layer", "geojson-source")
.withProperties(
PropertyFactory.iconImage("compass-needle-image-id"),
PropertyFactory.iconRotate(headDirection),
PropertyFactory.iconIgnorePlacement(true),
PropertyFactory.iconAllowOverlap(true)
);
mapboxMap.addLayer(aircraftLayer);
}
If addMarkerToSymbolLayer() is called after moving camera then the marker is visibile. Why is that adding marker dependent on camera position? I have to move camera many times to meet my requirement. How to handle this?
Also in the Deprecated MarkerView and MarkerViewOptions I didnt have any issues while adding marker.
I noticed that if given a delay of 100ms while calling the function addMarkerToSymbolLayer(45), the marker is visible and things work fine !
The onMapReady() is called even before all the styles have been loaded. It is necessary that all styles be loaded before adding a symbol layer. Add the following listener and call for adding marker inside it.
mapView.addOnDidFinishLoadingStyleListener(new MapView.OnDidFinishLoadingStyleListener() {
#Override
public void onDidFinishLoadingStyle() {
}
});
This is crazy, add marker doesn't seem to do what it suppose to do. Adding marker does nothing:
{
if(!isVisible())
return;
mPinBitmapUtils.retrievePinBitmapAsync(getContext(), recommendation, new OnPinBitmapProcessed()
{
#Override
public void onBitmapProcess(final Pin reco, PinType pinType, Map<String, String> directories)
{
if(!isVisible())
return;
recommendation.setName(pinType.getName());
new AsyncTask<String, Void, PinMarkerOptions>()
{
#Override
protected PinMarkerOptions doInBackground(String... strings)
{
IconFactory iconFactory = IconFactory.getInstance(getContext());
Bitmap bitmap = Utils.getBitmapFromFile(getContext()
, new File(strings[0])
, (int) Utils.convertDpToPixel(48, getContext())
, (int) Utils.convertDpToPixel(48, getContext()));
Icon icon = iconFactory.fromBitmap(bitmap);
LatLng position = new LatLng();
position.setLatitude(recommendation.getLatitude());
position.setLongitude(recommendation.getLongitude());
PinMarkerOptions markerOption = new PinMarkerOptions();
markerOption.setPosition(position);
markerOption.setMetaData(recommendation.getJSON().toString());
if(icon != null)
markerOption.setIcon(icon);
return markerOption;
}
#Override
public void onPostExecute(PinMarkerOptions marker)
{
mMapboxMap.addMarker(marker);
}
}.execute(directories.get(Constants.BLUE_PIN));
}
});
}
Every time I called addMarkers or addMarker its not being shown. I checked the size of the MapBox map instance and it indeed show it increased in size. This happens especially when I remove a marker, or called MapBoxMap#clear(). Nothing happens afterwards.
I am using MapBox API v4.2.2.
There is one problem with markers - you can't add them on top of your layers.
If that is a case, then you should use SymbolLayer for that purpose.
Info about markers from documentation.
Example of how use SympolLayer with BubbleLayout to create a marker with text inside.
I'm developing a simple GXT widget - it's a TreePanel with a ToolBar added using setTopComponent.
The problem is that as soon as the tree is large enough so that it can be scrolled, the scroll-bar doesn't scroll the tree only, but scrolls the ToolBar as well.
What should be change so that ToolBar remains on the top of page, and only the tree is scrolled.
public class TreePanelExample extends LayoutContainer {
#Override
protected void onRender(Element parent, int index) {
super.onRender(parent, index);
Folder model = getTreeModel();
TreeStore<ModelData> store = new TreeStore<ModelData>();
store.add(model.getChildren(), true);
final TreePanel<ModelData> tree = new TreePanel<ModelData>(store);
tree.setDisplayProperty("name");
tree.setAutoLoad(true);
ToolBar toolBar = new ToolBar();
toolBar.setBorders(true);
toolBar.add(new Button("Dummy button", new SelectionListener<ButtonEvent>() {
public void componentSelected(ButtonEvent ce) {
Info.display("Dummy button", "I'm so dumb!");
}
}));
ContentPanel panel = new ContentPanel();
panel.setHeaderVisible(false);
panel.setCollapsible(false);
panel.setFrame(false);
panel.setAutoWidth(true);
panel.setAutoHeight(true);
// setting fixed size doesn't make any difference
// panel.setHeight(100);
panel.setTopComponent(toolBar);
panel.add(tree);
add(panel);
}
The problem is that
TreePanelExample extends LayoutContainer
while instead it should extend Viewport.
Additionally I shouldn't have used
panel.setAutoWidth(true);
panel.setAutoHeight(true);
Plus it is necessary to add the main panel using
new BorderLayoutData(LayoutRegion.CENTER);
Here is the complete solution:
public class TreePanelExample extends Viewport {
public TreePanelExample() {
super();
setLayout(new BorderLayout());
Folder model = getTreeModel();
TreeStore<ModelData> store = new TreeStore<ModelData>();
store.add(model.getChildren(), true);
final TreePanel<ModelData> treePanel = new TreePanel<ModelData>(store);
treePanel.setDisplayProperty("name");
treePanel.setAutoLoad(true);
ToolBar toolBar = new ToolBar();
toolBar.setBorders(true);
toolBar.add(new Button("Dummy button", new SelectionListener<ButtonEvent>() {
public void componentSelected(ButtonEvent ce) {
Info.display("Dummy button", "I'm so dumb!");
}
}));
ContentPanel panel = new ContentPanel();
panel.setBodyBorder(false);
panel.setHeaderVisible(false);
panel.setTopComponent(toolBar);
panel.setLayout(new FitLayout());
panel.add(treePanel);
BorderLayoutData centerData = new BorderLayoutData(LayoutRegion.CENTER);
centerData.setMargins(new Margins(5, 5, 5, 5));
centerData.setCollapsible(true);
panel.syncSize();
add(panel, centerData);
}
I need to show a "continuous" color palette for color selection inside a ContextMenu. Similar to CustomColorDialog that pops up on ColorPicker.
Is there a different class for this purpose or is it possible to work around by extending ColorPicker and showing directly CustomColorDialog instead of first showing ColorPicker.
TIA
For starters, com.sun.javafx.scene.control.skin.CustomColorDialog is private API, and it's not advisable to use it, as it may change in the future without notice.
Besides, it is a Dialog, what means you can't embed it into a ContextMenu, it has its own window and it's modal.
This is a short example of using this (very big, not customizable) dialog in your application, without using a ColorPicker.
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Open Custom Color Dialog");
btn.setOnAction(e -> {
CustomColorDialog dialog = new CustomColorDialog(primaryStage.getOwner());
dialog.show();
});
Scene scene = new Scene(new StackPane(btn), 300, 250);
primaryStage.setTitle("CustomColorDialog");
primaryStage.setScene(scene);
primaryStage.show();
}
You'll get the dialog, but you won't get any possibility to send a custom color or retrieve the selected color, since properties like customColorProperty() are only accesible within the com.sun.javafx.scene.control.skin package.
So we need another way to implement our custom color selector. If you have a look at the source code of CustomColorDialog you'll see that it's relatively a simple control, and most important, almost based on public API: panes, regions and color.
Trying to put all in a ContextMenu could be overkilling, so I've come up with this basic example, where I'll just use the left part of the dialog, displaying the central bar on top. Most of the code is from the class. The CSS styling was also taken from modena.css (under custom-color-dialog CSS selector), but was customized as some of the nodes were rotated 90º.
This is a short version of CustomColorDialog class:
public class MyCustomColorPicker extends VBox {
private final ObjectProperty<Color> currentColorProperty =
new SimpleObjectProperty<>(Color.WHITE);
private final ObjectProperty<Color> customColorProperty =
new SimpleObjectProperty<>(Color.TRANSPARENT);
private Pane colorRect;
private final Pane colorBar;
private final Pane colorRectOverlayOne;
private final Pane colorRectOverlayTwo;
private Region colorRectIndicator;
private final Region colorBarIndicator;
private Pane newColorRect;
private DoubleProperty hue = new SimpleDoubleProperty(-1);
private DoubleProperty sat = new SimpleDoubleProperty(-1);
private DoubleProperty bright = new SimpleDoubleProperty(-1);
private DoubleProperty alpha = new SimpleDoubleProperty(100) {
#Override protected void invalidated() {
setCustomColor(new Color(getCustomColor().getRed(), getCustomColor().getGreen(),
getCustomColor().getBlue(), clamp(alpha.get() / 100)));
}
};
public MyCustomColorPicker() {
getStyleClass().add("my-custom-color");
VBox box = new VBox();
box.getStyleClass().add("color-rect-pane");
customColorProperty().addListener((ov, t, t1) -> colorChanged());
colorRectIndicator = new Region();
colorRectIndicator.setId("color-rect-indicator");
colorRectIndicator.setManaged(false);
colorRectIndicator.setMouseTransparent(true);
colorRectIndicator.setCache(true);
final Pane colorRectOpacityContainer = new StackPane();
colorRect = new StackPane();
colorRect.getStyleClass().addAll("color-rect", "transparent-pattern");
Pane colorRectHue = new Pane();
colorRectHue.backgroundProperty().bind(new ObjectBinding<Background>() {
{
bind(hue);
}
#Override protected Background computeValue() {
return new Background(new BackgroundFill(
Color.hsb(hue.getValue(), 1.0, 1.0),
CornerRadii.EMPTY, Insets.EMPTY));
}
});
colorRectOverlayOne = new Pane();
colorRectOverlayOne.getStyleClass().add("color-rect");
colorRectOverlayOne.setBackground(new Background(new BackgroundFill(
new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(255, 255, 255, 1)),
new Stop(1, Color.rgb(255, 255, 255, 0))),
CornerRadii.EMPTY, Insets.EMPTY)));
EventHandler<MouseEvent> rectMouseHandler = event -> {
final double x = event.getX();
final double y = event.getY();
sat.set(clamp(x / colorRect.getWidth()) * 100);
bright.set(100 - (clamp(y / colorRect.getHeight()) * 100));
updateHSBColor();
};
colorRectOverlayTwo = new Pane();
colorRectOverlayTwo.getStyleClass().addAll("color-rect");
colorRectOverlayTwo.setBackground(new Background(new BackgroundFill(
new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(0, 0, 0, 0)), new Stop(1, Color.rgb(0, 0, 0, 1))),
CornerRadii.EMPTY, Insets.EMPTY)));
colorRectOverlayTwo.setOnMouseDragged(rectMouseHandler);
colorRectOverlayTwo.setOnMousePressed(rectMouseHandler);
Pane colorRectBlackBorder = new Pane();
colorRectBlackBorder.setMouseTransparent(true);
colorRectBlackBorder.getStyleClass().addAll("color-rect", "color-rect-border");
colorBar = new Pane();
colorBar.getStyleClass().add("color-bar");
colorBar.setBackground(new Background(new BackgroundFill(createHueGradient(),
CornerRadii.EMPTY, Insets.EMPTY)));
colorBarIndicator = new Region();
colorBarIndicator.setId("color-bar-indicator");
colorBarIndicator.setMouseTransparent(true);
colorBarIndicator.setCache(true);
colorRectIndicator.layoutXProperty().bind(
sat.divide(100).multiply(colorRect.widthProperty()));
colorRectIndicator.layoutYProperty().bind(
Bindings.subtract(1, bright.divide(100)).multiply(colorRect.heightProperty()));
colorBarIndicator.layoutXProperty().bind(
hue.divide(360).multiply(colorBar.widthProperty()));
colorRectOpacityContainer.opacityProperty().bind(alpha.divide(100));
EventHandler<MouseEvent> barMouseHandler = event -> {
final double x = event.getX();
hue.set(clamp(x / colorRect.getWidth()) * 360);
updateHSBColor();
};
colorBar.setOnMouseDragged(barMouseHandler);
colorBar.setOnMousePressed(barMouseHandler);
newColorRect = new Pane();
newColorRect.getStyleClass().add("color-new-rect");
newColorRect.setId("new-color");
newColorRect.backgroundProperty().bind(new ObjectBinding<Background>() {
{
bind(customColorProperty);
}
#Override protected Background computeValue() {
return new Background(new BackgroundFill(customColorProperty.get(), CornerRadii.EMPTY, Insets.EMPTY));
}
});
colorBar.getChildren().setAll(colorBarIndicator);
colorRectOpacityContainer.getChildren().setAll(colorRectHue, colorRectOverlayOne, colorRectOverlayTwo);
colorRect.getChildren().setAll(colorRectOpacityContainer, colorRectBlackBorder, colorRectIndicator);
VBox.setVgrow(colorRect, Priority.SOMETIMES);
box.getChildren().addAll(colorBar, colorRect, newColorRect);
getChildren().add(box);
if (currentColorProperty.get() == null) {
currentColorProperty.set(Color.TRANSPARENT);
}
updateValues();
}
private void updateValues() {
hue.set(getCurrentColor().getHue());
sat.set(getCurrentColor().getSaturation()*100);
bright.set(getCurrentColor().getBrightness()*100);
alpha.set(getCurrentColor().getOpacity()*100);
setCustomColor(Color.hsb(hue.get(), clamp(sat.get() / 100),
clamp(bright.get() / 100), clamp(alpha.get()/100)));
}
private void colorChanged() {
hue.set(getCustomColor().getHue());
sat.set(getCustomColor().getSaturation() * 100);
bright.set(getCustomColor().getBrightness() * 100);
}
private void updateHSBColor() {
Color newColor = Color.hsb(hue.get(), clamp(sat.get() / 100),
clamp(bright.get() / 100), clamp(alpha.get() / 100));
setCustomColor(newColor);
}
#Override
protected void layoutChildren() {
super.layoutChildren();
colorRectIndicator.autosize();
}
static double clamp(double value) {
return value < 0 ? 0 : value > 1 ? 1 : value;
}
private static LinearGradient createHueGradient() {
double offset;
Stop[] stops = new Stop[255];
for (int x = 0; x < 255; x++) {
offset = (double)((1.0 / 255) * x);
int h = (int)((x / 255.0) * 360);
stops[x] = new Stop(offset, Color.hsb(h, 1.0, 1.0));
}
return new LinearGradient(0f, 0f, 1f, 0f, true, CycleMethod.NO_CYCLE, stops);
}
public void setCurrentColor(Color currentColor) {
this.currentColorProperty.set(currentColor);
updateValues();
}
Color getCurrentColor() {
return currentColorProperty.get();
}
final ObjectProperty<Color> customColorProperty() {
return customColorProperty;
}
void setCustomColor(Color color) {
customColorProperty.set(color);
}
Color getCustomColor() {
return customColorProperty.get();
}
}
This is the color.css file:
.context-menu{
-fx-background-color: derive(#ececec,26.4%);
}
.menu-item:focused {
-fx-background-color: transparent;
}
/* CUSTOM COLOR */
.my-custom-color {
-fx-background-color: derive(#ececec,26.4%);
-fx-padding: 1.25em;
-fx-spacing: 1.25em;
-fx-min-width: 20em;
-fx-pref-width: 20em;
-fx-max-width: 20em;
}
.my-custom-color:focused,
.my-custom-color:selected {
-fx-background-color: transparent;
}
.my-custom-color > .color-rect-pane {
-fx-spacing: 0.75em;
-fx-pref-height: 16.666667em;
-fx-alignment: top-left;
-fx-fill-height: true;
}
.my-custom-color .color-rect-pane .color-rect {
-fx-min-width: 16.666667em;
-fx-min-height: 16.666667em;
}
.my-custom-color .color-rect-pane .color-rect-border {
-fx-border-color: derive(#ececec, -20%);
}
.my-custom-color > .color-rect-pane #color-rect-indicator {
-fx-background-color: null;
-fx-border-color: white;
-fx-border-radius: 0.4166667em;
-fx-translate-x: -0.4166667em;
-fx-translate-y: -0.4166667em;
-fx-pref-width: 0.833333em;
-fx-pref-height: 0.833333em;
-fx-effect: dropshadow(three-pass-box, black, 2, 0.0, 0, 1);
}
.my-custom-color > .color-rect-pane > .color-bar {
-fx-min-height: 1.666667em;
-fx-min-width: 16.666667em;
-fx-max-height: 1.666667em;
-fx-border-color: derive(#ececec, -20%);
}
.my-custom-color > .color-rect-pane > .color-bar > #color-bar-indicator {
-fx-border-radius: 0.333333em;
-fx-border-color: white;
-fx-effect: dropshadow(three-pass-box, black, 2, 0.0, 0, 1);
-fx-pref-height: 2em;
-fx-pref-width: 0.833333em;
-fx-translate-y: -0.1666667em;
-fx-translate-x: -0.4166667em;
}
.my-custom-color .transparent-pattern {
-fx-background-image: url("pattern-transparent.png");
-fx-background-repeat: repeat;
-fx-background-size: auto;
}
.my-custom-color .color-new-rect {
-fx-min-width: 10.666667em;
-fx-min-height: 1.75em;
-fx-pref-width: 10.666667em;
-fx-pref-height: 1.75em;
-fx-border-color: derive(#ececec, -20%);
}
The image can be found here.
And finally, our application class.
public class CustomColorContextMenu extends Application {
private final ObjectProperty<Color> sceneColorProperty =
new SimpleObjectProperty<>(Color.WHITE);
#Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(400,400);
rect.fillProperty().bind(sceneColorProperty);
Scene scene = new Scene(new StackPane(rect), 400, 400);
scene.getStylesheets().add(getClass().getResource("color.css").toExternalForm());
scene.setOnMouseClicked(e->{
if(e.getButton().equals(MouseButton.SECONDARY)){
MyCustomColorPicker myCustomColorPicker = new MyCustomColorPicker();
myCustomColorPicker.setCurrentColor(sceneColorProperty.get());
CustomMenuItem itemColor = new CustomMenuItem(myCustomColorPicker);
itemColor.setHideOnClick(false);
sceneColorProperty.bind(myCustomColorPicker.customColorProperty());
ContextMenu contextMenu = new ContextMenu(itemColor);
contextMenu.setOnHiding(t->sceneColorProperty.unbind());
contextMenu.show(scene.getWindow(),e.getScreenX(),e.getScreenY());
}
});
primaryStage.setTitle("Custom Color Selector");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Note the use of CustomMenuItem to allow clicking on the color selectors without closing the context menu. To close it just click anywhere outside the popup window.
This is how it looks like:
Based on this custom dialog, you can improve it and add the functionality you may need.
Here is how I use com.sun.javafx.scene.control.skin.CustomColorDialog:
public Color showColorDialog(String title, Color initialColor) {
CountDownLatch countDownLatch = new CountDownLatch(1);
ObjectHolder<Color> selectedColorHolder = new ObjectHolder<>();
Platform.runLater(new Runnable() {
#Override
public void run() {
try {
final CustomColorDialog customColorDialog = new CustomColorDialog(getWindow());
customColorDialog.setCurrentColor(initialColor);
// remove save button
VBox controllBox = (VBox) customColorDialog.getChildren().get(1);
HBox buttonBox = (HBox) controllBox.getChildren().get(2);
buttonBox.getChildren().remove(0);
Runnable saveUseRunnable = new Runnable() {
#Override
public void run() {
try {
Field customColorPropertyField = CustomColorDialog.class
.getDeclaredField("customColorProperty"); //$NON-NLS-1$
customColorPropertyField.setAccessible(true);
#SuppressWarnings("unchecked")
ObjectProperty<Color> customColorPropertyValue = (ObjectProperty<Color>) customColorPropertyField
.get(customColorDialog);
selectedColorHolder.setObject(customColorPropertyValue.getValue());
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
LOG.error(e, e);
}
}
};
customColorDialog.setOnUse(saveUseRunnable);
customColorDialog.setOnHidden(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
countDownLatch.countDown();
}
});
Field dialogField = CustomColorDialog.class.getDeclaredField("dialog"); //$NON-NLS-1$
dialogField.setAccessible(true);
Stage dialog = (Stage) dialogField.get(customColorDialog);
dialog.setTitle(title);
customColorDialog.show();
dialog.centerOnScreen();
} catch (Exception e) {
LOG.error(e, e);
countDownLatch.countDown();
}
}
});
try {
countDownLatch.await();
} catch (InterruptedException e) {
LOG.error(e, e);
}
return selectedColorHolder.getObject();
}
The showColorDialog() method of #Bosko Popovic didn't work for me. When I called it from the JavaFX thread (for example, on response to a button-click) it blocks and freezes the application. I still think there is merit in his approach, so here is a slightly modified version:
public static Optional<Color> showColorDialog(Window owner, String title, Optional<Color> initialColor) {
AtomicReference<Color> selectedColor = new AtomicReference<>();
// Create custom-color-dialog.
CustomColorDialog customColorDialog = new CustomColorDialog(owner);
Stage dialog = customColorDialog.getDialog();
// Initialize current-color-property with supplied initial color.
initialColor.ifPresent(customColorDialog::setCurrentColor);
// Hide the Use-button.
customColorDialog.setShowUseBtn(false);
// Change the Save-button text to 'OK'.
customColorDialog.setSaveBtnToOk();
// When clicking save, we store the selected color.
customColorDialog.setOnSave(() -> selectedColor.set(customColorDialog.getCustomColor()));
// Exit the nested-event-loop when the dialog is hidden.
customColorDialog.setOnHidden(event -> {
Toolkit.getToolkit().exitNestedEventLoop(dialog, null);
});
// Show the dialog.
dialog.setTitle(title);
// Call the custom-color-dialog's show() method so that the color-pane
// is initialized with the correct color.
customColorDialog.show();
// Need to request focus as dialog can be stuck behind popup-menus.
dialog.requestFocus();
// Center the dialog or else it will show up to the right-hand side
// of the screen.
dialog.centerOnScreen();
// Enter nested-event-loop to simulate a showAndWait(). This will
// basically cause the dialog to block input from the rest of the
// window until the dialog is closed.
Toolkit.getToolkit().enterNestedEventLoop(dialog);
return Optional.ofNullable(selectedColor.get());
}
The dialog field no longer has to be retrieved via reflection. You can get it directly by calling customColorDialog.getDialog(). You also don't need to get the color from the customColorProperty field via reflection as you can directly get it by calling customColorDialog.getCustomColor(). The nested-event-loop is needed to simulate a showAndWait() call to prevent input to the background Window when the dialog is shown.
You can store this method in a utility class, and when the day comes where the API is deprecated (or changed) as #José Pereda mentions, you can then implement a custom color dialog by making use of his example code.
Anybody knows how to remake something similar to what Batchgeo does when clusters/groups markers with dynamic pie charts?
Example: batchgeo map with pie clustering
Thanks,
Regards
I don't have a full how to for you, but do have some pointers which might help you and appear to be used by BatchGeo.
I would study the cluster example on Google maps:
https://developers.google.com/maps/articles/toomanymarkers
Which covers clustering pretty well... then you would need to look at changing the marker image to a call to Google Charts:
https://developers.google.com/chart/image/docs/gallery/pie_charts
Note: this is deprecated and will drop support 2015... but is used by BatchGeo I believe.
There is also an example here that might help get you on your way with the custom cluster marker images, which I can't post (as limited to 2 links) (such as the Hearts, People etc... set in CLUSTER STYLE). If you Google 'google map v3 cluster example' you should find it in the top results.
If you put all that together then I think you should get there.
Regards,
James
I needed a solution to the same problem so I solved it by extending the Google Maps Marker Cluster Library to use Pie charts instead of cluster markers. You can download the solution from my GitHub repository: https://github.com/hassanlatif/chart-marker-clusterer
Quite late, but I needed to replicate BatchGeo map using Google map. So here I present my CustomRendererClass which I used to draw PieChart as Cluster Icon with Size of Cluster as well. Pass dataset as per your custom requirements.
public class TbmClusterRenderer extends DefaultClusterRenderer<VenueItemData> {
private IconGenerator mClusterIconGenerator;
private Context context;
public TbmClusterRenderer(Context context, GoogleMap map,
ClusterManager<VenueItemData> clusterManager) {
super(context, map, clusterManager);
this.context = context;
mClusterIconGenerator = new IconGenerator(context);
}
#Override
protected void onBeforeClusterItemRendered(VenueItemData item,
MarkerOptions markerOptions) {
try {
int markerColor = item.getMarkerColor();
Bitmap icon;
icon = BitmapFactory.decodeResource(context.getResources(),
R.drawable.map_marker).copy(Bitmap.Config.ARGB_8888, true);
Paint paint = new Paint();
ColorFilter filter = new PorterDuffColorFilter(markerColor,
PorterDuff.Mode.SRC_IN);
paint.setColorFilter(filter);
Canvas canvas = new Canvas(icon);
canvas.drawBitmap(icon, 0, 0, paint);
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
} catch (Exception ex) {
ex.printStackTrace();
Crashlytics.logException(ex);
}
}
#Override
protected void onClusterItemRendered(VenueItemData clusterItem, Marker marker) {
super.onClusterItemRendered(clusterItem, marker);
}
#Override
protected void onBeforeClusterRendered(Cluster<VenueItemData> cluster, MarkerOptions markerOptions) {
ArrayList<VenueItemData> a = (ArrayList<VenueItemData>) cluster.getItems();
Canvas canvas;
#SuppressLint("UseSparseArrays")
HashMap<Integer, Integer> markerColors = new HashMap<>();
// ConcurrentHashMap<Integer, Integer> markerColors = new ConcurrentHashMap<>();
for (VenueItemData data : a) {
if (data.getMarkerColor() != 0) {
if (!markerColors.containsValue(data.getMarkerColor())) {
markerColors.put(data.getMarkerColor(), 0);
}
}
}
Set set = markerColors.entrySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object aSet = iterator.next();
Map.Entry<Integer, Integer> entry = (Map.Entry<Integer, Integer>) aSet;
for (VenueItemData data : a) {
if (data.getMarkerColor() == entry.getKey()) {
entry.setValue(entry.getValue() + 1);
}
}
}
Log.e("graph values", new Gson().toJson(markerColors));
Bitmap myBitmap = Bitmap.createBitmap(70, 70, Bitmap.Config.ARGB_8888);
canvas = new Canvas(myBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1f);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
RectF rectf = new RectF(2, 2, myBitmap.getWidth() - 2, myBitmap.getHeight() - 2);
set = markerColors.entrySet();
float startAngle = 0.0f;
for (Object aSet : set) {
Map.Entry<Integer, Integer> entry = (Map.Entry<Integer, Integer>) aSet;
float angle = (360.0f / (float) cluster.getSize()) * (float) entry.getValue();
paint.setColor(entry.getKey());
canvas.drawArc(rectf, startAngle, angle, true, paint);
startAngle += angle;
}
BitmapDrawable clusterIcon = new BitmapDrawable(context.getResources(), myBitmap);
if (set.isEmpty()) {
mClusterIconGenerator.setBackground(context.getResources().getDrawable(R.drawable.circle1));
} else {
mClusterIconGenerator.setBackground(clusterIcon);
}
mClusterIconGenerator.setTextAppearance(R.style.ClusterTextAppearance);
Bitmap icon = mClusterIconGenerator.makeIcon(String.valueOf(cluster.getSize()));
markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon));
mClusterIconGenerator.setContentPadding(20, 20, 20, 20);
}
}
Here I attach Screenshot for reference: