Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plotutil 3rd dashed line only partially renders at certain resolutions #231

Closed
dskinner opened this issue Nov 23, 2015 · 8 comments
Closed

Comments

@dskinner
Copy link

I found this result rather confusing a few weeks back thinking I had incomplete data until I discovered it was the plot itself. Here's a sample program and plot of the result

package main

import (
    "log"
    "math"

    "github.com/gonum/plot"
    "github.com/gonum/plot/plotter"
    "github.com/gonum/plot/plotutil"
)

func xyer(out []float64) plotter.XYs {
    n := len(out)
    xys := make(plotter.XYs, n)
    for i, v := range out {
        xys[i].X = float64(i) / float64(n)
        xys[i].Y = v
    }
    return xys
}

func decay() []float64 {
    sig := make([]float64, 1024)
    n := float64(len(sig))
    for i := 0.0; i < n; i++ {
        sig[int(i)] = math.Exp(2 * math.Pi * -(i / n))
    }
    return sig
}

func main() {
    plt, err := plot.New()
    if err != nil {
        log.Fatal(err)
    }
    plt.X.Min, plt.X.Max = 0, 1
    plt.Y.Min, plt.Y.Max = -1, 1

    empty := make([]float64, 1024)
    plotutil.AddLines(plt,
        "empty", xyer(empty),
        "empty", xyer(empty), // comment out and decay plots ok
        "decay", xyer(decay()), // this dashed line is bugged
    )

    plt.Save(1000, 500, "out.png")
    // Increase size and decay plots ok
    // plt.Save(2000, 1000, "out.png")
}

out

@kortschak
Copy link
Member

The same fault happens when you save to jpg and tiff, but not to svg. I suspect this is a github.com/llgcode/draw2d bug.

@dskinner
Copy link
Author

interesting library. I'll see about reproducing specific to draw2d and if it's an issue that can actually be addressed there. Though to some degree, I might expect a drawing library to drop data too small to render in a given frame where as I might expect a plotter to compensate to produce something useful.

@kortschak
Copy link
Member

A way to approach this might be to capture what it being drawn with recorder.Canvas and replaying sections of the captured actions to figure out what is going on. I may be able to get to this today.

@dskinner
Copy link
Author

I'm certainly in no rush and only discovered today that an increase in
resolution resolves the issue. It'd likely be 7+ days before I could look
into it and being rather unfamiliar with everything I'd probably not be
very productive :)

On Mon, Nov 23, 2015 at 4:52 PM Dan Kortschak notifications@github.com
wrote:

A way to approach this might be to capture what it being drawn with
recorder.Canvas
https://godoc.org/github.com/gonum/plot/vg/recorder#Canvas and
replaying sections of the captured actions to figure out what is going on.
I may be able to get to this today.


Reply to this email directly or view it on GitHub
#231 (comment).

@kortschak
Copy link
Member

Here is a simpler reproducer; I'm not sure the recorder will help here.

package main

import (
    "log"
    "math"

    "github.com/gonum/plot"
    "github.com/gonum/plot/plotter"
    "github.com/gonum/plot/vg"
)

func decay() plotter.XYs {
    sig := make(plotter.XYs, 1024)
    n := float64(len(sig))
    for i := range sig {
        sig[i].X = float64(i) / n
        sig[i].Y = math.Exp(2 * math.Pi * -(float64(i) / n))
    }
    return sig
}

func main() {
    plt, err := plot.New()
    if err != nil {
        log.Fatal(err)
    }
    plt.X.Min, plt.X.Max = 0, 1
    plt.Y.Min, plt.Y.Max = -1, 1

    l, err := plotter.NewLine(decay())
    if err != nil {
        log.Fatalf("failed to make new line: %v", err)
    }
    l.Dashes = []vg.Length{vg.Points(2), vg.Points(2)}

    plt.Add(l)

    plt.HideX()
    plt.HideY()

    plt.Save(1000, 500, "out.png")
}

If the dash/space length is 2 or 3 you get the effect (2 causes continuous line and 3 causes blank). Above that it is always a continuous line. The point at which break happens is not consistent between the 2 and 3 cases. Interestingly if you have replicas of the lines, they all break at the same part of the curve. The error appears to be in draw2dbase.(*DashVertexConverter).lineTo where alternation between gap and line is calculated, though it's not obvious to me how. \cc @llgcode.

@kortschak
Copy link
Member

The issue is that the condition here is preventing saving the correct state of the dash for the next call to lineTo. So when the distance moved in a call to lineTo is too small compared to a dash segment, no progression through the dash space is possible. This results in either a blank or a solid line (why all the higher value dash segments above result in solid lines).

This fixes it AFAICS:

func (dasher *DashVertexConverter) lineTo(x, y float64) {
    rest := dasher.dash[dasher.currentDash] - dasher.distance
    for rest < 0 {
        dasher.distance -= dasher.dash[dasher.currentDash]
        dasher.nextDash()
        rest = dasher.dash[dasher.currentDash] - dasher.distance
    }
    dasher.distance += math.Hypot(dasher.x-x, dasher.y-y)
    for dasher.distance >= rest {
        k := rest / dasher.distance
        dasher.x += k * (x - dasher.x)
        dasher.y += k * (y - dasher.y)
        dasher.dashTo(dasher.x, dasher.y)
        dasher.distance -= rest
        dasher.nextDash()
        rest = dasher.dash[dasher.currentDash]
    }

    dasher.dashTo(x, y)
    if dasher.distance >= rest {
        dasher.distance -= rest
        dasher.nextDash()
    }
    dasher.x, dasher.y = x, y
}

func (dasher *DashVertexConverter) nextDash() {
    dasher.currentDash++
    dasher.currentDash %= len(dasher.dash)
}

func (dasher *DashVertexConverter) dashTo(x, y float64) {
    if dasher.currentDash%2 == 0 {
        // line
        dasher.next.LineTo(x, y)
    } else {
        // gap
        dasher.next.End()
        dasher.next.MoveTo(x, y)
    }
}

This is still not correct (the dash lengths look odd in places - actually off by a factor of two when compared to the svg output which is clearly correct: use dashes []vg.Length{72, 72} to see that, outputing to png and svg), but it does retain state and alternate.

@eaburns
Copy link
Member

eaburns commented Nov 24, 2015

Thanks for tracking this down. I think we should open a bug with draw2d.

sbinet added a commit to sbinet-gonum/plot that referenced this issue Feb 13, 2019
sbinet added a commit to sbinet-gonum/plot that referenced this issue Feb 18, 2019
sbinet added a commit to sbinet-gonum/plot that referenced this issue Feb 19, 2019
@sbinet sbinet closed this as completed in af8bb81 Feb 19, 2019
@kortschak
Copy link
Member

W00t!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants