Page MenuHomePhabricator

bar.js
No OneTemporary

{
// TODO add styles to stylesheet instead
function scaffold({target, width, height, top, left, right, buffer}) {
const svg = mg_get_svg_child_of(target);
// main margins
svg.append('line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', top)
.attr('y2', top)
.attr('stroke', 'black');
svg.append('line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', height - bottom)
.attr('y2', height - bottom)
.attr('stroke', 'black');
svg.append('line')
.attr('x1', left)
.attr('x2', left)
.attr('y1', 0)
.attr('y2', height)
.attr('stroke', 'black');
svg.append('line')
.attr('x1', width - right)
.attr('x2', width - right)
.attr('y1', 0)
.attr('y2', height)
.attr('stroke', 'black');
// plot area margins
svg.append('line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', height - bottom - buffer)
.attr('y2', height - bottom - buffer)
.attr('stroke', 'gray');
svg.append('line')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', top + buffer)
.attr('y2', top + buffer)
.attr('stroke', 'gray');
svg.append('line')
.attr('x1', left + buffer)
.attr('x2', left + buffer)
.attr('y1', 0)
.attr('y2', args.height)
.attr('stroke', 'gray');
svg.append('line')
.attr('x1', width - right - buffer)
.attr('x2', width - right - buffer)
.attr('y1', 0)
.attr('y2', height)
.attr('stroke', 'gray');
}
// barchart re-write.
function mg_targeted_legend({legend_target, orientation, scales}) {
let labels;
const plot = '';
if (legend_target) {
const div = d3.select(legend_target).append('div').classed('mg-bar-target-legend', true);
if (orientation == 'horizontal') labels = scales.Y.domain();
else labels = scales.X.domain();
labels.forEach(label => {
const outer_span = div.append('span').classed('mg-bar-target-element', true);
outer_span.append('span')
.classed('mg-bar-target-legend-shape', true)
.style('color', scales.COLOR(label))
.text('\u25FC ');
outer_span.append('span')
.classed('mg-bar-target-legend-text', true)
.text(label);
});
}
}
function legend_on_graph(svg, args) {
// draw each element at the top right
// get labels
let labels;
if (args.orientation=='horizontal') labels = args.scales.Y.domain();
else labels = args.scales.X.domain();
let lineCount = 0;
const lineHeight = 1.1;
const g = svg.append('g').classed("mg-bar-legend", true);
const textContainer = g.append('text');
textContainer
.selectAll('*')
.remove();
textContainer
.attr('width', args.right)
.attr('height', 100)
.attr('text-anchor', 'start');
labels.forEach(label => {
const sub_container = textContainer.append('tspan')
.attr('x', mg_get_plot_right(args))
.attr('y', args.height / 2)
.attr('dy', `${lineCount * lineHeight}em`);
sub_container.append('tspan')
.text('\u25a0 ')
.attr('fill', args.scales.COLOR(label))
.attr('font-size', 20);
sub_container.append('tspan')
.text(label)
.attr('font-weight', 300)
.attr('font-size', 10);
lineCount++;
});
// d.values.forEach(function (datum) {
// formatted_y = mg_format_y_rollover(args, num, datum);
// if (args.y_rollover_format !== null) {
// formatted_y = number_rollover_format(args.y_rollover_format, datum, args.y_accessor);
// } else {
// formatted_y = args.yax_units + num(datum[args.y_accessor]);
// }
// sub_container = textContainer.append('tspan').attr('x', 0).attr('y', (lineCount * lineHeight) + 'em');
// formatted_y = mg_format_y_rollover(args, num, datum);
// mouseover_tspan(sub_container, '\u2014 ')
// .color(args, datum);
// mouseover_tspan(sub_container, formatted_x + ' ' + formatted_y);
// lineCount++;
// });
}
function barChart(args) {
this.args = args;
this.init = (args) => {
this.args = args;
args.x_axis_type = mg_infer_type(args, 'x');
args.y_axis_type = mg_infer_type(args, 'y');
// this is specific to how rects work in svg, let's keep track of the bar orientation to
// plot appropriately.
if (args.x_axis_type == 'categorical') {
args.orientation = 'vertical';
} else if (args.y_axis_type == 'categorical') {
args.orientation = 'horizontal';
} else if (args.x_axis_type != 'categorical' && args.y_axis_type != 'categorical') {
// histogram.
args.orientation = 'vertical';
}
raw_data_transformation(args);
process_point(args);
init(args);
let xMaker;
let yMaker;
if (args.x_axis_type === 'categorical') {
xMaker = MG.scale_factory(args)
.namespace('x')
.categoricalDomainFromData()
.categoricalRangeBands([0, args.xgroup_height], args.xgroup_accessor === null);
if (args.xgroup_accessor) {
new MG.scale_factory(args)
.namespace('xgroup')
.categoricalDomainFromData()
.categoricalRangeBands('bottom');
} else {
args.scales.XGROUP = d => mg_get_plot_left(args);
args.scalefns.xgroupf = d => mg_get_plot_left(args);
}
args.scalefns.xoutf = d => args.scalefns.xf(d) + args.scalefns.xgroupf(d);
} else {
xMaker = MG.scale_factory(args)
.namespace('x')
.inflateDomain(true)
.zeroBottom(args.y_axis_type === 'categorical')
.numericalDomainFromData((args.baselines || []).map(d => d[args.x_accessor]))
.numericalRange('bottom');
args.scalefns.xoutf = args.scalefns.xf;
}
// y-scale generation. This needs to get simplified.
if (args.y_axis_type === 'categorical') {
yMaker = MG.scale_factory(args)
.namespace('y')
.zeroBottom(true)
.categoricalDomainFromData()
.categoricalRangeBands([0, args.ygroup_height], true);
if (args.ygroup_accessor) {
new MG.scale_factory(args)
.namespace('ygroup')
.categoricalDomainFromData()
.categoricalRangeBands('left');
} else {
args.scales.YGROUP = () => mg_get_plot_top(args);
args.scalefns.ygroupf = d => mg_get_plot_top(args);
}
args.scalefns.youtf = d => args.scalefns.yf(d) + args.scalefns.ygroupf(d);
} else {
const baselines = (args.baselines || []).map(d => d[args.y_accessor]);
yMaker = MG.scale_factory(args)
.namespace('y')
.inflateDomain(true)
.zeroBottom(args.x_axis_type === 'categorical')
.numericalDomainFromData(baselines)
.numericalRange('left');
args.scalefns.youtf = d => args.scalefns.yf(d);
}
if (args.ygroup_accessor !== null) {
args.ycolor_accessor = args.y_accessor;
MG.scale_factory(args)
.namespace('ycolor')
.scaleName('color')
.categoricalDomainFromData()
.categoricalColorRange();
}
if (args.xgroup_accessor !== null) {
args.xcolor_accessor = args.x_accessor;
MG.scale_factory(args)
.namespace('xcolor')
.scaleName('color')
.categoricalDomainFromData()
.categoricalColorRange();
}
// if (args.ygroup_accessor !== null) {
// MG.scale_factory(args)
// .namespace('ygroup')
// .categoricalDomainFromData()
// .categoricalColorRange();
// }
new MG.axis_factory(args)
.namespace('x')
.type(args.x_axis_type)
.zeroLine(args.y_axis_type === 'categorical')
.position(args.x_axis_position)
.draw();
new MG.axis_factory(args)
.namespace('y')
.type(args.y_axis_type)
.zeroLine(args.x_axis_type === 'categorical')
.position(args.y_axis_position)
.draw();
//mg_categorical_group_color_scale(args);
this.mainPlot();
this.markers();
this.rollover();
this.windowListeners();
//scaffold(args)
return this;
};
this.mainPlot = () => {
const svg = mg_get_svg_child_of(args.target);
const data = args.data[0];
let barplot = svg.select('g.mg-barplot');
const fresh_render = barplot.empty();
let bars, predictor_bars, pp, pp0, baseline_marks;
const perform_load_animation = fresh_render && args.animate_on_load;
const should_transition = perform_load_animation || args.transition_on_update;
const transition_duration = args.transition_duration || 1000;
// draw the plot on first render
if (fresh_render) {
barplot = svg.append('g')
.classed('mg-barplot', true);
}
bars = barplot.selectAll('.mg-bar')
.data(data)
.enter()
.append('rect')
.classed('mg-bar', true)
.classed('default-bar', args.scales.hasOwnProperty('COLOR') ? false : true);
// TODO - reimplement
// reference_accessor {}
// if (args.predictor_accessor) {
// predictor_bars = barplot.selectAll('.mg-bar-prediction')
// .data(data.filter(function(d) {
// return d.hasOwnProperty(args.predictor_accessor) }));
// predictor_bars.exit().remove();
// predictor_bars.enter().append('rect')
// .classed('mg-bar-prediction', true);
// }
// if (args.baseline_accessor) {
// baseline_marks = barplot.selectAll('.mg-bar-baseline')
// .data(data.filter(function(d) {
// return d.hasOwnProperty(args.baseline_accessor) }));
// baseline_marks.exit().remove();
// baseline_marks.enter().append('line')
// .classed('mg-bar-baseline', true);
// }
let appropriate_size;
// setup transitions
// if (should_transition) {
// bars = bars.transition()
// .duration(transition_duration);
// if (predictor_bars) {
// predictor_bars = predictor_bars.transition()
// .duration(transition_duration);
// }
// if (baseline_marks) {
// baseline_marks = baseline_marks.transition()
// .duration(transition_duration);
// }
// }
//appropriate_size = args.scales.Y_ingroup.rangeBand()/1.5;
let length, width, length_type, width_type, length_coord, width_coord,
length_scalefn, width_scalefn, length_scale, width_scale,
length_accessor, width_accessor, length_coord_map, width_coord_map,
length_map, width_map;
let reference_length_map, reference_length_coord_fn;
if (args.orientation == 'vertical') {
length = 'height';
width = 'width';
length_type = args.y_axis_type;
width_type = args.x_axis_type;
length_coord = 'y';
width_coord = 'x';
length_scalefn = length_type == 'categorical' ? args.scalefns.youtf : args.scalefns.yf;
width_scalefn = width_type == 'categorical' ? args.scalefns.xoutf : args.scalefns.xf;
length_scale = args.scales.Y;
width_scale = args.scales.X;
length_accessor = args.y_accessor;
width_accessor = args.x_accessor;
length_coord_map = d => {
let l;
l = length_scalefn(d);
if (d[length_accessor] < 0) {
l = length_scale(0);
}
return l;
};
length_map = d => Math.abs(length_scalefn(d) - length_scale(0));
reference_length_map = d => Math.abs(length_scale(d[args.reference_accessor]) - length_scale(0));
reference_length_coord_fn = d => length_scale(d[args.reference_accessor]);
}
if (args.orientation == 'horizontal') {
length = 'width';
width = 'height';
length_type = args.x_axis_type;
width_type = args.y_axis_type;
length_coord = 'x';
width_coord = 'y';
length_scalefn = length_type == 'categorical' ? args.scalefns.xoutf : args.scalefns.xf;
width_scalefn = width_type == 'categorical' ? args.scalefns.youtf : args.scalefns.yf;
length_scale = args.scales.X;
width_scale = args.scales.Y;
length_accessor = args.x_accessor;
width_accessor = args.y_accessor;
length_coord_map = d => {
let l;
l = length_scale(0);
return l;
};
length_map = d => Math.abs(length_scalefn(d) - length_scale(0));
reference_length_map = d => Math.abs(length_scale(d[args.reference_accessor]) - length_scale(0));
reference_length_coord_fn = d => length_scale(0);
}
// if (perform_load_animation) {
// bars.attr(length, 0);
// if (predictor_bars) {
// predictor_bars.attr(length, 0);
// }
// // if (baseline_marks) {
// // baseline_marks.attr({
// // x1: args.scales.X(0),
// // x2: args.scales.X(0)
// // });
// // }
// }
bars.attr(length_coord, length_coord_map);
// bars.attr(length_coord, 40)
//bars.attr(width_coord, 70)
bars.attr(width_coord, d => {
let w;
if (width_type == 'categorical') {
w = width_scalefn(d);
} else {
w = width_scale(0);
if (d[width_accessor] < 0) {
w = width_scalefn(d);
}
}
w = w - args.bar_thickness/2;
return w;
});
if (args.scales.COLOR) {
bars.attr('fill', args.scalefns.colorf);
}
bars
.attr(length, length_map)
.attr(width, d => args.bar_thickness);
if (args.reference_accessor !== null) {
const reference_data = data.filter(d => d.hasOwnProperty(args.reference_accessor));
const reference_bars = barplot.selectAll('.mg-categorical-reference')
.data(reference_data)
.enter()
.append('rect');
reference_bars
.attr(length_coord, reference_length_coord_fn)
.attr(width_coord, d => width_scalefn(d) - args.reference_thickness/2)
.attr(length, reference_length_map)
.attr(width, args.reference_thickness);
}
if (args.comparison_accessor !== null) {
let comparison_thickness = null;
if (args.comparison_thickness === null) {
comparison_thickness = args.bar_thickness/2;
} else {
comparison_thickness = args.comparison_thickness;
}
const comparison_data = data.filter(d => d.hasOwnProperty(args.comparison_accessor));
const comparison_marks = barplot.selectAll('.mg-categorical-comparison')
.data(comparison_data)
.enter()
.append('line');
comparison_marks
.attr(`${length_coord}1`, d => length_scale(d[args.comparison_accessor]))
.attr(`${length_coord}2`, d => length_scale(d[args.comparison_accessor]))
.attr(`${width_coord}1`, d => width_scalefn(d) - comparison_thickness/2)
.attr(`${width_coord}2`, d => width_scalefn(d) + comparison_thickness/2)
.attr('stroke', 'black')
.attr('stroke-width', args.comparison_width);
}
//bars.attr(width_coord, );
// bars.attr('width', 50);
// bars.attr('height', 50);
// bars.attr('y', function(d){
// var y = args.scales.Y(0);
// if (d[args.y_accessor] < 0) {
// y = args.scalefns.yf(d);
// }
// return y;
// });
// bars.attr('x', function(d){
// return 40;
// })
// bars.attr('width', function(d){
// return 100;
// });
// bars.attr('height', 100);
// bars.attr('fill', 'black');
// bars.attr('x', function(d) {
// var x = args.scales.X(0);
// if (d[args.x_accessor] < 0) {
// x = args.scalefns.xf(d);
// }
// return x;
// })
// TODO - reimplement.
// if (args.predictor_accessor) {
// predictor_bars
// .attr('x', args.scales.X(0))
// .attr('y', function(d) {
// return args.scalefns.ygroupf(d) + args.scalefns.yf(d) + args.scales.Y.rangeBand() * (7 / 16) // + pp0 * appropriate_size/(pp*2) + appropriate_size / 2;
// })
// .attr('height', args.scales.Y.rangeBand() / 8) //appropriate_size / pp)
// .attr('width', function(d) {
// return args.scales.X(d[args.predictor_accessor]) - args.scales.X(0);
// });
// }
// TODO - reimplement.
// if (args.baseline_accessor) {
// baseline_marks
// .attr('x1', function(d) {
// return args.scales.X(d[args.baseline_accessor]); })
// .attr('x2', function(d) {
// return args.scales.X(d[args.baseline_accessor]); })
// .attr('y1', function(d) {
// return args.scalefns.ygroupf(d) + args.scalefns.yf(d) + args.scales.Y.rangeBand() / 4
// })
// .attr('y2', function(d) {
// return args.scalefns.ygroupf(d) + args.scalefns.yf(d) + args.scales.Y.rangeBand() * 3 / 4
// });
// }
if (args.legend || (args.color_accessor !== null && args.ygroup_accessor !== args.color_accessor)) {
if (!args.legend_target) legend_on_graph(svg, args);
else mg_targeted_legend(args);
}
return this;
};
this.markers = () => {
markers(args);
return this;
};
this.rollover = () => {
const svg = mg_get_svg_child_of(args.target);
let g;
if (svg.selectAll('.mg-active-datapoint-container').nodes().length === 0) {
mg_add_g(svg, 'mg-active-datapoint-container');
}
//remove the old rollovers if they already exist
svg.selectAll('.mg-rollover-rect').remove();
svg.selectAll('.mg-active-datapoint').remove();
// get orientation
let length, width, length_type, width_type, length_coord, width_coord,
length_scalefn, width_scalefn, length_scale, width_scale,
length_accessor, width_accessor;
let length_coord_map, width_coord_map, length_map, width_map;
if (args.orientation == 'vertical') {
length = 'height';
width = 'width';
length_type = args.y_axis_type;
width_type = args.x_axis_type;
length_coord = 'y';
width_coord = 'x';
length_scalefn = length_type == 'categorical' ? args.scalefns.youtf : args.scalefns.yf;
width_scalefn = width_type == 'categorical' ? args.scalefns.xoutf : args.scalefns.xf;
length_scale = args.scales.Y;
width_scale = args.scales.X;
length_accessor = args.y_accessor;
width_accessor = args.x_accessor;
length_coord_map = d => mg_get_plot_top(args);
length_map = d => args.height -args.top-args.bottom-args.buffer*2;
}
if (args.orientation == 'horizontal') {
length = 'width';
width = 'height';
length_type = args.x_axis_type;
width_type = args.y_axis_type;
length_coord = 'x';
width_coord = 'y';
length_scalefn = length_type == 'categorical' ? args.scalefns.xoutf : args.scalefns.xf;
width_scalefn = width_type == 'categorical' ? args.scalefns.youtf : args.scalefns.yf;
length_scale = args.scales.X;
width_scale = args.scales.Y;
length_accessor = args.x_accessor;
width_accessor = args.y_accessor;
length_coord_map = d => {
let l;
l = length_scale(0);
return l;
};
length_map = d => args.width -args.left-args.right-args.buffer*2;
}
//rollover text
let rollover_x, rollover_anchor;
if (args.rollover_align === 'right') {
rollover_x = args.width - args.right;
rollover_anchor = 'end';
} else if (args.rollover_align === 'left') {
rollover_x = args.left;
rollover_anchor = 'start';
} else {
rollover_x = (args.width - args.left - args.right) / 2 + args.left;
rollover_anchor = 'middle';
}
svg.append('text')
.attr('class', 'mg-active-datapoint')
.attr('xml:space', 'preserve')
.attr('x', rollover_x)
.attr('y', args.top * 0.75)
.attr('dy', '.35em')
.attr('text-anchor', rollover_anchor);
g = svg.append('g')
.attr('class', 'mg-rollover-rect');
//draw rollover bars
const bars = g.selectAll(".mg-bar-rollover")
.data(args.data[0]).enter()
.append("rect")
.attr('class', 'mg-bar-rollover');
bars.attr('opacity', 0)
.attr(length_coord, length_coord_map)
.attr(width_coord, d => {
let w;
if (width_type == 'categorical') {
w = width_scalefn(d);
} else {
w = width_scale(0);
if (d[width_accessor] < 0) {
w = width_scalefn(d);
}
}
w = w - args.bar_thickness/2;
return w;
});
bars.attr(length, length_map);
bars.attr(width, d => args.bar_thickness);
bars
.on('mouseover', this.rolloverOn(args))
.on('mouseout', this.rolloverOff(args))
.on('mousemove', this.rolloverMove(args));
return this;
};
this.rolloverOn = (args) => {
const svg = mg_get_svg_child_of(args.target);
const label_accessor = this.is_vertical ? args.x_accessor : args.y_accessor;
const data_accessor = this.is_vertical ? args.y_accessor : args.x_accessor;
const label_units = this.is_vertical ? args.yax_units : args.xax_units;
return (d, i) => {
const fmt = MG.time_format(args.utc_time, '%b %e, %Y');
const num = format_rollover_number(args);
//highlight active bar
const bar = svg.selectAll('g.mg-barplot .mg-bar')
.filter((d, j) => j === i).classed('active', true);
if (args.scales.hasOwnProperty('COLOR')) {
bar.attr('fill', d3.rgb(args.scalefns.colorf(d)).darker());
} else {
bar.classed('default-active', true);
}
//update rollover text
if (args.show_rollover_text) {
const mouseover = mg_mouseover_text(args, { svg });
let row = mouseover.mouseover_row();
if (args.ygroup_accessor) row.text(`${d[args.ygroup_accessor]} `).bold();
row.text(mg_format_x_mouseover(args, d));
row.text(`${args.y_accessor}: ${d[args.y_accessor]}`);
if (args.predictor_accessor || args.baseline_accessor) {
row = mouseover.mouseover_row();
if (args.predictor_accessor) row.text(mg_format_data_for_mouseover(args, d, null, args.predictor_accessor, false));
if (args.baseline_accessor) row.text(mg_format_data_for_mouseover(args, d, null, args.baseline_accessor, false));
}
}
if (args.mouseover) {
args.mouseover(d, i);
}
};
};
this.rolloverOff = (args) => {
const svg = mg_get_svg_child_of(args.target);
return (d, i) => {
//reset active bar
const bar = svg.selectAll('g.mg-barplot .mg-bar.active').classed('active', false);
if (args.scales.hasOwnProperty('COLOR')) {
bar.attr('fill', args.scalefns.colorf(d));
} else {
bar.classed('default-active', false);
}
//reset active data point text
svg.select('.mg-active-datapoint')
.text('');
mg_clear_mouseover_container(svg);
if (args.mouseout) {
args.mouseout(d, i);
}
};
};
this.rolloverMove = (args) => (d, i) => {
if (args.mousemove) {
args.mousemove(d, i);
}
};
this.windowListeners = () => {
mg_window_listeners(this.args);
return this;
};
this.init(args);
}
const options = {
buffer: [16, 'number'],
y_accessor: ['factor', 'string'],
x_accessor: ['value', 'string'],
reference_accessor: [null, 'string'],
comparison_accessor: [null, 'string'],
secondary_label_accessor: [null, 'string'],
color_accessor: [null, 'string'],
color_type: ['category', ['number', 'category']],
color_domain: [null, 'number[]'],
reference_thickness: [1, 'number'],
comparison_width: [3, 'number'],
comparison_thickness: [null, 'number'],
legend: [false, 'boolean'],
legend_target: [null, 'string'],
mouseover_align: ['right', ['right', 'left']],
baseline_accessor: [null, 'string'],
predictor_accessor: [null, 'string'],
predictor_proportion: [5, 'number'],
show_bar_zero: [true, 'boolean'],
binned: [true, 'boolean'],
truncate_x_labels: [true, 'boolean'],
truncate_y_labels: [true, 'boolean']
};
MG.register('bar', barChart, options);
}

File Metadata

Mime Type
text/x-c
Expires
Fri, Feb 6, 12:06 AM (6 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
81606
Default Alt Text
bar.js (24 KB)

Event Timeline