cetz-package / cetz

CeTZ: ein Typst Zeichenpaket - A library for drawing stuff with Typst.
https://cetz-package.github.io
GNU Lesser General Public License v3.0
885 stars 36 forks source link

Add ability to plot functions with x and y as parameters (circle/contour plot) #215

Closed Andrew15-5 closed 1 year ago

Andrew15-5 commented 1 year ago

I think most libraries have struggled with easily drawing circles where the function has both x and y as its parameters. Matplotlib has a contour function that takes x and y list and the f(x, y). pgfplots only supports drawing upper and lower halves of a circle. Same with cetz. I was wondering if it is possible to straight up write (x, y) => calc.pow(x - 1, 2) + calc.pow(y - 1, 2) == 9. The problem is that domain is only for x and normal function is f(x) = float and not f(x, y) = boolean. Is it possible to make an "exception" for drawing circles (ellipses)?

Another function example can be a (x, y) => (x - 1) * (y - 1) == 1, but in this case it can be transformed into function with 1 argument (no square roots).

Related: cetz-package/cetz-plot#4.

Andrew15-5 commented 1 year ago

A related problem is that I can't just write x => calc.sqrt(5 - calc.pow((x - 1), 2)) + 1, because I have to provide a valid domain, no more (will give an error) no less (won't be a complete circle). This introduces a manual and painful offset guessing:

code ```typ #cetz.canvas({ import cetz.draw: * import cetz.plot plot.plot( size: (10, 10), x-min: -2, x-max: 4, y-min: -2, y-max: 4, x-ticks: (1,), y-ticks: (1,), axis-style: "school-book", { let offset = .236067977 plot.add( domain: (-1 - offset, 3 + offset), x => calc.sqrt(5 - calc.pow((x - 1), 2)) + 1, ) plot.add( domain: (-1 - offset, 3 + offset), x => -calc.sqrt(5 - calc.pow((x - 1), 2)) + 1, ) let dashed = (stroke: (paint: gray, dash: "dashed")) plot.add(domain: (-2, 4), style: dashed, x => 1) plot.add(style: dashed, ((1, -2), (1, 4))) }, ) }) ```
screenshot ![image](https://github.com/johannes-wolf/cetz/assets/37143421/b3b81057-47fe-4329-b944-b7aa408cc006)
johannes-wolf commented 1 year ago

Ok, I am very interested in implementing contour plots. But I am not sure when I've got time to do so.

Andrew15-5 commented 1 year ago

I find it frustrating that I couldn't achieve the same simplicity of the input and the same complexity of the output with matplotlib as I did with Desmos:

Desmos screenshot ![image](https://github.com/johannes-wolf/cetz/assets/37143421/e79b28d5-8803-4863-a1ba-b306e6b3ffa5)

It would be perfect if cetz can compete with this app in terms of contour plotting + area filling (if the condition has less/greater signs), because Desmos can only save plots to the cloud, not into downloadable files (unlike typst+cetz).

johannes-wolf commented 1 year ago

Hello @Andrew15-5, I have started work on contour plots. Are you using Typst localy? If so, can you try out the branch of the linked PR? Filling does not yet work, though. See the tests of the PR for an example.

It currently also results in pretty strange plots sometimes… :grin: It is too late for me, maybe.

johannes-wolf commented 1 year ago

Btw. https://www.geogebra.org/graphing allows you to export plots as SVG and Desmos does, too. Although the export options are quite limited.

Andrew15-5 commented 1 year ago

can you try out the branch of the linked PR?

I'm not sure when I can, but not right now. Maybe a bit later. Looks good though.

Btw. https://www.geogebra.org/graphing allows you to export plots as SVG and Desmos does, too.

Oh, I forgot that Desmos has a web version. Yes, it's all very limited, but at least I have some options, thanks.

Andrew15-5 commented 1 year ago

Filling does not yet work

It can "not work" if the contour is out of the visible bounds:

screenshot ![image](https://github.com/johannes-wolf/cetz/assets/37143421/57963f29-2300-4b2b-809b-acdc37f2bec6)
code ```typ #import "@local/cetz:0.1.2" #cetz.canvas({ import cetz.draw: * import cetz.plot plot.plot(size: (15, 15), { plot.add-contour( (x, y) => x, fill: true, x-domain: (-10, 25), y-domain: (-24, 24), ) }) }) ```

But it works (without color blending) if the limit is visible:

screenshot ![image](https://github.com/johannes-wolf/cetz/assets/37143421/75101dd6-5503-4e05-b0d4-c29a180da14a)
code ```typ #import "@local/cetz:0.1.2" #cetz.canvas({ import cetz.draw: * import cetz.plot plot.plot(size: (15, 15), axis-style: "school-book", { plot.add-contour( (x, y) => x, fill: true, style: (stroke: green, fill: green), x-domain: (0, 10), y-domain: (-10, 10.8), x-samples: 250, y-samples: 250, ) plot.add-contour( (x, y) => y, offset: 0, x-domain: (-9.8, 10), y-domain: (0, 11), fill: true, style: (stroke: purple, fill: purple), x-samples: 250, y-samples: 250, ) plot.add-contour( (x, y) => 30 - (calc.pow(1 - x, 2) + calc.pow(1 - y, 2)), fill: true, style: (stroke: blue, fill: blue), x-domain: (-10, 10), y-domain: (-10, 10), ) plot.add-contour( (x, y) => 2 - (x - 1) * (y - 1), fill: true, line: (type: "spline", samples: 5), style: (stroke: red, fill: red), x-domain: (-10, 10), y-domain: (-10, 11), ) plot.add(domain: (-10, +10), style: (stroke: black + 3pt), x => x) }) }) ```

It's very rough, but it's a good start. I also have to wait for more than 15 seconds for it to compile (with set max settings).

P.S. This is what I plotted in Desmos (mobile app) before opening this issue.

johannes-wolf commented 1 year ago

I could improve it a lot (there was a bug in the interpolation): image

The upper example uses 25x25 samples, the lower 50x50 samples. Filling now also works correctly. The changes are not yet pushed.

johannes-wolf commented 1 year ago

Merged to 0.2.0 branch.