1
0
mirror of https://github.com/lana-k/sqliteviz.git synced 2026-08-05 23:39:17 +08:00

Compare commits

...

4 Commits

Author SHA1 Message Date
lana-k
529c3390b3 #139 fix deselection in multi-trace charts 2026-07-26 20:34:08 +02:00
lana-k
acea6090e8 0.31.0 2026-07-26 17:55:10 +02:00
lana-k
130996b8c1 #139 tests 2026-07-26 17:54:06 +02:00
lana-k
6074ccc835 #139 tests 2026-07-26 14:59:08 +02:00
6 changed files with 350 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "sqliteviz", "name": "sqliteviz",
"version": "0.30.1", "version": "0.31.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"private": true, "private": true,
"type": "module", "type": "module",

View File

@@ -105,10 +105,10 @@ export default {
}, },
watch: { watch: {
dataSources() { dataSources() {
chartHelper.clearSelection(this.state.data, this.state.layout)
// we need to update state.data in order to update the graph // we need to update state.data in order to update the graph
// https://github.com/plotly/react-chart-editor/issues/948 // https://github.com/plotly/react-chart-editor/issues/948
if (this.dataSources) { if (this.dataSources) {
chartHelper.clearSelection(this.state.data, this.state.layout)
dereference.default(this.state.data, this.dataSources) dereference.default(this.state.data, this.dataSources)
this.updatePlotly() this.updatePlotly()
} }
@@ -152,14 +152,11 @@ export default {
this.$refs.plotlyEditor?.$el?.querySelector('.js-plotly-plot') this.$refs.plotlyEditor?.$el?.querySelector('.js-plotly-plot')
plotlyDiv?.on('plotly_selected', selectionEvent => { plotlyDiv?.on('plotly_selected', selectionEvent => {
if (selectionEvent) { if (selectionEvent) {
this.selectedItems = chartHelper.getRowsByIndexFromDataSources( this.selectedItems = this.getSelectedData()
this.dataSources,
selectionEvent.points.map(point => point.pointIndex)
)
} }
}) })
plotlyDiv?.on('plotly_deselect', () => { plotlyDiv?.on('plotly_deselect', () => {
this.selectedItems = null this.selectedItems = this.getSelectedData()
}) })
}, },
activated() { activated() {
@@ -219,6 +216,12 @@ export default {
}, },
prepareCopy(type = 'png') { prepareCopy(type = 'png') {
return chartHelper.getImageDataUrl(this.$refs.plotlyEditor.$el, type) return chartHelper.getImageDataUrl(this.$refs.plotlyEditor.$el, type)
},
getSelectedData() {
return chartHelper.getRowsByIndexFromDataSources(
this.dataSources,
chartHelper.getSelectedPointsIndexes(this.state.data)
)
} }
} }
} }

View File

