diff --git a/docs/pages/datavisualisation/lines/doc.md b/docs/pages/datavisualisation/lines/doc.md index 8ead3ddf1..82e02b781 100644 --- a/docs/pages/datavisualisation/lines/doc.md +++ b/docs/pages/datavisualisation/lines/doc.md @@ -1,7 +1,3 @@ ---- -badge: unstable ---- - Component do draw dead simple line charts. :::sample-card diff --git a/lib/datavisualisations/ColumnChart.vue b/lib/datavisualisations/ColumnChart.vue index 814eb9168..eaddd9404 100644 --- a/lib/datavisualisations/ColumnChart.vue +++ b/lib/datavisualisations/ColumnChart.vue @@ -152,11 +152,11 @@ export default { }, }, mounted () { - window.addEventListener('resize', this.onResize) - this.onResize() + window.addEventListener('resize', this.setSizes) + this.setSizes() }, beforeDestroy () { - window.removeEventListener('resize', this.onResize) + window.removeEventListener('resize', this.setSizes) }, watch: { width () { @@ -174,7 +174,7 @@ export default { this.initialize() this.update() }, - onResize() { + setSizes() { this.width = this.$el.offsetWidth this.height = this.$el.offsetWidth * this.baseHeightRatio }, diff --git a/lib/datavisualisations/LineChart.vue b/lib/datavisualisations/LineChart.vue index 2649581a3..f46e7ce02 100644 --- a/lib/datavisualisations/LineChart.vue +++ b/lib/datavisualisations/LineChart.vue @@ -45,6 +45,14 @@ export default { type: String, default: 'value' }, + /** + * Argument for x-axis ticks + * @see https://github.com/d3/d3-axis#axis_ticks + */ + xAxisTicks: { + type: [Object, Number, Function], + default: null + }, /** * Function to apply to format y axis ticks */ @@ -53,10 +61,11 @@ export default { default: identity }, /** - * Number of y axis ticks + * Argument for y-axis ticks + * @see https://github.com/d3/d3-axis#axis_ticks */ yAxisTicks: { - type: Number, + type: [Object, Number, Function], default: 5 } }, @@ -64,8 +73,7 @@ export default { return { width: 0, height: 0, - line: null, - points: [] + line: null } }, computed: { @@ -150,17 +158,18 @@ export default { this.scale.x.domain(d3.extent(this.formattedData, d => d.date)) this.scale.y.domain([0, d3.max(this.formattedData, d => d[this.seriesName])]) - this.points = this.formattedData.map(d => { + const points = this.formattedData.map(d => { return { x: this.scale.x(d.date), y: this.scale.y(d[this.seriesName]), } }) - this.line = this.createLine(this.points) + this.line = this.createLine(points) d3.select(this.$el).select(".line-chart__axis--x") .call(d3.axisBottom(this.scale.x) + .ticks(this.xAxisTicks) .tickFormat(d => castCall(this.xAxisYearFormat, d.getFullYear()))) d3.select(this.$el).select(".line-chart__axis--y") diff --git a/lib/mixins/chart.js b/lib/mixins/chart.js index 3df21dad9..190e00b5e 100644 --- a/lib/mixins/chart.js +++ b/lib/mixins/chart.js @@ -69,10 +69,10 @@ export default { } }, elementsMaxBBox ({ selector = 'text', defaultWidth = null, defaultHeight = null } = { }) { - if (!this.mounted) { + const elements = this.mounted ? this.$el.querySelectorAll(selector) : [] + if (!elements.length) { return { width: defaultWidth, height: defaultHeight } } - const elements = this.$el.querySelectorAll(selector) const width = max([...elements].map(l => { return l.getBBox ? l.getBBox().width : defaultWidth })) diff --git a/tests/unit/datavisualisations/ColumnChart.spec.js b/tests/unit/datavisualisations/ColumnChart.spec.js index dbf973a9d..dfdc2db79 100644 --- a/tests/unit/datavisualisations/ColumnChart.spec.js +++ b/tests/unit/datavisualisations/ColumnChart.spec.js @@ -1,6 +1,6 @@ import * as d3 from 'd3' import { mount } from '@vue/test-utils' -import ColumChart from '@/datavisualisations/ColumnChart.vue' +import ColumnChart from '@/datavisualisations/ColumnChart.vue' jest.mock('d3', () => { return { @@ -39,9 +39,9 @@ describe('ColumnChart.vue', () => { ] } - wrapper = mount(ColumChart, { propsData }) + wrapper = mount(ColumnChart, { propsData }) wrapper.vm.$el.style.width = '500px' - wrapper.vm.onResize() + wrapper.vm.setSizes() await wrapper.vm.$nextTick() }) @@ -107,9 +107,9 @@ describe('ColumnChart.vue', () => { beforeEach(async () => { const propsData = { data: 'http://localhost/data.json' } - wrapper = mount(ColumChart, { propsData }) + wrapper = mount(ColumnChart, { propsData }) wrapper.vm.$el.style.width = '500px' - wrapper.vm.onResize() + wrapper.vm.setSizes() await wrapper.vm.$nextTick() }) @@ -156,24 +156,28 @@ describe('ColumnChart.vue', () => { beforeAll(async () => { d3.csv = jest.fn().mockReturnValue([ - { date: 2000, value: 0, highlight: false }, - { date: 2001, value: 10, highlight: false }, - { date: 2002, value: 20, highlight: false }, - { date: 2003, value: 30, highlight: false }, - { date: 2004, value: 40, highlight: false }, - { date: 2005, value: 50, highlight: true }, - { date: 2006, value: 60, highlight: false }, - { date: 2007, value: 70, highlight: true }, - { date: 2008, value: 80, highlight: false }, - { date: 2009, value: 90, highlight: false } + { date: 2000, indicator: 0, highlight: false }, + { date: 2001, indicator: 10, highlight: false }, + { date: 2002, indicator: 20, highlight: false }, + { date: 2003, indicator: 30, highlight: false }, + { date: 2004, indicator: 40, highlight: false }, + { date: 2005, indicator: 50, highlight: true }, + { date: 2006, indicator: 60, highlight: false }, + { date: 2007, indicator: 70, highlight: true }, + { date: 2008, indicator: 80, highlight: false }, + { date: 2009, indicator: 90, highlight: false } ]) }) beforeEach(async () => { - const propsData = { data: 'http://localhost/data.csv', dataUrlType: 'csv' } - wrapper = mount(ColumChart, { propsData }) + const propsData = { + data: 'http://localhost/data.csv', + dataUrlType: 'csv', + seriesName: 'indicator' + } + wrapper = mount(ColumnChart, { propsData }) wrapper.vm.$el.style.width = '500px' - wrapper.vm.onResize() + wrapper.vm.setSizes() await wrapper.vm.$nextTick() }) diff --git a/tests/unit/datavisualisations/LineChart.spec.js b/tests/unit/datavisualisations/LineChart.spec.js new file mode 100644 index 000000000..d8c61e4a3 --- /dev/null +++ b/tests/unit/datavisualisations/LineChart.spec.js @@ -0,0 +1,158 @@ +import * as d3 from 'd3' +import { mount } from '@vue/test-utils' +import LineChart from '@/datavisualisations/LineChart.vue' + +jest.mock('d3', () => { + return { + __esModule: true, + ...jest.requireActual('d3') + } +}) + + +// Mock HTML element offset so the size of the chart can be calculated +// dynamicly using JSDOM and tests +Object.defineProperties(window.HTMLElement.prototype, { + offsetWidth: { + get () { return parseFloat(this.style.width) || 0 } + }, + offsetHeight: { + get () { return parseFloat(this.style.height) || 0 } + } +}) + +describe('LineChart.vue', () => { + + describe('a single chart', () => { + + let wrapper + + beforeEach(async () => { + + const propsData = { + xAxisTicks: d3.timeYear.every(1), + data: [ + { date: 2000, value: 0 }, + { date: 2001, value: 1 }, + { date: 2002, value: 2 }, + { date: 2003, value: 3 }, + { date: 2004, value: 4 } + ] + } + + wrapper = mount(LineChart, { propsData }) + wrapper.vm.$el.style.width = '500px' + wrapper.vm.setSizes() + await wrapper.vm.$nextTick() + }) + + it('is a Vue instance', () => { + expect(wrapper.vm).toBeTruthy() + }) + + it('creates five x-axis ticks', async () => { + expect(wrapper.findAll('.line-chart__axis--x .tick')).toHaveLength(5) + }) + + it('creates x-axis ticks with the right years ', async () => { + const ticks = wrapper.findAll('.line-chart__axis--x .tick') + expect(ticks.at(0).text()).toBe('2000') + expect(ticks.at(1).text()).toBe('2001') + expect(ticks.at(2).text()).toBe('2002') + expect(ticks.at(3).text()).toBe('2003') + expect(ticks.at(4).text()).toBe('2004') + }) + }) + + describe('a two steps line chart using remote JSON', () => { + + let wrapper + + beforeEach(async () => { + d3.json = jest.fn().mockReturnValue([ + { date: 2019, value: 50 }, + { date: 2020, value: 100 } + ]) + + const propsData = { + data: 'http://localhost/data.json', + xAxisTicks: d3.timeYear.every(1) + } + wrapper = mount(LineChart, { propsData }) + wrapper.vm.$el.style.width = '500px' + wrapper.vm.setSizes() + await wrapper.vm.$nextTick() + }) + + it('is a Vue instance', () => { + expect(wrapper.vm).toBeTruthy() + }) + + it('creates two x-axis ticks', async () => { + expect(wrapper.findAll('.line-chart__axis--x .tick')).toHaveLength(2) + }) + + it('creates x-axis ticks with the right years ', async () => { + const ticks = wrapper.findAll('.line-chart__axis--x .tick') + expect(ticks.at(0).text()).toBe('2019') + expect(ticks.at(1).text()).toBe('2020') + }) + }) + + describe('a 10 columns chart with two highlights using remote CSV', () => { + + let wrapper + + beforeEach(async () => { + d3.csv = jest.fn().mockReturnValue([ + { date: 2000, indicator: 0, highlight: false }, + { date: 2001, indicator: 10, highlight: false }, + { date: 2002, indicator: 20, highlight: false }, + { date: 2003, indicator: 30, highlight: false }, + { date: 2004, indicator: 40, highlight: false }, + { date: 2005, indicator: 50, highlight: true }, + { date: 2006, indicator: 60, highlight: false }, + { date: 2007, indicator: 70, highlight: true }, + { date: 2008, indicator: 80, highlight: false }, + { date: 2009, indicator: 90, highlight: false } + ]) + + const propsData = { + data: 'http://localhost/data.csv', + dataUrlType: 'csv', + xAxisTicks: d3.timeYear.every(1), + seriesName: 'indicator' + } + wrapper = mount(LineChart, { propsData }) + wrapper.vm.$el.style.width = '500px' + wrapper.vm.setSizes() + await wrapper.vm.$nextTick() + }) + + afterEach(async () => { + wrapper.destroy() + }) + + it('is a Vue instance', () => { + expect(wrapper.vm).toBeTruthy() + }) + + it('creates ten x-axis ticks', () => { + expect(wrapper.findAll('.line-chart__axis--x .tick')).toHaveLength(10) + }) + + it('creates x-axis ticks with the right years', () => { + const ticks = wrapper.findAll('.line-chart__axis--x .tick') + expect(ticks.at(0).text()).toBe('2000') + expect(ticks.at(1).text()).toBe('2001') + expect(ticks.at(2).text()).toBe('2002') + expect(ticks.at(3).text()).toBe('2003') + expect(ticks.at(4).text()).toBe('2004') + expect(ticks.at(5).text()).toBe('2005') + expect(ticks.at(6).text()).toBe('2006') + expect(ticks.at(7).text()).toBe('2007') + expect(ticks.at(8).text()).toBe('2008') + expect(ticks.at(9).text()).toBe('2009') + }) + }) +})