KristofferStrube / Blazor.SVGEditor

A basic SVG editor written in Blazor.
https://kristofferstrube.github.io/Blazor.SVGEditor/
MIT License
310 stars 51 forks source link

Feature: Add automatic centering and scaling function after svg file loaded #27

Closed allrightsreserved closed 8 months ago

allrightsreserved commented 8 months ago

After SVGEditor.Elements is loaded, is it possible to center all elements to SVgEdit according to the display area, and automatically match the appropriate scaling(maybe a padding value there) according to the display area so that it fills the display area? I saw that SVGEdit has “SVGEditor. Scale” and “SVGEditor.Translate”, but it seems that after loading is completed, the boundarybox of all elements and the boundarybox of SVgEdit need to be calculated in order to calculate the new "Translate" and "Scale". that will be great with this automatic centering and scaling function.

form : 3

to (center and bigger/smaller to fill the display area): 4

KristofferStrube commented 8 months ago

Hey @allrightsreserved, Great idea. I've stated a similar feature in issue #21. And I've actually made some progress on an MVP in my Blazor.Graph library. You can try that out here: https://kristofferstrube.github.io/Blazor.GraphEditor/LiveData Things that I still want to improve on in that demo are requiring less code on the consumer's part and being able to have the transition be smooth no matter how often the model refreshes. The current code needed to do this fit to transform is the following in that library:

double prevUnixTimeSeconds = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0;
while (running)
{
    await GraphEditor.ForceDirectedLayout();
    double unixTimeSeconds = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0;
    GraphEditor.FitToShapes(delta: Math.Min((unixTimeSeconds - prevUnixTimeSeconds) * 4, 1));
    prevUnixTimeSeconds = unixTimeSeconds;
    await Task.Delay(1);
}
KristofferStrube commented 8 months ago

I have adjusted the code from that project so that it can be included in this project directly. Would the following methods meet the needs that you have? :)

/// <summary>
/// Fits all shapes in the editor to the viewport.
/// </summary>
/// <param name="delta">
///     A number between <c>0</c> and <c>1</c> inclusive that defines how much of the full transformation should be made.
///     <c>1</c> mean that it fits the shapes perfectly in the center.
///     <c>0.5</c> means that it makes half the zoom and translation change torwards the perfect fit.
/// </param>
/// <param name="padding">How much padding there should be around the shapes to the edge of the editor. The unit is pixels in the rendered space not the space of the SVG that is edited.</param>
public void FitViewportToAllShapes(double delta = 1, double padding = 20)
{
    FitViewportToShapes(Elements.Where(e => e is Shape).Cast<Shape>(), delta, padding);
}

/// <summary>
/// Fits the selected shapes in the editor to the viewport.
/// </summary>
/// <param name="delta">
///     A number between <c>0</c> and <c>1</c> inclusive that defines how much of the full transformation should be made.
///     <c>1</c> mean that it fits the shapes perfectly in the center.
///     <c>0.5</c> means that it makes half the zoom and translation change torwards the perfect fit.
/// </param>
/// <param name="padding">How much padding there should be around the shapes to the edge of the editor. The unit is pixels in the rendered space not the space of the SVG that is edited.</param>
public void FitViewporToSelectedShapes(double delta = 1, double padding = 20)
{
    FitViewportToShapes(SelectedShapes, delta, padding);
}

/// <summary>
/// Fits the <paramref name="shapes"/> to the viewport.
/// </summary>
/// <param name="shapes">The shapes to fit the viewport to.</param>
/// <param name="delta">
///     A number between <c>0</c> and <c>1</c> inclusive that defines how much of the full transformation should be made.<br />
///     <c>1</c> mean that it fits the shapes perfectly in the center.<br />
///     <c>0.5</c> means that it makes half the zoom and translation change torwards the perfect fit.
/// </param>
/// <param name="padding">How much padding there should be around the shapes to the edge of the editor. The unit is pixels in the rendered space not the space of the SVG that is edited.</param>
public void FitViewportToShapes(IEnumerable<Shape> shapes, double delta = 1, double padding = 20)
{
    if (BBox is null) return;
    double lowerX = double.MaxValue, lowerY = double.MaxValue;
    double upperX = double.MinValue, upperY = double.MinValue;
    foreach (Shape shape in shapes)
    {
        foreach ((double x, double y) in shape.SelectionPoints)
        {
            double strokeWidth = double.TryParse(shape.StrokeWidth, CultureInfo.InvariantCulture, out double result) ? result : 0;
            lowerX = Math.Min(x - strokeWidth, lowerX);
            upperX = Math.Max(x + strokeWidth, upperX);
            lowerY = Math.Min(y - strokeWidth, lowerY);
            upperY = Math.Max(y + strokeWidth, upperY);
        }
    }
    double elementsWidth = upperX - lowerX;
    double elementsHeight = upperY - lowerY;
    double newScale = Math.Min((BBox.Width - (padding * 2)) / elementsWidth, (BBox.Height - (padding * 2)) / elementsHeight);
    (double x, double y) newTranslate = ((BBox.Width / 2) - ((lowerX + (elementsWidth / 2)) * newScale), (BBox.Height / 2) - ((lowerY + (elementsHeight / 2)) * newScale));
    Scale = (Scale * (1 - delta)) + (newScale * delta);
    Translate = ((Translate.x * (1 - delta)) + (newTranslate.x * delta), (Translate.y * (1 - delta)) + (newTranslate.y * delta));
}
allrightsreserved commented 8 months ago

Awesome! after added the code in this, it works fine, the only problem is that when loading, you can see the process of the position and size changing from the original position to the new position. It seems that the JavaScript of this BBox = await GetBoundingBox(svgElementReference); cannot be executed in advance. protected override async Task OnAfterRenderAsync(bool firstRender) { BBox = await GetBoundingBox(svgElementReference);

    **if (firstRender)
    {
        FitViewportToAllShapes();
        await InvokeAsync(StateHasChanged);
    }**
}
KristofferStrube commented 8 months ago

That is correct. We can't get the size of the viewport before the site has loaded.