Skip to content
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
3 changes: 3 additions & 0 deletions superset/assets/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@
"func-names": 0,
"react/jsx-no-bind": 0,
"no-confusing-arrow": 0,
},
"globals": {
"document": true,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down
8 changes: 8 additions & 0 deletions superset/assets/javascripts/modules/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
24 changes: 17 additions & 7 deletions superset/assets/visualizations/big_number.css
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}

233 changes: 145 additions & 88 deletions superset/assets/visualizations/big_number.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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');

g.selectAll('text')
.style('font-size', '10px');

.style('text-anchor', 'end')
.attr('y', '-7')
.attr('x', '-4');

// 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);
});
}
}
Expand Down