Skip to content

[DM-7921] Multiple series histogram display (not connected to table) #203

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

Merged
merged 1 commit into from
Oct 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions src/firefly/html/demo/ffapi-highlevel-charttest.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,38 @@ <h2>
onFireflyLoaded= function(firefly) {

// show histogram data no options, no connection to the table)
// data is an array of rows,
// first col - nPoints or number of points in the bin, second col - binMin, third col - binMax
// (supports variable length bins)
// define overlapping histograms in series array
const props = {
// data is an array of rows,
// first col - nPoints or number of points in the bin, second col - binMin, third col - binMax
// (supports variable length bins
series: [
{data : [
[1,1,10],
[10,10,100],
[100,100,1000],
[1000,1000,10000],
[100,10000,100000],
[10,100000,1000000],
[1,1000000,10000000]],
binColor:'#336699',
name:'sample1'},
{data : [
[6, 50,150],
[80,150,1200],
[15,1200,30400]],
binColor:'#CB3515',
name:'sample2'}
],
width: 800,
height: 550,
logs: 'xy',
desc: 'Both X and Y are using log scale'
};

/**
// one histogram
const props = {
data : [
[1,1,10],
[10,10,100],
Expand All @@ -50,7 +78,8 @@ <h2>
logs: 'xy',
desc: 'Both X and Y are using log scale',
binColor: '#336699'
};
};
**/
firefly.util.renderDOM('histogramHere', firefly.ui.Histogram, props);

// show scatter chart
Expand Down
106 changes: 66 additions & 40 deletions src/firefly/js/charts/ui/Histogram.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
import React, {PropTypes} from 'react';
import ReactHighcharts from 'react-highcharts';
import numeral from 'numeral';
import {set} from 'lodash';
import {getFormatString} from '../../util/MathUtil.js';
import {logError} from '../../util/WebUtil.js';

/*
* @param {String} color - hex color, exactly seven characters log, starting with '#'
* @param {Number} persentage (0.1 means 10 percent lighter, -0.1 - 10 percent darker)
* @param {Number} percentage (0.1 means 10 percent lighter, -0.1 - 10 percent darker)
* @return {String} lighter or darker shade of the given hex color
* from http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
*/
Expand All @@ -27,6 +28,13 @@ function padLeft(num) {
return num-Math.abs(num*Math.pow(10,-9));
}

function getMinY(data) {
if (data.length > 0) {
return data.reduce((minVal, row) => {
return Math.min(minVal, row[0]);
}, data[0][0]);
}
}

export class Histogram extends React.Component {

Expand All @@ -45,15 +53,15 @@ export class Histogram extends React.Component {
*/
constructor(props) {
super(props);
this.setChartConfig = this.setChartConfig.bind(this);
this.addDataSeries = this.addDataSeries.bind(this);
this.validateData = this.validateData.bind(this);
}

shouldComponentUpdate(nextProps) {
const {data, width, height, logs, reversed, desc, binColor} = this.props;
const {series, data, width, height, logs, reversed, desc, binColor} = this.props;
// should rerender only if data or bin color has changed
// otherwise just change the existing chart
if (data !== nextProps.data || binColor !== nextProps.binColor) { return true; }
if (series !== nextProps.series || data !== nextProps.data || binColor !== nextProps.binColor) { return true; }
const chart = this.refs.chart && this.refs.chart.getChart();
if (chart) {
let doUpdate = false;
Expand Down Expand Up @@ -130,15 +138,21 @@ export class Histogram extends React.Component {

/*
* @param config
* @param {Object} seriesOptions
* @param {Array.number[]} seriesOptions.data
* @param {string} seriesOptions.binColor
* @param {string} seriesOptions.name
* @param {number} minY - minimum plot Y
* @param {number} seriesIdx - index of the series
* @return {Boolean} f data points are set, false if no points are present
*/
setChartConfig(config) {
addDataSeries(config, seriesOptions, minY, seriesIdx=0) {

// how many significant digits should we preserve? ~12?
// EPSILON 2^(-52)
const TINY_OFFSET = 100*Number.EPSILON;

const {binColor, logs, data}= this.props;
const {name, binColor, data}= seriesOptions;

if (!data || data.length < 1) {
return false;
Expand All @@ -149,35 +163,28 @@ export class Histogram extends React.Component {
return false;
}

var points = [], zones=[];
var lighterColor = shadeColor(binColor, 0.1);
const points = [], zones=[];
const lighterColor = shadeColor(binColor, 0.1);
var error;

// use column chart for only one point
var areaPlot = (data.length > 1);

// zones mess up log scale - do not do them
var doZones = (areaPlot); // && (!logs || logs.indexOf('x')===-1));
// zones - color ajacent bins slightly differently
var doZones = (areaPlot);


if (!areaPlot && data.length === 1) {
const xrange = data[0][2] - data[0][1];
if (xrange <= TINY_OFFSET) {
config.plotOptions.column.maxPointWidth = 10;
} else {
config.plotOptions.column.maxPointWidth = 50;
areaPlot = true;
}
}

try {

// what should be the minimum y value?
let minY = 0;
if (logs && logs.includes('y') && data.length>0) {
const minYData = data.reduce((minVal, row) => {
return Math.min(minVal, row[0]);
}, data[0][0]);
minY = minYData/10;
}
let lastBinMax = data[0][1];
if (areaPlot) {

Expand Down Expand Up @@ -280,30 +287,48 @@ export class Histogram extends React.Component {
if (error) {
return false;
} else {
config.series[0].data = points;
const dataSeries = {
name,
type: areaPlot ? 'area' : 'column',
turboThreshold: 0,
//fillOpacity: 0.9,
//color: binColor,
data: points
};
if (doZones) {
config.plotOptions.area.zones = zones;
dataSeries.zones = zones;
}
set(config, ['series',seriesIdx], dataSeries);
}
}

render() {

const { binColor, data, desc, width, height, logs, reversed }= this.props;
const { binColor, data, desc, width, height, logs, reversed}= this.props;
let series = this.props.series;
if (!series) {
series = [{data, binColor, name: 'data points'}];
}
const yReversed = (reversed && reversed.indexOf('y')>-1 ? true : false);

var chartType;
if (data.length < 2) {
chartType = 'column';
} else {
chartType = 'area';
}

let minY = 0;
// what should be the minimum y value be?
if (logs && logs.includes('y')) {
let minYData = Number.MAX_VALUE;
for (let i=0; i< series.length; i++) {
const data = series.data;
if (data && data.length>0) {
const seriesMinY = getMinY(data);
if (seriesMinY < minYData) { minYData = seriesMinY; }
}
}
minY = (minYData === Number.MAX_VALUE) ? 0.1 : minYData/10;
}

var config = {
chart: {
renderTo: 'container',
type: chartType,
alignTicks: false,
width: width? Number(width) : undefined,
height: height? Number(height) : undefined,
Expand All @@ -320,9 +345,11 @@ export class Histogram extends React.Component {
text: ''
},
tooltip: {
split: true,
followPointer: true,
borderWidth: 1,
formatter() {
if (this.y === minY) {return false;} // don't display tooltip
return '<span>'+(this.point.name ? `<b>Bin center:</b> ${this.point.name}<br/>` : '')+
(this.point.range ? `<b>Range:</b> ${this.point.range}<br/>` : '')+
`<b>Count:</b> ${this.y}</span>`;
Expand All @@ -346,12 +373,13 @@ export class Histogram extends React.Component {
}
},
zoneAxis: 'x',
zones: [] // color ajacent bins slightly different by defining zones
zones: [] // color adjacent bins slightly different by defining zones
},
column: {
pointPadding: 0.2
}
},
series: [],
xAxis: {
lineColor: '#999',
tickColor: '#ccc',
Expand All @@ -368,27 +396,22 @@ export class Histogram extends React.Component {
tickLength: 3,
tickColor: '#ccc',
lineColor: '#ccc',
min: minY,
endOnTick: false,
title: {
text: ''
},
reversed: yReversed,
type: (logs && logs.indexOf('y')>-1 ? 'logarithmic' : 'linear')
},
series: [{
name: 'data points',
turboThreshold: 0,
color: binColor,
data: []
}],
credits: {
enabled: false // removes a reference to Highcharts.com from the chart
}
};

if (data.length > 0) {
this.setChartConfig(config);
}
series.forEach((s, idx) => {
this.addDataSeries(config, s, minY, idx);
});

return (
<div>
Expand All @@ -403,13 +426,16 @@ Histogram.defaultProps = {
binColor: '#d1d1d1'
};


// when more than one histogram is defined use series
Histogram.propTypes = {
data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)), // array of numbers [0] - nInBin, [1] - binMin, [2] - binMax
series: PropTypes.arrayOf(PropTypes.object), // array of objects with data, binColor, and name properties
width: PropTypes.number,
height: PropTypes.number,
logs: PropTypes.oneOf(['x','y','xy']),
reversed: PropTypes.oneOf(['x','y','xy']),
desc: PropTypes.string,
data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)), // array of numbers [0] - nInBin, [1] - binMin, [2] - binMax
binColor(props, propName, componentName) {
if (props[propName] && !/^#[0-9a-f]{6}/.test(props[propName])) {
return new Error(`Invalid bin color in ${componentName}, should be hex with exactly 7 characters long.`);
Expand Down