Customization
Learn how to customize the appearance and behavior of your route optimization maps.
Overview
The library provides extensive customization options for:
- Map appearance and styles
- Route polylines
- Markers and labels
- Optimization behavior
- Event handling
Map Customization
Map Styles
Apply custom styles to your map:
import { MapConfig } from '@route-optimization/core';
const config: MapConfig = {
apiKey: 'YOUR_API_KEY',
center: { lat: 13.7563, lng: 100.5018 },
zoom: 12,
styles: [
{
featureType: 'water',
elementType: 'geometry',
stylers: [{ color: '#a2daf2' }],
},
{
featureType: 'road',
elementType: 'geometry',
stylers: [{ color: '#ffffff' }, { visibility: 'on' }],
},
{
featureType: 'poi',
stylers: [{ visibility: 'off' }],
},
],
};Dark Mode
Create a dark mode map:
const darkModeStyles: google.maps.MapTypeStyle[] = [
{ elementType: 'geometry', stylers: [{ color: '#242f3e' }] },
{ elementType: 'labels.text.stroke', stylers: [{ color: '#242f3e' }] },
{ elementType: 'labels.text.fill', stylers: [{ color: '#746855' }] },
{
featureType: 'water',
elementType: 'geometry',
stylers: [{ color: '#17263c' }],
},
{
featureType: 'road',
elementType: 'geometry',
stylers: [{ color: '#38414e' }],
},
{
featureType: 'road',
elementType: 'labels.text.fill',
stylers: [{ color: '#9ca5b3' }],
},
];
const darkConfig: MapConfig = {
apiKey: 'YOUR_API_KEY',
styles: darkModeStyles,
};Map Type
Set different map types:
const config: MapConfig = {
apiKey: 'YOUR_API_KEY',
mapTypeId: 'satellite', // 'roadmap' | 'satellite' | 'hybrid' | 'terrain'
};Gesture Handling
Control user interactions:
const config: MapConfig = {
apiKey: 'YOUR_API_KEY',
gestureHandling: 'cooperative', // Requires Ctrl+scroll to zoom
// Options: 'cooperative' | 'greedy' | 'none' | 'auto'
};Map Restrictions
Restrict the map to specific regions:
const config: MapConfig = {
apiKey: 'YOUR_API_KEY',
restriction: {
latLngBounds: {
north: 20.5,
south: 5.5,
east: 106.0,
west: 97.0,
},
strictBounds: false,
},
};Route Customization
Polyline Styling
Customize route appearance:
import { Route, RouteOptions } from '@route-optimization/core';
const route: Route = {
stops: [
{ id: '1', location: { lat: 13.7563, lng: 100.5018 } },
{ id: '2', location: { lat: 13.7467, lng: 100.5352 } },
],
options: {
polylineOptions: {
strokeColor: '#FF6B6B',
strokeOpacity: 1.0,
strokeWeight: 6,
geodesic: true,
},
},
};Animated Routes
Create animated routes with gradient colors:
const animatedRoute: Route = {
stops: [...],
options: {
polylineOptions: {
strokeColor: '#4ECDC4',
strokeOpacity: 0.8,
strokeWeight: 8,
geodesic: true,
icons: [
{
icon: {
path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
scale: 3,
strokeColor: '#FFFFFF',
},
offset: '100%',
repeat: '50px',
},
],
},
},
};Multi-Route Styling
Different styles for different routes:
const routes = {
express: {
stops: [...],
options: {
polylineOptions: {
strokeColor: '#FF0000',
strokeWeight: 8,
},
},
},
standard: {
stops: [...],
options: {
polylineOptions: {
strokeColor: '#0000FF',
strokeWeight: 5,
},
},
},
economy: {
stops: [...],
options: {
polylineOptions: {
strokeColor: '#00FF00',
strokeWeight: 3,
},
},
},
};Marker Customization
Custom Marker Icons
Use custom images for markers:
const route: Route = {
stops: [...],
options: {
markerOptions: {
startMarker: {
icon: {
url: '/icons/start-pin.png',
scaledSize: new google.maps.Size(40, 40),
anchor: new google.maps.Point(20, 40),
},
},
endMarker: {
icon: {
url: '/icons/end-pin.png',
scaledSize: new google.maps.Size(40, 40),
anchor: new google.maps.Point(20, 40),
},
},
stopMarker: {
icon: {
url: '/icons/stop-pin.png',
scaledSize: new google.maps.Size(30, 30),
anchor: new google.maps.Point(15, 30),
},
},
},
},
};SVG Markers
Use SVG for scalable markers:
const svgMarker = {
path: 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z',
fillColor: '#FF6B6B',
fillOpacity: 1,
strokeColor: '#000000',
strokeWeight: 2,
scale: 1,
};
const route: Route = {
stops: [...],
options: {
markerOptions: {
stopMarker: {
icon: svgMarker,
},
},
},
};Marker Labels
Add custom labels to markers:
const route: Route = {
stops: [...],
options: {
markerOptions: {
showLabels: true,
labelColor: '#FFFFFF',
startMarker: {
label: {
text: 'START',
color: '#FFFFFF',
fontWeight: 'bold',
fontSize: '14px',
},
},
endMarker: {
label: {
text: 'END',
color: '#FFFFFF',
fontWeight: 'bold',
fontSize: '14px',
},
},
},
},
};Dynamic Marker Colors
Color-code markers by status:
function getMarkerColor(status: string): string {
const colors = {
pending: '#FFA500',
inProgress: '#4CAF50',
completed: '#2196F3',
failed: '#F44336',
};
return colors[status] || '#9E9E9E';
}
const stops: Stop[] = [
{
id: '1',
location: { lat: 13.7563, lng: 100.5018 },
metadata: { status: 'completed' },
},
{
id: '2',
location: { lat: 13.7467, lng: 100.5352 },
metadata: { status: 'inProgress' },
},
];
// React example
stops.forEach((stop) => {
addMarker(stop.location, {
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 10,
fillColor: getMarkerColor(stop.metadata.status),
fillOpacity: 1,
strokeColor: '#FFFFFF',
strokeWeight: 2,
},
});
});Optimization Customization
Search Mode
Control optimization behavior:
import { OptimizationRequest } from '@route-optimization/core';
const request: OptimizationRequest = {
shipments: [...],
vehicles: [...],
searchMode: 'RETURN_FAST', // Fast results
// or
searchMode: 'CONSUME_ALL_AVAILABLE_TIME', // Best results
};Time Windows
Set delivery time constraints:
const request: OptimizationRequest = {
shipments: [
{
id: 'delivery-1',
deliveryLocation: { latitude: 13.7563, longitude: 100.5018 },
deliveryTimeWindows: [
{
startTime: '2025-12-12T09:00:00Z',
endTime: '2025-12-12T12:00:00Z',
},
],
},
{
id: 'delivery-2',
deliveryLocation: { latitude: 13.7467, longitude: 100.5352 },
deliveryTimeWindows: [
{
startTime: '2025-12-12T14:00:00Z',
endTime: '2025-12-12T17:00:00Z',
},
],
},
],
vehicles: [...],
};Vehicle Constraints
Configure vehicle capacity and costs:
const request: OptimizationRequest = {
shipments: [...],
vehicles: [
{
id: 'truck-1',
loadLimits: [
{
type: 'weight',
maxLoad: 1000, // kg
},
{
type: 'volume',
maxLoad: 50, // cubic meters
},
],
costPerKilometer: 2.5,
costPerHour: 25,
travelMode: 'DRIVING',
},
{
id: 'bike-1',
loadLimits: [
{
type: 'weight',
maxLoad: 20,
},
],
costPerKilometer: 0.5,
costPerHour: 10,
travelMode: 'BICYCLING',
},
],
};Custom Cost Functions
Implement custom cost calculations:
interface CustomVehicle extends OptimizationVehicle {
customCostFactor?: number;
}
function calculateCustomCost(distance: number, duration: number, vehicle: CustomVehicle): number {
const baseCost =
distance * (vehicle.costPerKilometer || 0) + duration * (vehicle.costPerHour || 0);
const customFactor = vehicle.customCostFactor || 1;
return baseCost * customFactor;
}Framework-Specific Customization
React Customization
Custom Hooks
Create reusable custom hooks:
import { useRouteMap, useRouteOptimization } from '@route-optimization/react';
import { useState, useCallback } from 'react';
function useOptimizedRouteMap(config: MapConfig) {
const { mapRef, isReady, renderRoute, clearRoute } = useRouteMap(config);
const { optimize, data, isLoading } = useRouteOptimization({
apiKey: config.apiKey,
});
const [currentRoute, setCurrentRoute] = useState<Route | null>(null);
const optimizeAndRender = useCallback(
async (request: OptimizationRequest) => {
clearRoute();
const result = await optimize(request);
if (result.success && result.routes?.[0]) {
const route = convertToRoute(result.routes[0]);
setCurrentRoute(route);
renderRoute(route);
}
},
[optimize, renderRoute, clearRoute]
);
return {
mapRef,
isReady,
isLoading,
currentRoute,
optimizeAndRender,
clearRoute,
};
}Styled Components
Integrate with styling libraries:
import styled from 'styled-components';
const MapContainer = styled.div`
width: 100%;
height: 600px;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
@media (max-width: 768px) {
height: 400px;
}
`;
function StyledMapComponent() {
const { mapRef } = useRouteMap({ apiKey: 'YOUR_API_KEY' });
return <MapContainer ref={mapRef} />;
}Vue Customization
Custom Composables
Create reusable composables:
import { useRouteMap, useRouteOptimization } from '@route-optimization/vue';
import { ref, computed, type Ref } from 'vue';
export function useOptimizedRouteMap(
mapElement: Ref<HTMLDivElement | undefined>,
config: MapConfig
) {
const { isReady, renderRoute, clearRoute } = useRouteMap({
mapElement,
...config,
});
const { optimize, data, isLoading } = useRouteOptimization({
apiKey: config.apiKey,
});
const currentRoute = ref<Route | null>(null);
const optimizeAndRender = async (request: OptimizationRequest) => {
clearRoute();
const result = await optimize(request);
if (result.success && result.routes?.[0]) {
const route = convertToRoute(result.routes[0]);
currentRoute.value = route;
renderRoute(route);
}
};
return {
isReady,
isLoading,
currentRoute: computed(() => currentRoute.value),
optimizeAndRender,
clearRoute,
};
}Scoped Styles
Use scoped styles in Vue components:
<template>
<div class="map-wrapper">
<div ref="mapElement" class="map-container" />
</div>
</template>
<style scoped>
.map-wrapper {
padding: 20px;
background: #f5f5f5;
}
.map-container {
width: 100%;
height: 600px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
.map-container {
height: 400px;
}
}
</style>Vanilla Customization
Custom Classes
Extend base classes:
import { RouteMap, RouteMapConfig } from '@route-optimization/vanilla';
class CustomRouteMap extends RouteMap {
private theme: 'light' | 'dark';
constructor(config: RouteMapConfig, theme: 'light' | 'dark' = 'light') {
super({
...config,
styles: theme === 'dark' ? darkModeStyles : config.styles,
});
this.theme = theme;
}
setTheme(theme: 'light' | 'dark'): void {
this.theme = theme;
const map = this.getMap();
if (map) {
map.setOptions({
styles: theme === 'dark' ? darkModeStyles : [],
});
}
}
getTheme(): 'light' | 'dark' {
return this.theme;
}
}Event-Driven Customization
Add custom event handling:
import { RouteOptimizer } from '@route-optimization/vanilla';
class CustomRouteOptimizer extends RouteOptimizer {
private analytics: Analytics;
constructor(config: RouteCalculatorConfig, analytics: Analytics) {
super(config);
this.analytics = analytics;
this.setupAnalytics();
}
private setupAnalytics(): void {
this.on('start', () => {
this.analytics.trackEvent('optimization_started');
});
this.on('success', (response) => {
this.analytics.trackEvent('optimization_completed', {
routes: response.routes?.length || 0,
distance: response.metrics?.totalDistance,
});
});
this.on('error', (error) => {
this.analytics.trackEvent('optimization_failed', {
error: error.message,
});
});
}
}Theming
CSS Variables
Use CSS variables for consistent theming:
:root {
--map-height: 600px;
--map-border-radius: 12px;
--map-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--route-color-primary: #4caf50;
--route-color-secondary: #2196f3;
--route-color-warning: #ffc107;
--route-color-danger: #f44336;
--marker-size: 40px;
--marker-color: #ff6b6b;
}
[data-theme='dark'] {
--route-color-primary: #66bb6a;
--route-color-secondary: #42a5f5;
--marker-color: #ff8a80;
}
.map-container {
height: var(--map-height);
border-radius: var(--map-border-radius);
box-shadow: var(--map-shadow);
}Dynamic Theming
Switch themes at runtime:
// React
function ThemeSwitcher() {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const config: MapConfig = {
apiKey: 'YOUR_API_KEY',
styles: theme === 'dark' ? darkModeStyles : [],
};
const { mapRef } = useRouteMap(config);
return (
<div>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
<div ref={mapRef} />
</div>
);
}Best Practices
1. Consistent Styling
Use a design system:
const designSystem = {
colors: {
primary: '#4CAF50',
secondary: '#2196F3',
danger: '#F44336',
},
routes: {
express: {
strokeColor: '#F44336',
strokeWeight: 8,
},
standard: {
strokeColor: '#4CAF50',
strokeWeight: 6,
},
},
markers: {
size: 40,
anchor: { x: 20, y: 40 },
},
};2. Accessibility
Ensure sufficient color contrast:
const accessibleColors = {
route: '#0066CC', // WCAG AA compliant
marker: '#CC0000',
background: '#FFFFFF',
text: '#000000',
};3. Performance
Optimize custom markers:
// ✅ Good - Reuse marker icons
const markerIcon = {
url: '/icons/marker.png',
scaledSize: new google.maps.Size(40, 40),
};
stops.forEach((stop) => {
addMarker(stop.location, { icon: markerIcon });
});
// ❌ Avoid - Creating new icons for each marker
stops.forEach((stop) => {
addMarker(stop.location, {
icon: {
url: '/icons/marker.png',
scaledSize: new google.maps.Size(40, 40),
},
});
});4. Responsive Design
Adapt to screen sizes:
function getResponsiveMapConfig(): MapConfig {
const isMobile = window.innerWidth < 768;
return {
apiKey: 'YOUR_API_KEY',
zoom: isMobile ? 10 : 12,
gestureHandling: isMobile ? 'cooperative' : 'auto',
};
}Next Steps
- Learn about Performance optimization
- Troubleshoot common issues in Troubleshooting
- Explore API Reference