Circle Layer in Flutter Maps (SfMaps)

19 Oct 202224 minutes to read

Circle layer is a sublayer that renders a group of MapCircle on MapShapeLayer and MapTileLayer. This section helps to learn about how to add the circles and customize them.

Adding circles

The circles is a collection of MapCircle. Every single MapCircle renders a circle using the MapCircle.center and MapCircle.radius properties.

NOTE

It is applicable for both the tile layer and shape layer.

In the shape layer

late List<MapLatLng> circles;
late MapShapeSource dataSource;

@override
void initState() {
    circles = <MapLatLng>[
      MapLatLng(15.2993, 74.1240),
      MapLatLng(15.5057, 80.0499),
      MapLatLng(19.7515, 75.7139),
      MapLatLng(23.0225, 72.5714),
      MapLatLng(24.8607, 67.0011),
      MapLatLng(27.0238, 74.2179),
      MapLatLng(26.8467, 80.9462),
      MapLatLng(21.2787, 85.2799),
      MapLatLng(20.9517, 85.0985),
      MapLatLng(25.0961, 85.3131),
      MapLatLng(24.6637, 93.9063),
      MapLatLng(26.2006, 92.9376),
      MapLatLng(28.7041, 77.1025),
      MapLatLng(29.0588, 76.0856),
      MapLatLng(30.0668, 79.0193),
      MapLatLng(31.1471, 75.3412),
    ];

    dataSource = MapShapeSource.asset(
      'assets/india.json',
      shapeDataField: 'name',
    );
    super.initState();
}

@override
Widget build(BuildContext context) {
   return Scaffold(
      body: SfMaps(
        layers: [
          MapShapeLayer(
            source: dataSource,
            sublayers: [
              MapCircleLayer(
                circles: List<MapCircle>.generate(
                  circles.length,
                  (int index) {
                    return MapCircle(
                      center: circles[index],
                    );
                  },
                ).toSet(),
              ),
            ],
          ),
        ],
      ),
   );
}

In the tile layer

late List<MapLatLng> circles;

@override
void initState() {
    circles = <MapLatLng>[
      MapLatLng(15.2993, 74.1240),
      MapLatLng(15.5057, 80.0499),
      MapLatLng(19.7515, 75.7139),
      MapLatLng(23.0225, 72.5714),
      MapLatLng(24.8607, 67.0011),
      MapLatLng(27.0238, 74.2179),
      MapLatLng(26.8467, 80.9462),
      MapLatLng(21.2787, 85.2799),
      MapLatLng(20.9517, 85.0985),
      MapLatLng(25.0961, 85.3131),
      MapLatLng(24.6637, 93.9063),
      MapLatLng(26.2006, 92.9376),
      MapLatLng(28.7041, 77.1025),
      MapLatLng(29.0588, 76.0856),
      MapLatLng(30.0668, 79.0193),
      MapLatLng(31.1471, 75.3412),
    ];
    super.initState();
}

@override
Widget build(BuildContext context) {
   return Scaffold(
      body: SfMaps(
        layers: [
          MapTileLayer(
            urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
            initialFocalLatLng: MapLatLng(20.5937, 78.9629),
            initialZoomLevel: 4,
            sublayers: [
              MapCircleLayer(
                circles: List<MapCircle>.generate(
                  circles.length,
                  (int index) {
                    return MapCircle(
                      center: circles[index],
                    );
                  },
                ).toSet(),
              ),
            ],
          ),
        ],
      ),
   );
}

Default circle shape

Radius

You can change the size of the circles using the MapCircle.radius property. The default value of the MapCircle.radius property is 5.

late List<MapLatLng> circles;
late MapShapeSource dataSource;

@override
void initState() {
    circles = <MapLatLng>[
      MapLatLng(15.2993, 74.1240),
      MapLatLng(15.5057, 80.0499),
      MapLatLng(19.7515, 75.7139),
      MapLatLng(23.0225, 72.5714),
      MapLatLng(24.8607, 67.0011),
      MapLatLng(27.0238, 74.2179),
      MapLatLng(26.8467, 80.9462),
      MapLatLng(21.2787, 85.2799),
      MapLatLng(20.9517, 85.0985),
      MapLatLng(25.0961, 85.3131),
      MapLatLng(24.6637, 93.9063),
      MapLatLng(26.2006, 92.9376),
      MapLatLng(28.7041, 77.1025),
      MapLatLng(29.0588, 76.0856),
      MapLatLng(30.0668, 79.0193),
      MapLatLng(31.1471, 75.3412),
    ];

    dataSource = MapShapeSource.asset(
      'assets/india.json',
      shapeDataField: 'name',
    );
    super.initState();
}

@override
Widget build(BuildContext context) {
   return Scaffold(
      body: SfMaps(
        layers: [
          MapShapeLayer(
            source: dataSource,
            sublayers: [
              MapCircleLayer(
                circles: List<MapCircle>.generate(
                  circles.length,
                  (int index) {
                    return MapCircle(
                      center: circles[index],
                      radius: 10,
                    );
                  },
                ).toSet(),
              ),
            ],
          ),
        ],
      ),
   );
}

Circle radius

Fill color

You can apply the same color for all MapCircle in the circles collection using the MapCircleLayer.color property. Alternatively, you can apply different colors to each MapCircle in the circles collection using the individual MapCircle.color property.

late List<MapCircleModel> circles;
late MapShapeSource dataSource;

@override
void initState() {
    circles = <MapCircleModel>[
      MapCircleModel(MapLatLng(15.2993, 74.1240), Colors.pink),
      MapCircleModel(MapLatLng(15.5057, 80.0499), Colors.teal),
      MapCircleModel(MapLatLng(19.7515, 75.7139), Colors.purple),
      MapCircleModel(MapLatLng(23.0225, 72.5714), Colors.green),
      MapCircleModel(MapLatLng(24.8607, 67.0011), Colors.orange),
      MapCircleModel(MapLatLng(27.0238, 74.2179), Colors.blue),
      MapCircleModel(MapLatLng(26.8467, 80.9462), Colors.purpleAccent),
      MapCircleModel(MapLatLng(21.2787, 85.2799), Colors.redAccent),
    ];

    dataSource = MapShapeSource.asset(
      'assets/india.json',
      shapeDataField: 'name',
    );
    super.initState();
}

@override
Widget build(BuildContext context) {
   return Scaffold(
      body: SfMaps(
        layers: [
          MapShapeLayer(
            source: dataSource,
            sublayers: [
              MapCircleLayer(
                circles: List<MapCircle>.generate(
                  circles.length,
                  (int index) {
                    return MapCircle(
                      center: circles[index].center,
                      radius: 10,
                      color: circles[index].color,
                    );
                  },
                ).toSet(),
              ),
            ],
          ),
        ],
      ),
   );
}

class MapCircleModel {
  MapCircleModel(this.center, this.color);

  final MapLatLng center;
  final Color color;
}

Circle color

Stroke width and color

You can apply the same stroke width for all MapCircle in the circles collection using the MapCircleLayer.strokeWidth property. Alternatively, you can apply different stroke width to each MapCircle in the circles collection using the individual MapCircle.strokeWidth property. The default value of the MapCircleLayer.strokeWidth property is 2.

You can apply the same stroke color for all MapCircle in the circles collection using the MapCircleLayer.strokeColor property. Alternatively, you can apply different stroke color to each MapCircle in the circles collection using the individual MapCircle.strokeColor property.

late List<MapCircleModel> circles;
late MapShapeSource dataSource;

@override
void initState() {
    circles = <MapCircleModel>[
      MapCircleModel(MapLatLng(15.2993, 74.1240), Colors.pink),
      MapCircleModel(MapLatLng(15.5057, 80.0499), Colors.teal),
      MapCircleModel(MapLatLng(19.7515, 75.7139), Colors.purple),
      MapCircleModel(MapLatLng(23.0225, 72.5714), Colors.green),
      MapCircleModel(MapLatLng(24.8607, 67.0011), Colors.orange),
      MapCircleModel(MapLatLng(27.0238, 74.2179), Colors.blue),
      MapCircleModel(MapLatLng(26.8467, 80.9462), Colors.purpleAccent),
      MapCircleModel(MapLatLng(21.2787, 85.2799), Colors.redAccent),
    ];

    dataSource = MapShapeSource.asset(
      'assets/india.json',
      shapeDataField: 'name',
    );
    super.initState();
}

@override
Widget build(BuildContext context) {
   return Scaffold(
      body: SfMaps(
        layers: [
          MapShapeLayer(
            source: dataSource,
            sublayers: [
              MapCircleLayer(
                circles: List<MapCircle>.generate(
                  circles.length,
                  (int index) {
                    return MapCircle(
                      center: circles[index].center,
                      radius: index % 2 == 0 ? 10 : 20,
                      strokeWidth: 3,
                      strokeColor: circles[index].color,
                    );
                  },
                ).toSet(),
              ),
            ],
          ),
        ],
      ),
   );
}

class MapCircleModel {
  MapCircleModel(this.center, this.color);

  final MapLatLng center;
  final Color color;
}

Circle stroke color

Animation

You can apply animation for the MapCircle using the MapCircleLayer.animation property and able to customize the animation flow, curve and duration.

By default, there will not be any animation.

late List<MapLatLng> circles;
late MapShapeSource dataSource;
late AnimationController animationController;
late Animation animation;

@override
void initState() {
    circles = <MapLatLng>[
      MapLatLng(15.2993, 74.1240),
      MapLatLng(15.5057, 80.0499),
      MapLatLng(19.7515, 75.7139),
      MapLatLng(23.0225, 72.5714),
      MapLatLng(24.8607, 67.0011),
      MapLatLng(27.0238, 74.2179),
      MapLatLng(26.8467, 80.9462),
      MapLatLng(21.2787, 85.2799),
      MapLatLng(20.9517, 85.0985),
      MapLatLng(25.0961, 85.3131),
      MapLatLng(24.6637, 93.9063),
      MapLatLng(26.2006, 92.9376),
      MapLatLng(28.7041, 77.1025),
      MapLatLng(29.0588, 76.0856),
      MapLatLng(30.0668, 79.0193),
      MapLatLng(31.1471, 75.3412),
    ];

    dataSource = MapShapeSource.asset(
      'assets/india.json',
      shapeDataField: 'name',
    );

    animationController = AnimationController(
      duration: Duration(seconds: 3),
      vsync: this,
    );
    animation = CurvedAnimation(
      parent: animationController,
      curve: Curves.easeInOut,
    );
    animationController.forward(from: 0);
    super.initState();
}

@override
void dispose() {
  animationController.dispose();
  super.dispose();
}

@override
Widget build(BuildContext context) {
   return Scaffold(
      body: SfMaps(
        layers: [
          MapShapeLayer(
            source: dataSource,
            sublayers: [
              MapCircleLayer(
                circles: List<MapCircle>.generate(
                  circles.length,
                  (int index) {
                    return MapCircle(
                      center: circles[index],
                      radius: 10,
                    );
                  },
                ).toSet(),
                animation: animation,
              ),
            ],
          ),
        ],
      ),
   );
}

Circle animation support

Tap

You can use the onTap callback to get a notification if the particular MapCircle is tapped. You can also customize the tapped MapCircle based on the index passed in the callback as shown in the below code snippet.

late List<MapLatLng> circles;
late MapShapeSource dataSource;
late int selectedIndex;

@override
void initState() {
    circles = <MapLatLng>[
      MapLatLng(15.2993, 74.1240),
      MapLatLng(15.5057, 80.0499),
      MapLatLng(19.7515, 75.7139),
      MapLatLng(23.0225, 72.5714),
      MapLatLng(24.8607, 67.0011),
      MapLatLng(27.0238, 74.2179),
      MapLatLng(26.8467, 80.9462),
      MapLatLng(21.2787, 85.2799),
      MapLatLng(20.9517, 85.0985),
      MapLatLng(25.0961, 85.3131),
      MapLatLng(24.6637, 93.9063),
      MapLatLng(26.2006, 92.9376),
      MapLatLng(28.7041, 77.1025),
      MapLatLng(29.0588, 76.0856),
      MapLatLng(30.0668, 79.0193),
      MapLatLng(31.1471, 75.3412),
    ];

    dataSource = MapShapeSource.asset(
      'assets/india.json',
      shapeDataField: 'name',
    );

    selectedIndex = -1;
    super.initState();
}

@override
Widget build(BuildContext context) {
   return Scaffold(
      body: SfMaps(
        layers: [
          MapShapeLayer(
            source: dataSource,
            sublayers: [
              MapCircleLayer(
                circles: List<MapCircle>.generate(
                  circles.length,
                  (int index) {
                    return MapCircle(
                      center: circles[index],
                      radius: 10,
                      color: selectedIndex == index ? Colors.pink : Colors.blue,
                      onTap: () {
                         setState(() {
                            selectedIndex = index;
                         });
                      },
                    );
                  },
                ).toSet(),
              ),
            ],
          ),
        ],
      ),
   );
}

Circle tap support

Tooltip

You can show additional information about the circles drawn using the tooltipBuilder property.

late List<MapLatLng> circles;
late MapShapeSource dataSource;
late Random random;

@override
void initState() {
    circles = <MapLatLng>[
      MapLatLng(15.2993, 74.1240),
      MapLatLng(15.5057, 80.0499),
      MapLatLng(19.7515, 75.7139),
      MapLatLng(23.0225, 72.5714),
      MapLatLng(24.8607, 67.0011),
      MapLatLng(27.0238, 74.2179),
      MapLatLng(26.8467, 80.9462),
      MapLatLng(21.2787, 85.2799),
      MapLatLng(20.9517, 85.0985),
      MapLatLng(25.0961, 85.3131),
      MapLatLng(24.6637, 93.9063),
      MapLatLng(26.2006, 92.9376),
      MapLatLng(28.7041, 77.1025),
      MapLatLng(29.0588, 76.0856),
      MapLatLng(30.0668, 79.0193),
      MapLatLng(31.1471, 75.3412),
    ];

    dataSource = MapShapeSource.asset(
      'assets/india.json',
      shapeDataField: 'name',
    );

    random = Random();
    super.initState();
}

@override
Widget build(BuildContext context) {
   final ThemeData themeData = Theme.of(context);
   final TextStyle textStyle = themeData.textTheme.caption!
        .copyWith(color: themeData.colorScheme.surface);
   return Scaffold(
      body: SfMaps(
        layers: [
          MapShapeLayer(
            source: dataSource,
            sublayers: [
              MapCircleLayer(
                circles: List<MapCircle>.generate(
                  circles.length,
                  (int index) {
                    return MapCircle(
                      center: circles[index],
                      radius: 10,
                    );
                  },
                ).toSet(),
                tooltipBuilder: (BuildContext context, int index) {
                   return Container(
                      padding: EdgeInsets.all(10),
                         child: Column(
                             mainAxisSize: MainAxisSize.min,
                             children: [
                                Row(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                     Text('Disease name  :', style: textStyle),
                                     Text('  ' + 'Corona', style: textStyle),
                                  ],
                                ),
                                Row(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                    Text('Active cases    :', style: textStyle),
                                    Text('  ' + random.nextInt(62342).toString(),
                                         style: textStyle),
                                  ],
                               ),
                         ],
                      ),
                   );
                 }
              ),
            ],
          ),
        ],
      ),
   );
}

Circle tooltip support

Tooltip customization

You can customize the appearance of the tooltip.

late List<MapLatLng> circles;
late MapShapeSource dataSource;
late Random random;

@override
void initState() {
    circles = <MapLatLng>[
      MapLatLng(15.2993, 74.1240),
      MapLatLng(15.5057, 80.0499),
      MapLatLng(19.7515, 75.7139),
      MapLatLng(23.0225, 72.5714),
      MapLatLng(24.8607, 67.0011),
      MapLatLng(27.0238, 74.2179),
      MapLatLng(26.8467, 80.9462),
      MapLatLng(21.2787, 85.2799),
      MapLatLng(20.9517, 85.0985),
      MapLatLng(25.0961, 85.3131),
      MapLatLng(24.6637, 93.9063),
      MapLatLng(26.2006, 92.9376),
      MapLatLng(28.7041, 77.1025),
      MapLatLng(29.0588, 76.0856),
      MapLatLng(30.0668, 79.0193),
      MapLatLng(31.1471, 75.3412),
    ];

    dataSource = MapShapeSource.asset(
      'assets/india.json',
      shapeDataField: 'name',
    );

    random = Random();
    super.initState();
}

@override
Widget build(BuildContext context) {
   return Scaffold(
      body: SfMaps(
        layers: [
          MapShapeLayer(
            source: dataSource,
            tooltipSettings: const MapTooltipSettings(
               color: Colors.white,
               strokeWidth: 2,
               strokeColor: Colors.black,
            ),
            sublayers: [
              MapCircleLayer(
                circles: List<MapCircle>.generate(
                  circles.length,
                  (int index) {
                    return MapCircle(
                      center: circles[index],
                      radius: 10,
                    );
                  },
                ).toSet(),
                tooltipBuilder: (BuildContext context, int index) {
                   return Container(
                      padding: EdgeInsets.all(10),
                         child: Column(
                             mainAxisSize: MainAxisSize.min,
                             children: [
                               Row(
                                 mainAxisSize: MainAxisSize.min,
                                 children: [
                                   Text('Disease name  :',
                                      style: TextStyle(
                                       color: Colors.black,
                                        fontWeight: FontWeight.bold)),
                                   Text('  ' + 'Corona',
                                        style: TextStyle(color: Colors.black)),
                                 ],
                               ),
                               Row(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                     Text('Active cases    :',
                                          style: TextStyle(
                                          color: Colors.black,
                                          fontWeight: FontWeight.bold)),
                                     Text('  ' + random.nextInt(62342).toString(),
                                          style: TextStyle(color: Colors.black)),
                                  ],
                               ),
                         ],
                      ),
                   );
                 }
              ),
            ],
          ),
        ],
      ),
   );
}

Circle tooltip customization

Inverted circle

You can apply color to the inverted circle by initializing the MapCircleLayer.inverted constructor. The inner circle color is transparent and the outer portion of the circle covered by an overlay color.

You can customize the inverted circle using the following properties:

  • Stroke color - Change the stroke color of the circle using the strokeColor property.
  • Stroke width - Change the stroke width of the circle using the strokeWidth property. The default value of the strokeWidth property is 1.
  • Overlay color - Change the outer portion color of the circle using the color property.
  • Tooltip - You can enable tooltip for the inverted circle using the tooltipBuilder property.
  • Animation - You can apply animation for the inverted circle using the animation property and able to customize the animation curve and duration.

NOTE

It is applicable for both the tile layer and shape layer.

IMPORTANT

The individual circle customization like MapCircle.color, MapCircle.strokeColor and MapCircle.strokeWidth are not supported for the inverted circle.

late MapZoomPanBehavior zoomPanBehavior;

@override
void initState() {
  zoomPanBehavior = MapZoomPanBehavior(zoomLevel: 4, maxZoomLevel: 15);
  super.initState();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
     body: SfMaps(
        layers: [
          MapTileLayer(
            urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
            initialFocalLatLng: MapLatLng(-14.2350, -51.9253),
            sublayers: [
              MapCircleLayer.inverted(
                circles: List<MapCircle>.generate(
                  1,
                      (int index) {
                    return MapCircle(
                      center: MapLatLng(-14.2350, -51.9253),
                      radius: 50,
                    );
                  },
                ).toSet(),
                color: Colors.black.withOpacity(0.3),
                strokeColor: Colors.red,
                strokeWidth: 1,
              ),
            ],
            zoomPanBehavior: zoomPanBehavior,
          ),
        ],
      ),
   );
}

Inverted circle

NOTE

  • Refer tooltip section, for adding and customizing tooltip to the inverted circle.
  • Refer animation section, for applying animation to the inverted circle.