import React from 'react'; import L from 'leaflet'; import BoundsDriftMarker, { BoundsDriftMarkerProps } from './BoundsDriftMarker'; import PiePlot from '../plots/PiePlot'; import { MarkerScaleAddon, MarkerScaleDefault, PiePlotDatum, ContainerStylesAddon, } from '../types/plots'; import { last } from 'lodash'; // ts definition for HistogramMarkerSVGProps: need some adjustment but for now, just use Donut marker one export interface DonutMarkerProps extends BoundsDriftMarkerProps, MarkerScaleAddon { data: { value: number; label: string; color?: string; }[]; // isAtomic: add a special thumbtack icon if this is true isAtomic?: boolean; onClick?: (event: L.LeafletMouseEvent) => void | undefined; /** center title/number for marker (defaults to sum of data[].value) */ markerLabel?: string; /** cumulative mode: values are expected in order and to **already** be cumulative in nature. * That is, values 20, 40, 60, 80, 100 would generate five equal-sized segments. The final * value does not have to be 100. 2,4,6,8,10 would produce the same donut * (but with different mouse-overs in the enlarged version.) */ cumulative?: boolean; } // convert to Cartesian coord. toCartesian(centerX, centerY, Radius for arc to draw, arc (radian)) function toCartesian( centerX: number, centerY: number, radius: number, angleInRadianInput: number ) { const angleInRadians = angleInRadianInput - Math.PI / 2; return { x: centerX + radius * Math.cos(angleInRadians), y: centerY + radius * Math.sin(angleInRadians), }; } // input radian: makeArc(centerX, centerY, Radius for arc to draw, start point of arc (radian), end point of arc (radian)) function makeArc( x: number, y: number, radius: number, startAngle: number, endAngle: number ) { const endAngleOriginal = endAngle; if (endAngleOriginal - startAngle === 2 * Math.PI) { endAngle = (359 * Math.PI) / 180; } const start = toCartesian(x, y, radius, endAngle); const end = toCartesian(x, y, radius, startAngle); const arcSweep = endAngle - startAngle <= Math.PI ? '0' : '1'; if (endAngleOriginal - startAngle === 2 * Math.PI) { const dValue = [ 'M', start.x, start.y, 'A', radius, radius, 0, arcSweep, 0, end.x, end.y, 'z', ].join(' '); return dValue; } else { const dValue = [ 'M', start.x, start.y, 'A', radius, radius, 0, arcSweep, 0, end.x, end.y, ].join(' '); return dValue; } } /** * this is a SVG donut marker icon */ export default function DonutMarker(props: DonutMarkerProps) { const { html: svgHTML, size, markerLabel, sliceTextOverrides, } = donutMarkerSVGIcon(props); // set icon as divIcon const SVGDonutIcon: any = L.divIcon({ className: 'leaflet-canvas-icon', // may need to change this className but just leave it as it for now iconSize: new L.Point(size, size), // this will make icon to cover up SVG area! iconAnchor: new L.Point(size / 2, size / 2), // location of topleft corner: this is used for centering of the icon like transform/translate in CSS html: svgHTML, // divIcon HTML svg code generated above }); // anim check duration exists or not const duration: number = props.duration ? props.duration : 300; const plotSize = 150; const marginSize = 0; const popupPlot = ( ); return ( ); } type DonutMarkerStandaloneProps = Omit< DonutMarkerProps, | 'id' | 'position' | 'bounds' | 'onClick' | 'duration' | 'showPopup' | 'popupClass' | 'popupContent' > & ContainerStylesAddon; export function DonutMarkerStandalone(props: DonutMarkerStandaloneProps) { const { html, size } = donutMarkerSVGIcon(props); // NOTE: the font size and line height would normally come from the .leaflet-container class // but we won't be using that. You can override these with `containerStyles` if you like. return (
); } function donutMarkerSVGIcon( props: DonutMarkerStandaloneProps ): { html: string; size: number; sliceTextOverrides: string[]; markerLabel: string; } { const scale = props.markerScale ?? MarkerScaleDefault; const size = 40 * scale; let svgHTML: string = ''; //DKDK set drawing area svgHTML += ''; //DKDK initiate svg marker icon // what value corresponds to 360 degrees of the circle? // regular mode: summation of fullStat.value per marker icon // cumulative mode: take the last value const fullPieValue: number = props.cumulative ? last(props.data)?.value ?? 0 : props.data .map((o) => o.value) .reduce((a, c) => { return a + c; }); // for display, convert large value with k (e.g., 12345 -> 12k): return original value if less than a criterion const sumLabel = props.markerLabel ?? String(fullPieValue); //DKDK draw white circle svgHTML += ''; //DKDK set start point of arc = 0 let startValue = 0; let cumulativeSum = 0; const sliceTextOverrides: string[] = []; //DKDK create arcs for data props.data.forEach(function (el: PiePlotDatum) { //DKDK if fullPieValue = 0, do not draw arc if (fullPieValue > 0) { //DKDK compute the ratio of each data to the total number const thisValue = el.value - cumulativeSum; // subtracts nothing if not in cumulative mode, see below let arcValue: number = thisValue / fullPieValue; // for the magnified mouse-over pieplot sliceTextOverrides.push(arcValue >= 0.015 ? el.value.toString() : ''); if (props.cumulative) // only sum up in cumulative mode cumulativeSum += thisValue; //DKDK draw arc: makeArc(centerX, centerY, Radius for arc, start point of arc (radian), end point of arc (radian)) svgHTML += ''; //DKDK set next startValue to be previous arcValue startValue = startValue + arcValue * 2 * Math.PI; } }); //DKDK adding total number text/label and centering it svgHTML += '' + sumLabel + ''; //DKDK check isAtomic: draw pushpin if true if (props.isAtomic) { let pushPinCode = '🖈'; svgHTML += '' + pushPinCode + ''; } // DKDK closing svg tag svgHTML += ''; return { html: svgHTML, size, sliceTextOverrides, markerLabel: sumLabel }; }