@@ -13,6 +13,16 @@ export function getOptionsFromDataSources(dataSources) {
})) }))
} }
export function getSelectedPointsIndexes(stateData) {
if (!stateData) {
return []
}
return Array.from(
new Set(stateData.flatMap(data => data.selectedpoints || []))
)
}
export function getRowsByIndexFromDataSources(dataSources, rowIndexes) { export function getRowsByIndexFromDataSources(dataSources, rowIndexes) {
if (!dataSources) { if (!dataSources) {
return [] return []
@@ -98,5 +108,6 @@ export default {
getHtml, getHtml,
getChartData, getChartData,
getRowsByIndexFromDataSources, getRowsByIndexFromDataSources,
clearSelection clearSelection,
getSelectedPointsIndexes
} }

View File

@@ -244,4 +244,287 @@ describe('Chart.vue', () => {
) )
wrapper.unmount() wrapper.unmount()
}) })
it('selections are shown in value viewer', async () => {
const dataSources = {
name: ['Gryffindor', 'Hufflepuff', 'Ravenclaw', 'Slytherin'],
points: [100, 90, 95, 80]
}
const wrapper = mount(Chart, {
attachTo: document.body,
props: {
dataSources,
initOptions: {
data: [
{
type: 'scatter',
mode: 'markers',
x: ['Gryffindor', 'Hufflepuff', 'Ravenclaw', 'Slytherin'],
xsrc: 'name',
meta: {
columnNames: {
x: 'name',
y: 'points'
}
},
y: [100, 90, 95, 80],
ysrc: 'points',
selectedpoints: [1, 2]
}
],
layout: {
autosize: true,
mapbox: {
style: 'open-street-map'
},
dragmode: 'select',
selections: [
{
xref: 'x',
yref: 'y',
line: {
width: 1,
dash: 'dot'
},
type: 'rect',
x0: 0.6238202370500439,
y0: 98.1184336198663,
x1: 2.4324242756804213,
y1: 87.03915950334289
}
],
title: {
subtitle: {
text: 'Click to enter Plot subtitle'
}
},
xaxis: {
range: [-0.20324846356453027, 3.20324846356453],
autorange: true,
type: 'category'
},
yaxis: {
range: [78.4909264565425, 101.5090735434575],
autorange: true,
type: 'linear'
}
},
frames: []
},
showViewSettings: true,
showValueViewer: true
},
global: {
mocks: { $store }
}
})
await flushPromises()
const valueViewerText = wrapper
.findComponent({ name: 'ValueViewer' })
.text()
expect(valueViewerText).to.contain(`"name": "Hufflepuff"`)
expect(valueViewerText).to.contain(`"points": 90`)
expect(valueViewerText).to.contain(`"name": "Ravenclaw"`)
expect(valueViewerText).to.contain(`"points": 95`)
expect(valueViewerText).not.to.contain(`"name": "Gryffindor"`)
expect(valueViewerText).not.to.contain(`"points": 100`)
expect(valueViewerText).not.to.contain(`"name": "Slytherin"`)
expect(valueViewerText).not.to.contain(`"points": 80`)
wrapper.unmount()
})
it('clears selections on dataSources change', async () => {
const dataSources = {
name: ['Gryffindor', 'Hufflepuff', 'Ravenclaw', 'Slytherin'],
points: [100, 90, 95, 80]
}
const wrapper = mount(Chart, {
attachTo: document.body,
props: {
dataSources,
initOptions: {
data: [
{
type: 'scatter',
mode: 'markers',
x: ['Gryffindor', 'Hufflepuff', 'Ravenclaw', 'Slytherin'],
xsrc: 'name',
meta: {
columnNames: {
x: 'name',
y: 'points'
}
},
y: [100, 90, 95, 80],
ysrc: 'points',
selectedpoints: [1, 2]
}
],
layout: {
autosize: true,
mapbox: {
style: 'open-street-map'
},
dragmode: 'select',
selections: [
{
xref: 'x',
yref: 'y',
line: {
width: 1,
dash: 'dot'
},
type: 'rect',
x0: 0.6238202370500439,
y0: 98.1184336198663,
x1: 2.4324242756804213,
y1: 87.03915950334289
}
],
title: {
subtitle: {
text: 'Click to enter Plot subtitle'
}
},
xaxis: {
range: [-0.20324846356453027, 3.20324846356453],
autorange: true,
type: 'category'
},
yaxis: {
range: [78.4909264565425, 101.5090735434575],
autorange: true,
type: 'linear'
}
},
frames: []
},
showViewSettings: true,
showValueViewer: true
},
global: {
mocks: { $store }
}
})
await flushPromises()
const valueViewerText = wrapper
.findComponent({ name: 'ValueViewer' })
.text()
expect(valueViewerText).to.contain(`"name": "Hufflepuff"`)
expect(valueViewerText).to.contain(`"name": "Ravenclaw"`)
await wrapper.setProps({
dataSources: {
name: ['Gryffindor', 'Hufflepuff'],
points: [100, 90]
}
})
await flushPromises()
expect(wrapper.findComponent({ name: 'ValueViewer' }).text()).equal(
`No points selected to view`
)
wrapper.unmount()
})
it('clears selections on changes in chart settings', async () => {
const dataSources = {
name: ['Gryffindor', 'Hufflepuff', 'Ravenclaw', 'Slytherin'],
points: [100, 90, 95, 80]
}
const wrapper = mount(Chart, {
attachTo: document.body,
props: {
dataSources,
initOptions: {
data: [
{
type: 'scatter',
mode: 'markers',
x: ['Gryffindor', 'Hufflepuff', 'Ravenclaw', 'Slytherin'],
xsrc: 'name',
meta: {
columnNames: {
x: 'name',
y: 'points'
}
},
y: [100, 90, 95, 80],
ysrc: 'points',
selectedpoints: [1, 2]
}
],
layout: {
autosize: true,
mapbox: {
style: 'open-street-map'
},
dragmode: 'select',
selections: [
{
xref: 'x',
yref: 'y',
line: {
width: 1,
dash: 'dot'
},
type: 'rect',
x0: 0.6238202370500439,
y0: 98.1184336198663,
x1: 2.4324242756804213,
y1: 87.03915950334289
}
],
title: {
subtitle: {
text: 'Click to enter Plot subtitle'
}
},
xaxis: {
range: [-0.20324846356453027, 3.20324846356453],
autorange: true,
type: 'category'
},
yaxis: {
range: [78.4909264565425, 101.5090735434575],
autorange: true,
type: 'linear'
}
},
frames: []
},
showViewSettings: true,
showValueViewer: true
},
global: {
mocks: { $store }
}
})
await flushPromises()
const valueViewerText = wrapper
.findComponent({ name: 'ValueViewer' })
.text()
expect(valueViewerText).to.contain(`"name": "Hufflepuff"`)
expect(valueViewerText).to.contain(`"name": "Ravenclaw"`)
// Add another trace
await wrapper.find('button.js-add-button').wrapperElement.click()
await flushPromises()
expect(wrapper.findComponent({ name: 'ValueViewer' }).text()).equal(
`No points selected to view`
)
wrapper.unmount()
})
}) })

View File

