Skip to content

plotting js functions with dataviewjs (almost pull request) #26

Description

@FeelUsM

now I can plot next way:

```dataviewjs
// Load .obsidian/scripts/myplot.js
if (!window.__myPlotLoaded) {
	const code = await app.vault.adapter.read(
		app.vault.configDir + '/scripts/myplot.js'
	);
	eval(code);
}

function gamma(x) {
	if (x < 0.5) {
		return Math.PI / (Math.sin(Math.PI * x) * gamma(1 - x));
	}
	x -= 1;
	const g = 7;
	const c = [
		0.99999999999980993, 676.5203681218851, -1259.1392167224028,
		771.32342877765313, -176.61502916214059, 12.507343278686905,
		-0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7
	];
	let sum = c[0];
	for (let i = 1; i < c.length; i++) sum += c[i] / (x + i);
	const t = x + g + 0.5;
	return Math.sqrt(2 * Math.PI) * Math.pow(t, x + 0.5) * Math.exp(-t) * sum;
};

const data = [
	{
		fn: gamma,
		name: 'Г(x)', line: { color: 'blue' }
	},
	{
		fn: Math.sin,
		name: 'sin(x)', line: { color: 'red' }
	}
];
const layout = {
	title: 'Gamma and sinus',
	showlegend: true,
	xaxis: { title: 'x', range: [0.1, 6]}, // here range is necessary
	yaxis: { title: 'y', range: [-5, 20]} // here range is optional
};

window.myPlotly(this.container, data, layout);
```

I add .obsidian/scripts/myplot.js:

// .obsidian/scripts/myplot.js

const STEPS = 600;

function generatePoints(fn, xMin, xMax, steps = STEPS) {
	const xs = [];
	const ys = [];
	const step = (xMax - xMin) / steps;
	for (let x = xMin; x <= xMax; x += step) {
		const y = fn(x);
		if (isFinite(y) && Math.abs(y) < 1e10) {
			xs.push(x);
			ys.push(y);
		} else {
			xs.push(x);
			ys.push(null);
		}
	}
	return { xs, ys };
}

function prepareTracks(data, layout) {
	const plotlyData = [];
	const layoutRange = layout?.xaxis?.range;

	for (let i = 0; i < data.length; i++) {
		const { fn, ...rest } = data[i];

		if (typeof fn !== 'function') {
			throw new Error(`data[${i}]: fn не задана или не является функцией`);
		}

		const xMin = layoutRange?.[0];
		const xMax = layoutRange?.[1];

		if (typeof xMin !== 'number' || typeof xMax !== 'number') {
			throw new Error(`data[${i}]: задайте xMin/xMax в треке или xaxis.range в layout`);
		}
		if (xMin >= xMax) {
			throw new Error(`data[${i}]: xMin должен быть меньше xMax`);
		}

		const { xs, ys } = generatePoints(fn, xMin, xMax);
		plotlyData.push({ ...rest, x: xs, y: ys });
	}

	return plotlyData;
}

window.myPlotly = function(container, data, layout) {
	const Plotly = app.plugins.plugins['obsidian-plotly']?.Plotly;
	if (!Plotly) {
		throw new Error('Плагин obsidian-plotly не найден или не загружен');
	}

	let plotlyData;
	try {
		plotlyData = prepareTracks(data, layout);
	} catch (e) {
		container.createEl('pre', { text: `myPlotly error: ${e.message}` });
		return;
	}

	window.renderPlotly(container, plotlyData, layout);

	const plotDiv = container.querySelector('.js-plotly-plot') ?? container;

	plotDiv.on('plotly_relayout', (eventData) => {
		const newXMin = eventData['xaxis.range[0]'];
		const newXMax = eventData['xaxis.range[1]'];

		if (newXMin === undefined || newXMax === undefined) return;

		const indices = data.map((_, i) => i);
		const newXs = [];
		const newYs = [];

		for (const { fn } of data) {
			const { xs, ys } = generatePoints(fn, newXMin, newXMax);
			newXs.push(xs);
			newYs.push(ys);
		}

		Plotly.restyle(plotDiv, { x: newXs, y: newYs }, indices);
	});
};

// register
window.__myPlotLoaded = true;

It will be useful if you integrate it in Plotly plugin

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions