[vis] bug num improvements (#2523)
* fix js error, make should render viz more readable, add tooltips, style axis and add min/max to xaxis * put getTextWidth in utils * add document to eslint globals config
This commit is contained in:
parent
d93b1fc686
commit
c1d9918abe
|
|
@ -14,5 +14,8 @@
|
|||
"func-names": 0,
|
||||
"react/jsx-no-bind": 0,
|
||||
"no-confusing-arrow": 0,
|
||||
},
|
||||
"globals": {
|
||||
"document": true,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,19 +56,22 @@ class ChartContainer extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
(
|
||||
prevProps.queryResponse !== this.props.queryResponse ||
|
||||
prevProps.height !== this.props.height ||
|
||||
this.props.triggerRender
|
||||
) && !this.props.queryResponse.error
|
||||
&& this.props.chartStatus !== 'failed'
|
||||
&& this.props.chartStatus !== 'stopped'
|
||||
) {
|
||||
if (this.shouldRenderViz(prevProps)) {
|
||||
this.renderViz();
|
||||
}
|
||||
}
|
||||
|
||||
shouldRenderViz(prevProps) {
|
||||
const hasHeightChanged = prevProps.height !== this.props.height;
|
||||
const hasQueryChanged = prevProps.queryResponse !== this.props.queryResponse;
|
||||
const hasErrors = this.props.queryResponse && this.props.queryResponse.error;
|
||||
|
||||
return (hasQueryChanged || hasHeightChanged || this.props.triggerRender)
|
||||
&& !hasErrors
|
||||
&& this.props.chartStatus !== 'failed'
|
||||
&& this.props.chartStatus !== 'stopped';
|
||||
}
|
||||
|
||||
getMockedSliceObject() {
|
||||
const props = this.props;
|
||||
const getHeight = () => {
|
||||
|
|
|
|||
|
|
@ -188,3 +188,11 @@ export function customizeToolTip(chart, xAxisFormatter, yAxisFormatters) {
|
|||
return tooltip;
|
||||
});
|
||||
}
|
||||
|
||||
export function getTextWidth(text, fontDetails) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
context.font = fontDetails;
|
||||
const metrics = context.measureText(text);
|
||||
return metrics.width;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,18 @@
|
|||
.big_number_total g.axis text {
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
color: gray;
|
||||
fill: gray;
|
||||
color: #333333;
|
||||
fill: #333333;
|
||||
text-anchor: middle;
|
||||
alignment-baseline: middle;
|
||||
font-weight: none;
|
||||
font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.big_number text.big,
|
||||
.big_number_total text.big{
|
||||
stroke: black;
|
||||
stroke: #333333;
|
||||
text-anchor: middle;
|
||||
fill: black;
|
||||
fill: #333333;
|
||||
}
|
||||
|
||||
.big_number g.tick line,
|
||||
|
|
@ -25,6 +25,16 @@
|
|||
.big_number .domain,
|
||||
.big_number_total .domain{
|
||||
fill: none;
|
||||
stroke: black;
|
||||
stroke-width: 1;
|
||||
stroke: #333333;
|
||||
}
|
||||
|
||||
.line-tooltip {
|
||||
position: absolute;
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
import d3 from 'd3';
|
||||
import { formatDate } from '../javascripts/modules/dates';
|
||||
import { getTextWidth } from '../javascripts/modules/utils';
|
||||
|
||||
require('./big_number.css');
|
||||
|
||||
function getNumTicks(data, slice, margin) {
|
||||
let numTicks = parseInt((slice.width() - margin) * 0.01, 10);
|
||||
// if numTicks is greater than the total num of data points, show all data points
|
||||
if (numTicks > data.length) {
|
||||
numTicks = data.length;
|
||||
}
|
||||
return numTicks;
|
||||
}
|
||||
|
||||
function bigNumberVis(slice, payload) {
|
||||
const div = d3.select(slice.selector);
|
||||
// Define the percentage bounds that define color from red to green
|
||||
|
|
@ -38,70 +48,72 @@ function bigNumberVis(slice, payload) {
|
|||
}
|
||||
const dateExt = d3.extent(data, (d) => d[0]);
|
||||
const valueExt = d3.extent(data, (d) => d[1]);
|
||||
const yAxisLabelWidths = valueExt.map(value => getTextWidth(f(value), '10px Roboto'));
|
||||
const yAxisMaxWidth = Math.max(...yAxisLabelWidths);
|
||||
const margin = yAxisMaxWidth + (yAxisMaxWidth / 2);
|
||||
|
||||
const margin = 20;
|
||||
const scaleX = d3.time.scale.utc().domain(dateExt).range([margin, width - margin]);
|
||||
const scaleY = d3.scale.linear().domain(valueExt).range([height - (margin), margin]);
|
||||
const colorRange = [d3.hsl(0, 1, 0.3), d3.hsl(120, 1, 0.3)];
|
||||
const scaleColor = d3.scale
|
||||
.linear().domain([-1, 1])
|
||||
.interpolate(d3.interpolateHsl)
|
||||
.range(colorRange)
|
||||
.clamp(true);
|
||||
.linear().domain([-1, 1])
|
||||
.interpolate(d3.interpolateHsl)
|
||||
.range(colorRange)
|
||||
.clamp(true);
|
||||
const line = d3.svg.line()
|
||||
.x(function (d) {
|
||||
return scaleX(d[0]);
|
||||
})
|
||||
.y(function (d) {
|
||||
return scaleY(d[1]);
|
||||
})
|
||||
.interpolate('basis');
|
||||
.x(d => scaleX(d[0]))
|
||||
.y(d => scaleY(d[1]))
|
||||
.interpolate('basis');
|
||||
|
||||
let y = height / 2;
|
||||
let g = svg.append('g');
|
||||
|
||||
const formattedNumber = f(v);
|
||||
|
||||
// Printing big number
|
||||
g.append('g').attr('class', 'digits')
|
||||
.attr('opacity', 1)
|
||||
.append('text')
|
||||
.attr('x', width / 2)
|
||||
.attr('y', y)
|
||||
.attr('class', 'big')
|
||||
.attr('alignment-baseline', 'middle')
|
||||
.attr('id', 'bigNumber')
|
||||
.style('font-weight', 'bold')
|
||||
.style('cursor', 'pointer')
|
||||
.text(f(v))
|
||||
.style('font-size', d3.min([height, width]) / 3.5)
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('fill', 'black');
|
||||
g.append('g')
|
||||
.attr('class', 'digits')
|
||||
.attr('opacity', 1)
|
||||
.append('text')
|
||||
.attr('x', width / 2)
|
||||
.attr('y', y)
|
||||
.attr('class', 'big')
|
||||
.attr('alignment-baseline', 'middle')
|
||||
.attr('id', 'bigNumber')
|
||||
.style('font-weight', 'bold')
|
||||
.style('cursor', 'pointer')
|
||||
.text(formattedNumber)
|
||||
.attr('font-family', 'Roboto')
|
||||
.attr('font-size', (width / formattedNumber.length) * 1.3)
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('fill', 'black');
|
||||
|
||||
// Printing big number subheader text
|
||||
if (json.subheader !== null) {
|
||||
if (json.subheader) {
|
||||
const fontSize = (width / json.subheader.length) * 1.5;
|
||||
g.append('text')
|
||||
.attr('x', width / 2)
|
||||
.attr('y', (height / 16) * 12)
|
||||
.text(json.subheader)
|
||||
.attr('id', 'subheader_text')
|
||||
.style('font-size', d3.min([height, width]) / 8)
|
||||
.style('text-anchor', 'middle');
|
||||
.attr('x', width / 2)
|
||||
.attr('y', (height / 16) * 12)
|
||||
.text(json.subheader)
|
||||
.attr('id', 'subheader_text')
|
||||
.attr('font-family', 'Roboto')
|
||||
.attr('font-size', fontSize)
|
||||
.style('text-anchor', 'middle');
|
||||
}
|
||||
|
||||
if (fd.viz_type === 'big_number') {
|
||||
// Drawing trend line
|
||||
|
||||
g.append('path')
|
||||
.attr('d', function () {
|
||||
return line(data);
|
||||
})
|
||||
.attr('stroke-width', 5)
|
||||
.attr('opacity', 0.5)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke-linecap', 'round')
|
||||
.attr('stroke', 'grey');
|
||||
.attr('d', () => line(data))
|
||||
.attr('stroke-width', 5)
|
||||
.attr('opacity', 0.5)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke-linecap', 'round')
|
||||
.attr('stroke', 'grey');
|
||||
|
||||
g = svg.append('g')
|
||||
.attr('class', 'digits')
|
||||
.attr('opacity', 1);
|
||||
.attr('class', 'digits')
|
||||
.attr('opacity', 1);
|
||||
|
||||
if (vCompare !== null) {
|
||||
y = (height / 8) * 3;
|
||||
|
|
@ -112,71 +124,116 @@ function bigNumberVis(slice, payload) {
|
|||
// Printing compare %
|
||||
if (vCompare) {
|
||||
g.append('text')
|
||||
.attr('x', width / 2)
|
||||
.attr('y', (height / 16) * 12)
|
||||
.text(fp(vCompare) + json.compare_suffix)
|
||||
.style('font-size', d3.min([height, width]) / 8)
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('fill', c)
|
||||
.attr('stroke', c);
|
||||
.attr('x', width / 2)
|
||||
.attr('y', (height / 16) * 12)
|
||||
.text(fp(vCompare) + json.compare_suffix)
|
||||
.style('font-size', d3.min([height, width]) / 8)
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('fill', c)
|
||||
.attr('stroke', c);
|
||||
}
|
||||
|
||||
// axes
|
||||
const gAxis = svg.append('g').attr('class', 'axis').attr('opacity', 0);
|
||||
g = gAxis.append('g');
|
||||
const minMaxTickValues = scaleX.domain();
|
||||
// prepend the min value, and append the max value to the list of tick values
|
||||
const tickValues =
|
||||
[minMaxTickValues[0]]
|
||||
.concat(scaleX.ticks(getNumTicks(data, slice, margin)))
|
||||
.concat([minMaxTickValues[1]]);
|
||||
const xAxis = d3.svg.axis()
|
||||
.scale(scaleX)
|
||||
.orient('bottom')
|
||||
.ticks(4)
|
||||
.tickFormat(formatDate);
|
||||
.scale(scaleX)
|
||||
.orient('bottom')
|
||||
.tickValues(tickValues)
|
||||
.tickFormat(formatDate);
|
||||
g.call(xAxis);
|
||||
g.attr('transform', 'translate(0,' + (height - margin) + ')');
|
||||
g.attr('transform', 'translate(0,' + (height - margin) + ')').attr('class', 'xAxis');
|
||||
|
||||
g = gAxis.append('g').attr('transform', 'translate(' + (width - margin) + ',0)');
|
||||
g = gAxis.append('g').attr('transform', `translate(${margin}, 0)`).attr('class', 'yAxis');
|
||||
const yAxis = d3.svg.axis()
|
||||
.scale(scaleY)
|
||||
.orient('left')
|
||||
.tickFormat(d3.format(fd.y_axis_format))
|
||||
.tickValues(valueExt);
|
||||
.scale(scaleY)
|
||||
.orient('left')
|
||||
.tickFormat(d3.format(fd.y_axis_format))
|
||||
.tickValues(valueExt);
|
||||
g.call(yAxis);
|
||||
g.selectAll('text')
|
||||
.style('text-anchor', 'end')
|
||||
.attr('y', '-7')
|
||||
.attr('x', '-4');
|
||||
.style('text-anchor', 'end')
|
||||
.attr('y', '-7')
|
||||
.attr('x', '-4');
|
||||
|
||||
g.selectAll('text')
|
||||
.style('font-size', '10px');
|
||||
// Define the div for the tooltip
|
||||
const tooltipEl =
|
||||
d3.select('body')
|
||||
.append('div')
|
||||
.attr('class', 'line-tooltip')
|
||||
.attr('width', 200)
|
||||
.attr('height', 200)
|
||||
.style('opacity', 0);
|
||||
|
||||
const renderTooltip = (d) => {
|
||||
const date = formatDate(d[0]);
|
||||
const value = f(d[1]);
|
||||
return `
|
||||
<div>
|
||||
<span style="float: left; margin-right: 20px;"><strong>${date}</strong></span>
|
||||
<span style="float: right">${value}</span>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
// Add the scatterplot and trigger the mouse events for the tooltips
|
||||
svg
|
||||
.selectAll('dot')
|
||||
.data(data)
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('r', 10)
|
||||
.attr('cx', d => scaleX(d[0]))
|
||||
.attr('cy', d => scaleY(d[1]))
|
||||
.attr('fill-opacity', '0')
|
||||
.on('mouseover', (d) => {
|
||||
tooltipEl.html(renderTooltip(d))
|
||||
.style('left', (d3.event.pageX) + 'px')
|
||||
.style('top', (d3.event.pageY - 28) + 'px');
|
||||
tooltipEl.transition().duration(200).style('opacity', 0.9);
|
||||
})
|
||||
.on('mouseout', () => {
|
||||
tooltipEl.transition().duration(500).style('opacity', 0);
|
||||
});
|
||||
|
||||
// show hide x/y axis on mouseover/out
|
||||
div.on('mouseover', function () {
|
||||
const el = d3.select(this);
|
||||
el.selectAll('path')
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 1)
|
||||
.style('stroke-width', '2px');
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 1)
|
||||
.style('stroke-width', '2px');
|
||||
el.selectAll('g.digits')
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 0.1);
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 0.1);
|
||||
el.selectAll('g.axis')
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 1);
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 1);
|
||||
})
|
||||
.on('mouseout', function () {
|
||||
const el = d3.select(this);
|
||||
el.select('path')
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 0.5)
|
||||
.style('stroke-width', '5px');
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 0.5)
|
||||
.style('stroke-width', '5px');
|
||||
el.selectAll('g.digits')
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 1);
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 1);
|
||||
el.selectAll('g.axis')
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 0);
|
||||
.transition()
|
||||
.duration(500)
|
||||
.attr('opacity', 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue