diff --git a/README.md b/README.md index 6c6b91d..718fab2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![GitHub release](https://img.shields.io/github/v/release/pyrochlore/obsidian-tracker) - +screenshot This is an [Obsidian](https://obsidian.md/) plugin that helps you collect data from notes and represent it comprehensively. @@ -55,7 +55,7 @@ Version 1.10.9 ## !!! Breaking Changes !!! -From version 1.9.0, template variables, e.g. '{{sum}}', are deprecated. Instead, Tracker provide operators (+, -, *, /, %) and functions (dataset(), sum(), maxStreak(), ......etc) to help us do data processing. For users having code blocks from previous version, please replace '{{sum}}' by '{{sum()}}' or '{{sum(1)}}' by '{{sum(dataset(1))}}'. More information about the new expressions could be found [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Expressions.md). +From version 1.9.0, template variables, e.g. '{{sum}}', are deprecated. Instead, Tracker provide operators (+, -, *, /, %) and functions (dataset(), sum(), maxStreak(), ......etc.) to help us do data processing. For users having code blocks from previous version, please replace '{{sum}}' by '{{sum()}}' or '{{sum(1)}}' by '{{sum(dataset(1))}}'. More information about the new expressions could be found [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Expressions.md). ## Usage @@ -68,7 +68,7 @@ For more use cases, please download and open the [examples](https://github.com/p ## More Details You May Want to Know - [Installation](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Installation.md): Install the plugin from Obsidian or install it manually -- [Concepts](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Concepts.md): Explain how this plugin works and what to setup +- [Concepts](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Concepts.md): Explain how this plugin works and what to set up - [Target Evaluation](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md) - [Input Parameters](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/InputParameters.md) - [Expressions](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Expressions.md) diff --git a/examples/TestMonthlyData.md b/examples/TestMonthlyData.md new file mode 100644 index 0000000..7a87a22 --- /dev/null +++ b/examples/TestMonthlyData.md @@ -0,0 +1,11 @@ +# Test Monthly Data + +``` tracker +searchType: frontmatter +searchTarget: pushup +dateFormat: YYYY-MMM +line: + fillGap: true + xAxisTickInterval: 1M + xAxisTickLabelFormat: YYYY-MMM +``` \ No newline at end of file diff --git a/examples/TestMultipleGraphs.md b/examples/TestMultipleGraphs.md new file mode 100644 index 0000000..5e27eba --- /dev/null +++ b/examples/TestMultipleGraphs.md @@ -0,0 +1,20 @@ +# Test Multiple Graphs + +## Line + SVG +svg from [SVG REPO](https://www.svgrepo.com/svg/124402/weight) with [license](https://www.svgrepo.com/page/licensing) +``` tracker +searchType: tag +searchTarget: weight +folder: diary +endDate: 2021-01-31 +line: +svg: + element: '' +``` + +## Bar + Summary + + + +## Pie + Bar +MTG Deck Statistics \ No newline at end of file diff --git a/examples/TestSVG.md b/examples/TestSVG.md new file mode 100644 index 0000000..97e1c77 --- /dev/null +++ b/examples/TestSVG.md @@ -0,0 +1,13 @@ +# Test SVG + +svg from [SVG REPO](https://www.svgrepo.com/svg/343407/awkward-emoji-emoticon-happy-simle) with [license](https://www.svgrepo.com/page/licensing) + +``` tracker +searchType: tag +searchTarget: weight +folder: diary +endDate: 2021-01-31 +line: +svg: + element: '' +``` \ No newline at end of file diff --git a/examples/TestSummary.md b/examples/TestSummary.md index fb3d158..cd8ce13 100644 --- a/examples/TestSummary.md +++ b/examples/TestSummary.md @@ -1,6 +1,7 @@ # Test Summary ## Multiple Lines +Accept multitple lines by using a line break '\n' in between ``` tracker searchType: tag @@ -17,6 +18,20 @@ searchType: text searchTarget: ⭐ folder: diary summary: + template: "I have {{sum()}} stars in total." + style: "font-size:32px;fill:none;stroke-width:0.5;stroke:yellow" +``` + +## Multiple Summaries + +``` tracker +searchType: text +searchTarget: ⭐ +folder: diary +summary: + template: "I have {{sum()}} stars in total." + style: "font-size:20px;color:yellow;margin-left: 50px;margin-top:00px;" +summary1: template: "I have {{sum()}} stars in total." style: "font-size:20px;color:yellow;margin-left: 50px;margin-top:00px;" ``` diff --git a/examples/empty/.keep b/examples/empty/.keep new file mode 100644 index 0000000..e69de29 diff --git a/examples/monthly/2021-Apr.md b/examples/monthly/2021-Apr.md new file mode 100644 index 0000000..b211a84 --- /dev/null +++ b/examples/monthly/2021-Apr.md @@ -0,0 +1,4 @@ +--- +pushup: 1050 + +--- \ No newline at end of file diff --git a/examples/monthly/2021-Feb.md b/examples/monthly/2021-Feb.md new file mode 100644 index 0000000..dc140d8 --- /dev/null +++ b/examples/monthly/2021-Feb.md @@ -0,0 +1,4 @@ +--- +pushup: 900 + +--- \ No newline at end of file diff --git a/examples/monthly/2021-Jan.md b/examples/monthly/2021-Jan.md new file mode 100644 index 0000000..15a45a7 --- /dev/null +++ b/examples/monthly/2021-Jan.md @@ -0,0 +1,4 @@ +--- +pushup: 800 + +--- \ No newline at end of file diff --git a/examples/monthly/2021-Mar.md b/examples/monthly/2021-Mar.md new file mode 100644 index 0000000..a3de735 --- /dev/null +++ b/examples/monthly/2021-Mar.md @@ -0,0 +1,4 @@ +--- +pushup: 1000 + +--- \ No newline at end of file diff --git a/examples/monthly/2021-May.md b/examples/monthly/2021-May.md new file mode 100644 index 0000000..78d95e7 --- /dev/null +++ b/examples/monthly/2021-May.md @@ -0,0 +1,4 @@ +--- +pushup: 1080 + +--- \ No newline at end of file diff --git a/src/bullet.ts b/src/bullet.ts index 0494dcd..cc70845 100644 --- a/src/bullet.ts +++ b/src/bullet.ts @@ -15,7 +15,7 @@ import * as d3 from "d3"; import * as expr from "./expr"; function createAreas( - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, bulletInfo: BulletInfo ): ChartElements { @@ -24,8 +24,7 @@ function createAreas( if (!renderInfo || !bulletInfo) return; - let svg = d3 - .select(canvas) + let svg = svgCanvas .append("svg") .attr("id", "svg") .attr( @@ -73,11 +72,11 @@ function createAreas( } function setChartScale( - _canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo ) { - let canvas = d3.select(_canvas); + let canvas = svgCanvas; let svg = chartElements.svg; let svgWidth = parseFloat(svg.attr("width")); let svgHeight = parseFloat(svg.attr("height")); @@ -531,7 +530,7 @@ function renderMark( // Bullet graph https://en.wikipedia.org/wiki/Bullet_graph export function renderBullet( - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, bulletInfo: BulletInfo ) { @@ -549,7 +548,7 @@ export function renderBullet( renderInfo.dataAreaSize = { width: 24, height: 250 }; } - let chartElements = createAreas(canvas, renderInfo, bulletInfo); + let chartElements = createAreas(svgCanvas, renderInfo, bulletInfo); let retRenderAxis = renderAxis( chartElements, @@ -577,5 +576,5 @@ export function renderBullet( renderMark(chartElements, renderInfo, bulletInfo, dataset); - setChartScale(canvas, chartElements, renderInfo); + setChartScale(svgCanvas, chartElements, renderInfo); } diff --git a/src/collecting.ts b/src/collecting.ts index 1f0325a..309fe91 100644 --- a/src/collecting.ts +++ b/src/collecting.ts @@ -333,12 +333,12 @@ function extractDataUsingRegexWithMultipleValues( ): boolean { // console.log("extractDataUsingRegexWithMultipleValues"); + let extracted = false; let regex = new RegExp(strRegex, "gm"); let match; - let measure = 0.0; - let extracted = false; while ((match = regex.exec(text))) { // console.log(match); + let value = null; if (!renderInfo.ignoreAttachedValue[query.getId()]) { if ( typeof match.groups !== "undefined" && @@ -360,18 +360,14 @@ function extractDataUsingRegexWithMultipleValues( ); if (retParse.value !== null) { if (retParse.type === ValueType.Time) { - measure = retParse.value; - extracted = true; + value = retParse.value; query.valueType = ValueType.Time; - query.addNumTargets(); } else { if ( !renderInfo.ignoreZeroValue[query.getId()] || retParse.value !== 0 ) { - measure += retParse.value; - extracted = true; - query.addNumTargets(); + value = retParse.value; } } } @@ -388,40 +384,37 @@ function extractDataUsingRegexWithMultipleValues( //console.log(retParse); if (retParse.value !== null) { if (retParse.type === ValueType.Time) { - measure = retParse.value; - extracted = true; + value = retParse.value; query.valueType = ValueType.Time; - query.addNumTargets(); } else { - measure += retParse.value; - extracted = true; - query.addNumTargets(); + value = retParse.value; } } } } else { // no named groups, count occurrencies // console.log("count occurrencies"); - measure += renderInfo.constValue[query.getId()]; - extracted = true; - query.addNumTargets(); + value = renderInfo.constValue[query.getId()]; } } else { // force to count occurrencies // console.log("forced count occurrencies"); - measure += renderInfo.constValue[query.getId()]; - extracted = true; - query.addNumTargets(); + value = renderInfo.constValue[query.getId()]; } - } - if (extracted) { - let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]); - addToDataMap(dataMap, xValue, query, measure); - return true; + if (value !== null) { + let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]); + addToDataMap(dataMap, xValue, query, value); + query.addNumTargets(); + extracted = true; + + if (renderInfo.dataMergeMethod[query.getId()] === "first") { + break; + } + } } - return false; + return extracted; } // No value, count occurrences only @@ -439,10 +432,10 @@ export function collectDataFromFrontmatterTag( let frontMatter = fileCache.frontmatter; let frontMatterTags: string[] = []; + let tagExist = false; if (frontMatter && frontMatter.tags) { // console.log(frontMatter.tags); - let tagMeasure = 0.0; - let tagExist = false; + if (Array.isArray(frontMatter.tags)) { frontMatterTags = frontMatterTags.concat(frontMatter.tags); } else if (typeof frontMatter.tags === "string") { @@ -458,34 +451,33 @@ export function collectDataFromFrontmatterTag( // console.log(query.getTarget()); for (let tag of frontMatterTags) { + let value = null; + if (tag === query.getTarget()) { // simple tag - tagMeasure = tagMeasure + renderInfo.constValue[query.getId()]; - tagExist = true; - query.addNumTargets(); + value = renderInfo.constValue[query.getId()]; } else if (tag.startsWith(query.getTarget() + "/")) { // nested tag - tagMeasure = tagMeasure + renderInfo.constValue[query.getId()]; - tagExist = true; - query.addNumTargets(); - } else { - continue; + value = renderInfo.constValue[query.getId()]; } // valued-tag in frontmatter is not supported // because the "tag:value" in frontmatter will be consider as a new tag for each existing value - let value = null; - if (tagExist) { - value = tagMeasure; + if (value !== null) { + let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]); + addToDataMap(dataMap, xValue, query, value); + query.addNumTargets(); + tagExist = true; + + if (renderInfo.dataMergeMethod[query.getId()] === "first") { + break; + } } - let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]); - addToDataMap(dataMap, xValue, query, value); - return true; } } - return false; + return tagExist; } // In form 'key: value', target used to identify 'frontmatter key' diff --git a/src/data.ts b/src/data.ts index 46fa250..2513e4f 100644 --- a/src/data.ts +++ b/src/data.ts @@ -25,6 +25,7 @@ export enum GraphType { Month, Heatmap, Bullet, + Svg, Unknown, } @@ -568,6 +569,7 @@ export class RenderInfo { endDate: Moment | null; datasetName: string[]; constValue: number[]; + dataMergeMethod: string[]; ignoreAttachedValue: boolean[]; ignoreZeroValue: boolean[]; accum: boolean[]; @@ -591,6 +593,7 @@ export class RenderInfo { month: MonthInfo[]; heatmap: HeatmapInfo[]; bullet: BulletInfo[]; + svg: SvgInfo[]; customDataset: CustomDatasetInfo[]; public datasets: Datasets | null; @@ -610,6 +613,7 @@ export class RenderInfo { this.endDate = null; this.datasetName = []; // untitled this.constValue = [1.0]; + this.dataMergeMethod = []; // first, last, sum, product, diff, average this.ignoreAttachedValue = []; // false this.ignoreZeroValue = []; // false this.accum = []; // false, accum values start from zero over days @@ -633,6 +637,7 @@ export class RenderInfo { this.month = []; this.heatmap = []; this.bullet = []; + this.svg = []; this.customDataset = []; this.datasets = null; @@ -964,6 +969,15 @@ export class BulletInfo implements IGraph { } } +export class SvgInfo implements IGraph { + element: string; + constructor() { + this.element = ""; + } + public GetGraphType() { + return GraphType.Svg; + } +} export class Size { width: number; height: number; diff --git a/src/heatmap.ts b/src/heatmap.ts index 601e618..4e239a3 100644 --- a/src/heatmap.ts +++ b/src/heatmap.ts @@ -25,12 +25,12 @@ interface DayInfo { function createAreas( chartElements: ChartElements, - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, heatmapInfo: HeatmapInfo ): ChartElements { // clean areas - d3.select(canvas).select("#svg").remove(); + svgCanvas.select("#svg").remove(); var props = Object.getOwnPropertyNames(chartElements); for (var i = 0; i < props.length; i++) { // d3.select(chartElements[props[i]]).remove(); @@ -39,8 +39,7 @@ function createAreas( // console.log(chartElements); // whole area for plotting, includes margins - let svg = d3 - .select(canvas) + let svg = svgCanvas .append("svg") .attr("id", "svg") .attr( @@ -88,7 +87,7 @@ function createAreas( } function renderHeatmapHeader( - canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo, heatmapInfo: HeatmapInfo, @@ -97,22 +96,21 @@ function renderHeatmapHeader( // console.log("renderMonthHeader") if (!renderInfo || !heatmapInfo) return; - } function renderHeatmapDays( - canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo, heatmapInfo: HeatmapInfo, - dataset: Dataset, + dataset: Dataset ) { // console.log("renderHeatmapDays"); if (!renderInfo || !heatmapInfo) return; let cellSize = 20; - let dotRadius = cellSize / 2.0 * 0.6; + let dotRadius = (cellSize / 2.0) * 0.6; // Get min and max, null values will be treated as zero here let yMin = d3.min(dataset.getValues()); @@ -128,9 +126,7 @@ function renderHeatmapDays( // Prepare data for graph let daysInHeatmapView: Array = []; const dataStartDate = dataset.getStartDate().clone(); - let startDate = dataStartDate - .clone() - .subtract(dataStartDate.day(), "days"); + let startDate = dataStartDate.clone().subtract(dataStartDate.day(), "days"); if (heatmapInfo.startWeekOn.toLowerCase() === "mon") { startDate = startDate.add(1, "days"); } @@ -208,10 +204,7 @@ function renderHeatmapDays( return scale(d.row); }) .style("fill", function (d: DayInfo) { - return d3.interpolateLab( - "white", - heatmapColor - )(d.scaledValue); + return d3.interpolateLab("white", heatmapColor)(d.scaledValue); }) .style("cursor", "default"); @@ -220,8 +213,7 @@ function renderHeatmapDays( let svgHeight = parseFloat(chartElements.svg.attr("height")); let graphAreaWidth = parseFloat(chartElements.graphArea.attr("width")); let graphAreaHeight = parseFloat(chartElements.graphArea.attr("height")); - let totalHeight = - (indRow + 2) * cellSize;// + parseFloat(chartElements.header.attr("height")); + let totalHeight = (indRow + 2) * cellSize; // + parseFloat(chartElements.header.attr("height")); let totalWidth = (indCol + 1) * cellSize; if (totalHeight > svgHeight) { helper.expandArea(chartElements.svg, 0, totalHeight - svgHeight); @@ -242,7 +234,7 @@ function renderHeatmapDays( } export function renderHeatmap( - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, heatmapInfo: HeatmapInfo ) { @@ -253,7 +245,12 @@ export function renderHeatmap( return "Under construction"; let chartElements: ChartElements = {}; - chartElements = createAreas(chartElements, canvas, renderInfo, heatmapInfo); + chartElements = createAreas( + chartElements, + svgCanvas, + renderInfo, + heatmapInfo + ); let today = window.moment(); let lastDataMonthDate = renderInfo.datasets.getDates().last(); @@ -262,7 +259,7 @@ export function renderHeatmap( let dataset = renderInfo.datasets.getDatasetById(datasetId); renderHeatmapHeader( - canvas, + svgCanvas, chartElements, renderInfo, heatmapInfo, @@ -270,7 +267,7 @@ export function renderHeatmap( ); renderHeatmapDays( - canvas, + svgCanvas, chartElements, renderInfo, heatmapInfo, diff --git a/src/main.ts b/src/main.ts index 31d9835..0311bb8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -25,6 +25,7 @@ import { } from "./settings"; import * as helper from "./helper"; import { Moment } from "moment"; +import * as d3 from "d3"; // import { getDailyNoteSettings } from "obsidian-daily-notes-interface"; declare global { @@ -709,27 +710,53 @@ export default class Tracker extends Plugin { }); if (queryValuePairs.length > 0) { // Merge values of the same day same query - let value = null; + let collectedValues = []; for ( let indPair = 0; indPair < queryValuePairs.length; indPair++ ) { - let collected = queryValuePairs[indPair].value; + let collectedValue = queryValuePairs[indPair].value; if ( - Number.isNumber(collected) && - !Number.isNaN(collected) + Number.isNumber(collectedValue) && + !Number.isNaN(collectedValue) ) { - if (value === null) { - value = collected; - } else { - value += collected; - } + collectedValues.push(collectedValue); } } - // console.log(hasValue); - // console.log(value); - if (value !== null) { + + if (collectedValues.length !== 0) { + let value = null; + let mergeMethod = + renderInfo.dataMergeMethod[query.getId()]; + switch (mergeMethod) { + case "sum": + value = d3.sum(collectedValues); + break; + case "first": + value = collectedValues[0]; + break; + case "last": + value = + collectedValues[ + collectedValues.length - 1 + ]; + break; + case "average": + let sum = d3.sum(collectedValues); + value = sum / collectedValues.length; + break; + case "diff": + value = + collectedValues[ + collectedValues.length - 1 + ] - collectedValues[0]; + break; + default: + // sum + value = d3.sum(collectedValues); + break; + } dataset.setValue(curDate, value); } } diff --git a/src/month.ts b/src/month.ts index d4eba3f..6fdd748 100644 --- a/src/month.ts +++ b/src/month.ts @@ -34,11 +34,11 @@ interface DayInfo { } function setChartScale( - _canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo ) { - let canvas = d3.select(_canvas); + let canvas = svgCanvas; let svg = chartElements.svg; let svgWidth = parseFloat(svg.attr("width")); let svgHeight = parseFloat(svg.attr("height")); @@ -112,12 +112,12 @@ function toNextDataset(renderInfo: RenderInfo, monthInfo: MonthInfo): boolean { function createAreas( chartElements: ChartElements, - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, monthInfo: MonthInfo ): ChartElements { // clean areas - d3.select(canvas).select("#svg").remove(); + svgCanvas.select("#svg").remove(); var props = Object.getOwnPropertyNames(chartElements); for (var i = 0; i < props.length; i++) { // d3.select(chartElements[props[i]]).remove(); @@ -126,8 +126,7 @@ function createAreas( // console.log(chartElements); // whole area for plotting, includes margins - let svg = d3 - .select(canvas) + let svg = svgCanvas .append("svg") .attr("id", "svg") .attr( @@ -191,7 +190,7 @@ function clearSelection(chartElements: ChartElements, monthInfo: MonthInfo) { } function renderMonthHeader( - canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo, monthInfo: MonthInfo, @@ -325,7 +324,7 @@ function renderMonthHeader( clearSelection(chartElements, monthInfo); refresh( - canvas, + svgCanvas, chartElements, renderInfo, monthInfo, @@ -379,7 +378,7 @@ function renderMonthHeader( monthInfo.selectedDate = ""; let prevMonthDate = curMonthDate.clone().add(-1, "month"); refresh( - canvas, + svgCanvas, chartElements, renderInfo, monthInfo, @@ -408,7 +407,7 @@ function renderMonthHeader( let nextMonthDate = curMonthDate.clone().add(1, "month"); refresh( - canvas, + svgCanvas, chartElements, renderInfo, monthInfo, @@ -436,7 +435,7 @@ function renderMonthHeader( clearSelection(chartElements, monthInfo); let todayDate = helper.getDateToday(renderInfo.dateFormat); - refresh(canvas, chartElements, renderInfo, monthInfo, todayDate); + refresh(svgCanvas, chartElements, renderInfo, monthInfo, todayDate); }) .style("cursor", "pointer"); @@ -507,7 +506,7 @@ function renderMonthHeader( } function renderMonthDays( - canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo, monthInfo: MonthInfo, @@ -746,7 +745,7 @@ function renderMonthDays( logToConsole = false; } } - // console.log(daysInMonthView); + console.log(daysInMonthView); // console.log(daysInMonthView.filter(function (d: DayInfo) { // return d.streakIn; // })); @@ -1085,7 +1084,7 @@ function renderMonthDays( } function refresh( - canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo, monthInfo: MonthInfo, @@ -1095,22 +1094,33 @@ function refresh( // console.log(renderInfo); if (!renderInfo || !renderMonth) return; - chartElements = createAreas(chartElements, canvas, renderInfo, monthInfo); + chartElements = createAreas( + chartElements, + svgCanvas, + renderInfo, + monthInfo + ); // render renderMonthHeader( - canvas, + svgCanvas, chartElements, renderInfo, monthInfo, curMonthDate ); - renderMonthDays(canvas, chartElements, renderInfo, monthInfo, curMonthDate); + renderMonthDays( + svgCanvas, + chartElements, + renderInfo, + monthInfo, + curMonthDate + ); } export function renderMonth( - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, monthInfo: MonthInfo ) { @@ -1136,7 +1146,12 @@ export function renderMonth( } let chartElements: ChartElements = {}; - chartElements = createAreas(chartElements, canvas, renderInfo, monthInfo); + chartElements = createAreas( + chartElements, + svgCanvas, + renderInfo, + monthInfo + ); let monthDate: Moment = null; if (monthInfo.initMonth) { @@ -1158,9 +1173,15 @@ export function renderMonth( } if (!monthDate) return; - renderMonthHeader(canvas, chartElements, renderInfo, monthInfo, monthDate); + renderMonthHeader( + svgCanvas, + chartElements, + renderInfo, + monthInfo, + monthDate + ); - renderMonthDays(canvas, chartElements, renderInfo, monthInfo, monthDate); + renderMonthDays(svgCanvas, chartElements, renderInfo, monthInfo, monthDate); - setChartScale(canvas, chartElements, renderInfo); + setChartScale(svgCanvas, chartElements, renderInfo); } diff --git a/src/parsing.ts b/src/parsing.ts index dc4c17a..e53d52c 100644 --- a/src/parsing.ts +++ b/src/parsing.ts @@ -13,6 +13,7 @@ import { MonthInfo, HeatmapInfo, BulletInfo, + SvgInfo, Dataset, CustomDatasetInfo, } from "./data"; @@ -1112,6 +1113,7 @@ export function getRenderInfoFromYaml( let yamlMonthKeys = []; let yamlHeatmapKeys = []; let yamlBulletKeys = []; + let yamlSvgKeys = []; for (let key of keysFoundInYAML) { if (/^line[0-9]*$/.test(key)) { yamlLineKeys.push(key); @@ -1133,6 +1135,10 @@ export function getRenderInfoFromYaml( yamlBulletKeys.push(key); additionalAllowedKeys.push(key); } + if (/^svg[0-9]*$/.test(key)) { + yamlSvgKeys.push(key); + additionalAllowedKeys.push(key); + } if (/^month[0-9]*$/.test(key)) { yamlMonthKeys.push(key); additionalAllowedKeys.push(key); @@ -1185,10 +1191,11 @@ export function getRenderInfoFromYaml( yamlPieKeys.length + yamlSummaryKeys.length + yamlBulletKeys.length + + yamlSvgKeys.length + yamlMonthKeys.length + yamlHeatmapKeys.length; if (totalNumOutputs === 0) { - return "No output parameter provided, please place line, bar, pie, month, bullet, or summary."; + return "No output parameter provided, please place line, bar, pie, month, bullet, svg, or summary."; } // Root folder to search @@ -1416,6 +1423,21 @@ export function getRenderInfoFromYaml( renderInfo.constValue = retConstValue; // console.log(renderInfo.constValue); + // dataMergeMethod + let retDataMergeMethod = getStringArrayFromInput( + "dataMergeMethod", + yaml.dataMergeMethod, + numDatasets, + "sum", + null, + true + ); + if (typeof retDataMergeMethod === "string") { + return retDataMergeMethod; // errorMessage + } + renderInfo.dataMergeMethod = retDataMergeMethod; + // console.log(renderInfo.dataMergeMethod); + // ignoreAttachedValue let retIgnoreAttachedValue = getBoolArrayFromInput( "ignoreAttachedValue", @@ -2373,5 +2395,30 @@ export function getRenderInfoFromYaml( } // Bullet related parameters // console.log(renderInfo.bullet); + // svg related parameters + for (let svgKey of yamlSvgKeys) { + let svg = new SvgInfo(); + let yamlSvg = yaml[svgKey]; + + let keysOfSvgInfo = getAvailableKeysOfClass(svg); + let keysFoundInYAML = getAvailableKeysOfClass(yamlSvg); + // console.log(keysOfSvgInfo); + // console.log(keysFoundInYAML); + for (let key of keysFoundInYAML) { + if (!keysOfSvgInfo.includes(key)) { + errorMessage = "'" + key + "' is not an available key"; + return errorMessage; + } + } + + // element + if (typeof yamlSvg?.element === "string") { + svg.element = yamlSvg.element; + } + // console.log(bullet.markValue); + + renderInfo.svg.push(svg); + } + // console.log(renderInfo.svg); return renderInfo; } diff --git a/src/pie.ts b/src/pie.ts index 992cef6..154428c 100644 --- a/src/pie.ts +++ b/src/pie.ts @@ -18,11 +18,11 @@ import * as expr from "./expr"; import { pie } from "d3"; function setChartScale( - _canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo ) { - let canvas = d3.select(_canvas); + let canvas = svgCanvas; let svg = chartElements.svg; let svgWidth = parseFloat(svg.attr("width")); let svgHeight = parseFloat(svg.attr("height")); @@ -47,12 +47,12 @@ function setChartScale( function createAreas( chartElements: ChartElements, - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, pieInfo: PieInfo ): ChartElements { // clean areas - d3.select(canvas).select("#svg").remove(); + svgCanvas.select("#svg").remove(); var props = Object.getOwnPropertyNames(chartElements); for (var i = 0; i < props.length; i++) { // d3.select(chartElements[props[i]]).remove(); @@ -61,8 +61,7 @@ function createAreas( // console.log(chartElements); // whole area for plotting, includes margins - let svg = d3 - .select(canvas) + let svg = svgCanvas .append("svg") .attr("id", "svg") .attr( @@ -110,7 +109,7 @@ function createAreas( } function renderTitle( - canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo, pieInfo: PieInfo @@ -151,7 +150,7 @@ function renderTitle( } function renderLegend( - canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo, pieInfo: PieInfo @@ -374,7 +373,7 @@ function renderLegend( } function renderPie( - canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo, pieInfo: PieInfo @@ -632,7 +631,7 @@ function renderPie( } export function renderPieChart( - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, pieInfo: PieInfo ) { @@ -643,7 +642,7 @@ export function renderPieChart( // return "Under construction"; let chartElements: ChartElements = {}; - chartElements = createAreas(chartElements, canvas, renderInfo, pieInfo); + chartElements = createAreas(chartElements, svgCanvas, renderInfo, pieInfo); // Set default dataColor if no dataColor provided let defaultDataColor = d3.schemeSpectral[pieInfo.dataColor.length]; @@ -653,13 +652,13 @@ export function renderPieChart( } } - renderTitle(canvas, chartElements, renderInfo, pieInfo); + renderTitle(svgCanvas, chartElements, renderInfo, pieInfo); - renderPie(canvas, chartElements, renderInfo, pieInfo); + renderPie(svgCanvas, chartElements, renderInfo, pieInfo); if (pieInfo.showLegend) { - renderLegend(canvas, chartElements, renderInfo, pieInfo); + renderLegend(svgCanvas, chartElements, renderInfo, pieInfo); } - setChartScale(canvas, chartElements, renderInfo); + setChartScale(svgCanvas, chartElements, renderInfo); } diff --git a/src/rendering.ts b/src/rendering.ts index 2d62188..75ec072 100644 --- a/src/rendering.ts +++ b/src/rendering.ts @@ -16,6 +16,7 @@ import { PieInfo, SummaryInfo, BulletInfo, + SvgInfo, MonthInfo, HeatmapInfo, } from "./data"; @@ -219,6 +220,8 @@ export function render(canvas: HTMLElement, renderInfo: RenderInfo) { // console.log("render"); // console.log(renderInfo.datasets); + let svgCanvas = d3.select(canvas).append("svg"); + // Data preprocessing for (let dataset of renderInfo.datasets) { if (dataset.getQuery().usedAsXDataset) continue; @@ -241,43 +244,50 @@ export function render(canvas: HTMLElement, renderInfo: RenderInfo) { } for (let lineInfo of renderInfo.line) { - let ret = renderLineChart(canvas, renderInfo, lineInfo); + let ret = renderLineChart(svgCanvas, renderInfo, lineInfo); if (typeof ret === "string") { return ret; } } for (let barInfo of renderInfo.bar) { - let ret = renderBarChart(canvas, renderInfo, barInfo); + let ret = renderBarChart(svgCanvas, renderInfo, barInfo); if (typeof ret === "string") { return ret; } } for (let pieInfo of renderInfo.pie) { - let ret = pie.renderPieChart(canvas, renderInfo, pieInfo); + let ret = pie.renderPieChart(svgCanvas, renderInfo, pieInfo); if (typeof ret === "string") { return ret; } } for (let summaryInfo of renderInfo.summary) { - let ret = summary.renderSummary(canvas, renderInfo, summaryInfo); + let ret = summary.renderSummary(svgCanvas, renderInfo, summaryInfo); if (typeof ret === "string") { return ret; } } for (let bulletInfo of renderInfo.bullet) { - let ret = bullet.renderBullet(canvas, renderInfo, bulletInfo); + let ret = bullet.renderBullet(svgCanvas, renderInfo, bulletInfo); if (typeof ret === "string") { return ret; } } for (let monthInfo of renderInfo.month) { - let ret = month.renderMonth(canvas, renderInfo, monthInfo); + let ret = month.renderMonth(svgCanvas, renderInfo, monthInfo); if (typeof ret === "string") { return ret; } } for (let heatmapInfo of renderInfo.heatmap) { - let ret = heatmap.renderHeatmap(canvas, renderInfo, heatmapInfo); + let ret = heatmap.renderHeatmap(svgCanvas, renderInfo, heatmapInfo); + if (typeof ret === "string") { + return ret; + } + } + + for (let svgInfo of renderInfo.svg) { + let ret = renderSvg(svgCanvas, renderInfo, svgInfo); if (typeof ret === "string") { return ret; } @@ -1445,11 +1455,11 @@ function renderTitle( } function setChartScale( - _canvas: HTMLElement, + svgCanvas: any, chartElements: ChartElements, renderInfo: RenderInfo ) { - let canvas = d3.select(_canvas); + let canvas = svgCanvas; let svg = chartElements.svg; let svgWidth = parseFloat(svg.attr("width")); let svgHeight = parseFloat(svg.attr("height")); @@ -1472,14 +1482,10 @@ function setChartScale( } } -function createAreas( - canvas: HTMLElement, - renderInfo: RenderInfo -): ChartElements { +function createAreas(svgCanvas: any, renderInfo: RenderInfo): ChartElements { let chartElements: ChartElements = {}; // whole area for plotting, includes margins - let svg = d3 - .select(canvas) + let svg = svgCanvas .append("svg") .attr("id", "svg") .attr( @@ -1527,7 +1533,7 @@ function createAreas( } function renderLineChart( - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, lineInfo: LineInfo ) { @@ -1536,7 +1542,7 @@ function renderLineChart( if (!renderInfo || !lineInfo) return; - let chartElements = createAreas(canvas, renderInfo); + let chartElements = createAreas(svgCanvas, renderInfo); renderTitle(chartElements, renderInfo, lineInfo); @@ -1605,11 +1611,11 @@ function renderLineChart( renderLegend(chartElements, renderInfo, lineInfo); } - setChartScale(canvas, chartElements, renderInfo); + setChartScale(svgCanvas, chartElements, renderInfo); } function renderBarChart( - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, barInfo: BarInfo ) { @@ -1617,7 +1623,7 @@ function renderBarChart( // console.log(renderInfo); if (!renderInfo || !barInfo) return; - let chartElements = createAreas(canvas, renderInfo); + let chartElements = createAreas(svgCanvas, renderInfo); renderTitle(chartElements, renderInfo, barInfo); @@ -1705,7 +1711,7 @@ function renderBarChart( renderLegend(chartElements, renderInfo, barInfo); } - setChartScale(canvas, chartElements, renderInfo); + setChartScale(svgCanvas, chartElements, renderInfo); } export function renderErrorMessage(canvas: HTMLElement, errorMessage: string) { @@ -1721,3 +1727,25 @@ export function renderErrorMessage(canvas: HTMLElement, errorMessage: string) { .style("padding", "10px") .style("color", "red"); } + +export function renderSvg( + svgCanvas: any, + renderInfo: RenderInfo, + svgInfo: SvgInfo +) { + // Remove graph not completed + // let graph = d3.select(canvas).select("#svg").remove(); + + if (svgInfo.element) { + let svg = svgInfo.element; + // unicode space + svg = svg.replace( + /[\u00A0\u1680​\u180e\u2000-\u2009\u200a​\u200b​\u202f\u205f​\u3000]/g, + " " + ); + // html space + svg = svg.replace(/ /g, " "); + // console.log(svg); + svgCanvas.append("svg").html(svg); + } +} diff --git a/src/summary.ts b/src/summary.ts index 053155c..e84193b 100644 --- a/src/summary.ts +++ b/src/summary.ts @@ -7,7 +7,7 @@ function checkSummaryTemplateValid(summaryTemplate: string): boolean { } export function renderSummary( - canvas: HTMLElement, + svgCanvas: any, renderInfo: RenderInfo, summaryInfo: SummaryInfo ) { @@ -31,20 +31,41 @@ export function renderSummary( outputSummary = retResolvedTemplate; if (outputSummary !== "") { - let textBlock = d3.select(canvas).append("div"); + let svg = svgCanvas + .append("svg") + .attr("id", "svg") + .attr("width", 200) + .attr("height", 200); + + let textBlock = svg.append("text") + .attr("x", 0) + .attr("y", 0); if (outputSummary.includes("\n") || outputSummary.includes("\\n")) { let outputLines = outputSummary.split(/(\n|\\n)/); // console.log(outputLines); for (let outputLine of outputLines) { - if (outputLine !== "\n" && outputLine !== "\\n") - textBlock.append("div").text(outputLine); + if (outputLine !== "\n" && outputLine !== "\\n") { + let line = textBlock.append("tspan").text(outputLine); + line.attr("x", 0) + .attr("dy", "1.5em") + .attr("class", "tracker-summary-text"); + + if (summaryInfo.style !== "") { + line.attr("style", summaryInfo.style); + } + } } } else { - textBlock.text(outputSummary); - } + let line = textBlock.text(outputSummary); + line.attr("x", 0) + .attr("dy", "1.5em") + .attr("class", "tracker-summary-text"); - if (summaryInfo.style !== "") { - textBlock.attr("style", summaryInfo.style); + if (summaryInfo.style !== "") { + line.attr("style", summaryInfo.style); + } } + + } } diff --git a/styles.css b/styles.css index 9cd8b4c..1196663 100644 --- a/styles.css +++ b/styles.css @@ -179,4 +179,10 @@ fill: var(--color-tick-label); stroke: none; font-size: 12px; +} + +.tracker-summary-text { + fill: var(--color-tick-label); + font-size: 18px; + stroke: none; } \ No newline at end of file