-
-
Notifications
You must be signed in to change notification settings - Fork 38
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
plot: Improve contour plots #270
Comments
@johannes-wolf, tell me if I should create a separate issue for every task (now or later) and do I need to add anything else (implementation optimizations or something like that). |
You can get a correct looking result with: #import "src/lib.typ"
#let cetz = lib
#cetz.canvas({
import cetz.draw: *
import cetz.plot
let get-style(color) = (line: (stroke: color, fill: color.lighten(75%)))
plot.plot(size: (15, 15), {
// x >= 0
plot.add-contour(
(x, y) => x >= 0,
x-domain: (0, 10),
y-domain: (-10, 10),
fill: true,
style: get-style(green),
)
// y >= 0
plot.add-contour(
(x, y) => y >= 0,
x-domain: (-10, 10),
y-domain: (0, 10),
fill: true,
style: get-style(purple),
)
// hyperbola
plot.add-contour(
(x, y) => (x - 1) * (y - 1),
x-domain: (-10, 10),
y-domain: (-10, 10),
fill: true,
z: 1,
style: get-style(red),
)
// circle
plot.add-contour(
(x, y) => 9 - (calc.pow((x - 1), 2) + calc.pow((y - 1), 2)),
x-domain: (-10, 10),
y-domain: (-10, 10),
z: 0,
fill: true,
style: get-style(blue),
)
// line
plot.add-contour(
(x, y) => x + 1 - y,
x-domain: (-10, 10),
y-domain: (-10, 10),
style: get-style(black),
)
})
}) The offset of |
Please test against branch |
Can you give examples for those? |
You can use a color with transparency to get blending. |
Or did you mean, that plots with holes do not work? This is true, the hole gets stroked, but everything gets filled. |
I will go through all your comments later. In the meanwhile, please check the updated OP. I expanded it and included what you showed here: #270 (comment). But overall the issues are still present, except this one:
|
Are you setting
Unfixable, do not use booleans. Use the new operator support + z: I think, in some cases you are not using the correct z value(s). |
With source // x >= 0
plot.add-contour(
(x, y) => x,
z: 0,
x-domain: (0, 10),
y-domain: (-10, 10),
fill: true,
style: get-style(green),
)
// y >= 0
plot.add-contour(
(x, y) => y,
z: 0,
x-domain: (-10, 10),
y-domain: (0, 10),
fill: true,
style: get-style(purple),
)
Whaa? I thought |
Have you tested with the branch from the PR? |
The first one is already resolved. for the second one just use smaller domain than the canvas: codeplot.add-contour(
(x, y) => x,
z: 0,
x-domain: (0, 10),
y-domain: (-1, 1),
fill: true,
) I would increase border/contour thickness, but it has a weird bug (which isn't noted), or I do something wrong: code#cetz.canvas({
import cetz.draw: *
import cetz.plot
let get-style(color) = (line: (stroke: color + 1em, fill: color))
// let get-style(color) = (line: (stroke: color, fill: color.lighten(25%)))
plot.plot(size: (15, 15), axis-style: "school-book",
{
// x >= 0
plot.add-contour(
(x, y) => x,
z: 0,
x-domain: (0, 10),
y-domain: (-1, 1),
fill: true,
style: get-style(rgb("#378A467F")),
)
// hyperbola
plot.add-contour(
(x, y) => (x - 1) * (y - 1),
z: 19,
x-domain: (-10, 10),
y-domain: (-10, 10),
x-samples: 50,
y-samples: 50,
fill: true,
style: get-style(rgb("#C8423F7F")),
)
})
}) Oh, right, it's not C/U, it's actually O (closed path), at least with big thickness. It supposed to be one line along |
This is as close as I could get to the first Desmos screenshot: code#import "@local/cetz:0.1.2"
#set page(width: 16.13cm, height: 14.03cm, margin: 0pt)
#set box(inset: -1em)
#cetz.canvas({
import cetz.draw: *
import cetz.plot
// Bug with thickness.
// let get-style(color) = (line: (stroke: color + 1em, fill: color.lighten(25%)))
let get-style(color) = (line: (stroke: color, fill: color.lighten(25%)))
set-style(axes: (
tick: (stroke: none),
stroke: none,
// grid: (stroke: yellow), // Doesn't work.
))
let x-min = -8
let x-max = 15
let y-min = -10
let y-max = 10
let width = x-max - x-min
let height = y-max - y-min
let factor = 0.7
width = width * factor
height = height * factor
plot.plot(
size: (width, height),
axis-style: "school-book",
x-label: none,
y-label: none,
x-tick-step: none,
y-tick-step: none,
x-ticks: range(-1, 3).map(it => it * 5),
y-ticks: range(-1, 2).map(it => it * 5),
{
// hyperbola
plot.add-contour(
(x, y) => (x - 1) * (y - 1),
z: 1,
x-domain: (x-min, x-max),
y-domain: (y-min, y-max),
x-samples: 50,
y-samples: 50,
fill: true,
style: get-style(rgb("#C8423F7F")), // red
)
// circle
plot.add-contour(
(x, y) => (calc.pow((x - 1), 2) + calc.pow((y - 1), 2)),
op: "<=",
z: 9,
x-domain: (x-min, x-max),
y-domain: (y-min, y-max),
x-samples: 50,
y-samples: 50,
fill: true,
style: get-style(rgb("#2D6FB47F")), // blue
)
// x >= 0
plot.add-contour(
(x, y) => x,
z: 0,
x-domain: (0, x-max),
y-domain: (y-min, y-max),
fill: true,
style: get-style(rgb("#378A467F")), // green
)
// y >= 0
plot.add-contour(
(x, y) => y,
z: 0,
x-domain: (x-min, x-max),
y-domain: (0, y-max),
fill: true,
style: get-style(rgb("#6042A57F")), // purple
)
// x (y = x)
plot.add(domain: (x-min, y-max), x => x, style: (stroke: black))
// `op: "=="` doesn't work.
// x (y = x)
// plot.add-contour(
// // (x, y) => x == y,
// (x, y) => x - y,
// // op: "==",
// z: 0,
// x-domain: (x-min, x-max),
// y-domain: (y-min, y-max),
// // style: get-style(black),
// style: get-style(rgb("#0000007F")),
// )
})
}) The main visual problem is that I couldn't (for some reason) add a |
The problem here is, that the sampled domain is the visible domain. Since the function gets sampled between [0, 10] it can not know that there are no vertical "borders" because no values outsides that range are known. A solution would be having a bigger sample-domain and not showing the fully sampled domain. We could add a flag "oversample" that samples left and right of the sampling domain, bot not modifying the visible domain? |
This is strange as I can not reproduce it. Code#cetz.canvas({
import cetz.draw: *
import cetz.plot
plot.plot(size: (10, 4), y-max: 4, {
plot.add-contour(
(x, y) => x,
z: 0,
x-domain: (0, 10),
y-domain: (-1, 1),
fill: true,
style: (stroke: 5pt + blue),
)
})
}) |
This is expected and has to do with sampling. The sampling is not "hitting" the values where the function is zero. Try using different We could either allow EDIT: I now added the option to pass a function |
This is very complicated to solve as of now. Please create a separate ticket for this. Detecting holes is a contour is currently not supported. We could test if a polygon lies insides another polygon. BUT Typst is currently unable to draw paths with holes in them. |
The first argument is the z-value you passed to |
Hold on, which data? I only pass a function and about 6 named arguments. Do I miss something obvious?
Wow, I can actually plot 2 circles for the price of one with |
The matrix of the sampled values. But you can also pass a matrix as an array of arrays of numbers if you want to.
Yes, take a look at the test images here: https://github.com/johannes-wolf/cetz/pull/271/files#diff-c42484ff5212737b6f585e90895e76fde4994035aa35f36f1ebb8e898c2d8ec1 I want to add the option to set a gradient for z-values in the future, so it can interpolate between colors for the different z-levels. |
I was referring to the count of |
So far it looks very close, but the redundant contour lines (that shouldn't exist) are still there: code#import "@local/cetz:0.1.2"
#set page(width: 16.13cm, height: 14.03cm, margin: 0pt)
#set box(inset: -3.9mm)
#set text(font: "Fira Sans", size: 16pt, fill: rgb(0, 0, 0, 70%))
#cetz.canvas({
import cetz.draw: *
import cetz.plot
let get-style(color) = {
let (r, g, b, ..) = color.to-rgba();
let stroke-color = rgb(r, g, b, 80%)
let fill-color = rgb(r, g, b, 50%)
(line: (stroke: stroke-color + 2.5pt, fill: fill-color.lighten(25%)))
}
set-style(axes: (
tick: (stroke: none),
stroke: none,
grid: (stroke: rgb("#88888888") + 0.6pt),
))
let x-min = -8
let x-max = 15
let y-min = -10
let y-max = 10
let width = x-max - x-min
let height = y-max - y-min
let factor = 0.7
width = width * factor
height = height * factor
plot.plot(
size: (width, height),
axis-style: "school-book",
x-label: none,
y-label: none,
x-tick-step: none,
y-tick-step: none,
x-ticks: range(-1, 3).map(it => it * 5),
y-ticks: range(-1, 2).map(it => it * 5),
x-minor-tick-step: 1,
y-minor-tick-step: 1,
x-grid: "both",
y-grid: "both",
x-format: value => [#value],
y-format: value => [#value],
{
// hyperbola
plot.add-contour(
(x, y) => (x - 1) * (y - 1),
z: 1,
x-domain: (x-min, x-max),
y-domain: (y-min, y-max),
x-samples: 50,
y-samples: 50,
fill: true,
style: get-style(rgb("#C8423F")), // red
)
// circle
plot.add-contour(
(x, y) => (calc.pow((x - 1), 2) + calc.pow((y - 1), 2)),
op: "<=",
// z: 9,
z: 9,
x-domain: (x-min, x-max),
y-domain: (y-min, y-max),
x-samples: 50,
y-samples: 50,
fill: true,
style: get-style(rgb("#2D6FB4")), // blue
)
// x >= 0
plot.add-contour(
(x, y) => x,
z: 0,
x-domain: (0, x-max),
y-domain: (y-min, y-max),
fill: true,
style: get-style(rgb("#378A46")), // green
)
// y >= 0
plot.add-contour(
(x, y) => y,
z: 0,
x-domain: (x-min, x-max),
y-domain: (0, y-max),
fill: true,
style: get-style(rgb("#6042A5")), // purple
)
// x (y = x)
plot.add-contour(
(x, y) => x - y,
op: (z, r) => calc.abs(r - z) < 1,
z: 0,
x-domain: (x-min, x-max),
y-domain: (y-min, y-max),
fill: true,
style: get-style(rgb("#000000")), // black
)
},
)
}) |
The way to get rid of them is fixing your x-min/x-max y-min/y-max to a value so that the domain is outsides that window. |
This is also known as hacking. The problem is that the API is supposed to be created for the user to use, so it should be straightforward. But a lot of time the API is either not flexible enough yet or for eternity. I've encountered this kind of plotting hacking where the output isn't really what it should have been, so you are trying hard to get it work just like you wanted by making a lot of hacky stuff, like instead of defining range for an axis from -15 to +15, you define it as [-14.6; +14.6], so that the This problem has the same idea. I will try changing the domain values, but you already said about "oversampling", if I'm understanding correctly, it will do the same thing as what you have just said (about domain). So try implementing such a thing, and we will then test it and see if it's good or not. |
I checked against matplotlib output and I will try to replicate the edge behavior (soonish). |
I'm really happy that we went from 0 to 60 (percent) real quick (#215). But it still has a lot of rough edges. So I figured, that I will list them here and then probably convert it to a tracking issue (of dependency sub-issues).
Notes:
(x, y) =>
;Issues that must be fixed (high priority):
No issues with conditionals (x >= 0
,y >= 0
,x == y
).Note: boolean usage is discouraged (see this and this).
x
is now showing asx = 1
(offsets by+1
to the right) andy
is now showing asy = 1
(offsets by+1
to the top).Note: if
z: 0
then it works correctly (default isz: 1
, at least at the time of testing).Correctly fills if9 - x² + y²
(which is9 = x² + y²
or a positive float).Note: use
x² + y²
andz: 9
(for the right-hand side) instead.Correctly fills ifx² + y² <= 9
.Note: boolean usage is discouraged (see this and this).
Doesn't work ifx² + y² - 9
(which isx² + y² = 9
or a negative float).Note: use
x² + y²
andz: 9
(for the right-hand side) instead.Circle contour is wrong ifbool
is returnedbool
(x² + y² <= 9
).Note: boolean usage is discouraged (see this and this).
Hyperbola contour is wrong ifbool
is returnedbool
(x ⋅ y >= 1
).Note: boolean usage is discouraged (see this and this).
Hyperbola contour is inverted if1 - x ⋅ y
(negative float), but it's correct ifx ⋅ y - 1
(positive float).Note: use
x ⋅ y
andz: 1
(for the right-hand side) instead.Issues that should be fixed (medium priority):
x
can only fill to the right andy
can only fill to the top (same withx - y
).This probably can only be fixed (ideally) with
x <= 0
/y <= 0
, i.e., changing return value fromfloat
tobool
.Note:
logical/boolean operators (conditionals) are already supported andwith the right value forx-domain
/y-domain
the fill direction is correct. Use a newop: "<="
(or">="
) option and leavex
/y
as is.x
andy
is drawn as a C and U (sometime even O) shapes instead of straight lines (with limited length).Features that are nice to have (low priority):
Note: use
style: (line: (stroke: rgb("#378A467F"), fill: rgb("#378A467F").lighten(25%)))
inside theadd-contour
(seergb()
).This issue was created after testing against the
master#0a92100
. The base code through which all of the above problems/missing features were discovered:base.typ
Plot screenshot
Reference screenshots from Desmos
New issues after testing against
redesign-internals#742d560
.When using
op: "=="
to plot straight lines (x - y
+z: 0
) nothing is plotted (the line isn't present).When using
x² + y²
+op: ">="
+z: 9
+fill: true
it fills outside the circle and inside of it (although it works as expected with hyperbola). Fill only outside of a circle contour cetz-plot#6With
style: (line: (stroke: color + 1em, fill: color))
(foradd-contour()
) instead of adding thickness to the contour line (border) it also increases area of the fill, including outside the borders:screenshot
code
In the above scenario, the thickened contour stroke is added to the right of the RHS, instead of the center.
Note: this is a blending effect, not a bug, see note of the task above.
The text was updated successfully, but these errors were encountered: