@steelbreeze/landscape
Advanced tools
Comparing version 3.0.0 to 3.0.1
@@ -1,1 +0,1 @@ | ||
var landscape;(()=>{"use strict";var e={};(()=>{var a=e;function t(e,a,t,n){return e.forEach(((e,c)=>{const l=a[c];for(let a=0;a<l;++a)n.push(t(e,l,a,c))})),n}function n(e,a){return e*a/c(e,a)}function c(e,a){return a?c(a,e%a):e}function l(e){return Object.assign(Object.assign({},e),{rowSpan:1,colSpan:1})}function r(e,a){return l({text:e.value,className:`axis ${a} ${e.key}`})}Object.defineProperty(a,"__esModule",{value:!0}),a.merge=a.table=void 0,a.table=function(e,a,c,o,s){const p=a.map(((a,t)=>s?e.map((e=>e[t].length||1)).reduce(n):1)),u=e.map((e=>e.map((e=>s?1:e.length||1)).reduce(n)));return t(e,u,((e,a,n,s)=>t(e,p,((e,t,c)=>l(e.length?o(e[Math.floor(e.length*(n+c)/(t*a))]):{text:"",className:"empty"})),c[s].data.map((e=>r(e,"y"))))),a[0].data.map(((e,n)=>t(a,p,(e=>r(e.data[n],"x")),c[0].data.map((()=>r({key:"",value:""},"xy")))))))},a.merge=function(e,a=!0,t=!0){let n;for(let c=e.length;c--;){const l=e[c];for(let r=l.length;r--;){const o=l[r];t&&c&&(n=e[c-1][r])&&n.text===o.text&&n.className===o.className&&n.colSpan===o.colSpan?(n.rowSpan+=o.rowSpan,l.splice(r,1)):a&&r&&(n=l[r-1])&&n.text===o.text&&n.className===o.className&&n.rowSpan===o.rowSpan&&(n.colSpan+=o.colSpan,l.splice(r,1))}}}})(),landscape=e})(); | ||
var landscape;(()=>{"use strict";var e={};(()=>{var a=e;function t(e,a,t,n){return e.forEach(((e,r)=>{const c=a[r];for(let a=0;a<c;++a)n.push(t(e,c,a,r))})),n}function n(e,a){for(let t=e.length;t--;)a(e[t],t)}function r(e,a){return e.text===a.text&&e.className===a.className}function c(e,a){return e.map((e=>a(e)||1)).reduce(((e,a)=>e*a/o(e,a)))}function o(e,a){return a?o(a,e%a):e}function l(e){return Object.assign(Object.assign({},e),{rowSpan:1,colSpan:1})}function p(e,a){return l({text:e.value,className:`axis ${a} ${e.key}`})}Object.defineProperty(a,"__esModule",{value:!0}),a.merge=a.table=void 0,a.table=function(e,a,n,r,o){const s=a.map(((a,t)=>o?c(e,(e=>e[t].length)):1)),u=e.map((e=>o?1:c(e,(e=>e.length))));return t(e,u,((e,a,c,o)=>t(e,s,((e,t,n)=>l(e.length?r(e[Math.floor(e.length*(c+n)/(t*a))]):{text:"",className:"empty"})),n[o].data.map((e=>p(e,"y"))))),a[0].data.map(((e,r)=>t(a,s,(e=>p(e.data[r],"x")),n[0].data.map((()=>p({key:"",value:""},"xy")))))))},a.merge=function(e,a,t){let c;n(e,((o,l)=>{n(o,((n,p)=>{t&&l&&(c=e[l-1][p])&&r(c,n)&&c.colSpan===n.colSpan?(c.rowSpan+=n.rowSpan,o.splice(p,1)):a&&p&&(c=o[p-1])&&r(c,n)&&c.rowSpan===n.rowSpan&&(c.colSpan+=n.colSpan,o.splice(p,1))}))}))}})(),landscape=e})(); |
@@ -5,29 +5,28 @@ "use strict"; | ||
/** | ||
* Generates a table from a cube and its axis. | ||
* Generates a table from a cube and it's axis. | ||
* @param cube The source cube. | ||
* @param xAxis The x axis. | ||
* @param yAxis The y axis. | ||
* @param x The dimension used as the x axis. | ||
* @param y The dimension used as the y axis. | ||
* @param getKey A callback to generate a key containing the text and className used in the table from the source records, | ||
* @param onX A flag to indicate if cells in cube containing multiple values should be split on the x axis (if not, the y axis will be used). | ||
*/ | ||
function table(cube, xAxis, yAxis, getKey, onX) { | ||
const xSplits = xAxis.map((_, xIndex) => onX ? cube.map(row => row[xIndex].length || 1).reduce(leastCommonMultiple) : 1); | ||
const ySplits = cube.map(row => row.map(table => onX ? 1 : table.length || 1).reduce(leastCommonMultiple)); | ||
// iterate and expand the y axis | ||
return expand(cube, ySplits, (row, ySplit, ysi, yIndex) => { | ||
// iterate and expand the x axis | ||
function table(cube, x, y, getKey, onX) { | ||
const xSplits = x.map((_, iX) => onX ? leastCommonMultiple(cube, row => row[iX].length) : 1); | ||
const ySplits = cube.map(row => onX ? 1 : leastCommonMultiple(row, table => table.length)); | ||
// iterate and expand the y axis based on the split data | ||
return expand(cube, ySplits, (row, ySplit, ysi, iY) => { | ||
// iterate and expand the x axis based on the split data | ||
return expand(row, xSplits, (values, xSplit, xsi) => { | ||
// generate data cell | ||
// generate the cube cells | ||
return cell(values.length ? getKey(values[Math.floor(values.length * (ysi + xsi) / (xSplit * ySplit))]) : { text: '', className: 'empty' }); | ||
// generate the y axis header cells | ||
}, yAxis[yIndex].data.map(pair => axis(pair, 'y'))); | ||
}, | ||
// generate the x axis header rows | ||
xAxis[0].data.map((_, yIndex) => { | ||
// generate an x header row | ||
return expand(xAxis, xSplits, xPoint => { | ||
// generate an x header row cell | ||
return axis(xPoint.data[yIndex], 'x'); | ||
// create the x/y header cells | ||
}, yAxis[0].data.map(() => axis({ key: '', value: '' }, 'xy'))); | ||
// generate the y axis row header cells | ||
}, y[iY].data.map(pair => axis(pair, 'y'))); | ||
// generate the x axis column header rows | ||
}, x[0].data.map((_, iY) => { | ||
// iterate and expand the x axis | ||
return expand(x, xSplits, xPoint => { | ||
// generate the x axis cells | ||
return axis(xPoint.data[iY], 'x'); | ||
// generate the x/y header | ||
}, y[0].data.map(() => axis({ key: '', value: '' }, 'xy'))); | ||
})); | ||
@@ -42,18 +41,16 @@ } | ||
*/ | ||
function merge(table, onX = true, onY = true) { | ||
function merge(table, onX, onY) { | ||
let next; | ||
for (let iY = table.length; iY--;) { | ||
const row = table[iY]; | ||
for (let iX = row.length; iX--;) { | ||
const cell = row[iX]; | ||
if (onY && iY && (next = table[iY - 1][iX]) && next.text === cell.text && next.className === cell.className && next.colSpan === cell.colSpan) { | ||
next.rowSpan += cell.rowSpan; | ||
forEachRev(table, (row, iY) => { | ||
forEachRev(row, (value, iX) => { | ||
if (onY && iY && (next = table[iY - 1][iX]) && keyEquals(next, value) && next.colSpan === value.colSpan) { | ||
next.rowSpan += value.rowSpan; | ||
row.splice(iX, 1); | ||
} | ||
else if (onX && iX && (next = row[iX - 1]) && next.text === cell.text && next.className === cell.className && next.rowSpan === cell.rowSpan) { | ||
next.colSpan += cell.colSpan; | ||
else if (onX && iX && (next = row[iX - 1]) && keyEquals(next, value) && next.rowSpan === value.rowSpan) { | ||
next.colSpan += value.colSpan; | ||
row.splice(iX, 1); | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
@@ -65,7 +62,7 @@ exports.merge = merge; | ||
*/ | ||
function expand(values, splits, f, seed) { | ||
values.forEach((value, valueIndex) => { | ||
const split = splits[valueIndex]; | ||
for (let splitIndex = 0; splitIndex < split; ++splitIndex) { | ||
seed.push(f(value, split, splitIndex, valueIndex)); | ||
function expand(values, splits, callbackfn, seed) { | ||
values.forEach((value, iValue) => { | ||
const split = splits[iValue]; | ||
for (let iSplit = 0; iSplit < split; ++iSplit) { | ||
seed.push(callbackfn(value, split, iSplit, iValue)); | ||
} | ||
@@ -76,9 +73,25 @@ }); | ||
/** | ||
* Returns the least common multiple of two integers | ||
* A reverse for loop | ||
* @param hidden | ||
*/ | ||
function forEachRev(values, callbackfn) { | ||
for (let index = values.length; index--;) { | ||
callbackfn(values[index], index); | ||
} | ||
} | ||
/** | ||
* Compare two keys for equality | ||
* @hidden | ||
*/ | ||
function leastCommonMultiple(a, b) { | ||
return (a * b) / greatestCommonFactor(a, b); | ||
function keyEquals(a, b) { | ||
return a.text === b.text && a.className === b.className; | ||
} | ||
/** | ||
* Returns the least common multiple of a set of integers generated from an object. | ||
* @hidden | ||
*/ | ||
function leastCommonMultiple(source, callbackfn) { | ||
return source.map(value => callbackfn(value) || 1).reduce((a, b) => (a * b) / greatestCommonFactor(a, b)); | ||
} | ||
/** | ||
* Returns the greatest common factor of two numbers | ||
@@ -85,0 +98,0 @@ * @hidden |
@@ -1,1 +0,1 @@ | ||
var landscape;(()=>{"use strict";var e={};(()=>{var a=e;function t(e,a,t,n){return e.forEach(((e,c)=>{const l=a[c];for(let a=0;a<l;++a)n.push(t(e,l,a,c))})),n}function n(e,a){return e*a/c(e,a)}function c(e,a){return a?c(a,e%a):e}function l(e){return Object.assign(Object.assign({},e),{rowSpan:1,colSpan:1})}function r(e,a){return l({text:e.value,className:`axis ${a} ${e.key}`})}Object.defineProperty(a,"__esModule",{value:!0}),a.merge=a.table=void 0,a.table=function(e,a,c,o,s){const p=a.map(((a,t)=>s?e.map((e=>e[t].length||1)).reduce(n):1)),u=e.map((e=>e.map((e=>s?1:e.length||1)).reduce(n)));return t(e,u,((e,a,n,s)=>t(e,p,((e,t,c)=>l(e.length?o(e[Math.floor(e.length*(n+c)/(t*a))]):{text:"",className:"empty"})),c[s].data.map((e=>r(e,"y"))))),a[0].data.map(((e,n)=>t(a,p,(e=>r(e.data[n],"x")),c[0].data.map((()=>r({key:"",value:""},"xy")))))))},a.merge=function(e,a=!0,t=!0){let n;for(let c=e.length;c--;){const l=e[c];for(let r=l.length;r--;){const o=l[r];t&&c&&(n=e[c-1][r])&&n.text===o.text&&n.className===o.className&&n.colSpan===o.colSpan?(n.rowSpan+=o.rowSpan,l.splice(r,1)):a&&r&&(n=l[r-1])&&n.text===o.text&&n.className===o.className&&n.rowSpan===o.rowSpan&&(n.colSpan+=o.colSpan,l.splice(r,1))}}}})(),landscape=e})(); | ||
var landscape;(()=>{"use strict";var e={};(()=>{var a=e;function t(e,a,t,n){return e.forEach(((e,r)=>{const c=a[r];for(let a=0;a<c;++a)n.push(t(e,c,a,r))})),n}function n(e,a){for(let t=e.length;t--;)a(e[t],t)}function r(e,a){return e.text===a.text&&e.className===a.className}function c(e,a){return e.map((e=>a(e)||1)).reduce(((e,a)=>e*a/o(e,a)))}function o(e,a){return a?o(a,e%a):e}function l(e){return Object.assign(Object.assign({},e),{rowSpan:1,colSpan:1})}function p(e,a){return l({text:e.value,className:`axis ${a} ${e.key}`})}Object.defineProperty(a,"__esModule",{value:!0}),a.merge=a.table=void 0,a.table=function(e,a,n,r,o){const s=a.map(((a,t)=>o?c(e,(e=>e[t].length)):1)),u=e.map((e=>o?1:c(e,(e=>e.length))));return t(e,u,((e,a,c,o)=>t(e,s,((e,t,n)=>l(e.length?r(e[Math.floor(e.length*(c+n)/(t*a))]):{text:"",className:"empty"})),n[o].data.map((e=>p(e,"y"))))),a[0].data.map(((e,r)=>t(a,s,(e=>p(e.data[r],"x")),n[0].data.map((()=>p({key:"",value:""},"xy")))))))},a.merge=function(e,a,t){let c;n(e,((o,l)=>{n(o,((n,p)=>{t&&l&&(c=e[l-1][p])&&r(c,n)&&c.colSpan===n.colSpan?(c.rowSpan+=n.rowSpan,o.splice(p,1)):a&&p&&(c=o[p-1])&&r(c,n)&&c.rowSpan===n.rowSpan&&(c.colSpan+=n.colSpan,o.splice(p,1))}))}))}})(),landscape=e})(); |
{ | ||
"name": "@steelbreeze/landscape", | ||
"version": "3.0.0", | ||
"version": "3.0.1", | ||
"description": "Landscape map viewpoint visualisation", | ||
@@ -5,0 +5,0 @@ "main": "lib/node/index.js", |
# landscape | ||
[![Maintainability](https://api.codeclimate.com/v1/badges/1106fd03a5f0df4cf80f/maintainability)](https://codeclimate.com/github/steelbreeze/landscape/maintainability) | ||
Landscape map visualisation of data. | ||
![landscape map viewpoint](https://steelbreeze.net/images/landscape-map.png) | ||
These visualisations conform to the [Archimate Landscape Map Viewpoint](https://pubs.opengroup.org/architecture/archimate2-doc/chap08.html#_Toc371945248). | ||
The tool takes as an input data set of the portfolio and dimensions they are associated with; it then can determine the optimal sequence of the values on the dimensions you select for the x and y axis for the optimal layout. It then generates the layout, splitting a cell in the table if multiple items in the portfolio are mapped to it, and joining items in neighbouring cells. | ||
@@ -38,3 +42,3 @@ | ||
// merge cells on both axes where possible | ||
landscape.merge(table); | ||
landscape.merge(table, true, true); | ||
@@ -41,0 +45,0 @@ // render the table in a designated element |
102
src/index.ts
// @steelbreeze/landscape | ||
// Copyright (c) 2019-21 David Mesquita-Morris | ||
import { Cube, Dimension, Func1, Row } from '@steelbreeze/pivot'; | ||
import { Cube, Dimension, Func1, Func2, Row } from '@steelbreeze/pivot'; | ||
@@ -24,39 +24,37 @@ /** The final text and class name to use when rendering cells in a table. */ | ||
/** | ||
* Generates a table from a cube and its axis. | ||
* Generates a table from a cube and it's axis. | ||
* @param cube The source cube. | ||
* @param xAxis The x axis. | ||
* @param yAxis The y axis. | ||
* @param x The dimension used as the x axis. | ||
* @param y The dimension used as the y axis. | ||
* @param getKey A callback to generate a key containing the text and className used in the table from the source records, | ||
* @param onX A flag to indicate if cells in cube containing multiple values should be split on the x axis (if not, the y axis will be used). | ||
*/ | ||
export function table<TRow extends Row>(cube: Cube<TRow>, xAxis: Dimension<TRow>, yAxis: Dimension<TRow>, getKey: Func1<TRow, Key>, onX: boolean): Array<Array<Cell>> { | ||
const xSplits = xAxis.map((_, xIndex) => onX ? cube.map(row => row[xIndex].length || 1).reduce(leastCommonMultiple) : 1); | ||
const ySplits = cube.map(row => row.map(table => onX ? 1 : table.length || 1).reduce(leastCommonMultiple)); | ||
export function table<TRow extends Row>(cube: Cube<TRow>, x: Dimension<TRow>, y: Dimension<TRow>, getKey: Func1<TRow, Key>, onX: boolean): Array<Array<Cell>> { | ||
const xSplits = x.map((_, iX) => onX ? leastCommonMultiple(cube, row => row[iX].length) : 1); | ||
const ySplits = cube.map(row => onX ? 1 : leastCommonMultiple(row, table => table.length)); | ||
// iterate and expand the y axis | ||
return expand(cube, ySplits, (row, ySplit, ysi, yIndex) => { | ||
// iterate and expand the y axis based on the split data | ||
return expand(cube, ySplits, (row, ySplit, ysi, iY) => { | ||
// iterate and expand the x axis | ||
// iterate and expand the x axis based on the split data | ||
return expand(row, xSplits, (values, xSplit, xsi) => { | ||
// generate data cell | ||
// generate the cube cells | ||
return cell(values.length ? getKey(values[Math.floor(values.length * (ysi + xsi) / (xSplit * ySplit))]) : { text: '', className: 'empty' }); | ||
// generate the y axis header cells | ||
}, yAxis[yIndex].data.map(pair => axis(pair, 'y'))); | ||
}, | ||
// generate the y axis row header cells | ||
}, y[iY].data.map(pair => axis(pair, 'y'))); | ||
// generate the x axis header rows | ||
xAxis[0].data.map((_, yIndex) => { | ||
// generate the x axis column header rows | ||
}, x[0].data.map((_, iY) => { | ||
// generate an x header row | ||
return expand(xAxis, xSplits, xPoint => { | ||
// iterate and expand the x axis | ||
return expand(x, xSplits, xPoint => { | ||
// generate an x header row cell | ||
return axis(xPoint.data[yIndex], 'x'); | ||
// generate the x axis cells | ||
return axis(xPoint.data[iY], 'x'); | ||
// create the x/y header cells | ||
}, yAxis[0].data.map(() => axis({ key: '', value: '' }, 'xy'))); | ||
}) | ||
); | ||
// generate the x/y header | ||
}, y[0].data.map(() => axis({ key: '', value: '' }, 'xy'))); | ||
})); | ||
} | ||
@@ -70,22 +68,18 @@ | ||
*/ | ||
export function merge(table: Array<Array<Cell>>, onX = true, onY = true): void { | ||
export function merge(table: Array<Array<Cell>>, onX: boolean, onY: boolean): void { | ||
let next; | ||
for (let iY = table.length; iY--;) { | ||
const row = table[iY]; | ||
forEachRev(table, (row, iY) => { | ||
forEachRev(row, (value, iX) => { | ||
if (onY && iY && (next = table[iY - 1][iX]) && keyEquals(next, value) && next.colSpan === value.colSpan) { | ||
next.rowSpan += value.rowSpan; | ||
for (let iX = row.length; iX--;) { | ||
const cell = row[iX]; | ||
if (onY && iY && (next = table[iY - 1][iX]) && next.text === cell.text && next.className === cell.className && next.colSpan === cell.colSpan) { | ||
next.rowSpan += cell.rowSpan; | ||
row.splice(iX, 1); | ||
} else if (onX && iX && (next = row[iX - 1]) && next.text === cell.text && next.className === cell.className && next.rowSpan === cell.rowSpan) { | ||
next.colSpan += cell.colSpan; | ||
} else if (onX && iX && (next = row[iX - 1]) && keyEquals(next, value) && next.rowSpan === value.rowSpan) { | ||
next.colSpan += value.colSpan; | ||
row.splice(iX, 1); | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
@@ -97,8 +91,8 @@ | ||
*/ | ||
function expand<TSource, TResult>(values: TSource[], splits: number[], f: (value: TSource, split: number, splitIndex: number, valueIndex: number) => TResult, seed: TResult[]): TResult[] { | ||
values.forEach((value, valueIndex) => { | ||
const split = splits[valueIndex]; | ||
function expand<TSource, TResult>(values: TSource[], splits: number[], callbackfn: (value: TSource, split: number, iSplit: number, iValue: number) => TResult, seed: TResult[]): TResult[] { | ||
values.forEach((value, iValue) => { | ||
const split = splits[iValue]; | ||
for (let splitIndex = 0; splitIndex < split; ++splitIndex) { | ||
seed.push(f(value, split, splitIndex, valueIndex)); | ||
for (let iSplit = 0; iSplit < split; ++iSplit) { | ||
seed.push(callbackfn(value, split, iSplit, iValue)); | ||
} | ||
@@ -111,7 +105,25 @@ }); | ||
/** | ||
* Returns the least common multiple of two integers | ||
* A reverse for loop | ||
* @param hidden | ||
*/ | ||
function forEachRev<TValue>(values: Array<TValue>, callbackfn: Func2<TValue, number, void>): void { | ||
for (let index = values.length; index--;) { | ||
callbackfn(values[index], index); | ||
} | ||
} | ||
/** | ||
* Compare two keys for equality | ||
* @hidden | ||
*/ | ||
function keyEquals(a: Key, b: Key): boolean { | ||
return a.text === b.text && a.className === b.className; | ||
} | ||
/** | ||
* Returns the least common multiple of a set of integers generated from an object. | ||
* @hidden | ||
*/ | ||
function leastCommonMultiple(a: number, b: number): number { | ||
return (a * b) / greatestCommonFactor(a, b); | ||
function leastCommonMultiple<TSource>(source: Array<TSource>, callbackfn: Func1<TSource, number>): number { | ||
return source.map(value => callbackfn(value) || 1).reduce((a, b) => (a * b) / greatestCommonFactor(a, b)); | ||
} | ||
@@ -118,0 +130,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
430337
4027
52
0