/// <summary>
        /// performs a translation transform on the diagram in order to push it to the "top-left" corner of the diagram.
        /// TODO: might make sense if the scale transform ends up using this as well.
        /// </summary>
        /// <param name="state"></param>
        /// <param name="diagram"></param>
        /// <returns></returns>
        private static IDiagram NormalizeToTopLeftCorner(SvgDrawState state, IDiagram diagram)
        {
            var vector =
                Vector.FromCoordinates(
                    diagram.Bounds.TopLeft,
                    state.Converter.BoundingBox.TopLeft
                    );

            //TODO: once implemented, should this use the more "programmatic" offset approach? (that doesn't rely on the SVG transform?)
            return(diagram.WithOffset(vector));
        }
        public static State <SvgDrawState, IEnumerable <XAttribute> > GenerateStyleAttributes(IStyleProperty styleProperty)
        {
            if (styleProperty is FillProperty fillProperty)
            {
                return(new[]
                {
                    new XAttribute("fill", SvgColor(fillProperty.FillColor)),
                    new XAttribute("fill-opacity", SvgOpacity(fillProperty.FillColor)),
                }
                       .Pipe(State.Return <SvgDrawState, IEnumerable <XAttribute> >));
            }
            if (styleProperty is GradientFillProperty gradientFillProperty)
            {
                var gradientState = State.Return <SvgDrawState, XElementBuilder>(() => BuildGradientElement(gradientFillProperty.Gradient));

                return
                    (from id in SvgDrawState.IncludeDefinition(gradientFillProperty.Gradient, gradientState)
                     select new[] { new XAttribute("fill", $"url(#{id})") }.AsEnumerable());
            }
            if (styleProperty is FillPatternProperty fillPatternProperty)
            {
                var patternFactory = RenderPattern(fillPatternProperty.PatternDiagram, fillPatternProperty.FillWidthRatio, fillPatternProperty.FillHeightRatio);

                return
                    (from id in SvgDrawState.IncludeDefinition(fillPatternProperty, patternFactory)
                     select new[] { new XAttribute("fill", $"url(#{id})") }.AsEnumerable());
            }
            if (styleProperty is FilterProperty filterProperty)
            {
                var filterFactory =
                    from drawState in State.Get <SvgDrawState>()
                    select SvgFilterFactory.BuildFilter(filterProperty.FilterComponent, drawState.Converter);

                return
                    (from id in SvgDrawState.IncludeDefinition(filterProperty, filterFactory)
                     select new[] { new XAttribute("filter", $"url(#{id})") }.AsEnumerable());
            }
            if (styleProperty is StrokeColorProperty strokeColorProperty)
            {
                return
                    (new[]
                {
                    new XAttribute("stroke", SvgColor(strokeColorProperty.Color)),
                    new XAttribute("stroke-opacity", SvgOpacity(strokeColorProperty.Color))
                }
                     .AsEnumerable()
                     .Pipe(State.Return <SvgDrawState, IEnumerable <XAttribute> >));
            }
            if (styleProperty is StrokeWidthProperty strokeWidthProperty)
            {
                return
                    (from state in State.Get <SvgDrawState>()
                     let svgStrokeWidth = CalculateStrokeWidth(strokeWidthProperty.StrokeWidth, state.Converter)
                                          select new[]
                {
                    new XAttribute("stroke-width", svgStrokeWidth)
                }.AsEnumerable());
            }

            throw new Exception($"unable to render fill type of style property {styleProperty.GetType()}");
        }