[SIP-5] Refactor calendar chart (#5760)

* remove stroke

* update style and annotate data type

* update prop type

* bring back utc code

* add comments
This commit is contained in:
Krist Wongsuphasawat 2018-09-05 10:34:44 -07:00 committed by Chris Williams
parent 0c33f80c1d
commit 2811498ec9
4 changed files with 101 additions and 30 deletions

View File

@ -1078,7 +1078,7 @@ export const controls = {
isInt: true,
validators: [v.integer],
renderTrigger: true,
default: 0,
default: 2,
label: t('Cell Padding'),
description: t('The distance between cells, in pixels'),
},

View File

@ -8,7 +8,3 @@
margin-left: 20px;
margin-top: 5px;
}
.graph-legend rect {
stroke: #aaa;
stroke-location: inside;
}

View File

@ -1,31 +1,73 @@
import d3 from 'd3';
import PropTypes from 'prop-types';
import { colorScalerFactory } from '../modules/colors';
import CalHeatMap from '../../vendor/cal-heatmap/cal-heatmap';
import '../../vendor/cal-heatmap/cal-heatmap.css';
import { d3TimeFormatPreset, d3FormatPreset } from '../modules/utils';
import './cal_heatmap.css';
import { UTC } from '../modules/dates';
import '../../vendor/cal-heatmap/cal-heatmap.css';
import './cal_heatmap.css';
const UTCTS = uts => UTC(new Date(uts)).getTime();
function calHeatmap(slice, payload) {
const fd = slice.formData;
const steps = fd.steps;
const valueFormatter = d3FormatPreset(fd.y_axis_format);
const timeFormatter = d3TimeFormatPreset(fd.x_axis_time_format);
const propTypes = {
data: PropTypes.shape({
// Object hashed by metric name,
// then hashed by timestamp (in seconds, not milliseconds) as float
// the innermost value is count
// e.g. { count_distinct_something: { 1535034236.0: 3 } }
data: PropTypes.object,
domain: PropTypes.string,
range: PropTypes.number,
// timestamp in milliseconds
start: PropTypes.number,
subdomain: PropTypes.string,
}),
height: PropTypes.number,
cellPadding: PropTypes.number,
cellRadius: PropTypes.number,
cellSize: PropTypes.number,
linearColorScheme: PropTypes.string,
showLegend: PropTypes.bool,
showMetricName: PropTypes.bool,
showValues: PropTypes.bool,
steps: PropTypes.number,
timeFormat: PropTypes.string,
valueFormat: PropTypes.string,
verboseMap: PropTypes.object,
};
const container = d3.select(slice.selector).style('height', slice.height());
function Calendar(element, props) {
PropTypes.checkPropTypes(propTypes, props, 'prop', 'Calendar');
const {
data,
height,
cellPadding = 3,
cellRadius = 0,
cellSize = 10,
linearColorScheme,
showLegend,
showMetricName,
showValues,
steps,
timeFormat,
valueFormat,
verboseMap,
} = props;
const valueFormatter = d3FormatPreset(valueFormat);
const timeFormatter = d3TimeFormatPreset(timeFormat);
const container = d3.select(element)
.style('height', height);
container.selectAll('*').remove();
const div = container.append('div');
const data = payload.data;
const subDomainTextFormat = fd.show_values ? (date, value) => valueFormatter(value) : null;
const cellPadding = fd.cell_padding !== '' ? fd.cell_padding : 2;
const cellRadius = fd.cell_radius || 0;
const cellSize = fd.cell_size || 10;
const subDomainTextFormat = showValues ? (date, value) => valueFormatter(value) : null;
// Trick to convert all timestamps to UTC
// TODO: Verify if this conversion is really necessary
// since all timestamps should always be in UTC.
const metricsData = {};
Object.keys(data.data).forEach((metric) => {
metricsData[metric] = {};
@ -36,15 +78,16 @@ function calHeatmap(slice, payload) {
Object.keys(metricsData).forEach((metric) => {
const calContainer = div.append('div');
if (fd.show_metric_name) {
calContainer.append('h4').text(slice.verboseMetricName(metric));
if (showMetricName) {
calContainer.text(`Metric: ${verboseMap[metric] || metric}`);
}
const timestamps = metricsData[metric];
const extents = d3.extent(Object.keys(timestamps), key => timestamps[key]);
const step = (extents[1] - extents[0]) / (steps - 1);
const colorScale = colorScalerFactory(fd.linear_color_scheme, null, null, extents);
const colorScale = colorScalerFactory(linearColorScheme, null, null, extents);
const legend = d3.range(steps).map(i => extents[0] + (step * i));
const legend = d3.range(steps)
.map(i => extents[0] + (step * i));
const legendColors = legend.map(colorScale);
const cal = new CalHeatMap();
@ -72,7 +115,7 @@ function calHeatmap(slice, payload) {
max: legendColors[legendColors.length - 1],
empty: 'white',
},
displayLegend: fd.show_legend,
displayLegend: showLegend,
itemName: '',
valueFormatter,
timeFormatter,
@ -80,4 +123,41 @@ function calHeatmap(slice, payload) {
});
});
}
module.exports = calHeatmap;
Calendar.propTypes = propTypes;
function adaptor(slice, payload) {
const { selector, formData, datasource } = slice;
const {
cell_padding: cellPadding,
cell_radius: cellRadius,
cell_size: cellSize,
linear_color_scheme: linearColorScheme,
show_legend: showLegend,
show_metric_name: showMetricName,
show_values: showValues,
steps,
x_axis_time_format: timeFormat,
y_axis_format: valueFormat,
} = formData;
const { verbose_map: verboseMap } = datasource;
const element = document.querySelector(selector);
return Calendar(element, {
data: payload.data,
height: slice.height(),
cellPadding,
cellRadius,
cellSize,
linearColorScheme,
showLegend,
showMetricName,
showValues,
steps,
timeFormat,
valueFormat,
verboseMap,
});
}
export default adaptor;

View File

@ -4,11 +4,6 @@
display: block;
}
.cal-heatmap-container .graph
{
font-family: "Lucida Grande", Lucida, Verdana, sans-serif;
}
.cal-heatmap-container .graph-label
{
fill: #999;