williaster commented on a change in pull request #3518: Full Annotation 
Framework
URL: 
https://github.com/apache/incubator-superset/pull/3518#discussion_r155895464
 
 

 ##########
 File path: superset/assets/visualizations/nvd3_vis.js
 ##########
 @@ -511,82 +514,247 @@ function nvd3Vis(slice, payload) {
       .attr('width', width)
       .call(chart);
 
-      // add annotation_layer
-      if (isTimeSeries && payload.annotations && payload.annotations.length) {
-        const tip = d3tip()
+      // on scroll, hide tooltips. throttle to only 4x/second.
+      $(window).scroll(throttle(hideTooltips, 250));
+
+      const annotationLayers = (slice.formData.annotation_layers || [])
+          .filter(x => x.show);
+      if (isTimeSeries && annotationLayers) {
+        // Formula annotations
+        const formulas = annotationLayers.filter(a => a.annotationType === 
AnnotationTypes.FORMULA)
+          .map(a => ({ ...a, formula: mathjs.parse(a.value) }));
+
+        let xMax;
+        let xMin;
+        let xScale;
+        if (vizType === VIZ_TYPES.bar) {
+          xMin = d3.min(data[0].values, d => (d.x));
+          xMax = d3.max(data[0].values, d => (d.x));
+          xScale = d3.scale.quantile()
+            .domain([xMin, xMax])
+            .range(chart.xAxis.range());
+        } else {
+          xMin = chart.xAxis.scale().domain()[0].valueOf();
+          xMax = chart.xAxis.scale().domain()[1].valueOf();
+          xScale = chart.xScale();
+        }
+
+        if (Array.isArray(formulas) && formulas.length) {
+          const xValues = [];
+          if (vizType === VIZ_TYPES.bar) {
+            // For bar-charts we want one data point evaluated for every
+            // data point that will be displayed.
+            const distinct = data.reduce((xVals, d) => {
+              d.values.forEach(x => xVals.add(x.x));
+              return xVals;
+            }, new Set());
+            xValues.push(...distinct.values());
+            xValues.sort();
+          } else {
+            // For every other time visualization it should be ok, to have a
+            // data points in even intervals.
+            let period = Math.min(...data.map(d =>
+              Math.min(...d.values.slice(1).map((v, i) => v.x - 
d.values[i].x))));
+            const dataPoints = (xMax - xMin) / (period || 1);
+            // make sure that there are enough data points and not too many
+            period = dataPoints < 100 ? (xMax - xMin) / 100 : period;
+            period = dataPoints > 500 ? (xMax - xMin) / 500 : period;
+            xValues.push(xMin);
+            for (let x = xMin; x < xMax; x += period) {
+              xValues.push(x);
+            }
+            xValues.push(xMax);
+          }
+          const formulaData = formulas.map(fo => ({
+            key: fo.name,
+            values: xValues.map((x => ({ y: fo.formula.eval({ x }), x }))),
+            color: fo.color,
+            strokeWidth: fo.width,
+            classed: `${fo.opacity} ${fo.style}`,
+          }));
+          data.push(...formulaData);
+        }
+
+        const annotationHeight = chart.yAxis.scale().range()[0];
+        const tipFactory = layer => d3tip()
           .attr('class', 'd3-tip')
           .direction('n')
           .offset([-5, 0])
           .html((d) => {
-            if (!d || !d.layer) {
+            if (!d) {
               return '';
             }
-
-            const title = d.short_descr ?
-              d.short_descr + ' - ' + d.layer :
-              d.layer;
-            const body = d.long_descr;
+            const title = d[layer.titleColumn] && d[layer.titleColumn].length ?
+              d[layer.titleColumn] + ' - ' + layer.name :
+              layer.name;
+            const body = Array.isArray(layer.descriptionColumns) ?
+              layer.descriptionColumns.map(c => d[c]) : Object.values(d);
             return '<div><strong>' + title + '</strong></div><br/>' +
-            '<div>' + body + '</div>';
+              '<div>' + body.join(', ') + '</div>';
           });
 
-        const hh = chart.yAxis.scale().range()[0];
+        // Native annotations layers
+        annotationLayers.filter(x => (
+          x.annotationType === AnnotationTypes.NATIVE && payload && 
payload.annotations
+        )).forEach((e, index) => {
+          const annotations = payload.annotations
+            .filter(x => x.layer_id === e.value);
+          if (annotations.length) {
+            let annotationLayer;
+            let minStep;
+            if (vizType === VIZ_TYPES.bar) {
+              minStep = chart.xAxis.range()[1] - chart.xAxis.range()[0];
+              annotationLayer = 
d3.select(slice.selector).select('.nv-barsWrap')
+                .insert('g', ':first-child')
+                .attr('class', `native-bar-annotation-layer-${index}`);
+            } else {
+              minStep = 1;
+              annotationLayer = 
d3.select(slice.selector).select('.nv-wrap').append('g')
+                .attr('class', `nv-native-annotation-layer-${index}`);
+            }
+            const aColor = e.color || getColorFromScheme(e.name, 
fd.color_scheme);
+            const tip = tipFactory(
+              {
+                name: e.name,
+                titleColumn: 'short_descr',
+                descriptionColumns: ['long_descr'],
+              });
+
+            annotationLayer.selectAll('rect')
+              .data(annotations)
+              .enter()
+              .append('rect')
+              .attr('x', d => (xScale(d.start_dttm)))
+              .attr('y', 0)
+              .attr('width', (d) => {
+                const w = xScale(d.end_dttm) - xScale(d.start_dttm);
+                return w === 0 ? minStep : w;
+              })
+              .attr('height', annotationHeight)
+              .attr('class', `${e.opacity} ${e.style}`)
+              .style('stroke-width', e.width)
+              .style('stroke', aColor)
+              .style('fill', aColor)
+              .style('fill-opacity', 0.2)
+              .on('mouseover', tip.show)
+              .on('mouseout', tip.hide)
+              .call(tip);
+          }
+        });
 
-        let annotationLayer;
-        let xScale;
-        let minStep;
-        if (vizType === 'bar') {
-          const xMax = d3.max(payload.data[0].values, d => (d.x));
-          const xMin = d3.min(payload.data[0].values, d => (d.x));
-          minStep = chart.xAxis.range()[1] - chart.xAxis.range()[0];
-          annotationLayer = svg.select('.nv-barsWrap')
-            .insert('g', ':first-child');
-          xScale = d3.scale.quantile()
-            .domain([xMin, xMax])
-            .range(chart.xAxis.range());
-        } else {
-          minStep = 1;
-          annotationLayer = svg.select('.nv-background')
-            .append('g');
-          xScale = chart.xScale();
-        }
+        if (slice.annotationData && Object.keys(slice.annotationData).length) {
+          // Event annotations
+          annotationLayers.filter(x => (
+            x.annotationType === AnnotationTypes.EVENT &&
+            slice.annotationData && slice.annotationData[x.name]
+          )).forEach((e, index) => {
+            // Add event annotation layer
+            const annotations = 
d3.select(slice.selector).select('.nv-wrap').append('g')
+              .attr('class', `nv-event-annotation-layer-${index}`);
+            const aColor = e.color || getColorFromScheme(e.name, 
fd.color_scheme);
+
+            const tip = tipFactory(e);
+            const records = (slice.annotationData[e.name].records || 
[]).map((r) => {
+              const timeColumn = new Date(r[e.timeColumn]);
+              return {
+                ...r,
+                [e.timeColumn]: timeColumn,
+              };
+            }).filter(r => !Number.isNaN(r[e.timeColumn].getMilliseconds()));
+            if (records.length) {
+              annotations.selectAll('line')
+                .data(records)
+                .enter()
+                .append('line')
+                .attr({
+                  x1: d => xScale(new Date(d[e.timeColumn])),
+                  y1: 0,
+                  x2: d => xScale(new Date(d[e.timeColumn])),
+                  y2: annotationHeight,
+                })
+                .attr('class', `${e.opacity} ${e.style}`)
+                .style('stroke', aColor)
+                .style('stroke-width', e.width)
+                .on('mouseover', tip.show)
+                .on('mouseout', tip.hide)
+                .call(tip);
+            }
+          });
 
-        annotationLayer
-          .attr('class', 'annotation-container')
-          .append('defs')
-          .append('pattern')
-          .attr('id', 'diagonal')
-          .attr('patternUnits', 'userSpaceOnUse')
-          .attr('width', 8)
-          .attr('height', 10)
-          .attr('patternTransform', 'rotate(45 50 50)')
-          .append('line')
-          .attr('stroke-width', 7)
-          .attr('y2', 10);
-
-        annotationLayer.selectAll('rect')
-          .data(payload.annotations)
-          .enter()
-          .append('rect')
-          .attr('class', 'annotation')
-          .attr('x', d => (xScale(d.start_dttm)))
-          .attr('y', 0)
-          .attr('width', (d) => {
-            const w = xScale(d.end_dttm) - xScale(d.start_dttm);
-            return w === 0 ? minStep : w;
-          })
-          .attr('height', hh)
-          .attr('fill', 'url(#diagonal)')
-          .on('mouseover', tip.show)
-          .on('mouseout', tip.hide);
-
-        annotationLayer.selectAll('rect').call(tip);
-      }
-    }
 
-    // on scroll, hide tooltips. throttle to only 4x/second.
-    $(window).scroll(throttle(hideTooltips, 250));
+          // Interval annotations
 
 Review comment:
   can this not be merged / consolidated with the "native" annotations that are 
also intervals?

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to