Skip to content
Commits on Source (3)
q2-taxa (2019.7.0-1) UNRELEASED; urgency=medium
* New upstream version
-- Liubov Chuprikova <chuprikovalv@gmail.com> Sat, 07 Sep 2019 14:39:31 +0200
q2-taxa (2019.4.0-1) unstable; urgency=medium
* Initial release (Closes: #932050)
......
......@@ -23,9 +23,9 @@ def get_keywords():
# setup.py/versioneer.py will grep for the variable names, so they must
# each be defined on a line of their own. _version.py will just call
# get_keywords().
git_refnames = " (tag: 2019.4.0)"
git_full = "b68f7f2cf216a6dd942c115337a4c0eaeda84006"
git_date = "2019-05-03 04:14:42 +0000"
git_refnames = " (tag: 2019.7.0)"
git_full = "bc018fe8540f9f0fb4f4cf6729d840a53f5d8c31"
git_date = "2019-07-30 18:15:55 +0000"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
......
......@@ -24,6 +24,11 @@ TEMPLATES = pkg_resources.resource_filename('q2_taxa', 'assets')
def barplot(output_dir: str, table: pd.DataFrame, taxonomy: pd.Series,
metadata: Metadata) -> None:
ids_not_in_metadata = set(table.index) - set(metadata.ids)
if ids_not_in_metadata:
raise ValueError('Feature IDs found in the table are missing in the '
f'metadata: {ids_not_in_metadata!r}.')
metadata = metadata.to_dataframe()
jsonp_files, csv_files = [], []
collapsed_tables = _extract_to_level(taxonomy, table)
......
......@@ -2,8 +2,8 @@ import { select } from 'd3';
import render from './render';
import {
availableColorSchemes,
addTaxaPicker,
addWidthSlider,
addColorPicker,
addSortByPicker,
addDownloadLinks,
......@@ -12,9 +12,20 @@ import { setupData, sort } from './data';
import plotLegend from './legend';
export default function init(level) {
/* Re-initializes the display.
*
* "state" is an object that should contain the following entries:
*
* level -- an integer indicating the currently selected index in the
* "Taxonomic Level" dropdown (starts at 0)
*
* colorScheme -- the name of the current color scheme
*
* barWidth -- an integer indicating the currently selected bar width value
*/
export default function init(state) {
/* global d */
const data = d[level].data;
const data = d[state.level].data;
// DOM
const body = select('body .container-fluid');
......@@ -47,19 +58,19 @@ export default function init(level) {
.style('font', '12px sans-serif')
.text('Sample');
const initialColorScheme = availableColorSchemes[0].name;
const dataMeta = setupData(d[level], svgBar);
const dataMeta = setupData(d[state.level], svgBar);
const { sortedKeysReverse, levels } = dataMeta;
const initialSort = sort(data, [sortedKeysReverse[0]], ['Ascending'], [false], dataMeta);
const chartInfo = render(svgBar, initialColorScheme, initialSort, dataMeta);
const chartInfo = render(svgBar, state.colorScheme, initialSort, dataMeta, state.barWidth);
plotLegend(legendCol, chartInfo);
// Controls
const ctrlRowOne = controls.append('div').attr('class', 'row');
addDownloadLinks(ctrlRowOne, svgBar, legendCol.select('svg'), level + 1);
addTaxaPicker(ctrlRowOne, levels, level + 1);
addColorPicker(ctrlRowOne, svgBar, legendCol, data, dataMeta);
addDownloadLinks(ctrlRowOne, svgBar, legendCol.select('svg'), state.level + 1);
addTaxaPicker(ctrlRowOne, levels, state.level + 1);
addColorPicker(ctrlRowOne, svgBar, legendCol, data, dataMeta, state.colorScheme);
addSortByPicker(ctrlRowOne, svgBar, data, dataMeta);
addWidthSlider(ctrlRowOne, svgBar, data, dataMeta, state.barWidth);
}
import init from './init';
import { getBarWidth, getColorScheme } from './toolbar';
init(0);
init({ level: 0, colorScheme: getColorScheme(), barWidth: getBarWidth() });
......@@ -14,9 +14,9 @@ import { availableColorSchemes } from './toolbar';
export const transitionDur = 500;
export default function render(svg, colorScheme, xOrdering, dataMeta) {
export default function render(svg, colorScheme, xOrdering, dataMeta, barWidth) {
const { sortMap, sortedSampleIDs } = xOrdering;
const width = sortedSampleIDs.length * 10;
const width = sortedSampleIDs.length * barWidth;
const height = 600;
const margin = { top: 20, left: 60, right: 0, bottom: 50 };
const { keys } = dataMeta;
......
......@@ -27,7 +27,30 @@ export const availableColorSchemes = [
{ name: 'Spectral', scheme: d3chromo.interpolateSpectral, type: 's' },
];
export const defaultBarWidth = 10;
// HELPERS
export function getBarWidth() {
let barWidth = defaultBarWidth;
// This line derived from https://stackoverflow.com/a/20154105/10730311.
const slider = select('#barWidthSlider').node();
if (slider !== null && slider !== undefined) {
barWidth = slider.value;
}
return barWidth;
}
export function getColorScheme() {
// this is just how initialColorScheme was set in init() -- default to the
// first listed color scheme (currently, this is schemeAccent)
let scheme = availableColorSchemes[0].name;
const sel = select('#colorPickerSelect').node();
if (sel !== null && sel !== undefined) {
scheme = sel.value;
}
return scheme;
}
function _getSort(sel, svg, data, dataMeta) {
const sorts = sel.selectAll('.xCtrl').nodes().map(d => d.options[d.selectedIndex].value);
const orders = sel.selectAll('.xOrder').nodes().map(d => d.options[d.selectedIndex].value);
......@@ -37,7 +60,7 @@ function _getSort(sel, svg, data, dataMeta) {
function _updateSort(sel, svg, data, dataMeta) {
const xOrdering = _getSort(sel, svg, data, dataMeta);
render(svg, svg.property('colorScheme'), xOrdering, dataMeta);
render(svg, svg.property('colorScheme'), xOrdering, dataMeta, getBarWidth());
}
function _appendSortByPicker(sel, svg, data, dataMeta) {
......@@ -111,9 +134,11 @@ export function addTaxaPicker(row, levels, selectedLevel) {
grp.append('select')
.attr('class', 'form-control')
.on('change', function appendTaxaPicker() {
const currBarWidth = getBarWidth();
const currColorScheme = getColorScheme();
const container = select('.container-fluid');
container.select('.viz.row').remove();
init(this.selectedIndex);
init({ level: this.selectedIndex, colorScheme: currColorScheme, barWidth: currBarWidth });
})
.selectAll('option')
.data(levels)
......@@ -125,7 +150,30 @@ export function addTaxaPicker(row, levels, selectedLevel) {
return grp;
}
export function addColorPicker(row, svg, legendCol, data, dataMeta) {
/* Adds a slider to let users control the width of the barplot's bars.
*
* This function was cobbled together from parts of code from
* addColorPicker(), addSortByPicker(), and _updateSort().
*/
export function addWidthSlider(row, svg, data, dataMeta, currentValue) {
const grp = row.append('div').attr('class', 'col-lg-2 form-group widthSlider');
grp.append('label').text('Bar Width');
grp.append('input')
.attr('type', 'range')
.attr('id', 'barWidthSlider')
.attr('min', '10')
.attr('max', '80')
.attr('value', currentValue)
.attr('class', 'form-control')
.style('padding', '0px')
.on('input', () => {
const xOrdering = _getSort(row, svg, data, dataMeta);
render(svg, svg.property('colorScheme'), xOrdering, dataMeta, getBarWidth());
});
return grp;
}
export function addColorPicker(row, svg, legendCol, data, dataMeta, currentValue) {
const grp = row.append('div').attr('class', 'col-lg-2 form-group colorPicker');
grp.append('label').text('Color Palette');
grp.append('a')
......@@ -138,10 +186,11 @@ export function addColorPicker(row, svg, legendCol, data, dataMeta) {
.attr('class', 'glyphicon glyphicon-info-sign');
const sel = grp.append('select')
.attr('class', 'form-control')
.attr('id', 'colorPickerSelect')
.on('change', function changeColorPicker() {
const colorScheme = this.options[this.selectedIndex].value;
const xOrdering = _getSort(row, svg, data, dataMeta);
const chartInfo = render(svg, colorScheme, xOrdering, dataMeta);
const chartInfo = render(svg, colorScheme, xOrdering, dataMeta, getBarWidth());
plotLegend(legendCol, chartInfo);
});
......@@ -165,6 +214,14 @@ export function addColorPicker(row, svg, legendCol, data, dataMeta) {
.attr('value', d => d.name)
.text(d => d.name);
// Set the selected color scheme
// (useful if init() is called after the user has been playing with the
// visualization -- for example, when the taxonomic level is adjusted)
//
// Also, note that we have to use .property() instead of .attr() here. See
// https://stackoverflow.com/a/28933863/10730311 for context.
sel.property('value', currentValue);
return grp;
}
......
......@@ -18,21 +18,47 @@ from q2_taxa import barplot
class BarplotTests(unittest.TestCase):
def test_barplot(self):
table = pd.DataFrame([[2.0, 2.0], [1.0, 1.0], [9.0, 8.0], [0.0, 4.0]],
def setUp(self):
self.table = pd.DataFrame([[2.0, 2.0], [1.0, 1.0], [9.0, 8.0],
[0.0, 4.0]],
index=['A', 'B', 'C', 'D'],
columns=['feat1', 'feat2'])
taxonomy = pd.Series(['a; b; c', 'a; b; d'],
self.taxonomy = pd.Series(['a; b; c', 'a; b; d'],
index=['feat1', 'feat2'])
def test_barplot(self):
metadata = qiime2.Metadata(
pd.DataFrame({'val1': ['1.0', '2.0', '3.0', '4.0']},
index=pd.Index(['A', 'B', 'C', 'D'], name='id')))
with tempfile.TemporaryDirectory() as output_dir:
barplot(output_dir, table, taxonomy, metadata)
barplot(output_dir, self.table, self.taxonomy, metadata)
index_fp = os.path.join(output_dir, 'index.html')
self.assertTrue(os.path.exists(index_fp))
self.assertTrue("src='level-1.jsonp?callback=load_data'" in
open(index_fp).read())
csv_lvl3_fp = os.path.join(output_dir, 'level-3.csv')
self.assertTrue(os.path.exists(csv_lvl3_fp))
def test_barplot_metadata_extra_id(self):
metadata = qiime2.Metadata(
pd.DataFrame({'val1': ['1.0', '2.0', '3.0', '4.0', '5.0']},
index=pd.Index(['A', 'B', 'C', 'D', 'E'], name='id')))
with tempfile.TemporaryDirectory() as output_dir:
barplot(output_dir, self.table, self.taxonomy, metadata)
index_fp = os.path.join(output_dir, 'index.html')
self.assertTrue(os.path.exists(index_fp))
self.assertTrue("src='level-1.jsonp?callback=load_data'" in
open(index_fp).read())
csv_lvl3_fp = os.path.join(output_dir, 'level-3.csv')
self.assertTrue(os.path.exists(csv_lvl3_fp))
def test_barplot_metadata_missing_id(self):
metadata = qiime2.Metadata(
pd.DataFrame({'val1': ['1.0', '2.0', '3.0']},
index=pd.Index(['A', 'B', 'C'], name='id')))
with tempfile.TemporaryDirectory() as output_dir:
with self.assertRaisesRegex(ValueError, 'missing.*D'):
barplot(output_dir, self.table, self.taxonomy, metadata)