Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

maintain ordinal axis sort on redraw #279

Open
wants to merge 18 commits into
base: dev-v1.11.7
Choose a base branch
from
7,063 changes: 4,202 additions & 2,861 deletions build/webcharts.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions build/webcharts.min.js

Large diffs are not rendered by default.

11 changes: 4 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 25 additions & 40 deletions src/chart/draw/consolidateData/setDomain.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,69 @@ import naturalSorter from '../../../dataOps/naturalSorter';
import { set, merge, ascending, nest, min, extent } from 'd3';

export default function setDomain(axis) {
const otherAxis = axis === 'x' ? 'y' : 'x';
const thisAxis = this.config[axis];
const thatAxis = this.config[axis === 'x' ? 'y' : 'x'];
let dom;

if (this.config[axis].type === 'ordinal') {
if (thisAxis.type === 'ordinal') {
//ordinal domains
if (this.config[axis].domain) {
if (thisAxis.domain) {
//user-defined domain
this[axis + '_dom'] = this.config[axis].domain;
} else if (this.config[axis].order) {
dom = thisAxis.domain;
} else if (thisAxis.order) {
//data-driven domain with user-defined domain order
this[axis + '_dom'] = set(merge(this.marks.map(mark => mark[axis + '_dom'])))
dom = set(merge(this.marks.map(mark => mark[axis + '_dom'])))
.values()
.sort((a, b) =>
ascending(
this.config[axis].order.indexOf(a),
this.config[axis].order.indexOf(b)
)
);
} else if (this.config[axis].sort && this.config[axis].sort === 'alphabetical-ascending') {
.sort((a, b) => ascending(thisAxis.order.indexOf(a), thisAxis.order.indexOf(b)));
} else if (thisAxis.sort && thisAxis.sort === 'alphabetical-ascending') {
//data-driven domain with user-defined domain sort algorithm that sorts the axis
//alphanumerically, first to last
this[axis + '_dom'] = set(merge(this.marks.map(mark => mark[axis + '_dom'])))
dom = set(merge(this.marks.map(mark => mark[axis + '_dom'])))
.values()
.sort(naturalSorter);
} else if (
['time', 'linear'].indexOf(this.config[otherAxis].type) > -1 &&
this.config[axis].sort === 'earliest'
) {
} else if (['time', 'linear'].indexOf(thatAxis.type) > -1 && thisAxis.sort === 'earliest') {
//data-driven domain plotted against a time or linear axis that sorts the axis values
//by earliest event/datum; generally used with timeline charts
this[axis + '_dom'] = nest()
.key(d => d[this.config[axis].column])
dom = nest()
.key(d => d[thisAxis.column])
.rollup(d => {
return d
.map(m => m[this.config[otherAxis].column])
.filter(f => f instanceof Date);
return d.map(m => m[thatAxis.column]).filter(f => f instanceof Date);
})
.entries(this.filtered_data)
.sort((a, b) => min(b.values) - min(a.values))
.map(m => m.key);
} else if (
!this.config[axis].sort ||
this.config[axis].sort === 'alphabetical-descending'
) {
} else if (!thisAxis.sort || thisAxis.sort === 'alphabetical-descending') {
//data-driven domain with default/user-defined domain sort algorithm that sorts the
//axis alphanumerically, last to first
this[axis + '_dom'] = set(merge(this.marks.map(mark => mark[axis + '_dom'])))
dom = set(merge(this.marks.map(mark => mark[axis + '_dom'])))
.values()
.sort(naturalSorter)
.reverse();
} else {
//data-driven domain with an invalid user-defined sort algorithm that captures a unique
//set of values as they appear in the data
this[axis + '_dom'] = set(merge(this.marks.map(mark => mark[axis + '_dom']))).values();
dom = set(merge(this.marks.map(mark => mark[axis + '_dom']))).values();
}
} else if (
this.config.marks
.map(m => m['summarize' + axis.toUpperCase()] === 'percent')
.indexOf(true) > -1
) {
//rate domains run from 0 to 1
this[axis + '_dom'] = [0, 1];
dom = [0, 1];
} else {
//continuous domains run from the minimum to the maximum raw (or is it summarized...?) value
//TODO: they should really run from the minimum to the maximum summarized value, e.g. a
//TODO: means over time chart should plot over the range of the means, not the range of the
//TODO: raw data
this[axis + '_dom'] = extent(merge(this.marks.map(mark => mark[axis + '_dom'])));
dom = extent(merge(this.marks.map(mark => mark[axis + '_dom'])));
}

//Give the domain a range when the range of the variable is 0.
if (this.config[axis].type === 'linear' && this[axis + '_dom'][0] === this[axis + '_dom'][1])
this[axis + '_dom'] =
this[axis + '_dom'][0] !== 0
? [
this[axis + '_dom'][0] - this[axis + '_dom'][0] * 0.01,
this[axis + '_dom'][1] + this[axis + '_dom'][1] * 0.01
]
: [-1, 1];
if (thisAxis.type === 'linear' && dom[0] === dom[1])
dom = dom[0] !== 0 ? [dom[0] - dom[0] * 0.01, dom[1] + dom[1] * 0.01] : [-1, 1];

return this[axis + '_dom'];
this[axis + '_dom'] = dom;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea to keep that in a var


return dom;
}
11 changes: 7 additions & 4 deletions src/chart/draw/consolidateData/transformData.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export default function transformData(raw, mark) {
let filt1_dom_x = extent(merge(filt1_xs));
let filt1_dom_y = extent(merge(filt1_ys));

// why are we calling makeNest twice?
let current_nested = makeNest.call(this, mark, filtered, sublevel);

let flex_dom_x = current_nested.dom_x;
Expand Down Expand Up @@ -208,12 +209,14 @@ export default function transformData(raw, mark) {
}

if (config.x.type === 'ordinal' && !config.x.order) {
//config.x.order = current_nested.totalOrder;
x_dom.sort((a,b) => current_nested.totalOrder.indexOf(a) - current_nested.totalOrder.indexOf(b))
x_dom.sort(
(a, b) => current_nested.totalOrder.indexOf(a) - current_nested.totalOrder.indexOf(b)
);
}
if (config.y.type === 'ordinal' && !config.y.order) {
//config.y.order = current_nested.totalOrder;
y_dom.sort((a,b) => current_nested.totalOrder.indexOf(a) - current_nested.totalOrder.indexOf(b))
y_dom.sort(
(a, b) => current_nested.totalOrder.indexOf(a) - current_nested.totalOrder.indexOf(b)
);
}

this.current_data = current_nested.nested;
Expand Down
2 changes: 1 addition & 1 deletion src/chart/draw/consolidateData/transformData/makeNest.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export default function makeNest(mark, entries, sublevel) {
(this.config.y.sort === 'total-ascending' && this.config.y.type == 'ordinal')
) {
totalOrder = test.sort((a, b) => descending(+a.total, +b.total)).map(m => m.key);
}
} else totalOrder = test.map(m => m.key);

return { nested: test, dom_x: dom_x, dom_y: dom_y, totalOrder: totalOrder };
}
43 changes: 36 additions & 7 deletions test-page/simpleBarchart/index.html
Original file line number Diff line number Diff line change
@@ -1,22 +1,51 @@
<html lang="en">
<html lang = 'en'>
<head>
<title>Webcharts - Simple Bar Chart</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv = 'Content-Type' content = 'text/html; charset = utf-8'>

<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="../../build/webcharts.js"></script>
<script type = 'text/javascript' src = 'https://d3js.org/d3.v3.min.js'></script>
<script type = 'text/javascript' src = '../../build/webcharts.js'></script>

<link rel="stylesheet" href="../../css/webcharts.css">
<link type = 'text/css' rel = 'stylesheet' href = '../../css/webcharts.css'>
<link type = 'text/css' rel = 'stylesheet' href = '../index.css'>

<style>
#container {
position: relative;
}
.controls {
width: 100%;
}
.charts {
position: relative;
}
.chart {
position: absolute;
top: 0;
}
.chart--vertical {
width: 25%;
left: 0
}
.chart--horizontal {
width: 75%;
height: 500px;
left: 27.5%;
}
</style>
</head>

