Added basic sheet source

This commit is contained in:
Jomar 2026-04-27 12:53:56 -07:00
parent 9f24ae18af
commit b9920980ad
3 changed files with 142 additions and 27 deletions

View file

@ -73,7 +73,7 @@ export function buildDatasets(data: PlotData[], parsedMd: LineProperties[]) {
});
}
// Transform data is meant for non-scatter/line charts. I.e. pie, radar, etc
function transformData(data: PlotData[], chartType: ChartType){
switch(chartType){
case "pie":
@ -110,10 +110,3 @@ function transformData(data: PlotData[], chartType: ChartType){
}
}
const getRandomRGB = (): string => {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
return `rgb(${r}, ${g}, ${b})`;
};

View file

@ -13,6 +13,7 @@ import { string } from "mathjs";
import { isArray } from "chart.js/dist/helpers/helpers.core";
import {validObjProperties,validRoots} from "./plotProperties"
import { sign } from "mathjs";
import { column } from "mathjs";
const math = create(all);
math.import({ // Created an alias so the user can write the more "normal" ln(x) and Mathjs wont hate me.
@ -66,7 +67,7 @@ export type parsedText = {
equations: Equation[],
nestedEquations: NestedEquations[],
manualData: PlotData[]
tableData: PlotData[]
tableData: PlotData[] | any
}
@ -92,7 +93,7 @@ export async function handleMarkdown(markdown: string[], defaultProperties: Char
const equations = getEquations(handleEquations(lines.filter(s => equationRegex.test(s))));
const nestedEquations = getEquations(handleNestedEquations(lines.filter(s => nestedRegex.test(s))));
const manualData = getData(handleManualData(lines.filter(s => s.includes("::") || (!s.includes("=") && !propertyPattern.test(s)))));
const tableData = await handleTableData(lines.filter(s => s.includes("source(") && s.includes("::")));
const tableData = await handleSourceData(lines.filter(s => s.includes("source(") && s.includes("::")));
const parsedText: parsedText = {
lineProperties: lineProperties,
chartOptions: chartOptions,
@ -116,7 +117,7 @@ export async function handleMarkdown(markdown: string[], defaultProperties: Char
equations: [],
nestedEquations: [],
manualData: getData(handleManualData(lines.filter(s => s.includes("::") || (!s.includes("=") && !propertyPattern.test(s))))),
tableData: await handleTableData(lines.filter(s => s.includes("source(") && s.includes("::"))),
tableData: await handleSourceData(lines.filter(s => s.includes("source(") && s.includes("::"))),
}
}
@ -133,7 +134,7 @@ export async function handleMarkdown(markdown: string[], defaultProperties: Char
}
}
// For data entries in the form of data(name) :: [x,y]
function handleManualData(lines: string[]) {
const datasets: RawExpr[] = [];
let current: RawExpr | undefined = undefined;
@ -492,6 +493,7 @@ function isNumberString(val: string): boolean {
return val.trim() !== "" && !isNaN(Number(val));
}
// For when the nested equation is of the form D : [start, end , step]. We make a plot for each step
function handleNestedArray(mainExpr: string ,variable: string, localRange: [number, number, number], v: number[], name: string, baseScope: Record<string, string | number>, legendName?: string): PlotData[] {
const expr = math.compile(mainExpr);
const results: PlotData[] = [];
@ -578,14 +580,20 @@ function handleDiscontinuities(mDataPoints: Data[], localRange: [number, number,
return isDiscontinuity;
}
export async function handleTableData(lines: string[]) {
// Handles source :: types
export async function handleSourceData(lines: string[]) {
const app = getApp();
const results = [];
for (let line of lines) {
// Syntax is source(dataLabel) :: tableSelector[col1,col2] is table from path
// For cases where the source is a spreadsheet in the vault. The syntax is
// source(name) :: [col1, col2] is sheet from path
let [signature, expr] = line.split("::"); // [source(name), tableSelector[] is table from "path"]
if (!signature || !expr) continue;
if (!line.includes("is")) {
customNotice(`Missing syntax key: is on source`,"notice-error",5000);
continue;
@ -595,13 +603,17 @@ export async function handleTableData(lines: string[]) {
}
const info = expr.split(/ is | from /); // [tableSelector, table, path]
const name = signature.replace("source","").replace("(","").replace(")","").trim();
// Internally I used the signature as the name for every dataset. So here I grab the name within parentheses
const name = signature.replace("source","").replace("(","").replace(")","").trim();
signature = name;
if (!info[0]) {
customNotice(`Cannot find TableName for ${signature}`,'notice-error',5000);
continue;
}
const sourceInfo = info[0];
// Here we check what type of source it is. table, sheet, csv, query, etc
if (info[1] === "table") {
const path = info[2]?.trim() + ".md";
@ -613,13 +625,34 @@ export async function handleTableData(lines: string[]) {
}
}
}
else if (info[1] === "sheet") {
if (!info[2]?.endsWith(".sheets")) {
const path = info[2]?.trim() + ".sheets";
const file = app.vault.getAbstractFileByPath(path);
if (file instanceof TFile) {
const text = await app.vault.read(file);
try {
const data = JSON.parse(text);
results.push(extractSheet(data, signature, sourceInfo));
} catch {
customNotice("Could not find source data. Check sheet.", "notice-warning");
}
}
}
}
}
return results;
}
// This function parses the Markdown table data when the table type is used with source ::
function extractTable(markdown: string, signature: string, sourceInfo: string) {
const lines = markdown.split("\n");
const data: Data[] = [];
@ -725,10 +758,98 @@ function extractTable(markdown: string, signature: string, sourceInfo: string) {
}}
return { signature: signature, data: data };
}
function extractSheet(sheetData: any, signature: string, sourceInfo: string) {
// SourceInfo can be three things
// 1. [col1 , col2] where each col is a letter A,B, etc or a custom Name. In this case we search the whole column until no more blanks
// 2. [col1(row1) , col2(row1)] starting row. Same as above but start at a custom row
// 3. [col1(row1:row2), col2(row1:row2)] start and end specified
const data: Data[] = []
if (!sourceInfo.trim().startsWith("[") || !sourceInfo.trim().endsWith("]")) {
customNotice("Missing bracket for sheet source info.","notice-error")
return;
}
// Split the info so that its an array with [col1, col2]
const info = sourceInfo.replace("[","").replace("]","").split(",") ;
const col1Info = info[0]?.trim() ?? "A"; // Defaults to A
const col2Info = info[1]?.trim() ?? "B"; // Defaults to B
let col1: string = "0";
let col2: string = "1";
let col1WRows: string | string[];
let col2WRows: string | string[];
let sFlag = 0;
if (!col1Info?.includes("(")) {
// Scenario 1
col1 = col1Info // col1 identifier becomes whatever was written
if (/^[A-Za-z]$/.test(col1)) {
const num = col1.toUpperCase().charCodeAt(0) - 65; // Take the column name A-Z and make it a number
col1 = String(num);
} else {
// col1 is a custom name (hopefully). So I need to get the column number associated with that name
const num = Number(Object.keys(sheetData.columns).find(k => sheetData.columns[k] === col1));
col1 = String(num);
}
sFlag = 1;
} else if (col1Info.includes("(") && col1Info.includes(")")){
// Scenarios 2 and 3
// Here the key needs to be row1:col1 -> row2:col1
col1WRows = col1Info.replace(")","").split("(") // Becomes [col1 , row1:]
}
if (!col2Info?.includes("(")) {
// Scenario 1
col2 = col2Info // col2 identifier becomes whatever was written
if (/^[A-Za-z]$/.test(col2)) {
const num = col2.toUpperCase().charCodeAt(0) - 65; // Take the column name A-Z and make it a number
col2 = String(num);
} else {
// col2 is a custom name (hopefully). So I need to get the column number associated with that name
const num = Number(Object.keys(sheetData.columns).find(k => sheetData.columns[k] === col2));
col2 = String(num);
}
} else if (col2Info.includes("(") && col2Info.includes(")")){
// Scenarios 2 and 3
// Here the key needs to be row1:col1 -> row2:col1
col2WRows = col2Info.replace(")","").split("(") // Becomes [col1 , row1:]
}
switch (sFlag) {
case 1: {
// Scenario 1 get data where col is just a column name not rows specified
for (let key in sheetData.values) {
const [row, col] = key.split(":");
if (col === col1) {
const yKey = `${row}:${col2}`;
const xV = sheetData.values[key];
const yV = sheetData.values[yKey];
if (yV !== undefined) {
data.push({x: isNaN(Number(xV)) ? xV : Number(xV), y: isNaN(Number(yV)) ? yV : Number(yV)});
}
}
}
return {signature: signature, data: data};
}
default:
return {signature: signature, data: data}; // data will be empty here
}
}
// This was an attempt to autosuggest possible properties before I added the autocomplete feature. It is still useful but less so.
export function findPossibleProperty(key: string, validProps: string[], flag: string, validRoots?: string[], ) {
switch (flag) {

View file

@ -1,5 +1,5 @@
import {handleMarkdown, evaluateExpressions, PlotData, parsedText, handleTableData, GlobalProperties, handleGlobalOptions} from "../helpers/parser"
import {handleMarkdown, evaluateExpressions, PlotData, parsedText, handleSourceData, GlobalProperties, handleGlobalOptions} from "../helpers/parser"
import { createPlot, buildDatasets} from "../helpers/graphs";
import {Menu, Notice, Plugin, MarkdownView} from "obsidian";
import { apply } from "mathjs";
@ -53,7 +53,7 @@ export default class PlotPlugin extends Plugin {
const refreshTableData = async () => {
const sourceLines = newMarkdown.filter(s => s.includes("source(") && s.includes("::"));
cachedParsedText.tableData = await handleTableData(sourceLines);
cachedParsedText.tableData = await handleSourceData(sourceLines);
};
const renderCurrentChart = async () => {
@ -112,16 +112,16 @@ export default class PlotPlugin extends Plugin {
}
async refreshOpenCharts() {
this.app.workspace.getLeavesOfType("markdown").forEach(async (leaf) => {
const view = leaf.view as MarkdownView;
view.previewMode?.rerender(true);
});
}
this.app.workspace.getLeavesOfType("markdown").forEach(async (leaf) => {
const view = leaf.view as MarkdownView;
view.previewMode?.rerender(true);
});
}
getChartTypes(markdown: string[]): ChartType {
const lines = markdown;
for (const line of lines) {
if (line.startsWith("type::")) {
if (line.startsWith("type::") || line.startsWith("Type::")) {
const path = line.split("::");
if ( path[1] === undefined) continue;
const type = path[1].trim() as ChartType
@ -215,7 +215,8 @@ export default class PlotPlugin extends Plugin {
})
chartInstance = createPlot(canvas,data,parsedText.lineProperties,parsedText.chartOptions, chartType); // Chart is created.
this.charts.add(chartInstance);
this.charts.add(chartInstance); // Adds chart to list of charts
// Right-Click menu for chart options
canvas.addEventListener("contextmenu", async (e) => {
e.preventDefault();
@ -243,7 +244,7 @@ export default class PlotPlugin extends Plugin {
}
return chartInstance;
};
// Export functions
async exportChartPNG(chartInstance: any, path = `${this.settings.saveImagesPath}/Chart_${Date.now()}.png`){
const base64 = chartInstance.toBase64Image("image/png",1);
const base64Data = base64.replace(/^data:image\/png;base64,/,"");