changeset 2822:f2e4c39cdcfa

client: spuc8: implemented tooltips
author Markus Kottlaender <markus@intevation.de>
date Wed, 27 Mar 2019 12:02:41 +0100
parents f87783a28c34
children b150e5b37afe
files client/src/components/gauge/HydrologicalConditions.vue
diffstat 1 files changed, 171 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/components/gauge/HydrologicalConditions.vue	Wed Mar 27 11:36:15 2019 +0100
+++ b/client/src/components/gauge/HydrologicalConditions.vue	Wed Mar 27 12:02:41 2019 +0100
@@ -18,7 +18,7 @@
       stroke-width: 2
       fill: none
       &.mean
-        stroke: steelblue
+        stroke: red
       &.median
         stroke: black
       &.q25
@@ -70,6 +70,24 @@
       .handle
         stroke: rgba($color-info, 0.5)
         fill: rgba($color-info, 0.5)
+
+    .chart-dots
+      clip-path: url(#clip)
+      .chart-dot
+        fill: black
+        stroke: black
+        pointer-events: none
+        opacity: 0
+        transition: opacity 0.1s
+    .chart-tooltip
+      opacity: 0
+      transition: opacity 0.3s
+      rect
+        fill: #fff
+        stroke: #ccc
+      text
+        fill: #666
+        font-size: 12px
 </style>
 
 <script>
@@ -206,6 +224,7 @@
         );
 
       this.createZoom(updaters, eventRect);
+      this.createTooltips(eventRect);
     },
     getDimensions() {
       // dimensions and margins
@@ -526,10 +545,157 @@
 
       eventRect.call(zoom);
     },
-    isNext(seconds) {
-      // helper to check whether points in the chart are "next to each other"
-      // for that they need to be exactly the specified amount of seconds apart.
-      return (prev, current) => current.date - prev.date === seconds * 1000;
+    createTooltips(eventRect) {
+      // create clippable container for the dot
+      this.diagram
+        .append("g")
+        .attr("class", "chart-dots")
+        .append("circle")
+        .attr("class", "chart-dot")
+        .attr("r", 4);
+
+      // create container for the tooltip
+      const tooltip = this.diagram.append("g").attr("class", "chart-tooltip");
+      tooltip
+        .append("rect")
+        .attr("rx", "0.25rem")
+        .attr("ry", "0.25rem");
+
+      // create container for multiple text rows
+      const tooltipText = tooltip.append("text").attr("text-anchor", "middle");
+
+      // padding inside the tooltip box and diagram padding to determine left
+      // and right offset from the diagram boundaries for the tooltip position.
+      const tooltipPadding = 10;
+      const diagramPadding = 5;
+
+      eventRect
+        .on("mouseover", () => {
+          this.diagram.select(".chart-dot").style("opacity", 1);
+          this.diagram.select(".chart-tooltip").style("opacity", 1);
+        })
+        .on("mouseout", () => {
+          this.diagram.select(".chart-dot").style("opacity", 0);
+          this.diagram.select(".chart-tooltip").style("opacity", 0);
+        })
+        .on("mousemove", () => {
+          // find data point closest to mouse
+          const x0 = this.scale.x.invert(
+              d3.mouse(document.querySelector(".zoom"))[0]
+            ),
+            i = d3.bisector(d => d.date).left(this.longtermWaterlevels, x0, 1),
+            d0 = this.longtermWaterlevels[i - 1],
+            d1 = this.longtermWaterlevels[i] || d0,
+            d = x0 - d0.date > d1.date - x0 ? d1 : d0;
+
+          const coords = {
+            x: this.scale.x(d.date),
+            y: this.scale.y(d.median)
+          };
+
+          // position the dot
+          this.diagram
+            .select(".chart-dot")
+            .style("opacity", 1)
+            .attr("transform", `translate(${coords.x}, ${coords.y})`);
+
+          // remove current texts
+          tooltipText.selectAll("tspan").remove();
+
+          // write date
+          tooltipText
+            .append("tspan")
+            .attr("alignment-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text(
+              d.date.toLocaleString([], {
+                year: "2-digit",
+                month: "2-digit",
+                day: "2-digit"
+              })
+            );
+
+          tooltipText
+            .append("tspan")
+            .attr("x", 0)
+            .attr("y", 0)
+            .attr("dy", "1.3rem")
+            .attr("alignment-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text("Q75%: " + d.q75 + " cm");
+          tooltipText
+            .append("tspan")
+            .attr("x", 0)
+            .attr("y", 0)
+            .attr("dy", "2.3rem")
+            .attr("alignment-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text("Median: " + d.median + " cm");
+          tooltipText
+            .append("tspan")
+            .attr("x", 0)
+            .attr("y", 0)
+            .attr("dy", "3.3rem")
+            .attr("alignment-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text("Q25%: " + d.q25 + " cm");
+          tooltipText
+            .append("tspan")
+            .attr("x", 0)
+            .attr("y", 0)
+            .attr("dy", "4.3rem")
+            .attr("alignment-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text("Max: " + d.max + " cm");
+          tooltipText
+            .append("tspan")
+            .attr("x", 0)
+            .attr("y", 0)
+            .attr("dy", "5.3rem")
+            .attr("alignment-baseline", "hanging")
+            .attr("text-anchor", "middle")
+            .text("Min: " + d.min + " cm");
+
+          const dYear = this.yearWaterlevels.find(
+            ywl => ywl.date.getTime() === d.date.getTime()
+          );
+          if (dYear) {
+            tooltipText
+              .append("tspan")
+              .attr("x", 0)
+              .attr("y", 0)
+              .attr("dy", "6.3rem")
+              .attr("alignment-baseline", "hanging")
+              .attr("text-anchor", "middle")
+              .text("Year: " + dYear.mean.toFixed(1) + " cm");
+          }
+
+          // get text dimensions
+          const textBBox = tooltipText.node().getBBox();
+
+          this.diagram
+            .selectAll(".chart-tooltip text tspan")
+            .attr("x", textBBox.width / 2 + tooltipPadding)
+            .attr("y", tooltipPadding);
+
+          // position and scale tooltip box
+          const xMax =
+            this.dimensions.width -
+            (textBBox.width + diagramPadding + tooltipPadding * 2);
+          const tooltipX = Math.max(
+            diagramPadding,
+            Math.min(coords.x - (textBBox.width + tooltipPadding * 2) / 2, xMax)
+          );
+          const tooltipY =
+            coords.y - (textBBox.height + tooltipPadding * 2) - 10;
+          this.diagram
+            .select(".chart-tooltip")
+            .style("opacity", 1)
+            .attr("transform", `translate(${tooltipX}, ${tooltipY})`)
+            .select("rect")
+            .attr("width", textBBox.width + tooltipPadding * 2)
+            .attr("height", textBBox.height + tooltipPadding * 2);
+        });
     }
   },
   created() {