Mercurial > gemma
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() {