<body>
<div id = 'title'>Webcharts</div>
<div id = 'subtitle'>Simple Bar Chart</div>
<div id = 'container'>
<div class="chart"></div>
<div class = 'controls'></div>
<div class = 'charts'>
<div class = 'chart chart--vertical'></div>
<div class = 'chart chart--horizontal'></div>
</div>
</div>
</body>

<script src="index.js" defer></script>
<script src = 'index.js' defer></script>
</html>
104 changes: 78 additions & 26 deletions test-page/simpleBarchart/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,45 @@ d3.csv(
},
function(data) {
const settings = {
max_width: '500',
x: {
type: 'linear',
label: 'Total Gold Medals',
column: 'n_combined_gold',
domain: [0, null],
},
y: {
type: 'ordinal',
label: 'Country (IOC code)',
column: 'country_code',
sort: 'total-descending',
range_band: 10,
},
marks: [
{
type: 'bar',
per: ['country_code'],
tooltip: '[country_code] won [n_combined_gold] medals'
tooltip: '[country_code] won $ medals'
}
],
gridlines: 'x'
resizable: false,
};
const linearAxis = {
type: 'linear',
label: 'Total Gold Medals',
column: 'n_combined_gold',
};
const ordinalAxis = {
type: 'ordinal',
label: 'Country (IOC code)',
column: 'country_code',
sort: 'total-descending',
range_band: 15,
};

// controls
const controls = new webCharts.createControls(
'.chart',
'.controls',
{
inputs: [
{
type: 'dropdown',
label: 'Variable',
option: 'x.column',
option: 'column',
require: true,
values: Object.keys(data[0]).filter(key => key !== 'country_code'),
},
{
type: 'radio',
type: 'dropdown',
label: 'Sort',
option: 'y.sort',
option: 'sort',
require: true,
values: [
'alphabetical-ascending',
'alphabetical-descending',
Expand All @@ -55,16 +55,68 @@ d3.csv(
}
);

const chart = webCharts.createChart(
'.chart',
settings,
// ordinal y-axis
const verticalSettings = JSON.parse(JSON.stringify(settings));
verticalSettings.linearAxis = 'x';
verticalSettings.ordinalAxis = 'y';
verticalSettings.x = Object.assign({}, linearAxis);
verticalSettings.y = Object.assign({}, ordinalAxis);
verticalSettings.marks[0].tooltip = verticalSettings.marks[0].tooltip.replace('$', '$x');
verticalSettings.gridlines = 'x';
const verticalChart = webCharts.createChart(
'.chart--vertical',
verticalSettings,
controls
);
verticalChart.init(data);

chart.on('draw', function() {
console.log(this.config.y);
// ordinal x-axis
const horizontalSettings = JSON.parse(JSON.stringify(settings));
horizontalSettings.linearAxis = 'y';
horizontalSettings.ordinalAxis = 'x';
horizontalSettings.x = Object.assign({}, ordinalAxis);
horizontalSettings.y = Object.assign({}, linearAxis);
horizontalSettings.marks[0].tooltip = horizontalSettings.marks[0].tooltip.replace('$', '$y');
horizontalSettings.gridlines = 'y';
horizontalSettings.height = 500;
horizontalSettings.margin = {
bottom: 150,
left: 100,
};
const horizontalChart = webCharts.createChart(
'.chart--horizontal',
horizontalSettings,
controls
);
horizontalChart.on('resize', function() {
this.svg
.selectAll('.x.axis .tick text')
.attr('transform', 'rotate(-45) translate(-5,-5)')
.style('text-anchor', 'end');
});
horizontalChart.init(data);

chart.init(data);
const variableControl = controls.wrap
.selectAll('.control-group select')
.filter(d => d.label === 'Variable');
variableControl.selectAll('option').property('selected', d => d === linearAxis.column);
variableControl
.on('change', function(d) {
controls.targets.forEach(target => {
target.config[target.config.linearAxis].column = this.value;
target.draw();
});
});
const sortControl = controls.wrap
.selectAll('.control-group select')
.filter(d => d.label === 'Sort');
sortControl.selectAll('option').property('selected', d => d === ordinalAxis.sort);
sortControl
.on('change', function(d) {
controls.targets.forEach(target => {
target.config[target.config.ordinalAxis].sort = this.value;
target.draw();
});
});
}
);