Skip to content

Commit

Permalink
scatter filter color/opacity
Browse files Browse the repository at this point in the history
closes #938
simplify some of the logic
introduce excludedSize (instead of double-use for highlightedSize)
introduce excludedColor, excludedOpacity
test all of these (once)

also introduces emptySize as a better name for hiddenSize
  • Loading branch information
gordonwoodhull committed Feb 19, 2016
1 parent 6dcb929 commit 5dc00e1
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 49 deletions.
104 changes: 88 additions & 16 deletions spec/scatter-plot-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ describe('dc.scatterPlot', function () {
.group(group)
.width(500).height(180)
.x(d3.scale.linear().domain([0, 70]))
.excludedColor('#ccc')
.excludedOpacity(0.25)
.transitionDuration(0);
});

Expand Down Expand Up @@ -132,45 +134,115 @@ describe('dc.scatterPlot', function () {
expect(chart.select('.resize path').empty()).toBeTruthy();
});

describe('highlighting', function () {
describe('excluded points', function () {
var selectedPoints;

beforeEach(function () {
selectedPoints = symbolsOfRadius(chart.highlightedSize());
jasmine.clock().tick(100);
});

it('should highlight the selected points', function () {
var isOpaque = function () {
return +d3.select(this).attr('opacity') === 1;
}, isTranslucent = function () {
return +d3.select(this).attr('opacity') === 0.25;
}, isBlue = function () {
return d3.select(this).attr('fill') === '#1f77b4';
}, isGrey = function () {
return d3.select(this).attr('fill') === '#ccc';
};

it('should not shrink the included points', function () {
selectedPoints = symbolsOfRadius(chart.symbolSize());
expect(selectedPoints.length).toBe(2);
expect(selectedPoints[0].key).toEqual([22, -2]);
expect(selectedPoints[1].key).toEqual([33, 1]);
});

it('should remove highlighting when the brush is removed from the selected points', function () {
it('should shrink the excluded points', function () {
selectedPoints = symbolsOfRadius(chart.excludedSize());
expect(selectedPoints.length).toBe(7);
expect(selectedPoints[0].key).toEqual([22, 10]);
expect(selectedPoints[1].key).toEqual([44, -3]);
});

it('should keep the included points opaque', function () {
selectedPoints = symbolsMatching(isOpaque);
expect(selectedPoints.length).toBe(2);
expect(selectedPoints[0].key).toEqual([22, -2]);
expect(selectedPoints[1].key).toEqual([33, 1]);
});

it('should make the excluded points translucent', function () {
selectedPoints = symbolsMatching(isTranslucent);
expect(selectedPoints.length).toBe(7);
expect(selectedPoints[0].key).toEqual([22, 10]);
expect(selectedPoints[1].key).toEqual([44, -3]);
});

it('should keep the included points blue', function () {
selectedPoints = symbolsMatching(isBlue);
expect(selectedPoints.length).toBe(2);
expect(selectedPoints[0].key).toEqual([22, -2]);
expect(selectedPoints[1].key).toEqual([33, 1]);
});

it('should make the excluded points grey', function () {
selectedPoints = symbolsMatching(isGrey);
expect(selectedPoints.length).toBe(7);
expect(selectedPoints[0].key).toEqual([22, 10]);
expect(selectedPoints[1].key).toEqual([44, -3]);
});

it('should restore sizes, colors, and opacity when the brush is empty', function () {
chart.brush().extent([[22, 2], [22, -3]]);
chart.brush().on('brush')();
selectedPoints = symbolsOfRadius(chart.highlightedSize());
expect(selectedPoints.length).toBe(0);
jasmine.clock().tick(100);

selectedPoints = symbolsOfRadius(chart.symbolSize());
expect(selectedPoints.length).toBe(9);

selectedPoints = symbolsMatching(isBlue);
expect(selectedPoints.length).toBe(9);

selectedPoints = symbolsMatching(isOpaque);
expect(selectedPoints.length).toBe(9);

chart.redraw();
selectedPoints = symbolsOfRadius(chart.highlightedSize());
expect(selectedPoints.length).toBe(0);

selectedPoints = symbolsOfRadius(chart.symbolSize());
expect(selectedPoints.length).toBe(9);

selectedPoints = symbolsMatching(isBlue);
expect(selectedPoints.length).toBe(9);

selectedPoints = symbolsMatching(isOpaque);
expect(selectedPoints.length).toBe(9);
});
});
});
});

function symbolsOfRadius (r) {
function getData (symbols) {
return symbols[0].map(function (symbol) {
return d3.select(symbol).datum();
});
}
return getData(chart.selectAll('path.symbol').filter(function () {
function matchSymbolSize (r) {
return function () {
var symbol = d3.select(this);
var size = Math.pow(r, 2);
var path = d3.svg.symbol().size(size)();
var result = comparePaths(symbol.attr('d'), path);
return result.pass;
}));
};
}

function symbolsMatching (pred) {
function getData (symbols) {
return symbols[0].map(function (symbol) {
return d3.select(symbol).datum();
});
}
return getData(chart.selectAll('path.symbol').filter(pred));
}

function symbolsOfRadius (r) {
return symbolsMatching(matchSymbolSize(r));
}

describe('legends', function () {
Expand Down
122 changes: 91 additions & 31 deletions src/scatter-plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,24 @@ dc.scatterPlot = function (parent, chartGroup) {

var _locator = function (d) {
return 'translate(' + _chart.x()(_chart.keyAccessor()(d)) + ',' +
_chart.y()(_chart.valueAccessor()(d)) + ')';
_chart.y()(_chart.valueAccessor()(d)) + ')';
};

var _symbolSize = 3;
var _highlightedSize = 5;
var _hiddenSize = 0;
var _highlightedSize = 7;
var _symbolSize = 5;
var _excludedSize = 3;
var _excludedColor = null;
var _excludedOpacity = 1.0;
var _emptySize = 0;
var _filtered = [];

_symbol.size(function (d) {
_symbol.size(function (d, i) {
if (!_existenceAccessor(d)) {
return _hiddenSize;
} else if (this.filtered) {
return Math.pow(_highlightedSize, 2);
} else {
return _emptySize;
} else if (_filtered[i]) {
return Math.pow(_symbolSize, 2);
} else {
return Math.pow(_excludedSize, 2);
}
});

Expand All @@ -61,19 +65,30 @@ dc.scatterPlot = function (parent, chartGroup) {

_chart.plotData = function () {
var symbols = _chart.chartBodyG().selectAll('path.symbol')
.data(_chart.data());
.data(_chart.data());

symbols
.enter()
.append('path')
.append('path')
.attr('class', 'symbol')
.attr('opacity', 0)
.attr('fill', _chart.getColor)
.attr('transform', _locator);

symbols.each(function (d, i) {
_filtered[i] = !_chart.filter() || _chart.filter().isFiltered(d.key);
});

dc.transition(symbols, _chart.transitionDuration())
.attr('opacity', function (d) { return _existenceAccessor(d) ? 1 : 0; })
.attr('fill', _chart.getColor)
.attr('opacity', function (d, i) {
return !_existenceAccessor(d) ? 0 :
_filtered[i] ? 1 : _chart.excludedOpacity();
})
.attr('fill', function (d, i) {
return _chart.excludedColor() && !_filtered[i] ?
_chart.excludedColor() :
_chart.getColor(d);
})
.attr('transform', _locator)
.attr('d', _symbol);

Expand All @@ -85,13 +100,13 @@ dc.scatterPlot = function (parent, chartGroup) {
* Get or set the existence accessor. If a point exists, it is drawn with
* {@link #dc.scatterPlot+symbolSize symbolSize} radius and
* opacity 1; if it does not exist, it is drawn with
* {@link #dc.scatterPlot+hiddenSize hiddenSize} radius and opacity 0. By default,
* {@link #dc.scatterPlot+emptySize emptySize} radius and opacity 0. By default,
* the existence accessor checks if the reduced value is truthy.
* @method existenceAccessor
* @memberof dc.scatterPlot
* @instance
* @see {@link #dc.scatterPlot+symbolSize symbolSize}
* @see {@link #dc.scatterPlot+hiddenSize hiddenSize}
* @see {@link #dc.scatterPlot+emptySize emptySize}
* @example
* // default accessor
* chart.existenceAccessor(function (d) { return d.value; });
Expand Down Expand Up @@ -167,21 +182,77 @@ dc.scatterPlot = function (parent, chartGroup) {
return _chart;
};

/**
* Set or get size for symbols excluded from this chart's filter. If null, no
* special size is applied for symbols based on their filter status
* @method excludedSize
* @memberof dc.scatterPlot
* @instance
* @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_size d3.svg.symbol().size()}
* @param {Number} [excludedSize=null]
* @return {Number}
* @return {dc.scatterPlot}
*/
_chart.excludedSize = function (excludedSize) {
if (!arguments.length) {
return _excludedSize;
}
_excludedSize = excludedSize;
return _chart;
};

/**
* Set or get color for symbols excluded from this chart's filter. If null, no
* special color is applied for symbols based on their filter status
* @method excludedColor
* @memberof dc.scatterPlot
* @instance
* @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_size d3.svg.symbol().size()}
* @param {Number} [excludedColor=null]
* @return {Number}
* @return {dc.scatterPlot}
*/
_chart.excludedColor = function (excludedColor) {
if (!arguments.length) {
return _excludedColor;
}
_excludedColor = excludedColor;
return _chart;
};

/**
* Set or get opacity for symbols excluded from this chart's filter.
* @method excludedOpacity
* @memberof dc.scatterPlot
* @instance
* @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_size d3.svg.symbol().size()}
* @param {Number} [excludedOpacity=1.0]
* @return {Number}
* @return {dc.scatterPlot}
*/
_chart.excludedOpacity = function (excludedOpacity) {
if (!arguments.length) {
return _excludedOpacity;
}
_excludedOpacity = excludedOpacity;
return _chart;
};

/**
* Set or get radius for symbols when the group is empty.
* @method hiddenSize
* @method emptySize
* @memberof dc.scatterPlot
* @instance
* @see {@link https://github.com/mbostock/d3/wiki/SVG-Shapes#symbol_size d3.svg.symbol().size()}
* @param {Number} [hiddenSize=0]
* @param {Number} [emptySize=0]
* @return {Number}
* @return {dc.scatterPlot}
*/
_chart.hiddenSize = function (hiddenSize) {
_chart.hiddenSize = _chart.emptySize = function (emptySize) {
if (!arguments.length) {
return _hiddenSize;
return _emptySize;
}
_hiddenSize = hiddenSize;
_emptySize = emptySize;
return _chart;
};

Expand Down Expand Up @@ -237,14 +308,6 @@ dc.scatterPlot = function (parent, chartGroup) {
return _chart.brush().empty() || !extent || extent[0][0] >= extent[1][0] || extent[0][1] >= extent[1][1];
};

function resizeFiltered (filter) {
var symbols = _chart.selectAll('.chart-body path.symbol').each(function (d) {
this.filtered = filter && filter.isFiltered(d.key);
});

dc.transition(symbols, _chart.transitionDuration()).attr('d', _symbol);
}

_chart._brushing = function () {
var extent = _chart.extendBrush();

Expand All @@ -256,8 +319,6 @@ dc.scatterPlot = function (parent, chartGroup) {
_chart.redrawGroup();
});

resizeFiltered(false);

} else {
var ranged2DFilter = dc.filters.RangedTwoDimensionalFilter(extent);
dc.events.trigger(function () {
Expand All @@ -266,7 +327,6 @@ dc.scatterPlot = function (parent, chartGroup) {
_chart.redrawGroup();
}, dc.constants.EVENT_DELAY);

resizeFiltered(ranged2DFilter);
}
};

Expand Down
4 changes: 2 additions & 2 deletions web/examples/scatter-brushing.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@
.x(d3.scale.linear().domain([0, 20]))
.yAxisLabel("y")
.xAxisLabel("x")
.symbolSize(8)
.clipPadding(10)
.dimension(dim1)
.excludedOpacity(0.5)
.group(group1);

chart2.width(300)
.height(300)
.x(d3.scale.linear().domain([0, 20]))
.yAxisLabel("z")
.xAxisLabel("y")
.symbolSize(8)
.clipPadding(10)
.dimension(dim2)
.excludedColor('#ddd')
.group(group2);

dc.renderAll();
Expand Down

0 comments on commit 5dc00e1

Please sign in to comment.