Commit a1ae5c5b authored by amarcic's avatar amarcic
Browse files

Merge branch 'd3timelinezoom'

parents c4c003f5 3caff468
......@@ -11,6 +11,7 @@
"Data sources": "Herkunft der Daten",
"Timeline": "Zeitstrahl",
"Temporal distribution": "Zeitliche Verteilung",
"item/s": "Objekt/e",
"Turn on/off marker clustering": "Gruppieren von Markern",
"Resize map to show all markers": "Kartenausschnitt für aktuelle Marker anpassen",
......
......@@ -2,7 +2,7 @@ import React, {useEffect, useRef, useState} from "react";
import { Grid } from "@material-ui/core";
import { useTranslation } from "react-i18next";
import { useStyles } from "../../styles";
import {select, scaleBand, axisBottom, axisLeft, scaleLinear, max} from "d3";
import {select, scaleBand, axisBottom, axisLeft, scaleLinear, max, scaleQuantize} from "d3";
import {prepareHistogramData, binTimespanObjects} from "../../utils";
export const Histogram = (props) => {
......@@ -74,6 +74,10 @@ export const Histogram = (props) => {
svg.select(".yAxis")
.call(axisLeft(y));
const colorScale = scaleQuantize()
.domain([0,maxYValue])
.range(["#5AE6BA","#4BC8A3","#3EAA8C","#318D75","#25725F"]);
//calculate and append histogram bars for each bin
svg.select(".bars")
.attr("transform",`translate(${margin.left}, ${margin.top})`)
......@@ -87,8 +91,8 @@ export const Histogram = (props) => {
.style("transform", "scale(1,-1)")
.attr("width", x.bandwidth())
.on("mouseenter", (event, value) => {
const element = svg.selectAll(".bar").nodes();
const index = element.indexOf(event.target);
//const element = svg.selectAll(".bar").nodes();
//const index = element.indexOf(event.target);
//console.log(value);
svg
.selectAll(".tooltip")
......@@ -105,7 +109,7 @@ export const Histogram = (props) => {
//.on("mouseleave", () => svg.select(".tooltip").remove())
.transition()
.attr("height", value => height - y(value.values.length))
.attr("fill", "#69b3a2");
.attr("fill", value => colorScale(value.values.length));
}
}, [binnedData])
......
import React, {useEffect, useRef, useState} from "react";
import { useTranslation } from "react-i18next";
import { useStyles } from "../../styles";
import { select, scaleBand, axisBottom, scaleLinear, zoom } from "d3";
import { select, scaleBand, axisBottom, scaleLinear, scaleQuantize, zoom, extent } from "d3";
import {getTimeRangeOfTimelineData, newGroupByPeriods} from "../../utils";
export const TimelineChart = (props) => {
......@@ -11,8 +11,13 @@ export const TimelineChart = (props) => {
const svgRef = useRef();
//const filteredTimelineData = props.filteredTimelineData;
console.log("dimensions", props.dimensions)
//todo: make highlighted global state?
let highlighted = {
objects: [],
periods: [],
locations: []
};
const xDomain = getTimeRangeOfTimelineData(props.filteredTimelineData,"period");
const dataUnsorted = newGroupByPeriods(props.filteredTimelineData);
const data = dataUnsorted && new Map([...dataUnsorted.entries()]
......@@ -49,7 +54,9 @@ export const TimelineChart = (props) => {
const { data, svgRef, xDomain } = timelineConfig;
const { width, height, margin } = dimensions;
const svg = select(svgRef.current);
const labelRenderLimit = 8;
//empty canvas in case no data is found by query
if(!data||data.size===0) {
svg.selectAll(".bar").remove();
svg.selectAll(".label").remove();
......@@ -57,10 +64,11 @@ export const TimelineChart = (props) => {
}
//console.log([...data.values()]);
const selection = svg.select(".timelineGroup").selectAll("rect").data([...data.values()], data => data.periodId);
//console.log("initial selection", selection);
const selectionLabels = svg.select(".timelineGroup").selectAll(".label").data([...data.values()], data => data.periodId);
const periodIds = [...data.keys()];
const itemQuantityExtent = extent([...data.values()].map( value => value.items.length));
//scale for the x axis
const xScale = scaleLinear()
......@@ -78,6 +86,39 @@ export const TimelineChart = (props) => {
.attr("transform", `translate(0,${height+margin.top})`)
.call(xAxis);
//todo: application wide color scale should be used; colors are not that great
const colorScale = scaleQuantize()
.domain(itemQuantityExtent)
.range(["#5AE6BA","#4BC8A3","#3EAA8C","#318D75","#25725F"]);
//function to add labels to the bars (when bandwith is heigh enough for readable labels)
//todo: remove outer dependency on selectionLabels
const addLabels = (bandwidth, renderLimit) => {
if (bandwidth > renderLimit) {
selectionLabels
.enter()
.append("text")
.attr("class", "label")
.attr("x", value => xScale(value.periodSpan?.[0]))
.attr("y", value => yScale(value.periodId))
.text(value => value.periodName);
//position labels
selectionLabels
.attr("x", value => xScale(value.periodSpan?.[0]))
.attr("y", value => yScale(value.periodId))
//remove labels on exit
selectionLabels
.exit()
.remove()
} else {
svg.selectAll(".label")
.remove();
}
}
//add clip path to svg for later use
if (document.getElementById("clip")===null&&height&&width) {
svg.append("defs").append("clipPath")
.attr("id","clip")
......@@ -88,6 +129,7 @@ export const TimelineChart = (props) => {
.attr("y", 0);
}
//apply the prepared clip path to the svg group containing the bars and labels
svg.select(".timelineGroup")
.attr("clip-path", "url(#clip)");
......@@ -105,6 +147,8 @@ export const TimelineChart = (props) => {
xAxis.scale(xScaleNew);
xAxisDraw.call(xAxis);
addLabels(yScaleNew.bandwidth(), labelRenderLimit);
svg.selectAll(".label")
.attr("x", value => xScaleNew(value.periodSpan?.[0]))
.attr("y", value => yScaleNew(value.periodId));
......@@ -123,7 +167,7 @@ export const TimelineChart = (props) => {
enter => enter
.append("rect")
.attr("class", "bar")
.attr("fill", "#69b3a2")
//.attr("fill", "#69b3a2")
);
console.log("new and updating: ", selectionEnteringAndUpdating);
......@@ -135,25 +179,37 @@ export const TimelineChart = (props) => {
.attr("x", value => xScale(value.periodSpan?.[0]))
.attr("y", (value, index) => yScale(periodIds[index]))
.attr("width", value => Math.abs(xScale(value.periodSpan?.[0])-xScale(value.periodSpan?.[1]))||0)
.attr("fill", value => colorScale(value.items.length))
//add labels to the bars
selectionLabels
.enter()
.append("text")
.attr("class", "label")
.attr("x", value => xScale(value.periodSpan?.[0]))
.attr("y", value => yScale(value.periodId))
.text(value => value.periodName);
selectionLabels
.attr("x", value => xScale(value.periodSpan?.[0]))
.attr("y", value => yScale(value.periodId))
//display tooltip when mouse enters bar on chart
selectionEnteringAndUpdating
.on("mouseenter", (event, value) => {
highlighted.objects = value.items.map( item => item.id);
//console.log("high: ", highlighted)
svg
.selectAll(".tooltip")
.data([value])
.join("text")
.attr("class", "tooltip")
.text( value => yScale.bandwidth() <= labelRenderLimit
? `${value.periodName}: ${value.items.length} ${t("item/s")}`
: `${value.items.length} ${t("item/s")}`)
.attr("text-anchor", "middle")
.attr("x", value => xScale(value.periodSpan?.[0]))
.attr("y", value => yScale(value.periodId)+yScale.bandwidth()/*+margin.top*/)
});
//remove tooltips when mouse leaves the svg
svg
.on("mouseleave", () => {
svg
.selectAll(".tooltip")
.remove()
} )
//remove labels on exit
selectionLabels
.exit()
.remove()
addLabels(yScale.bandwidth(), labelRenderLimit);
//apply zoom
initZoom();
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment