import * as React from "react"; import * as ReactDom from "react-dom"; import { intersection, isEmpty, keys, toPairs, values } from "lodash/fp"; import { interpolate } from "../../spinoffs/interpolate"; import { getSVGReferenceType } from "../../spinoffs/formatSVGReference"; import { MarkerDefsProps, GetNamespacedMarkerId } from "../../types"; import { Marker } from "./Marker"; import { getSVGMarkerReferenceType, MARKER_PROPERTIES } from "./helpers"; export class MarkerDefs extends React.Component { getNamespacedMarkerId: GetNamespacedMarkerId; constructor(props: MarkerDefsProps) { super(props); const { entitiesById, getNamespacedMarkerId, markerDrawerMap, pathway } = props; this.getNamespacedMarkerId = getNamespacedMarkerId; const parentFill = pathway.fill; const edges = values(entitiesById).filter(x => x.kaavioType === "Edge"); const markerColors = Array.from( edges .filter(edge => edge.hasOwnProperty("stroke")) .reduce(function(acc, edge) { acc.add(edge.stroke); return acc; }, new Set()) ); const parentFills = [parentFill]; Array.from( values(entitiesById) .filter(x => x.kaavioType === "Group") .reduce(function(acc, group) { const fill = interpolate(parentFill, group.fill, group.fillOpacity); acc.add(fill); return acc; }, new Set()) ).forEach(function(groupColor: string) { parentFills.push(groupColor); }); const localMarkerNames = Array.from( edges.reduce(function(acc, edge: any) { intersection(MARKER_PROPERTIES, keys(edge)) .map((markerProperty: string): string => edge[markerProperty]) // The only markers that need defs are the ones that have funciris // (functional IRIs) as marker properties. Markers with other marker // properties like "none" and "inherit" don't need defs. // See https://www.w3.org/TR/SVG11/painting.html#MarkerProperties .filter( (markerName: string) => getSVGMarkerReferenceType(markerName) === "localIRI" ) .forEach(function(markerName) { if (markerDrawerMap.hasOwnProperty(markerName)) { acc.add(markerName); } else { // Can't draw it if we don't have a markerDrawer for it. console.warn(`Missing markerDrawer for "${markerName}"`); } }); return acc; }, new Set()) ); const defined = markerColors .map(stroke => ({ stroke: stroke })) .reduce(function(acc: any[], partialInput) { const pairs = toPairs(partialInput); return acc.concat( parentFills.map(function(parentFill) { return pairs.reduce(function(subAcc: any, pair) { const key = pair[0]; subAcc[key] = pair[1]; subAcc.parentFill = parentFill; return subAcc; }, {}); }) ); }, []) .reduce(function(acc: any[], partialInput) { const pairs = toPairs(partialInput); return acc.concat( MARKER_PROPERTIES.map(function(markerProperty) { return pairs.reduce(function(subAcc: any, pair) { const key = pair[0]; subAcc[key] = pair[1]; subAcc.markerProperty = markerProperty; return subAcc; }, {}); }) ); }, []) .reduce(function(acc: any[], partialInput) { const pairs = toPairs(partialInput); return acc.concat( localMarkerNames.map(function(markerName) { return pairs.reduce(function(subAcc: any, pair) { const key = pair[0]; subAcc[key] = pair[1]; subAcc.markerName = markerName; return subAcc; }, {}); }) ); }, []) as any[]; this.state = { defined, markerDrawerMap }; } /* If the diagram is updated after the initial render, this step will handle * the case of needing a marker definition that wasn't defined in the * constructor, such as if a user were to add a new group with a background * color that was not present initially and then dragged a marker on top of a * type of that group. This doesn't do anything on server-side rendering. */ componentWillReceiveProps(nextProps: MarkerDefsProps) { const { getNamespacedMarkerId, state, props } = this; const { defined } = state; const { latestMarkerReferenced, markerDrawerMap } = nextProps; if (!isEmpty(latestMarkerReferenced)) { const { markerProperty, markerName } = latestMarkerReferenced; if (getSVGMarkerReferenceType(markerName) === "localIRI") { const namespacedMarkerId = getNamespacedMarkerId( latestMarkerReferenced ); if (keys(defined).indexOf(namespacedMarkerId) === -1) { defined[namespacedMarkerId] = { markerProperty, markerName }; // TODO should we ever update markerDrawerMap? this.setState({ defined: defined, markerDrawerMap: markerDrawerMap }); } } } } render() { const { getNamespacedMarkerId } = this; const { defined, markerDrawerMap } = this.state; return ( {toPairs(defined).map(([namespacedMarkerId, details]) => { const { markerProperty, markerName } = details; return ( ); })} ); } }