Skip to content

Conversation

scrouthtv
Copy link

Hi!

Thanks for all your work on the cetz and related project, I love it!

For my past work, I have been experimenting with linear regression plotting.
Herein, I want to propose to you a proof of concept:

image

The x marks plot a simulated measurement error (see utility function in my example).
The dashed trend line is automatically fitted by cetz-plot for least square error.

#import "@preview/cetz:0.3.4"
#import "@local/cetz-plot:0.1.2": plot

// utility function to simulate a measurement error
// f [function float => float]
// samples [array of float]
// error [float]: default 1.0
// seed [int]: default 42
#let measor(f, samples, error: 1.0, seed: 42) = {
	import "@preview/suiji:0.3.0": gen-rng-f, uniform-f

	let (_, errors) = uniform-f(gen-rng-f(seed), high: error, size: samples.len())
	let out = (0,) * samples.len()

	for i in range(samples.len()) {
		let x = samples.at(i)
		let y = f(x) + errors.at(i) - error / 2.0
		out.at(i) = (x, y)
	}

	return out
}

// utility function to range on float values
#let rangef(start, end, step: 1.0) = range(int(start / step), int(end / step) + 1).map(x => x * step)

// =================================================================================

// For our examples, we have the "true" model and a measurement error.
// To these mesaurements, a model is fitted:
#let examples = (
	(f: x => 8 * x, error: 10, proto: "linear", color: blue),
	(f: x => 2 * calc.pow(x * 2, 2) - 50 * x - 2, error: 50, proto: x => (1, x, calc.pow(x, 2)), color: red),
	(f: x => 0.2 * calc.exp(x), error: 1000, proto: x => (1, x, calc.exp(x)), color: green),
	(f: x => 0, error: 10, proto: x => (1, calc.sin(x), calc.cos(x)), color: fuchsia)
)

#let samples = rangef(1, 10.1, step: 0.5)

#for ex in examples {
figure(cetz.canvas({
plot.plot(size: (15, 4), {
	// Simulate measurement samples:
	let data = measor(ex.f, samples, error: ex.error)
	// Plot measurement samples:
	plot.add(data, mark: "x", style: (stroke: none), mark-style: (stroke: ex.color))
	// Fit and plot trend line:
	plot.add-trend(data, ex.proto, style: (stroke: (paint: ex.color, dash: "dashed")))
})
}))
}

(The fourth example is simply random data where I attempt to fit a sine function.)

With this PR you find the code that I used for this proof of concept.

The main implementation challenge is that this needs some matrix operations (transpose, inverse, multiply). I don't think they are builtin to typst and neither is there a package for these.
Therefore, I have added a very basic implementation using the Gaussian algorithm.
In a real application, I think this would rather be done using wasm, as this implementation already exceeds 1 sec of compilation time on my machine...

With these matrix operations available, the rest is pretty straight-forward.

Let me know whether you would be interested in this feature for cetz-plot and I will continue working on this code, write docs and tests etc.!

@johannes-wolf johannes-wolf self-requested a review April 14, 2025 14:34
@johannes-wolf
Copy link
Member

Let me know whether you would be interested in this feature for cetz-plot and I will continue working on this code, write docs and tests etc.!

Sorry for answering that late, did not notice the PR until now.

Yes, I am interested in that feature!

I started a huge refactoring for cetz-plot that fixes some long-standing problems and allows features like shared axes etc. Problem is, that I do not really have time to complete that PR(s) and/or work on cetz-plot in general. And since there is an alternative, lilaq, motivation is pretty low. So I do not really know… merging features into the current state? Waiting for the refactoring?

@scrouthtv
Copy link
Author

I can't tell you either. If you were to finish your refactoring, I think it would be possible for me to update my feature to your refactored code.

@ramkumarkb
Copy link

@johannes-wolf - I generally think that ideas behind cetz-plot are very nice (i.e. offering low level APIs so that one can compose them for create complex plots).
Noted your point on the motivation levels - but would it be possible to create good-first-issues on the refactoring journey - so that the community can help. Or are there any ideas on how the 2 project - lilaq and cetz-plot can collaborate ?
Thank you again for the great work thus far 🙏 !

@johannes-wolf
Copy link
Member

I think being more low-level – more of a sandbox kind of lib to build plots and diagrams would be a better fit for cetz-plot; a library that features diagrams and tools to build them: axes with tick placement, scaling etc. but less "smart". I have to think about it, and I think it would be fine to just rewrite the plotting part from scratch.

@ramkumarkb
Copy link

If you are looking for some APIs for insipiration, I would recommend the APIs from Apache eCharts - for example the APIs for xAxis - https://echarts.apache.org/en/option.html#xAxis - it has "knobs" for almost eveything (axisLine, axisTick, minorTick, axisLabel, splitLine etc)

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

Successfully merging this pull request may close these issues.

3 participants