Plug-In: Append Generated Chart Image Apple Vision Pro

This plug-in uses the tabbed data copied to the clipboard, along with the QuickChart.io online charting service, to generate a basic bar chart image, and append it as an inline text attachment to the note of the selected project or task.

A basic bar chart with the value of four items tracked over time

IMPORTANT: It is expected that the copied tabbed data include BOTH row and column headers. Here are illustrations of acceptable tabbed data selected in Numbers (top) or in an OmniFocus note (below):

A Numbers spreadsheet with all rows and column headers selected Tabbed data selected in an OmniFocus note

Once the chart image has been generated by the online service, it will be appended to the end of the note of the selected OmniFocus project or task, as an inline text attachment.

Return to: OmniFocus Plug-In Collection

Append Generated Chart Image
   

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation-of.append-generated-chart-image", "version": "1.0", "description": "Uses the data on the clipboard with the QuickChart.io website to generate a chart image and append it to the note of the selected project or task.", "label": "Append Generated Chart", "shortLabel": "Append Chart", "paletteLabel": "Append Chart", "image": "chart.bar.xaxis.ascending" }*/ (() => { function checkLengths(rowData){ x = rowData[0].length for (i = 1; i < rowData.length; i++) { if(rowData[i].length !== x){return false} } return true } const action = new PlugIn.Action(async function(selection, sender){ try { // PROMPT USER alertTitle = "Append Bar Chart" alertMessage = "Using the online QuickChart service, plug-in will create and insert a basic bar chart using the spreadsheet currently on the clipboard." alert = new Alert(alertTitle, alertMessage) alert.addOption("Continue") alert.addOption("Visit QuickChart") buttonIndex = await alert.show() if (buttonIndex !== 0){ URL.fromString("https://quickchart.io").open() return "Cancel Operation" } // RETRIEVE CLIPBOARD TEXT clipboardStr = Pasteboard.general.string console.log("clipboardStr:\n", clipboardStr) // DIVIDE INTO ARRAYS OF ROW DATA clipboardData = clipboardStr.split('\n').map(line => line.split('\t')) console.log("clipboardData:\n", JSON.stringify(clipboardData)) // CHECK LENGTH OF EVERY ROW checkResult = checkLengths(clipboardData) if(!checkResult){ throw { name: "Data Integrity Issue", message: "Not all rows have the same number of columns." } } // FIRST ARRAY(ROW) IS COLUMN HEADERS columnHeaders = clipboardData.shift() // REMOVE 1st COLUMN HEADER columnHeaders.shift() console.log("columnHeaders:", columnHeaders) datasetsArray = new Array() for (indx in clipboardData){ aDataObj = clipboardData[indx] // ["Elephants","234.45","453.10","417.46","365.47"] aDataObjLabel = aDataObj.shift() aDatasetObj = new Object() aDatasetObj["label"] = aDataObjLabel aDatasetObj["data"] = aDataObj // {"label":"ROW A","data":[100, 200, 300, 400]} console.log("aDatasetObj", JSON.stringify(aDatasetObj)) datasetsArray.push(aDatasetObj) } // THE CHART DATA OBJECT dataObj = new Object() dataObj["labels"] = columnHeaders dataObj["datasets"] = datasetsArray chartItemsObj = new Object() chartItemsObj["type"] = "bar" chartItemsObj["data"] = dataObj console.log("chartItemsObj", JSON.stringify(chartItemsObj)) // RESULTING CHART DATA OBJECT /* { "type":"bar", "data":{ "labels":["Q1","Q2","Q3","Q4"], "datasets":[ {"label":"ROW A","data":[100,200,300,400]}, {"label":"ROW B","data":[100,200,300,400]}, ] } } */ // THE CHART OBJECT chartObj = new Object() chartObj["backgroundColor"] = "transparent" chartObj["format"] = "png" chartObj["chart"] = chartItemsObj // RESULTING CHART OBJECT /* { "backgroundColor":"transparent", "format":"png", "chart":{ "type":"bar", "data":{ "labels":["Q1","Q2","Q3","Q4"], "datasets":[ {"label":"ROW A","data":[100,200,300,400]}, {"label":"ROW B","data":[100,200,300,400]}, ] } } } */ // SELECTED PROJECT OR TASK item = selection.databaseObjects[0] noteObj = item.noteText nStyle = noteObj.style // CONSTRUCT FETCH REQUEST targetURLString = "https://quickchart.io/chart" request = URL.FetchRequest.fromString(targetURLString) request.headers = {"Content-Type":"application/json"} request.method = 'POST' request.bodyString = JSON.stringify(chartObj) // SEND REQUEST AND WAIT FOR RESULT response = await request.fetch() responseCode = response.statusCode if (responseCode >= 200 && responseCode < 300){ // INSERT IMAGE AS INLINE TEXT ATTACHMENT OBJECT wrapper = FileWrapper.withContents("ex-chart.png", response.bodyData) attachmentObj = Text.makeFileAttachment(wrapper, noteObj.style) if(noteObj.range.isEmpty){ noteObj.insert(noteObj.start, attachmentObj) } else { newLineObj = new Text('\n', noteObj.style) noteObj.append(newLineObj, noteObj.style) noteObj.append(attachmentObj, noteObj.style) } node = document.windows[0].content.selectedNodes[0] node.expandNote(false) } else { throw { name: "Server Response Code", message: `The server responded with code: ${responseCode}` } } } catch(err){ if(!err.causedByUserCancelling){ console.error(err.name, err.message) new Alert(err.name, err.message).show() } } }); action.validate = function(selection, sender){ singleItemIsSelected = ( selection.databaseObjects.length === 1 && selection.projects.length === 1 || selection.tasks.length === 1 ) return (Pasteboard.general.hasStrings && singleItemIsSelected) }; return action; })();