diff --git a/.gitignore b/.gitignore index 25fbf5a..c6f71ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ coverage/ +.idea/ diff --git a/README.md b/README.md index e894493..705efe5 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,34 @@ As styles are very different with each project, no CSS is included. You can copy } ``` +If you are using this within a Vue.js component, you need to wrap it in a ` +``` + + ## Usage In an example chart: @@ -68,7 +96,7 @@ new Chartist.Bar('.ct-chart', data, { Chartist.plugins.legend() ] }, -}); +) ``` | __Option__ | __Description__ | __Type__ | __Default__ | @@ -79,4 +107,4 @@ new Chartist.Bar('.ct-chart', data, { | `onClick` | Accepts a function that gets invoked if `clickable` is true. The function has the `chart`, and the click event (`e`), as arguments. | `mixed` | `false` | | `classNames` | Accepts a array of strings as long as the chart's series, those will be added as classes to the `li` elements. | `mixed` | `false` | | `removeAll` | Allow all series to be removed at once. | `bool` | `false` | -| `position` | Sets the position of the legend element. `top`, `bottom` or any DOM2 Element are currently accepted. If a DOM Element is given, the legend will be appended as it's last child. | `'top'|'bottom'|HTMLElement` | `'top'` | +| `position` | Sets the position of the legend element. `top`, `bottom` or the `id` of any DOM2 Element are currently accepted. If a DOM Element is given, the legend will be appended as it's last child. | `string` | `'top'` | diff --git a/chartist-plugin-legend.js b/chartist-plugin-legend.js index a8dc67b..2518767 100644 --- a/chartist-plugin-legend.js +++ b/chartist-plugin-legend.js @@ -33,26 +33,15 @@ Chartist.plugins.legend = function (options) { - // Catch invalid options + // Catch invalid options - position must be a string if (options && options.position) { - if (!(options.position === 'top' || options.position === 'bottom' || options.position instanceof HTMLElement)) { + if (!(typeof options.position === 'string')) { throw Error('The position you entered is not a valid position'); } - if (options.position instanceof HTMLElement) { - // Detatch DOM element from options object, because Chartist.extend - // currently chokes on circular references present in HTMLElements - var cachedDOMPosition = options.position; - delete options.position; - } } options = Chartist.extend({}, defaultOptions, options); - if (cachedDOMPosition) { - // Reattatch the DOM Element position if it was removed before - options.position = cachedDOMPosition - } - return function legend(chart) { function removeLegendElement() { @@ -124,19 +113,25 @@ // Append the legend element to the DOM function appendLegendToDOM(legendElement) { - if (!(options.position instanceof HTMLElement)) { - switch (options.position) { - case 'top': - chart.container.insertBefore(legendElement, chart.container.childNodes[0]); - break; - - case 'bottom': - chart.container.insertBefore(legendElement, null); - break; - } - } else { - // Appends the legend element as the last child of a given HTMLElement - options.position.insertBefore(legendElement, null); + // If you named your div 'top' or 'bottom', it won't be attached + switch (options.position) { + case 'top': + chart.container.insertBefore(legendElement, chart.container.childNodes[0]); + break; + + case 'bottom': + chart.container.insertBefore(legendElement, null); + break; + + default: + var pos = document.getElementById(options.position) + if (pos !== null) { + // Appends the legend element as the last child of a given HTMLElement + pos.insertBefore(legendElement, null); + } else { + throw Error('The position you entered is not a valid position'); + } + break; } } @@ -157,9 +152,11 @@ legend.active = false; li.classList.add('inactive'); - var activeCount = legends.filter(function(legend) { return legend.active; }).length; - if (!options.removeAll && activeCount == 0) { - // If we can't disable all series at the same time, let's + var activeCount = legends.filter(function (legend) { + return legend.active; + }).length; + if (!options.removeAll && activeCount === 0) { + // If we can't disable all series at the same time, var's // reenable all of them: for (var i = 0; i < legends.length; i++) { legends[i].active = true; @@ -172,7 +169,7 @@ var newLabels = []; for (var i = 0; i < seriesMetadata.length; i++) { - if (seriesMetadata[i].legend != -1 && legends[seriesMetadata[i].legend].active) { + if (seriesMetadata[i].legend !== -1 && legends[seriesMetadata[i].legend].active) { newSeries.push(seriesMetadata[i].data); newLabels.push(seriesMetadata[i].label); } @@ -221,7 +218,7 @@ }); }); - chart.on('created', function (data) { + chart.on('created', function () { appendLegendToDOM(legendElement); }); diff --git a/package.json b/package.json index c19a0b0..f93675e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chartist-plugin-legend", - "version": "0.6.2", + "version": "0.7.0", "author": "Kees Kluskens ", "description": "Legend plugin for Chartist.js.", "license": "ISC", diff --git a/test/test.legend.js b/test/test.legend.js index 7a56158..7991770 100644 --- a/test/test.legend.js +++ b/test/test.legend.js @@ -22,7 +22,7 @@ var chartDataPie = { }; // PhantomJS can't do a `.click()` on a DOM element, this is a workaround. -function click(el){ +function click(el) { var ev = document.createEvent('MouseEvent'); ev.initMouseEvent( 'click', @@ -48,7 +48,7 @@ function generateChart(type, chartData, legendOptions, chartNumber) { }); } -describe('Chartist plugin legend', function() { +describe('Chartist plugin legend', function () { var chart; var chart2; @@ -67,7 +67,7 @@ describe('Chartist plugin legend', function() { expect(window.Chartist.plugins.legend).to.exist; }); - before(function() { + before(function () { var chartEl = document.createElement('div'); chartEl.classList.add('ct-chart-1'); document.body.appendChild(chartEl); @@ -76,11 +76,13 @@ describe('Chartist plugin legend', function() { document.body.appendChild(chartEl2); }); - describe('work with a Line chart', function() { - before(function(done) { + describe('work with a Line chart', function () { + before(function (done) { chart = generateChart('Line', chartDataLine); - chart.on('created', function() { done(); }); + chart.on('created', function () { + done(); + }); }); after(destroyChart); @@ -99,7 +101,7 @@ describe('Chartist plugin legend', function() { var legendKey = 0; var parent = chart.container.querySelector('ul.ct-legend'); - [].forEach.call(parent.childNodes, function(item) { + [].forEach.call(parent.childNodes, function (item) { expect(item.dataset.legend).to.equal(String(legendKey)); expect(item.className).to.equal('ct-series-' + String(legendKey)); expect(item.innerHTML).to.equal(chartDataLine.series[legendKey].name); @@ -115,20 +117,22 @@ describe('Chartist plugin legend', function() { window.Chartist.plugins.legend()(chart); // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler chart.on('created', function () { - setTimeout(function () { - var matches = chart.container.querySelectorAll('ul.ct-legend'); - expect(matches.length).to.equal(1); + setTimeout(function () { + var matches = chart.container.querySelectorAll('ul.ct-legend'); + expect(matches.length).to.equal(1); - }, 10) + }, 10) }); }); }); - describe('work with a Pie chart', function() { - before(function(done) { + describe('work with a Pie chart', function () { + before(function (done) { chart = generateChart('Pie', chartDataPie); - chart.on('created', function() { done(); }); + chart.on('created', function () { + done(); + }); }); after(destroyChart); @@ -148,7 +152,7 @@ describe('Chartist plugin legend', function() { var legendKey = 0; var parent = chart.container.querySelector('ul.ct-legend'); - [].forEach.call(parent.childNodes, function(item) { + [].forEach.call(parent.childNodes, function (item) { expect(item.dataset.legend).to.equal(String(legendKey)); expect(item.className).to.equal('ct-series-' + String(legendKey)); expect(item.innerHTML).to.equal(chartDataPie.labels[legendKey]); @@ -157,14 +161,14 @@ describe('Chartist plugin legend', function() { }); }); - describe('work with two legends', function() { - before(function(done) { + describe('work with two legends', function () { + before(function (done) { chart = generateChart('Line', chartDataLine); - chart.on('created', function() { + chart.on('created', function () { chart.off('created'); chart2 = generateChart('Line', chart2DataLine, null, 2); - chart2.on('created', function() { + chart2.on('created', function () { chart2.off('created'); done(); }); @@ -174,41 +178,40 @@ describe('Chartist plugin legend', function() { after(destroyChart); it('should create unique legends', function () { - const legendText1 = chart.container.querySelector('ul.ct-legend').textContent; - const legendText2 = chart2.container.querySelector('ul.ct-legend').textContent; + var legendText1 = chart.container.querySelector('ul.ct-legend').textContent; + var legendText2 = chart2.container.querySelector('ul.ct-legend').textContent; expect(legendText1).to.equal('Blue pillRed pillPurple pill'); expect(legendText2).to.equal('Second pill'); }); }); - describe('allow options', function() { - it('should use custom legend names if provided', function(done) { + describe('allow options', function () { + it('should use custom legend names if provided', function (done) { var legendNames = ['Sheep', 'are', 'animals']; - chart = generateChart('Line', chartDataLine, { legendNames: legendNames }); + chart = generateChart('Line', chartDataLine, {legendNames: legendNames}); // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler chart.on('created', function () { - setTimeout(function () { - var legendKey = 0; - var parent = chart.container.querySelector('ul.ct-legend'); + setTimeout(function () { + var legendKey = 0; + var parent = chart.container.querySelector('ul.ct-legend'); - expect(parent.childNodes.length).to.equal(3); - [].forEach.call(parent.childNodes, function (item) - { - expect(item.innerHTML).to.equal(legendNames[legendKey]); - legendKey += 1; - }); + expect(parent.childNodes.length).to.equal(3); + [].forEach.call(parent.childNodes, function (item) { + expect(item.innerHTML).to.equal(legendNames[legendKey]); + legendKey += 1; + }); - destroyChart(); - done(); + destroyChart(); + done(); - }, 10) + }, 10) }); }); it('should use the data object name when labels are not defined', function (done) { var chartDataNoLabels = { - labels: [], // adding empty arry because chartist.js converts null or undefined labels into empty array + labels: [], // adding empty array because chartist.js converts null or undefined labels into empty array series: [ {name: 'Piece A', value: 20}, {name: 'Piece B', value: 10}, @@ -219,14 +222,14 @@ describe('Chartist plugin legend', function() { chart = generateChart('Pie', chartDataNoLabels); // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler - chart.on('created', function() { + chart.on('created', function () { setTimeout(function () { var legendKey = 0; var parent = chart.container.querySelector('ul.ct-legend'); expect(parent.childNodes.length).to.equal(4); - [].forEach.call(parent.childNodes, function(item) { + [].forEach.call(parent.childNodes, function (item) { expect(item.innerHTML).to.equal(chartDataNoLabels.series[legendKey].name); legendKey += 1; }); @@ -239,117 +242,119 @@ describe('Chartist plugin legend', function() { }); - it('should allow a custom class name', function(done) { - chart = generateChart('Line', chartDataLine, { className: 'bananas' }); + it('should allow a custom class name', function (done) { + chart = generateChart('Line', chartDataLine, {className: 'bananas'}); // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler chart.on('created', function () { - setTimeout(function () { - var legend = chart.container.querySelector('ul.ct-legend'); - expect(legend.classList[1]).to.equal('bananas'); - destroyChart(); - done(); + setTimeout(function () { + var legend = chart.container.querySelector('ul.ct-legend'); + expect(legend.classList[1]).to.equal('bananas'); + destroyChart(); + done(); - }, 10) + }, 10) }); }); it('should allow multiple custom class names', function (done) { - var classNames = ['multiclass-0', 'multiclass-1', 'multiclass-hidden']; - chart = generateChart('Line', chartDataLine, { classNames: classNames }); - - // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler - chart.on('created', function () { - setTimeout(function () { - var legend = chart.container.querySelector('ul.ct-legend'); - - expect(chart.data.series.length).to.equal(3); - expect(legend.children[0].classList.contains(classNames[0])).to.be.true; - expect(legend.children[1].classList.contains(classNames[1])).to.be.true; - expect(legend.children[2].classList.contains(classNames[2])).to.be.true; - destroyChart(); - done(); - - }, 10) - }); - }); + var classNames = ['multiclass-0', 'multiclass-1', 'multiclass-hidden']; + chart = generateChart('Line', chartDataLine, {classNames: classNames}); - describe('allow custom positioning', function () { - it('should allow top positioning', function (done) { - chart = generateChart('Line', chartDataLine, { position: 'top' }); - - // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler - chart.on('created', function () { - setTimeout(function () { - expect(chart.container.childNodes.length).to.equal(2); - var listElement = chart.container.querySelector("ul"); - expect(chart.container.childNodes[0]).to.equal(listElement); - done(); - }, 10) - }); - }); - - it('should allow bottom positioning', function (done) { - chart = generateChart('Line', chartDataLine, { position: 'bottom' }); - - // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler - chart.on('created', function () { - setTimeout(function () { - expect(chart.container.childNodes.length).to.equal(2); - var listElement = chart.container.querySelector("ul"); - expect(chart.container.childNodes[1]).to.equal(listElement); + // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler + chart.on('created', function () { + setTimeout(function () { + var legend = chart.container.querySelector('ul.ct-legend'); + + expect(chart.data.series.length).to.equal(3); + expect(legend.children[0].classList.contains(classNames[0])).to.be.true; + expect(legend.children[1].classList.contains(classNames[1])).to.be.true; + expect(legend.children[2].classList.contains(classNames[2])).to.be.true; + destroyChart(); done(); - }, 10) - }); - }); - it('should allow positioning to any DOM2 element', function (done) { - var testDOMElement = document.createElement('div'); - document.body.insertBefore(testDOMElement , null); + }, 10) + }); + }); - chart = generateChart('Line', chartDataLine, { position: testDOMElement }); + describe('allow custom positioning', function () { + it('should allow top positioning', function (done) { + chart = generateChart('Line', chartDataLine, {position: 'top'}); + + // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler + chart.on('created', function () { + setTimeout(function () { + expect(chart.container.childNodes.length).to.equal(2); + var listElement = chart.container.querySelector("ul"); + expect(chart.container.childNodes[0]).to.equal(listElement); + done(); + }, 10) + }); + }); - // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler - chart.on('created', function () { - setTimeout(function () { - expect(testDOMElement.childNodes.length).to.equal(1); - var listElement = testDOMElement.querySelector("ul"); - expect(testDOMElement.childNodes[0]).to.equal(listElement); + it('should allow bottom positioning', function (done) { + chart = generateChart('Line', chartDataLine, {position: 'bottom'}); + + // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler + chart.on('created', function () { + setTimeout(function () { + expect(chart.container.childNodes.length).to.equal(2); + var listElement = chart.container.querySelector("ul"); + expect(chart.container.childNodes[1]).to.equal(listElement); + done(); + }, 10) + }); + }); - // Clean up - document.body.removeChild(testDOMElement); - done(); - }, 100) - }); - }); + it('should allow positioning to any DOM2 element', function (done) { + var elementId = 'legend' + var testDOMElement = document.createElement('div'); + testDOMElement.setAttribute('id', elementId) + document.body.insertBefore(testDOMElement, null); + + chart = generateChart('Line', chartDataLine, {position: elementId}); + + // Set a delay on the test to ensure it doesn't overlap with the plugin native 'created' handler + chart.on('created', function () { + setTimeout(function () { + expect(testDOMElement.childNodes.length).to.equal(1); + var listElement = testDOMElement.querySelector("ul"); + expect(testDOMElement.childNodes[0]).to.equal(listElement); + + // Clean up + document.body.removeChild(testDOMElement); + done(); + }, 100) + }); + }); }); it('should allow to remove all series at once', function () { - chart = generateChart('Line', chart2DataLine, { removeAll: true }); - - chart.on('created', function () { - var seriesA = chart.container.querySelector('ul.ct-legend > .ct-series-0'); - - expect(chart.data.series.length).to.equal(1); - click(seriesA); - expect(chart.data.series.length).to.equal(0); - click(seriesA); - expect(chart.data.series.length).to.equal(1); - destroyChart(); - done(); - }); + chart = generateChart('Line', chart2DataLine, {removeAll: true}); + + chart.on('created', function () { + var seriesA = chart.container.querySelector('ul.ct-legend > .ct-series-0'); + + expect(chart.data.series.length).to.equal(1); + click(seriesA); + expect(chart.data.series.length).to.equal(0); + click(seriesA); + expect(chart.data.series.length).to.equal(1); + destroyChart(); + done(); + }); }) - describe('clickable', function() { - before(function(done) { + describe('clickable', function () { + before(function (done) { chart = generateChart('Line', chartDataLine, { clickable: true, - onClick: function(chart,e) { + onClick: function (chart) { chart.legendClicked = true; } }); - chart.on('created', function() { + chart.on('created', function () { chart.off('created'); done(); }); @@ -357,13 +362,13 @@ describe('Chartist plugin legend', function() { after(destroyChart); - it('should enforce a className for each series', function() { + it('should enforce a className for each series', function () { expect(chart.data.series[0].className).to.equal('ct-series-a'); expect(chart.data.series[1].className).to.equal('ct-series-b'); expect(chart.data.series[2].className).to.equal('ct-series-c'); }); - it('should hide a series after a click on the legend item', function() { + it('should hide a series after a click on the legend item', function () { var seriesA = chart.container.querySelector('ul.ct-legend > .ct-series-0'); var seriesB = chart.container.querySelector('ul.ct-legend > .ct-series-1'); var seriesC = chart.container.querySelector('ul.ct-legend > .ct-series-2'); @@ -400,7 +405,7 @@ describe('Chartist plugin legend', function() { expect(svgSeries2[2].className.baseVal).to.contain('ct-series-c'); }); - it('should update the legend item classes', function() { + it('should update the legend item classes', function () { var seriesA = chart.container.querySelector('ul.ct-legend > .ct-series-0'); var seriesB = chart.container.querySelector('ul.ct-legend > .ct-series-1'); var seriesC = chart.container.querySelector('ul.ct-legend > .ct-series-2'); @@ -420,40 +425,40 @@ describe('Chartist plugin legend', function() { // A click in the last active series should set all series active again. click(seriesA); click(seriesB); - var legendItems = chart.container.querySelectorAll('ul.ct-legend > li'); + legendItems = chart.container.querySelectorAll('ul.ct-legend > li'); expect(legendItems[0].className).to.equal('ct-series-0 inactive'); expect(legendItems[1].className).to.equal('ct-series-1 inactive'); expect(legendItems[2].className).to.equal('ct-series-2'); click(seriesC); - var inactiveItem = chart.container.querySelectorAll('ul.ct-legend > li.inactive'); + inactiveItem = chart.container.querySelectorAll('ul.ct-legend > li.inactive'); expect(inactiveItem.length).to.equal(0); }); - it('should call a function after a click on the legend item', function() { + it('should call a function after a click on the legend item', function () { var seriesB = chart.container.querySelector('ul.ct-legend > .ct-series-1'); click(seriesB); expect(chart.legendClicked).to.equal(true); - + //Clicking on an inactive series should also call the function. chart.legendClicked = false; click(seriesB); expect(chart.legendClicked).to.equal(true); }); }); - - describe('clickable with multiple series per legend item', function() { - before(function(done) { + + describe('clickable with multiple series per legend item', function () { + before(function (done) { chart = generateChart('Line', chartDataLine, { clickable: true, - onClick: function(chart,e) { + onClick: function (chart) { chart.legendClicked = true; }, - legendNames: [{name: 'Red-ish', series: [0,1]}, {name: 'Yellow', series: [2]}] + legendNames: [{name: 'Red-ish', series: [0, 1]}, {name: 'Yellow', series: [2]}] }); - chart.on('created', function() { + chart.on('created', function () { chart.off('created'); done(); }); @@ -461,13 +466,13 @@ describe('Chartist plugin legend', function() { after(destroyChart); - it('should enforce a className for each series', function() { + it('should enforce a className for each series', function () { expect(chart.data.series[0].className).to.equal('ct-series-a'); expect(chart.data.series[1].className).to.equal('ct-series-b'); expect(chart.data.series[2].className).to.equal('ct-series-c'); }); - it('should hide a series after a click on the legend item', function() { + it('should hide a series after a click on the legend item', function () { var seriesA = chart.container.querySelector('ul.ct-legend > .ct-series-0'); var seriesB = chart.container.querySelector('ul.ct-legend > .ct-series-1'); @@ -490,18 +495,18 @@ describe('Chartist plugin legend', function() { expect(svgSeries2[0].className.baseVal).to.contain('ct-series-a'); expect(svgSeries2[1].className.baseVal).to.contain('ct-series-b'); expect(svgSeries2[2].className.baseVal).to.contain('ct-series-c'); - + // Clicking on the first legend item should hide the two first series: click(seriesA); expect(chart.data.series.length).to.equal(1); expect(chart.data.series[0].name).to.equal('Purple pill'); - var svgSeries = chart.container.querySelectorAll('g.ct-series'); + svgSeries = chart.container.querySelectorAll('g.ct-series'); expect(svgSeries.length).to.equal(1); expect(svgSeries[0].className.baseVal).to.contain('ct-series-c'); // A second click should show the both series again. click(seriesA); - var svgSeries2 = chart.container.querySelectorAll('g.ct-series'); + svgSeries2 = chart.container.querySelectorAll('g.ct-series'); expect(svgSeries2.length).to.equal(3); expect(svgSeries2[0].className.baseVal).to.contain('ct-series-a'); expect(svgSeries2[1].className.baseVal).to.contain('ct-series-b'); @@ -518,7 +523,7 @@ describe('Chartist plugin legend', function() { expect(svgSeries2[2].className.baseVal).to.contain('ct-series-c'); }); - it('should update the legend item classes', function() { + it('should update the legend item classes', function () { var seriesA = chart.container.querySelector('ul.ct-legend > .ct-series-0'); var seriesB = chart.container.querySelector('ul.ct-legend > .ct-series-1'); @@ -535,35 +540,35 @@ describe('Chartist plugin legend', function() { // A click in the last active series should set all series active again. click(seriesA); - var legendItems = chart.container.querySelectorAll('ul.ct-legend > li'); + legendItems = chart.container.querySelectorAll('ul.ct-legend > li'); expect(legendItems[0].className).to.equal('ct-series-0 inactive'); expect(legendItems[1].className).to.equal('ct-series-1'); click(seriesB); - var inactiveItem = chart.container.querySelectorAll('ul.ct-legend > li.inactive'); + inactiveItem = chart.container.querySelectorAll('ul.ct-legend > li.inactive'); expect(inactiveItem.length).to.equal(0); }); - it('should call a function after a click on the legend item', function() { + it('should call a function after a click on the legend item', function () { var seriesB = chart.container.querySelector('ul.ct-legend > .ct-series-1'); click(seriesB); expect(chart.legendClicked).to.equal(true); - + //Clicking on an inactive series should also call the function. chart.legendClicked = false; click(seriesB); expect(chart.legendClicked).to.equal(true); }); }); - - describe('clickable for a pie', function() { - before(function(done) { + + describe('clickable for a pie', function () { + before(function (done) { chart = generateChart('Pie', chartDataPie, { clickable: true, }); - chart.on('created', function() { + chart.on('created', function () { chart.off('created'); done(); }); @@ -571,12 +576,12 @@ describe('Chartist plugin legend', function() { after(destroyChart); - it('should enforce a className for each series', function() { + it('should enforce a className for each series', function () { expect(chart.data.series[0].className).to.equal('ct-series-a'); expect(chart.data.series[1].className).to.equal('ct-series-b'); }); - it('should hide a series after a click on the legend item', function() { + it('should hide a series after a click on the legend item', function () { var seriesB = chart.container.querySelector('ul.ct-legend > .ct-series-1'); expect(chart.data.labels.length).to.equal(4);