@@ -21,6 +21,19 @@ describe('chartHelper.js', () => {
]) ])
}) })
it('getSelectedPointsIndexes returns unique selected point indexes', () => {
const stateData = [
{ selectedpoints: [1, 2, 3] },
{ selectedpoints: [3, 4] },
{ selectedpoints: [] },
{}
]
const indexes = chartHelper.getSelectedPointsIndexes(stateData)
expect(indexes).to.eql([1, 2, 3, 4])
})
it('getOptionsForSave', () => { it('getOptionsForSave', () => {
const state = { const state = {
data: [ data: [

View File

@@ -117,7 +117,11 @@ describe('storedInquiries.js', () => {
id: 'Yh1Hc9v7P3mRPZVM59QiD', id: 'Yh1Hc9v7P3mRPZVM59QiD',
query: 'SELECT * from test', query: 'SELECT * from test',
viewType: 'chart', viewType: 'chart',
viewOptions: 'some chart view options', viewOptions: {
data: [{ selectedpoints: [] }, {}],
layout: { selections: [{}, {}] },
frames: 'some chart frames'
},
name: 'student chart', name: 'student chart',
updatedAt: '2026-01-19T21:49:40.708Z', updatedAt: '2026-01-19T21:49:40.708Z',
createdAt: '2026-01-19T21:46:13.899Z' createdAt: '2026-01-19T21:46:13.899Z'
@@ -186,7 +190,11 @@ describe('storedInquiries.js', () => {
id: 'Yh1Hc9v7P3mRPZVM59QiD', id: 'Yh1Hc9v7P3mRPZVM59QiD',
query: 'SELECT * from test', query: 'SELECT * from test',
viewType: 'chart', viewType: 'chart',
viewOptions: 'some chart view options', viewOptions: {
data: [{}, {}],
layout: { selections: [] },
frames: 'some chart frames'
},
name: 'student chart', name: 'student chart',
updatedAt: '2026-01-19T21:49:40.708Z', updatedAt: '2026-01-19T21:49:40.708Z',
createdAt: '2026-01-19T21:46:13.899Z' createdAt: '2026-01-19T21:46:13.899Z'
@@ -275,7 +283,7 @@ describe('storedInquiries.js', () => {
const str = storedInquiries.serialiseInquiries(inquiryList) const str = storedInquiries.serialiseInquiries(inquiryList)
const parsedJson = JSON.parse(str) const parsedJson = JSON.parse(str)
expect(parsedJson.version).to.equal(4) expect(parsedJson.version).to.equal(5)
expect(parsedJson.inquiries).to.have.lengthOf(2) expect(parsedJson.inquiries).to.have.lengthOf(2)
expect(parsedJson.inquiries[1]).to.eql(inquiryList[1]) expect(parsedJson.inquiries[1]).to.eql(inquiryList[1])
expect(parsedJson.inquiries[0]).to.eql({ expect(parsedJson.inquiries[0]).to.eql({
@@ -336,7 +344,11 @@ describe('storedInquiries.js', () => {
"name": "foo", "name": "foo",
"query": "select * from foo", "query": "select * from foo",
"viewType": "chart", "viewType": "chart",
"viewOptions": [], "viewOptions": {
"data": [{"selectedpoints": []}, {}],
"layout": {"selections": [{}, {}]},
"frames": "some chart frames"
},
"createdAt": "2020-11-03T14:17:49.524Z" "createdAt": "2020-11-03T14:17:49.524Z"
}, },
{ {
@@ -401,7 +413,11 @@ describe('storedInquiries.js', () => {
name: 'foo', name: 'foo',
query: 'select * from foo', query: 'select * from foo',
viewType: 'chart', viewType: 'chart',
viewOptions: [], viewOptions: {
data: [{}, {}],
layout: { selections: [] },
frames: 'some chart frames'
},
createdAt: '2020-11-03T14:17:49.524Z' createdAt: '2020-11-03T14:17:49.524Z'
}, },
{ {
@@ -607,7 +623,11 @@ describe('storedInquiries.js', () => {
"name": "foo", "name": "foo",
"query": "select * from foo", "query": "select * from foo",
"viewType": "chart", "viewType": "chart",
"viewOptions": [], "viewOptions": {
"data": [{"selectedpoints": []}, {}],
"layout": {"selections": [{}, {}]},
"frames": "some chart frames"
},
"createdAt": "2020-11-03T14:17:49.524Z" "createdAt": "2020-11-03T14:17:49.524Z"
}, },
{ {
@@ -673,7 +693,11 @@ describe('storedInquiries.js', () => {
name: 'foo', name: 'foo',
query: 'select * from foo', query: 'select * from foo',
viewType: 'chart', viewType: 'chart',
viewOptions: [], viewOptions: {
data: [{}, {}],
layout: { selections: [] },
frames: 'some chart frames'
},
createdAt: '2020-11-03T14:17:49.524Z' createdAt: '2020-11-03T14:17:49.524Z'
}, },
{ {
@@ -735,7 +759,7 @@ describe('storedInquiries.js', () => {
it('readPredefinedInquiries', async () => { it('readPredefinedInquiries', async () => {
const str = `{ const str = `{
"version": 4, "version": 5,
"inquiries": [ "inquiries": [
{ {
"id": 1, "id": 1,