coconut-graph
Advanced tools
Comparing version 1.0.5 to 1.0.6
{ | ||
"name": "coconut-graph", | ||
"version": "1.0.5", | ||
"version": "1.0.6", | ||
"description": "Graph lib using d3 ", | ||
@@ -8,4 +8,4 @@ "main": "index.js", | ||
"test": "npm run bundle && npm run lint && npm run testsuite", | ||
"bundle": "browserify index.js -o bundle.js", | ||
"watchify": "watchify index.js -d -o bundle.js", | ||
"bundle": "browserify index.js --debug | exorcist bundle.js.map > bundle.js", | ||
"watchify": "watchify index.js -o 'exorcist bundle.js.map > bundle.js' -d", | ||
"lint": "eslint index.js src/ test/", | ||
@@ -28,3 +28,4 @@ "testsuite": "mocha-phantomjs test/index.html", | ||
"d3-legend": "^1.0.0", | ||
"d3-tip": "^0.6.7" | ||
"d3-tip": "^0.6.7", | ||
"extend": "^2.0.1" | ||
}, | ||
@@ -36,7 +37,9 @@ "devDependencies": { | ||
"eslint": "^0.21.2", | ||
"exorcist": "^0.4.0", | ||
"mocha": "^2.2.5", | ||
"mocha-phantomjs": "^3.5.3", | ||
"mversion": "^1.10.0", | ||
"phantomjs": "^1.9.17" | ||
"phantomjs": "^1.9.17", | ||
"source-map-support": "^0.3.1" | ||
} | ||
} |
# coconut-graph | ||
Quick and dirty reusable graph with d3. | ||
Quick and dirty reusable timeseries graphs with d3, developed to visualize usage. | ||
Currently supports line, bar, scatter. | ||
Depends heavily on json structure used in the coconut wepapp: | ||
Depends heavily on JSON structure used in the coconut wepapp: | ||
```json | ||
{ | ||
"meta": { | ||
"period": "day", | ||
"urls": { | ||
"next": "<url to get json for the next day>", | ||
"previous": "<url to get json for previous day>" | ||
}, | ||
"readable_interval": "15 minuten" | ||
}, | ||
"data": { | ||
"keys": ["timestamp", "amount"], | ||
"extents": ["2015-05-29T00:00:00", "2015-05-29T23:59:59"], | ||
"values": [ | ||
["2015-05-29T14:00:00", 50], | ||
["2015-05-29T14:15:00", 0], | ||
["2015-05-29T14:30:00", 5], | ||
["2015-05-29T15:00:00", 6] | ||
], | ||
"slices": 96 | ||
} | ||
"meta": { | ||
"period": "day", | ||
"urls": { | ||
"next": "<url to get json for the next day>", | ||
"previous": "<url to get json for previous day>" | ||
}, | ||
"readable_interval": "15 minuten" | ||
}, | ||
"data": { | ||
"keys": ["timestamp", "amount"], | ||
"extents": ["2015-05-29T00:00:00", "2015-05-29T23:59:59"], | ||
"values": [ | ||
["2015-05-29T14:00:00", 50], | ||
["2015-05-29T14:15:00", 0], | ||
["2015-05-29T14:30:00", 5], | ||
["2015-05-29T15:00:00", 6] | ||
], | ||
"slices": 96 | ||
} | ||
} | ||
@@ -39,3 +39,3 @@ ``` | ||
graphs: [{ | ||
container: 'graph-flow', | ||
container: 'container', | ||
plot: { type: 'bar', key: 'amount', label: 'verbruik [l]'} | ||
@@ -42,0 +42,0 @@ }] |
@@ -12,3 +12,3 @@ /*! | ||
L.extend = L.Util.extend; | ||
var extend = require('extend'); | ||
L.setOptions = L.Util.setOptions; | ||
@@ -57,3 +57,3 @@ | ||
if (props.statics) { | ||
L.extend(NewClass, props.statics); | ||
extend(NewClass, props.statics); | ||
delete props.statics; | ||
@@ -64,3 +64,3 @@ } | ||
if (props.includes) { | ||
L.Util.extend.apply(null, [proto].concat(props.includes)); | ||
extend.apply(null, [proto].concat(props.includes)); | ||
delete props.includes; | ||
@@ -71,7 +71,7 @@ } | ||
if (props.options && proto.options) { | ||
props.options = L.extend({}, proto.options, props.options); | ||
props.options = extend({}, proto.options, props.options); | ||
} | ||
// mix given properties into the prototype | ||
L.extend(proto, props); | ||
extend(proto, props); | ||
@@ -106,3 +106,3 @@ proto._initHooks = []; | ||
L.Class.include = function (props) { | ||
L.extend(this.prototype, props); | ||
extend(this.prototype, props); | ||
}; | ||
@@ -112,3 +112,3 @@ | ||
L.Class.mergeOptions = function (options) { | ||
L.extend(this.prototype.options, options); | ||
extend(this.prototype.options, options); | ||
}; | ||
@@ -115,0 +115,0 @@ |
132
src/graph.js
var Class = require('./class.js'); | ||
var extend = require('extend'); | ||
var Util = require('./util.js'); | ||
var d3 = require('d3'); | ||
var d3legend = require('d3-legend')(d3); | ||
var timeAxis = require('./time-xaxis.js'); | ||
var xaxis = require('./time-xaxis.js'); | ||
var yaxis = require('./yaxis.js'); | ||
@@ -20,2 +21,3 @@ var Graph = Class.extend({ | ||
axes: { | ||
x: {}, | ||
y: {orient: 'left'} | ||
@@ -33,4 +35,6 @@ } | ||
this.container = document.getElementById(options.container); | ||
// enable reuse of container; | ||
d3.select(this.container).selectAll('svg').remove(); | ||
options.axes = Util.extend({}, this.options.axes, options.axes || {}); | ||
options.axes = extend({}, this.options.axes, options.axes || {}); | ||
@@ -45,3 +49,3 @@ if (options.plot) { | ||
if (!('key' in plot)) { | ||
throw 'key must be suplied.'; | ||
throw 'key must be supplied.'; | ||
} | ||
@@ -65,3 +69,4 @@ if (!('axis' in plot)) { | ||
if (!Graph.onResizeCounter) { Graph.onResizeCounter = 0; } | ||
d3.select(window).on('resize.' + (Graph.onResizeCounter++), function () { | ||
this._resizeEvent = 'resize.' + (Graph.onResizeCounter++); | ||
d3.select(window).on(this._resizeEvent, function () { | ||
self.onResize(); | ||
@@ -71,2 +76,7 @@ }); | ||
remove: function () { | ||
d3.select(window).on(this._resizeEvent, null); | ||
d3.select(this.container).selectAll('svg').remove(); | ||
}, | ||
data_key: function (name) { | ||
@@ -91,4 +101,4 @@ return this.data.keys.indexOf(name); | ||
eachAxis: function (callback) { | ||
for (var name in this.options.axes) { | ||
callback.call(this, name, this.options.axes[name]); | ||
for (var name in this.axes) { | ||
callback.call(this, name, this.axes[name]); | ||
} | ||
@@ -103,56 +113,12 @@ }, | ||
_initAxes: function () { | ||
var scale = this.scale = { | ||
x: d3.time.scale().range([0, this.width()]) | ||
}; | ||
var axes = this.axes = {}; | ||
var axes = this.axes = { | ||
xticks: d3.svg.axis().scale(this.scale.x).orient('bottom').tickFormat(''), | ||
xlabels: d3.svg.axis().scale(this.scale.x).orient('bottom').tickSize(0).tickPadding(7) | ||
}; | ||
for (var name in this.options.axes) { | ||
var options = extend({name: name}, this.options.axes[name]); | ||
this.eachAxis(function (name, axis) { | ||
scale[name] = d3.scale.linear().rangeRound([this.height(), 0]); | ||
axes[name] = d3.svg.axis().scale(scale[name]).orient(axis.orient || 'left'); | ||
axes[name] = (name === 'x' ? xaxis : yaxis)(this, options); | ||
axes[name].render(); | ||
} | ||
}, | ||
if ('tickFormat' in axis) { | ||
if (typeof axis.tickFormat === 'string') { | ||
axes[name].tickFormat(d3.format(axis.tickFormat)); | ||
} else { | ||
axes[name].tickFormat(axis.tickFormat); | ||
} | ||
} | ||
}); | ||
var svg = this.svg; | ||
// initialize the (double) x-axis | ||
var height = this.height(); | ||
[ | ||
{axis: this.axes.xticks}, | ||
{axis: this.axes.xlabels, class: 'labels'} | ||
].forEach(function(axis) { | ||
svg.append('g').attr({ | ||
class: 'x axis ' + (axis.class || ''), | ||
transform: 'translate(0, ' + height + ')' | ||
}); | ||
}); | ||
// initialize the y axes | ||
this.eachAxis(function (name, axis) { | ||
var el = svg.append('g').attr('class', 'axis ' + name); | ||
var text = el.append('text') | ||
.attr({ | ||
class: 'axislabel', | ||
transform: 'rotate(-90)' | ||
}).text(axis.label || this.options.ylabel); | ||
if (axis.orient && axis.orient === 'right') { | ||
text.attr('dy', '-.5em'); | ||
el.attr('translate( ' + (this.width()) + ', 0)'); | ||
} else { | ||
text.attr({y: 6, dy: '.71em'}); | ||
} | ||
}); | ||
}, | ||
_initContainer: function () { | ||
@@ -173,27 +139,4 @@ var container = d3.select(this.container); | ||
_updateAxes: function () { | ||
var svg = this.svg; | ||
var width = this.width(); | ||
// x axis | ||
this.scale.x.range([0, width]); | ||
this.scale.x.domain(this.extents()); | ||
svg.selectAll('.x.axis').call(this.axes.xticks); | ||
svg.selectAll('.x.axis.labels').call(this.axes.xlabels); | ||
timeAxis[this.meta.period](this.axes, this.width()); | ||
svg.select('.x.axis').call(this.axes.xticks); | ||
svg.select('.x.axis.labels').call(this.axes.xlabels); | ||
var axes = this.axes; | ||
this.eachAxis(function (name, axis) { | ||
if (axis.ticks) { | ||
axes[name].ticks(axis.ticks); | ||
} | ||
// right oriented y axes | ||
var ax = svg.selectAll('.axis.' + name).call(axes[name]); | ||
if (axis.orient && axis.orient === 'right') { | ||
ax.attr('transform', 'translate(' + width + ', 0)'); | ||
} | ||
axis.update(); | ||
}); | ||
@@ -204,9 +147,3 @@ }, | ||
render: function (callback) { | ||
this._updateAxes(); | ||
var xscale = this.scale.x; | ||
this.plots = {}; | ||
// var yaxis_extents = {}; | ||
this.eachPlot(function(plot) { | ||
@@ -223,3 +160,2 @@ plot.data_key = this.data_key(plot.key); | ||
d3.select(this.container).style('display', 'block'); | ||
this._updateAxes(); | ||
} | ||
@@ -232,9 +168,11 @@ | ||
} | ||
// update the extent for this scale. | ||
this.scale[plot.axis].domain(extent).nice(); | ||
this.axes[plot.axis].domain(extent); | ||
}); | ||
this.svg.selectAll('.axis.' + plot.axis).call(this.axes[plot.axis]); | ||
this._updateAxes(); | ||
// Do the actual plotting | ||
this.plots[plot.key] = this['plot_' + plot.type](xscale, this.scale[plot.axis], plot, this); | ||
var xscale = this.axes.x.scale; | ||
this.eachPlot(function (plot) { | ||
var plot_fn = this['plot_' + plot.type]; | ||
plot.fn = plot_fn(xscale, this.axes[plot.axis].scale, plot, this); | ||
}); | ||
@@ -273,7 +211,3 @@ | ||
this.eachPlot(function (plot) { | ||
if (plot.type === 'bar') { | ||
this.svg.selectAll('.bar.' + plot.key).call(this.plots[plot.key]); | ||
} else { | ||
this.svg.selectAll('.plot.' + plot.key).call(this.plots[plot.key]); | ||
} | ||
this.svg.selectAll('.plot.series-' + plot.key).call(plot.fn); | ||
}); | ||
@@ -280,0 +214,0 @@ }, |
@@ -14,3 +14,3 @@ var d3 = require('d3'); | ||
} | ||
return amount + ' / ' + readable_interval; | ||
return amount + (readable_interval ? ' / ' + readable_interval : ''); | ||
}); | ||
@@ -21,14 +21,12 @@ }; | ||
var key = plot.data_key; | ||
var yaxis = graph.axes.y; | ||
var max = d3.max(graph.data.values, function(d) { return d[key]; }) * 1.1; | ||
y.domain([0, max]).nice(); | ||
// TODO: make sure it uses the right axis. | ||
if (max > 1000) { | ||
graph.axes.y.tickFormat(function (d) { return Math.round(d / 100) / 10; }); | ||
graph.svg.selectAll('.y.axis > text').text('verbruik [m³]'); | ||
} else { | ||
graph.axes.y.tickFormat(function (d) { return d; }); | ||
graph.svg.selectAll('.y.axis > text').text('verbruik [l]'); | ||
} | ||
graph.svg.selectAll('.axis.y').call(graph.axes.y); | ||
// scale axis if above 1000l | ||
// TODO: move out of plot_bar. | ||
yaxis.options.tickFormat = function (d) { return (max > 1000) ? (Math.round(d / 100) / 10) : d; }; | ||
yaxis.options.label = 'verbruik ' + ((max > 1000) ? '[m³]' : '[l]'); | ||
yaxis.update(); | ||
@@ -53,7 +51,7 @@ graph.svg.call(tip(key, graph.meta.readable_interval)); | ||
var sel = graph.plotContainer.selectAll('.bar.' + plot.key).data(graph.data.values); | ||
var sel = graph.plotContainer.selectAll('.plot.series-' + plot.key).data(graph.data.values); | ||
sel.exit().remove(); | ||
sel.enter().append('rect') | ||
.attr('class', 'bar ' + plot.key) | ||
.attr('class', 'plot plot-bar series-' + plot.key) | ||
.on({mouseover: tip.show, mouseout: tip.hide}); | ||
@@ -60,0 +58,0 @@ |
@@ -7,21 +7,24 @@ var d3 = require('d3'); | ||
var line = function (s) { | ||
s.attr('d', d3.svg.line() | ||
.x(function(d) { return x(d[0]); }) | ||
.y(function(d) { return y(d[plot.data_key]); }) | ||
); | ||
var line = d3.svg.line() | ||
.x(function(d) { return x(d[0]); }) | ||
.y(function(d) { return y(d[plot.data_key]); }); | ||
var line_plot = function (sel) { | ||
sel.attr('d', line); | ||
}; | ||
graph.plotContainer.selectAll('.line.' + plot.key) | ||
.data([graph.data.values]) | ||
.enter() | ||
.append('path') | ||
.attr('class', 'plot line ' + plot.key) | ||
.attr('data-legend', plot.label || 'series ' + unnamed_series++); | ||
var selection = graph.plotContainer | ||
.selectAll('.series-' + plot.key) | ||
.data([graph.data.values]); | ||
graph.svg.selectAll('.line.' + plot.key) | ||
.transition().duration(200) | ||
.call(line); | ||
selection.enter().append('path') | ||
.attr({ | ||
class: 'plot plot-line series-' + plot.key, | ||
'data-legend': plot.label || 'series ' + unnamed_series++ | ||
}) | ||
.call(line_plot); | ||
return line; | ||
selection.exit().remove(); | ||
return line_plot; | ||
}; |
@@ -8,3 +8,3 @@ module.exports = function(x, y, plot, graph) { | ||
}; | ||
var sel = graph.plotContainer.selectAll('circle.' + plot.key).data(graph.data.values); | ||
var sel = graph.plotContainer.selectAll('circle.series-' + plot.key).data(graph.data.values); | ||
sel.exit().remove(); | ||
@@ -14,3 +14,3 @@ sel.enter() | ||
.attr({ | ||
class: 'plot ' + plot.key, | ||
class: 'plot series-' + plot.key, | ||
r: 2 | ||
@@ -17,0 +17,0 @@ }).call(scatter); |
@@ -0,6 +1,7 @@ | ||
var extend = require('extend'); | ||
var d3 = require('d3'); | ||
require('./d3.nl_nl.js')(d3); | ||
module.exports = { | ||
day: function (axes, width) { | ||
var formatters = { | ||
day: function (xticks, xlabels, width) { | ||
var interval = width > 800 ? 1 : | ||
@@ -10,8 +11,8 @@ width > 550 ? 2 : | ||
axes.xticks.ticks(d3.time.hours, interval); | ||
axes.xlabels.ticks(d3.time.hours, interval) | ||
xticks.ticks(d3.time.hours, interval); | ||
xlabels.ticks(d3.time.hours, interval) | ||
.tickFormat(d3.locale.nl_NL.timeFormat('%H:%M')); | ||
}, | ||
week: function (axes, width) { | ||
axes.xticks.ticks(d3.time.day); | ||
week: function (xticks, xlabels, width) { | ||
xticks.ticks(d3.time.day); | ||
@@ -22,3 +23,3 @@ var tickFormat = (width > 700 ? '%A' : '%a') + ' %-d '; | ||
axes.xlabels | ||
xlabels | ||
.ticks(d3.time.hour, 12) | ||
@@ -31,7 +32,7 @@ .tickFormat(function (d) { | ||
}, | ||
month: function (axes, width) { | ||
month: function (xticks, xlabels, width) { | ||
var wide = width > 550; | ||
axes.xticks.ticks(d3.time.day, wide ? 1 : 4); | ||
axes.xlabels.ticks(d3.time.hour, wide ? 12 : 24) | ||
xticks.ticks(d3.time.day, wide ? 1 : 4); | ||
xlabels.ticks(d3.time.hour, wide ? 12 : 24) | ||
.tickFormat(function (d) { | ||
@@ -47,7 +48,7 @@ if (wide) { | ||
}, | ||
year: function (axes, width) { | ||
axes.xticks.ticks(d3.time.month); | ||
year: function (xticks, xlabels, width) { | ||
xticks.ticks(d3.time.month); | ||
var tickFormat = (width > 550 ? '%B' : '%b'); | ||
axes.xlabels | ||
xlabels | ||
.ticks(d3.time.day, 15) | ||
@@ -61,1 +62,42 @@ .tickFormat(function (d) { | ||
}; | ||
module.exports = function (graph, options) { | ||
options = extend({}, options); | ||
var name = options.name; | ||
var scale = d3.time.scale().range([0, graph.width()]); | ||
var xticks = d3.svg.axis().scale(scale).orient('bottom').tickFormat(''); | ||
var xlabels = d3.svg.axis().scale(scale).orient('bottom').tickSize(0).tickPadding(7); | ||
var Axis = function () {}; | ||
Axis.name = name; | ||
Axis.scale = scale; | ||
Axis.render = function () { | ||
var height = graph.height(); | ||
[ | ||
{axis: xticks}, | ||
{axis: xlabels, class: 'labels'} | ||
].forEach(function(axis) { | ||
graph.svg.append('g').attr({ | ||
class: 'x axis ' + (axis.class || ''), | ||
transform: 'translate(0, ' + height + ')' | ||
}); | ||
}); | ||
}; | ||
Axis.update = function (extent) { | ||
var svg = graph.svg; | ||
scale.range([0, graph.width()]); | ||
scale.domain(extent || graph.extents()); | ||
formatters[graph.meta.period](xticks, xlabels, graph.width()); | ||
svg.selectAll('.x.axis').call(xticks); | ||
svg.selectAll('.x.axis.labels').call(xlabels); | ||
}; | ||
return Axis; | ||
}; |
var util = { | ||
extend: function (dest) { | ||
var sources = Array.prototype.slice.call(arguments, 1), | ||
i, j, len, src; | ||
extend: require('extend'), | ||
template: require('./template.js'), | ||
for (j = 0, len = sources.length; j < len; j++) { | ||
src = sources[j] || {}; | ||
for (i in src) { | ||
if (src.hasOwnProperty(i)) { | ||
dest[i] = src[i]; | ||
} | ||
} | ||
} | ||
return dest; | ||
}, | ||
// create an object from a given prototype | ||
@@ -24,13 +13,3 @@ create: Object.create || function () { | ||
}, | ||
template: function (str, data) { | ||
return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) { | ||
var value = data[key]; | ||
if (value === undefined) { | ||
throw new Error('No value provided for variable ' + str); | ||
} else if (typeof value === 'function') { | ||
value = value(data); | ||
} | ||
return value; | ||
}); | ||
}, | ||
setOptions: function (obj, options) { | ||
@@ -37,0 +16,0 @@ obj.options = util.extend({}, obj.options, options); |
/* globals | ||
describe:true it:true chai:true | ||
describe:true | ||
it:true | ||
chai:true | ||
d3: true | ||
Graph:true | ||
@@ -16,11 +19,11 @@ */ | ||
'data': { | ||
'keys': ['timestamp', 'amount', 'test'], | ||
'keys': ['timestamp', 'amount', 'test', 'foo'], | ||
'extents': ['2015-05-29T00:00:00', '2015-05-29T23:59:59'], | ||
'values': [ | ||
['2015-05-29T00:00:00', 0, 1], | ||
['2015-05-29T14:00:00', 50, 2], | ||
['2015-05-29T14:15:00', 0, 3], | ||
['2015-05-29T14:30:00', 5, 4], | ||
['2015-05-29T14:45:00', 15, 5], | ||
['2015-05-29T15:00:00', 6, 5] | ||
['2015-05-29T00:00:00', 0, 1, 7], | ||
['2015-05-29T14:00:00', 10, 2, 5], | ||
['2015-05-29T14:15:00', 0, 3, 7], | ||
['2015-05-29T14:30:00', 5, 4, 7], | ||
['2015-05-29T14:45:00', 4, 5, 7], | ||
['2015-05-29T15:00:00', 6, 5, 7] | ||
], | ||
@@ -31,2 +34,12 @@ 'slices': 96 | ||
(function disableD3animations() { | ||
// add a duration function to the selection prototype | ||
d3.selection.prototype.duration = function() { return this; }; | ||
// hack the transition function of d3's select API | ||
d3.selection.prototype.transition = function() { return this; }; | ||
})(); | ||
var container = 'container'; | ||
var c = document.getElementById(container); | ||
describe('coconut-graph', function () { | ||
@@ -41,3 +54,3 @@ chai.should(); | ||
{ | ||
container: 'container1', | ||
container: container, | ||
className: 'extra', | ||
@@ -50,11 +63,10 @@ axes: {y: {includeZero: true}}, | ||
}); | ||
var container = document.getElementById('container1'); | ||
it('adds classnames to graph container', function () { | ||
container.className.should.contain('chart'); | ||
container.className.should.contain('extra'); | ||
c.className.should.contain('chart'); | ||
c.className.should.contain('extra'); | ||
}); | ||
it('preserves existing classnames on containers', function () { | ||
container.className.should.contain('container'); | ||
c.className.should.contain('container'); | ||
}); | ||
@@ -70,4 +82,7 @@ | ||
it('Can be removed', function () { | ||
loader.graphs[container].remove(); | ||
}); | ||
it('should create multiple plots in one chart', function () { | ||
var container = 'container2'; | ||
loader = new Graph.Loader(Graph.util.extend({}, data), { | ||
@@ -77,9 +92,11 @@ graphs: [ | ||
container: container, | ||
margin_right: 40, | ||
axes: { | ||
y: {includeZero: true}, | ||
y1: {orient: 'right'} | ||
y1: {includeZero: true, orient: 'right'} | ||
}, | ||
plots: [ | ||
{key: 'amount', axis: 'y', label: 'verbruik [l]', type: 'bar'}, | ||
{key: 'test', axis: 'y1', label: 'test', type: 'line'} | ||
{key: 'test', axis: 'y1', label: 'test', type: 'line'}, | ||
{key: 'foo', axis: 'y1', label: 'foo', type: 'scatter'} | ||
] | ||
@@ -91,11 +108,21 @@ } | ||
var graph = loader.graphs[container]; | ||
graph.plots.should.have.keys('amount', 'test'); | ||
// var graph = loader.graphs[container]; | ||
// graph.plots.should.have.keys('amount', 'test', 'foo'); | ||
}); | ||
}); | ||
}); | ||
describe('bar graph', function () { | ||
it('it should draw 6 bars', function () { | ||
d3.select(c).selectAll('rect')[0].length.should.equal(6); | ||
}); | ||
it('4/6 have non-zero height', function () { | ||
var nonzero = d3.select(c).selectAll('rect'); | ||
nonzero = nonzero.filter(function () { | ||
return d3.select(this).attr('height') > 0; | ||
}); | ||
nonzero[0].length.should.equal(4); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
25574
19
715
4
10
+ Addedextend@^2.0.1
+ Addedextend@2.0.2(transitive)