From 40bf95a3d8451b4df607552d03944fd53caf9dc0 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 16 Apr 2020 10:43:13 +0200 Subject: [PATCH 01/95] Radar setup --- packages/core/demo/data/index.ts | 17 +++++++ packages/core/demo/data/radar.ts | 21 ++++++++ packages/core/src/charts/index.ts | 1 + packages/core/src/charts/radar.ts | 51 ++++++++++++++++++++ packages/core/src/components/graphs/radar.ts | 25 ++++++++++ packages/core/src/configuration.ts | 14 +++++- packages/core/src/interfaces/charts.ts | 10 ++++ 7 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 packages/core/demo/data/radar.ts create mode 100644 packages/core/src/charts/radar.ts create mode 100644 packages/core/src/components/graphs/radar.ts diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 099476b985..a50344350d 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -6,6 +6,7 @@ import * as pieDemos from "./pie"; import * as scatterDemos from "./scatter"; import * as stepDemos from "./step"; import * as timeSeriesAxisDemos from "./time-series-axis"; +import * as radarDemos from "./radar"; export * from "./bar"; export * from "./bubble"; @@ -14,6 +15,7 @@ export * from "./line"; export * from "./pie"; export * from "./scatter"; export * from "./step"; +export * from "./radar"; import { createChartSandbox, @@ -65,6 +67,11 @@ export const chartTypes = { vanilla: "DonutChart", angular: "ibm-donut-chart", vue: "ccv-donut-chart" + }, + RadarChart: { + vanilla: "RadarChart", + angular: "ibm-radar-chart", + vue: "ccv-radar-chart" } }; @@ -315,6 +322,16 @@ let allDemoGroups = [ chartType: chartTypes.LineChart } ] + }, + { + title: "Radar", + demos: [ + { + data: radarDemos.radarData, + options: radarDemos.radarOptions, + chartType: chartTypes.RadarChart + } + ] } ] as any; diff --git a/packages/core/demo/data/radar.ts b/packages/core/demo/data/radar.ts new file mode 100644 index 0000000000..31e928dc68 --- /dev/null +++ b/packages/core/demo/data/radar.ts @@ -0,0 +1,21 @@ +export const radarData = [ + { group: "Sugar", key: "London", value: 7 }, + { group: "Oil", key: "London", value: 6 }, + { group: "Water", key: "London", value: 12 }, + { group: "Sugar", key: "Milan", value: 10 }, + { group: "Oil", key: "Milan", value: 6 }, + { group: "Water", key: "Milan", value: 28 }, + { group: "Sugar", key: "Paris", value: 12 }, + { group: "Oil", key: "Paris", value: 16 }, + { group: "Water", key: "Paris", value: 26 }, + { group: "Sugar", key: "New York", value: 4 }, + { group: "Oil", key: "New York", value: 15 }, + { group: "Water", key: "New York", value: 25 }, + { group: "Sugar", key: "Sydney", value: 12 }, + { group: "Oil", key: "Sydney", value: 16 }, + { group: "Water", key: "Sydney", value: 23 } +]; + +export const radarOptions = { + title: "Radar" +}; diff --git a/packages/core/src/charts/index.ts b/packages/core/src/charts/index.ts index 652290e923..3eb4bae05f 100644 --- a/packages/core/src/charts/index.ts +++ b/packages/core/src/charts/index.ts @@ -6,3 +6,4 @@ export * from "./line"; export * from "./scatter"; export * from "./pie"; export * from "./donut"; +export * from "./radar"; diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts new file mode 100644 index 0000000000..77ac8aad0e --- /dev/null +++ b/packages/core/src/charts/radar.ts @@ -0,0 +1,51 @@ +// Internal Imports +import { Chart } from "../chart"; +import * as Configuration from "../configuration"; +import { + ChartConfig, + RadarChartOptions +} from "../interfaces/index"; +import { Tools } from "../tools"; + +// Components +import { + // the imports below are needed because of typescript bug (error TS4029) + Legend, + LayoutComponent +} from "../components/index"; +import { Radar } from "../components/graphs/radar"; + +export class RadarChart extends Chart { + // TODO - Optimize the use of "extending" + constructor(holder: Element, chartConfigs: ChartConfig, extending = false) { + super(holder, chartConfigs); + + // TODO - Optimize the use of "extending" + if (extending) { + return; + } + + // Merge the default options for this chart + // With the user provided options + this.model.setOptions( + Tools.mergeDefaultChartOptions( + Configuration.options.radarChart, + chartConfigs.options + ) + ); + + // Initialize data, services, components etc. + this.init(holder, chartConfigs); + } + + getComponents() { + // Specify what to render inside the graph-frame + const graphFrameComponents = [ + new Radar(this.model, this.services) + ]; + + // get the base chart components and export with tooltip + const components: any[] = this.getChartComponents(graphFrameComponents); + return components; + } +} diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts new file mode 100644 index 0000000000..04260fe6f5 --- /dev/null +++ b/packages/core/src/components/graphs/radar.ts @@ -0,0 +1,25 @@ +// Internal Imports +import { Component } from "../component"; +import { DOMUtils } from "../../services"; +import { Tools } from "../../tools"; +import { + CalloutDirections, + Roles, + TooltipTypes, + Events +} from "../../interfaces"; + +// D3 Imports + + +export class Radar extends Component { + type = "radar"; + + init() { + + } + + render(animate = true) { + + } +} diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index c04f3b49b7..aa503798b2 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -9,6 +9,7 @@ import { PieChartOptions, DonutChartOptions, BubbleChartOptions, + RadarChartOptions, // Components GridOptions, AxesOptions, @@ -274,6 +275,16 @@ const donutChart: DonutChartOptions = Tools.merge({}, pieChart, { } } as DonutChartOptions); +/** + * options specific to radar charts + */ +const radarChart: RadarChartOptions = Tools.merge({}, chart, { + radar: { + opt1: true, + opt2: 2 + } +} as RadarChartOptions); + export const options = { chart, axisChart, @@ -284,7 +295,8 @@ export const options = { lineChart, scatterChart, pieChart, - donutChart + donutChart, + radarChart }; /** diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 8821191a7f..88ced3406f 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -188,3 +188,13 @@ export interface DonutChartOptions extends PieChartOptions { }; }; } + +/** + * options specific to radar charts + */ +export interface RadarChartOptions extends BaseChartOptions { + radar?: { + opt1?: boolean; + opt2?: number; + }; +} From 6ff0c5434684a5a83025a3af76fcb4d1516d6e7b Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 16 Apr 2020 15:45:14 +0200 Subject: [PATCH 02/95] Radar: create center and spokes --- packages/core/src/components/graphs/radar.ts | 116 ++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 04260fe6f5..2cd0ba3798 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -10,16 +10,130 @@ import { } from "../../interfaces"; // D3 Imports +import { scaleLinear } from "d3-scale"; +import { extent } from "d3-array"; +const DEBUG = true; + +interface Datum { + group: string; + key: string; + value: number; +} export class Radar extends Component { type = "radar"; init() { - + // console.log(`\nInit`); } render(animate = true) { + console.log(`\nRender`); + + ///////////////////////////// + // Containers + ///////////////////////////// + const svg = this.parent; + const { width, height } = DOMUtils.getSVGElementSize(svg, { useAttrs: true }); + const margin = { top: 10, right: 120, bottom: 10, left: 40 }; + + // draw backdrop + const backdropRect = DOMUtils.appendOrSelect(svg, "rect.radar-backdrop") + .attr("width", "100%") + .attr("height", "100%") + .attr("stroke", "red") + .attr("fill", "none"); + + const data: Array = this.model.getData(); + const displayData: Array = this.model.getDisplayData(); + const options = this.model.getOptions(); + + // console.log(" data:", data); + // console.log(" displayData:", displayData); + // console.log(" options:", options); + + ///////////////////////////// + // Computations + ///////////////////////////// + + const ticksNumber = 5; + const fontSize = 10; + const size = Math.min(width, height); + const diameter = size - 2 * fontSize; + const radius = diameter / 2; + const [minValue, maxValue] = extent(displayData.map(d => d.value)); + const valueScale = scaleLinear().domain([minValue, maxValue]).range([0, radius]).nice(); + const valueTicks = valueScale.ticks(ticksNumber); + + // center + const cx = width / 2; + const cy = height / 2; + const cr = 5; + + // angle between spokes + const spokesValues = uniqBy(data, "key"); + const spokesNumber = spokesValues.length; + const angle = (2 * Math.PI) / spokesNumber; + // console.log({ spokes, spokesNumber, angle }); + + ///////////////////////////// + // Draw + ///////////////////////////// + const debugContainer = DOMUtils.appendOrSelect(svg, "g.debug"); + + // center + const center = DOMUtils.appendOrSelect(debugContainer, "circle.center") + .attr("cx", cx) + .attr("cy", cy) + .attr("r", cr) + .attr("fill", "red"); + + // circumferences + const circumferences = DOMUtils.appendOrSelect(debugContainer, "g.circumferences"); + const circumferencesUpdate = circumferences.selectAll("circle").data(valueTicks); + circumferencesUpdate + .enter() + .append("circle") + .merge(circumferencesUpdate) + .attr("cx", cx) + .attr("cy", cy) + .attr("r", d => d) + .attr("fill", "none") + .attr("stroke", "red"); + + // spokes + const spokes = DOMUtils.appendOrSelect(debugContainer, "g.spokes"); + const spokesUpdate = spokes.selectAll("line").data(spokesValues); + spokesUpdate + .enter() + .append("line") + .merge(spokesUpdate) + .attr("x1", cx) + .attr("y1", cy) + .attr("x2", (d, i) => radius * Math.cos(i * angle) + cx) + .attr("y2", (d, i) => radius * Math.sin(i * angle) + cy) + .attr("stroke", "cyan"); } } + +function uniqBy(dataset: any, attribute: string): any[] { + const allTheValuesByAttribute = dataset.map(d => d[attribute]); + const uniqValues = [...Array.from(new Set(allTheValuesByAttribute))]; + return uniqValues; +} + +function radToDeg(rad: number) { + return rad * (180 / Math.PI); +} + +function degToRad(deg: number) { + return deg * (Math.PI / 180); +} + +function computeCoordinate(angle: number, radius: number) { + const x = radius * Math.cos(angle); + const y = radius * Math.sin(angle); + return { x, y }; +} From 9af448d851b517603dd7e70dffe161d6c39c7ae9 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 16 Apr 2020 16:44:05 +0200 Subject: [PATCH 03/95] Radar refactor --- packages/core/src/components/graphs/radar.ts | 51 +++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 2cd0ba3798..4d76a2b367 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -25,12 +25,9 @@ export class Radar extends Component { type = "radar"; init() { - // console.log(`\nInit`); } render(animate = true) { - console.log(`\nRender`); - ///////////////////////////// // Containers ///////////////////////////// @@ -57,6 +54,14 @@ export class Radar extends Component { // Computations ///////////////////////////// + // center + const cx = width / 2; + const cy = height / 2; + + // angle between spokes + const spokesValues = uniqBy(data, "key"); + const angleStep = (2 * Math.PI) / spokesValues.length; + const ticksNumber = 5; const fontSize = 10; const size = Math.min(width, height); @@ -66,16 +71,20 @@ export class Radar extends Component { const valueScale = scaleLinear().domain([minValue, maxValue]).range([0, radius]).nice(); const valueTicks = valueScale.ticks(ticksNumber); - // center - const cx = width / 2; - const cy = height / 2; - const cr = 5; - - // angle between spokes - const spokesValues = uniqBy(data, "key"); - const spokesNumber = spokesValues.length; - const angle = (2 * Math.PI) / spokesNumber; - // console.log({ spokes, spokesNumber, angle }); + const angleScale = (key: string) => { + const i = spokesValues.indexOf(key); + const angle = angleStep * i; + // rotate by -90° because of the first list should be vertical and not horizontal + return angle - Math.PI / 2; + }; + + const getCoordinates = (key: string, radius: number) => { + const angle = angleScale(key); + // translate by the center + const x = radius * Math.cos(angle) + cx; + const y = radius * Math.sin(angle) + cy; + return { x, y }; + }; ///////////////////////////// // Draw @@ -87,8 +96,8 @@ export class Radar extends Component { const center = DOMUtils.appendOrSelect(debugContainer, "circle.center") .attr("cx", cx) .attr("cy", cy) - .attr("r", cr) - .attr("fill", "red"); + .attr("r", 2) + .attr("fill", "gold"); // circumferences const circumferences = DOMUtils.appendOrSelect(debugContainer, "g.circumferences"); @@ -99,7 +108,7 @@ export class Radar extends Component { .merge(circumferencesUpdate) .attr("cx", cx) .attr("cy", cy) - .attr("r", d => d) + .attr("r", d => valueScale(d)) .attr("fill", "none") .attr("stroke", "red"); @@ -112,8 +121,8 @@ export class Radar extends Component { .merge(spokesUpdate) .attr("x1", cx) .attr("y1", cy) - .attr("x2", (d, i) => radius * Math.cos(i * angle) + cx) - .attr("y2", (d, i) => radius * Math.sin(i * angle) + cy) + .attr("x2", d => getCoordinates(d, radius).x) + .attr("y2", d => getCoordinates(d, radius).y) .attr("stroke", "cyan"); } } @@ -131,9 +140,3 @@ function radToDeg(rad: number) { function degToRad(deg: number) { return deg * (Math.PI / 180); } - -function computeCoordinate(angle: number, radius: number) { - const x = radius * Math.cos(angle); - const y = radius * Math.sin(angle); - return { x, y }; -} From 52efcac06b3be074896f18b37e2d4dbee084b9b0 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 16 Apr 2020 17:06:46 +0200 Subject: [PATCH 04/95] Radar: Do not render anything if width or height are 0 --- packages/core/src/components/graphs/radar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 4d76a2b367..0dc0f3acbd 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -33,7 +33,9 @@ export class Radar extends Component { ///////////////////////////// const svg = this.parent; const { width, height } = DOMUtils.getSVGElementSize(svg, { useAttrs: true }); - const margin = { top: 10, right: 120, bottom: 10, left: 40 }; + if (!width || !height) { + return; + } // draw backdrop const backdropRect = DOMUtils.appendOrSelect(svg, "rect.radar-backdrop") From 61804395bee77c714be62b720d418434c0f1f7e8 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Fri, 17 Apr 2020 14:19:22 +0200 Subject: [PATCH 05/95] Radar: create blobs (wip) --- packages/core/src/components/graphs/radar.ts | 78 ++++++++++++++------ 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 0dc0f3acbd..169e1e6537 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -10,8 +10,10 @@ import { } from "../../interfaces"; // D3 Imports -import { scaleLinear } from "d3-scale"; -import { extent } from "d3-array"; +import { scaleBand, scaleLinear } from "d3-scale"; +import { extent, max } from "d3-array"; +import { lineRadial, areaRadial, curveLinearClosed, curveCardinalClosed } from "d3-shape"; +import { nest } from "d3-collection"; const DEBUG = true; @@ -46,10 +48,16 @@ export class Radar extends Component { const data: Array = this.model.getData(); const displayData: Array = this.model.getDisplayData(); + // nest data by group + const nestedDataByGroup = nest() + .key(d => d.group) + .entries(displayData); + const options = this.model.getOptions(); // console.log(" data:", data); // console.log(" displayData:", displayData); + // console.log(" nestedDataByGroup:", nestedDataByGroup); // console.log(" options:", options); ///////////////////////////// @@ -60,31 +68,38 @@ export class Radar extends Component { const cx = width / 2; const cy = height / 2; - // angle between spokes - const spokesValues = uniqBy(data, "key"); - const angleStep = (2 * Math.PI) / spokesValues.length; - - const ticksNumber = 5; const fontSize = 10; const size = Math.min(width, height); const diameter = size - 2 * fontSize; const radius = diameter / 2; - const [minValue, maxValue] = extent(displayData.map(d => d.value)); - const valueScale = scaleLinear().domain([minValue, maxValue]).range([0, radius]).nice(); - const valueTicks = valueScale.ticks(ticksNumber); - - const angleScale = (key: string) => { - const i = spokesValues.indexOf(key); - const angle = angleStep * i; - // rotate by -90° because of the first list should be vertical and not horizontal - return angle - Math.PI / 2; - }; - const getCoordinates = (key: string, radius: number) => { - const angle = angleScale(key); + // scales + + // given a key, return the corrisponding angle in radiants + const xScale = scaleBand() + .domain(displayData.map(d => d.key)) + .range([0, 2 * Math.PI]); + + const ticksNumber = 5; + const yScale = scaleLinear() + .domain([0, max(displayData.map(d => d.value))]) + .range([0, radius]); + const yTicks = yScale.ticks(ticksNumber); + + // angle slice + const keysValues = uniqBy(displayData, "key"); + const angleSlice = (2 * Math.PI) / keysValues.length; + + const radialLineGenerator = lineRadial() + .angle(d => xScale(d.key)) + .radius(d => yScale(d.value)) + .curve(curveLinearClosed); + + const getCoordinates = (key: string, r: number) => { + const angle = xScale(key); // translate by the center - const x = radius * Math.cos(angle) + cx; - const y = radius * Math.sin(angle) + cy; + const x = r * Math.cos(angle) + cx; + const y = r * Math.sin(angle) + cy; return { x, y }; }; @@ -103,18 +118,35 @@ export class Radar extends Component { // circumferences const circumferences = DOMUtils.appendOrSelect(debugContainer, "g.circumferences"); - const circumferencesUpdate = circumferences.selectAll("circle").data(valueTicks); + const circumferencesUpdate = circumferences.selectAll("circle").data(yTicks); circumferencesUpdate .enter() .append("circle") .merge(circumferencesUpdate) .attr("cx", cx) .attr("cy", cy) - .attr("r", d => valueScale(d)) + .attr("r", d => yScale(d)) .attr("fill", "none") .attr("stroke", "red"); + // Create blobs + const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); + const blobWrapper = blobs.selectAll(".g") + .data(nestedDataByGroup) + .enter() + .append("g") + .attr("class", d => d.key); + // Append the backgrounds + blobWrapper + .append("path") + .attr("class", d => `blob-area-${d.key}`) + .attr("d", d => radialLineGenerator(d.values)) + .attr("stroke", "green") + .style("fill", "green") + .style("fill-opacity", 0.3); + // spokes + const spokesValues = uniqBy(data, "key"); const spokes = DOMUtils.appendOrSelect(debugContainer, "g.spokes"); const spokesUpdate = spokes.selectAll("line").data(spokesValues); spokesUpdate From f42d611b8bfcecb9a0d1d89cb0790d7be3bc6e1d Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Fri, 17 Apr 2020 14:41:40 +0200 Subject: [PATCH 06/95] Radar: set colors --- packages/core/src/components/graphs/radar.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 169e1e6537..d87d1553f3 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -86,6 +86,8 @@ export class Radar extends Component { .range([0, radius]); const yTicks = yScale.ticks(ticksNumber); + const colorScale = (key: string): string => this.model.getFillColor(key); + // angle slice const keysValues = uniqBy(displayData, "key"); const angleSlice = (2 * Math.PI) / keysValues.length; @@ -129,23 +131,22 @@ export class Radar extends Component { .attr("fill", "none") .attr("stroke", "red"); - // Create blobs - const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); - const blobWrapper = blobs.selectAll(".g") + // blobs + const blobContainer = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); + const blob = blobContainer.selectAll(".g") .data(nestedDataByGroup) .enter() .append("g") .attr("class", d => d.key); - // Append the backgrounds - blobWrapper + blob .append("path") .attr("class", d => `blob-area-${d.key}`) .attr("d", d => radialLineGenerator(d.values)) - .attr("stroke", "green") - .style("fill", "green") - .style("fill-opacity", 0.3); + .attr("stroke", d => colorScale(d.key)) + .attr("fill", d => colorScale(d.key)) + .style("fill-opacity", 0.2); - // spokes + // x axes const spokesValues = uniqBy(data, "key"); const spokes = DOMUtils.appendOrSelect(debugContainer, "g.spokes"); const spokesUpdate = spokes.selectAll("line").data(spokesValues); From 0905acc0ced7d9cd37042efe1670b40208936d46 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Fri, 17 Apr 2020 15:45:41 +0200 Subject: [PATCH 07/95] Radar: refactor --- packages/core/src/components/graphs/radar.ts | 74 ++++++++++++++------ 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index d87d1553f3..bf5660047c 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -27,6 +27,11 @@ export class Radar extends Component { type = "radar"; init() { + const { events } = this.services; + // Highlight correct line legend item hovers + events.addEventListener(Events.Legend.ITEM_HOVER, this.handleLegendOnHover); + // Un-highlight lines on legend item mouseouts + events.addEventListener(Events.Legend.ITEM_MOUSEOUT, this.handleLegendMouseOut); } render(animate = true) { @@ -48,16 +53,12 @@ export class Radar extends Component { const data: Array = this.model.getData(); const displayData: Array = this.model.getDisplayData(); - // nest data by group - const nestedDataByGroup = nest() - .key(d => d.group) - .entries(displayData); - + const groupedData = this.model.getGroupedData(); const options = this.model.getOptions(); // console.log(" data:", data); // console.log(" displayData:", displayData); - // console.log(" nestedDataByGroup:", nestedDataByGroup); + // console.log(" groupedData:", groupedData); // console.log(" options:", options); ///////////////////////////// @@ -73,8 +74,6 @@ export class Radar extends Component { const diameter = size - 2 * fontSize; const radius = diameter / 2; - // scales - // given a key, return the corrisponding angle in radiants const xScale = scaleBand() .domain(displayData.map(d => d.key)) @@ -88,10 +87,6 @@ export class Radar extends Component { const colorScale = (key: string): string => this.model.getFillColor(key); - // angle slice - const keysValues = uniqBy(displayData, "key"); - const angleSlice = (2 * Math.PI) / keysValues.length; - const radialLineGenerator = lineRadial() .angle(d => xScale(d.key)) .radius(d => yScale(d.value)) @@ -130,20 +125,30 @@ export class Radar extends Component { .attr("r", d => yScale(d)) .attr("fill", "none") .attr("stroke", "red"); + circumferencesUpdate.exit().remove(); // blobs const blobContainer = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); - const blob = blobContainer.selectAll(".g") - .data(nestedDataByGroup) + // bind data + const blob = blobContainer.selectAll("g").data(groupedData, group => group.name); + // remove node if necessary + blob.exit().attr("opacity", 0).remove(); + // add node if necessary + const enteringBlob = blob .enter() .append("g") - .attr("class", d => d.key); - blob + .classed("paths", true) + .attr("class", d => d.name); + const enteringPahts = enteringBlob .append("path") - .attr("class", d => `blob-area-${d.key}`) - .attr("d", d => radialLineGenerator(d.values)) - .attr("stroke", d => colorScale(d.key)) - .attr("fill", d => colorScale(d.key)) + .attr("opacity", 0); + // update node (?) + enteringPahts.merge(svg.selectAll("g.paths path")) + .attr("class", d => `blob-area-${d.name}`) + .attr("d", d => radialLineGenerator(d.data)) + .attr("stroke", d => colorScale(d.name)) + .attr("fill", d => colorScale(d.name)) + .attr("opacity", 1) .style("fill-opacity", 0.2); // x axes @@ -160,6 +165,35 @@ export class Radar extends Component { .attr("y2", d => getCoordinates(d, radius).y) .attr("stroke", "cyan"); } + + handleLegendOnHover = (event: CustomEvent) => { + const { hoveredElement } = event.detail; + this.parent.selectAll("g.blobs path") + .transition(this.services.transitions.getTransition("legend-hover-path")) + .attr("opacity", group => { + if (group.name !== hoveredElement.datum().name) { + return 0.1; + } + return 0.5; + }); + } + + handleLegendMouseOut = (event: CustomEvent) => { + this.parent.selectAll("g.blobs path") + .transition(this.services.transitions.getTransition("legend-mouseout-path")) + .attr("opacity", 0.5); + } + + destroy() { + // Remove event listeners + this.parent.selectAll("path") + .on("mousemove", null) + .on("mouseout", null); + // Remove legend listeners + const eventsFragment = this.services.events; + eventsFragment.removeEventListener(Events.Legend.ITEM_HOVER, this.handleLegendOnHover); + eventsFragment.removeEventListener(Events.Legend.ITEM_MOUSEOUT, this.handleLegendMouseOut); + } } function uniqBy(dataset: any, attribute: string): any[] { From 312ab2345ae226e2f7b81b6637320903a753d2fd Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Fri, 17 Apr 2020 16:22:41 +0200 Subject: [PATCH 08/95] Radar: create x axes --- packages/core/src/components/graphs/radar.ts | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index bf5660047c..f25e6cd14d 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -93,7 +93,7 @@ export class Radar extends Component { .curve(curveLinearClosed); const getCoordinates = (key: string, r: number) => { - const angle = xScale(key); + const angle = xScale(key) - Math.PI / 2; // translate by the center const x = r * Math.cos(angle) + cx; const y = r * Math.sin(angle) + cy; @@ -127,10 +127,24 @@ export class Radar extends Component { .attr("stroke", "red"); circumferencesUpdate.exit().remove(); + // x axes + const keysValues = uniqBy(data, "key"); + const spokes = DOMUtils.appendOrSelect(debugContainer, "g.axis"); + const spokesUpdate = spokes.selectAll("line").data(keysValues); + spokesUpdate + .enter() + .append("line") + .merge(spokesUpdate) + .attr("class", key => `axis-${key}`) + .attr("x1", cx) + .attr("y1", cy) + .attr("x2", key => getCoordinates(key, radius).x) + .attr("y2", key => getCoordinates(key, radius).y) + .attr("stroke", "cyan"); + // blobs - const blobContainer = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); - // bind data - const blob = blobContainer.selectAll("g").data(groupedData, group => group.name); + const blobsContainer = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); + const blob = blobsContainer.selectAll("g").data(groupedData, group => group.name); // remove node if necessary blob.exit().attr("opacity", 0).remove(); // add node if necessary @@ -150,20 +164,6 @@ export class Radar extends Component { .attr("fill", d => colorScale(d.name)) .attr("opacity", 1) .style("fill-opacity", 0.2); - - // x axes - const spokesValues = uniqBy(data, "key"); - const spokes = DOMUtils.appendOrSelect(debugContainer, "g.spokes"); - const spokesUpdate = spokes.selectAll("line").data(spokesValues); - spokesUpdate - .enter() - .append("line") - .merge(spokesUpdate) - .attr("x1", cx) - .attr("y1", cy) - .attr("x2", d => getCoordinates(d, radius).x) - .attr("y2", d => getCoordinates(d, radius).y) - .attr("stroke", "cyan"); } handleLegendOnHover = (event: CustomEvent) => { From 1ccd8b4edaa0085586fd2a283ae356ecfb56a707 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Fri, 17 Apr 2020 16:41:01 +0200 Subject: [PATCH 09/95] Radar: refactor --- packages/core/demo/data/radar.ts | 14 ++++---- packages/core/src/components/graphs/radar.ts | 38 +++++++++++--------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/packages/core/demo/data/radar.ts b/packages/core/demo/data/radar.ts index 31e928dc68..1e2b58bb74 100644 --- a/packages/core/demo/data/radar.ts +++ b/packages/core/demo/data/radar.ts @@ -1,16 +1,16 @@ export const radarData = [ - { group: "Sugar", key: "London", value: 7 }, + { group: "Sugar", key: "London", value: 25 }, { group: "Oil", key: "London", value: 6 }, { group: "Water", key: "London", value: 12 }, - { group: "Sugar", key: "Milan", value: 10 }, + { group: "Sugar", key: "Milan", value: 13 }, { group: "Oil", key: "Milan", value: 6 }, { group: "Water", key: "Milan", value: 28 }, - { group: "Sugar", key: "Paris", value: 12 }, + { group: "Sugar", key: "Paris", value: 19 }, { group: "Oil", key: "Paris", value: 16 }, - { group: "Water", key: "Paris", value: 26 }, - { group: "Sugar", key: "New York", value: 4 }, - { group: "Oil", key: "New York", value: 15 }, - { group: "Water", key: "New York", value: 25 }, + { group: "Water", key: "Paris", value: 10 }, + { group: "Sugar", key: "New York", value: 11 }, + { group: "Oil", key: "New York", value: 18 }, + { group: "Water", key: "New York", value: 8 }, { group: "Sugar", key: "Sydney", value: 12 }, { group: "Oil", key: "Sydney", value: 16 }, { group: "Water", key: "Sydney", value: 23 } diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index f25e6cd14d..227bc24260 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -11,11 +11,10 @@ import { // D3 Imports import { scaleBand, scaleLinear } from "d3-scale"; -import { extent, max } from "d3-array"; -import { lineRadial, areaRadial, curveLinearClosed, curveCardinalClosed } from "d3-shape"; -import { nest } from "d3-collection"; +import { max } from "d3-array"; +import { lineRadial, curveLinearClosed } from "d3-shape"; -const DEBUG = true; +const DEBUG = false; interface Datum { group: string; @@ -44,13 +43,6 @@ export class Radar extends Component { return; } - // draw backdrop - const backdropRect = DOMUtils.appendOrSelect(svg, "rect.radar-backdrop") - .attr("width", "100%") - .attr("height", "100%") - .attr("stroke", "red") - .attr("fill", "none"); - const data: Array = this.model.getData(); const displayData: Array = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); @@ -104,14 +96,22 @@ export class Radar extends Component { // Draw ///////////////////////////// + /////////////// const debugContainer = DOMUtils.appendOrSelect(svg, "g.debug"); + // backdrop + const backdropRect = DOMUtils.appendOrSelect(debugContainer, "rect.radar-backdrop") + .attr("width", "100%") + .attr("height", "100%") + .attr("stroke", "red") + .attr("fill", "none"); + // center const center = DOMUtils.appendOrSelect(debugContainer, "circle.center") .attr("cx", cx) .attr("cy", cy) - .attr("r", 2) - .attr("fill", "gold"); + .attr("r", 3) + .attr("fill", "red"); // circumferences const circumferences = DOMUtils.appendOrSelect(debugContainer, "g.circumferences"); @@ -127,9 +127,12 @@ export class Radar extends Component { .attr("stroke", "red"); circumferencesUpdate.exit().remove(); + svg.select("g.debug").attr("opacity", DEBUG ? 0.5 : 0); + /////////////// + // x axes const keysValues = uniqBy(data, "key"); - const spokes = DOMUtils.appendOrSelect(debugContainer, "g.axis"); + const spokes = DOMUtils.appendOrSelect(svg, "g.axis"); const spokesUpdate = spokes.selectAll("line").data(keysValues); spokesUpdate .enter() @@ -140,7 +143,7 @@ export class Radar extends Component { .attr("y1", cy) .attr("x2", key => getCoordinates(key, radius).x) .attr("y2", key => getCoordinates(key, radius).y) - .attr("stroke", "cyan"); + .attr("stroke", "#dcdcdc"); // blobs const blobsContainer = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); @@ -161,9 +164,10 @@ export class Radar extends Component { .attr("class", d => `blob-area-${d.name}`) .attr("d", d => radialLineGenerator(d.data)) .attr("stroke", d => colorScale(d.name)) + .attr("stroke-width", 1.5) .attr("fill", d => colorScale(d.name)) .attr("opacity", 1) - .style("fill-opacity", 0.2); + .style("fill-opacity", 0.5); } handleLegendOnHover = (event: CustomEvent) => { @@ -172,7 +176,7 @@ export class Radar extends Component { .transition(this.services.transitions.getTransition("legend-hover-path")) .attr("opacity", group => { if (group.name !== hoveredElement.datum().name) { - return 0.1; + return 0.2; } return 0.5; }); From 6e6f254388eb020545a1d58d7fb44457047895f7 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Fri, 17 Apr 2020 18:33:16 +0200 Subject: [PATCH 10/95] Radar: fix color opacity --- packages/core/src/components/graphs/radar.ts | 21 +++++++++++--------- packages/core/src/configuration.ts | 6 ++++-- packages/core/src/interfaces/charts.ts | 6 ++++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 227bc24260..1c63c3650a 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -1,6 +1,7 @@ // Internal Imports import { Component } from "../component"; import { DOMUtils } from "../../services"; +import * as Configuration from "../../configuration"; import { Tools } from "../../tools"; import { CalloutDirections, @@ -47,11 +48,13 @@ export class Radar extends Component { const displayData: Array = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); const options = this.model.getOptions(); + const configuration = Configuration.options.radarChart.radar; // console.log(" data:", data); // console.log(" displayData:", displayData); // console.log(" groupedData:", groupedData); // console.log(" options:", options); + // console.log(" configuration:", configuration); ///////////////////////////// // Computations @@ -149,7 +152,8 @@ export class Radar extends Component { const blobsContainer = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); const blob = blobsContainer.selectAll("g").data(groupedData, group => group.name); // remove node if necessary - blob.exit().attr("opacity", 0).remove(); + blob.exit() + .remove(); // add node if necessary const enteringBlob = blob .enter() @@ -157,8 +161,7 @@ export class Radar extends Component { .classed("paths", true) .attr("class", d => d.name); const enteringPahts = enteringBlob - .append("path") - .attr("opacity", 0); + .append("path"); // update node (?) enteringPahts.merge(svg.selectAll("g.paths path")) .attr("class", d => `blob-area-${d.name}`) @@ -166,26 +169,26 @@ export class Radar extends Component { .attr("stroke", d => colorScale(d.name)) .attr("stroke-width", 1.5) .attr("fill", d => colorScale(d.name)) - .attr("opacity", 1) - .style("fill-opacity", 0.5); + .style("fill-opacity", configuration.opacity.selected); } handleLegendOnHover = (event: CustomEvent) => { const { hoveredElement } = event.detail; + const { opacity } = Configuration.options.radarChart.radar; this.parent.selectAll("g.blobs path") .transition(this.services.transitions.getTransition("legend-hover-path")) - .attr("opacity", group => { + .style("fill-opacity", group => { if (group.name !== hoveredElement.datum().name) { - return 0.2; + return opacity.unselected; } - return 0.5; + return opacity.selected; }); } handleLegendMouseOut = (event: CustomEvent) => { this.parent.selectAll("g.blobs path") .transition(this.services.transitions.getTransition("legend-mouseout-path")) - .attr("opacity", 0.5); + .style("fill-opacity", 0.5); } destroy() { diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index aa503798b2..b2db9f71e0 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -280,8 +280,10 @@ const donutChart: DonutChartOptions = Tools.merge({}, pieChart, { */ const radarChart: RadarChartOptions = Tools.merge({}, chart, { radar: { - opt1: true, - opt2: 2 + opacity: { + unselected: 0.2, + selected: 0.5 + } } } as RadarChartOptions); diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 88ced3406f..c439414cad 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -194,7 +194,9 @@ export interface DonutChartOptions extends PieChartOptions { */ export interface RadarChartOptions extends BaseChartOptions { radar?: { - opt1?: boolean; - opt2?: number; + opacity: { + unselected: number, + selected: number + } }; } From c152a90f9d23db17606d11b479932dbcfe973a16 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 20 Apr 2020 10:45:11 +0200 Subject: [PATCH 11/95] Radar: Compute ticks with a minimum value --- packages/core/src/components/graphs/radar.ts | 51 ++++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 1c63c3650a..10e8f7d322 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -12,7 +12,7 @@ import { // D3 Imports import { scaleBand, scaleLinear } from "d3-scale"; -import { max } from "d3-array"; +import { min, max } from "d3-array"; import { lineRadial, curveLinearClosed } from "d3-shape"; const DEBUG = false; @@ -77,8 +77,10 @@ export class Radar extends Component { const ticksNumber = 5; const yScale = scaleLinear() .domain([0, max(displayData.map(d => d.value))]) - .range([0, radius]); - const yTicks = yScale.ticks(ticksNumber); + .range([0, radius]) + .nice(); + const completeYTicks = yScale.ticks(ticksNumber); // original d3 ticks + const yTicks = computeTicks(completeYTicks); // ticks starting from not 0 const colorScale = (key: string): string => this.model.getFillColor(key); @@ -87,11 +89,11 @@ export class Radar extends Component { .radius(d => yScale(d.value)) .curve(curveLinearClosed); - const getCoordinates = (key: string, r: number) => { + const getCoordinates = (key: string, r: number, tx = 0, ty = 0) => { const angle = xScale(key) - Math.PI / 2; - // translate by the center - const x = r * Math.cos(angle) + cx; - const y = r * Math.sin(angle) + cy; + // translate by tx and ty + const x = r * Math.cos(angle) + tx; + const y = r * Math.sin(angle) + ty; return { x, y }; }; @@ -118,7 +120,7 @@ export class Radar extends Component { // circumferences const circumferences = DOMUtils.appendOrSelect(debugContainer, "g.circumferences"); - const circumferencesUpdate = circumferences.selectAll("circle").data(yTicks); + const circumferencesUpdate = circumferences.selectAll("circle").data(completeYTicks); circumferencesUpdate .enter() .append("circle") @@ -144,8 +146,8 @@ export class Radar extends Component { .attr("class", key => `axis-${key}`) .attr("x1", cx) .attr("y1", cy) - .attr("x2", key => getCoordinates(key, radius).x) - .attr("y2", key => getCoordinates(key, radius).y) + .attr("x2", key => getCoordinates(key, radius, cx, cy).x) + .attr("y2", key => getCoordinates(key, radius, cx, cy).y) .attr("stroke", "#dcdcdc"); // blobs @@ -216,3 +218,32 @@ function radToDeg(rad: number) { function degToRad(deg: number) { return deg * (Math.PI / 180); } + +// Round x to the nearest multiple of n +// Example: +// x = 7, n = 5 -> 5 +// x = 8, n = 5 -> 10 +function roundXToN(x: number, n: number) { + const xRoundedToN = n * Math.round(x / n); + return xRoundedToN === 0 ? n : xRoundedToN; +} + +// Given an array of ticks generated by d3, returns another of ticks +// uniformly spaced (like the original d3 ticks array) but with the +// first tick computed as the nearest MIN_DEFAULT_TICK multiple of step. +// Example: +// yTicks = [0, 5, 10, 15, 20, 25] +// MIN_DEFAULT_TICK = 8 -> minRoundedTick = 10 +// -> [10, 15, 20, 25] +function computeTicks(yTicks: number[]) { + const stepTicks = yTicks[1] - yTicks[0]; + const MIN_DEFAULT_TICK = 8; + const minRoundedTick = roundXToN(MIN_DEFAULT_TICK, stepTicks); + return range(minRoundedTick, max(yTicks), stepTicks); +} + +// Returns an array of numbers between start and end (inclusive), equally spaced by step interval +function range(start: number, end: number, step: number) { + const arrayLenght = (end - start) / step + 1; + return Array.from({ length: arrayLenght }, (_, i) => (i * step) + start); +} From 27dd531955398e145d3107742b65c8f6fce694fb Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 20 Apr 2020 11:51:38 +0200 Subject: [PATCH 12/95] Radar: draw y axis --- packages/core/src/components/graphs/radar.ts | 33 +++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 10e8f7d322..a0906d95e9 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -12,13 +12,13 @@ import { // D3 Imports import { scaleBand, scaleLinear } from "d3-scale"; -import { min, max } from "d3-array"; +import { max } from "d3-array"; import { lineRadial, curveLinearClosed } from "d3-shape"; const DEBUG = false; interface Datum { - group: string; + group?: string; key: string; value: number; } @@ -89,6 +89,8 @@ export class Radar extends Component { .radius(d => yScale(d.value)) .curve(curveLinearClosed); + // given the key (= value corrisponding to a an x axis), a radius r and translation values + // return the coordinates of the corrisponding point stayng on the x axis with radius r const getCoordinates = (key: string, r: number, tx = 0, ty = 0) => { const angle = xScale(key) - Math.PI / 2; // translate by tx and ty @@ -135,15 +137,32 @@ export class Radar extends Component { svg.select("g.debug").attr("opacity", DEBUG ? 0.5 : 0); /////////////// + // y axes + const yAxes = DOMUtils.appendOrSelect(svg, "g.y-axis"); + const yAxesUpdate = yAxes.selectAll("path").data(yTicks); + yAxesUpdate + .enter() + .append("path") + .merge(yAxesUpdate) + .attr("transform", `translate(${cx}, ${cy})`) + .attr("d", tickValue => { + const xAxesKeys = xScale.domain(); + const points = xAxesKeys.map(key => ({ key, value: tickValue })); + return radialLineGenerator(points); + }) + .attr("fill", "none") + .attr("stroke", "#dcdcdc"); + yAxesUpdate.exit().remove(); + // x axes const keysValues = uniqBy(data, "key"); - const spokes = DOMUtils.appendOrSelect(svg, "g.axis"); - const spokesUpdate = spokes.selectAll("line").data(keysValues); - spokesUpdate + const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axis"); + const xAxesUpdate = xAxes.selectAll("line").data(keysValues); + xAxesUpdate .enter() .append("line") - .merge(spokesUpdate) - .attr("class", key => `axis-${key}`) + .merge(xAxesUpdate) + .attr("class", key => `x-axis-${key}`) .attr("x1", cx) .attr("y1", cy) .attr("x2", key => getCoordinates(key, radius, cx, cy).x) From de45c83adceacde5f5ead392c7f7762a66e11af2 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 20 Apr 2020 12:10:56 +0200 Subject: [PATCH 13/95] Radar: y axes start from the min tick value --- packages/core/src/components/graphs/radar.ts | 23 +++++--------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index a0906d95e9..98797e4d4b 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -16,6 +16,7 @@ import { max } from "d3-array"; import { lineRadial, curveLinearClosed } from "d3-shape"; const DEBUG = false; +const MIN_DEFAULT_TICK = 8; interface Datum { group?: string; @@ -80,7 +81,9 @@ export class Radar extends Component { .range([0, radius]) .nice(); const completeYTicks = yScale.ticks(ticksNumber); // original d3 ticks - const yTicks = computeTicks(completeYTicks); // ticks starting from not 0 + const stepTicks = completeYTicks[1] - completeYTicks[0]; + const minRoundedTick = roundXToN(MIN_DEFAULT_TICK, stepTicks); + const yTicks = range(minRoundedTick, max(completeYTicks), stepTicks); // ticks starting from rounded MIN_DEFAULT_TICK and not from 0 const colorScale = (key: string): string => this.model.getFillColor(key); @@ -163,8 +166,8 @@ export class Radar extends Component { .append("line") .merge(xAxesUpdate) .attr("class", key => `x-axis-${key}`) - .attr("x1", cx) - .attr("y1", cy) + .attr("x1", key => getCoordinates(key, yScale(minRoundedTick), cx, cy).x) + .attr("y1", key => getCoordinates(key, yScale(minRoundedTick), cx, cy).y) .attr("x2", key => getCoordinates(key, radius, cx, cy).x) .attr("y2", key => getCoordinates(key, radius, cx, cy).y) .attr("stroke", "#dcdcdc"); @@ -247,20 +250,6 @@ function roundXToN(x: number, n: number) { return xRoundedToN === 0 ? n : xRoundedToN; } -// Given an array of ticks generated by d3, returns another of ticks -// uniformly spaced (like the original d3 ticks array) but with the -// first tick computed as the nearest MIN_DEFAULT_TICK multiple of step. -// Example: -// yTicks = [0, 5, 10, 15, 20, 25] -// MIN_DEFAULT_TICK = 8 -> minRoundedTick = 10 -// -> [10, 15, 20, 25] -function computeTicks(yTicks: number[]) { - const stepTicks = yTicks[1] - yTicks[0]; - const MIN_DEFAULT_TICK = 8; - const minRoundedTick = roundXToN(MIN_DEFAULT_TICK, stepTicks); - return range(minRoundedTick, max(yTicks), stepTicks); -} - // Returns an array of numbers between start and end (inclusive), equally spaced by step interval function range(start: number, end: number, step: number) { const arrayLenght = (end - start) / step + 1; From d1b240fb3f11fc76ea3594db3a219467cdfa3d1d Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 20 Apr 2020 14:43:46 +0200 Subject: [PATCH 14/95] Radar: add exit for x axes --- packages/core/src/components/graphs/radar.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 98797e4d4b..26dedde77d 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -171,6 +171,7 @@ export class Radar extends Component { .attr("x2", key => getCoordinates(key, radius, cx, cy).x) .attr("y2", key => getCoordinates(key, radius, cx, cy).y) .attr("stroke", "#dcdcdc"); + xAxesUpdate.exit().remove(); // blobs const blobsContainer = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); From 9dae270214f9a23cb65e31851981dde873b48c97 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 20 Apr 2020 15:06:29 +0200 Subject: [PATCH 15/95] Radar: blob creation refactor --- packages/core/src/components/graphs/radar.ts | 22 ++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 26dedde77d..8f1602c5b4 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -174,27 +174,23 @@ export class Radar extends Component { xAxesUpdate.exit().remove(); // blobs - const blobsContainer = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); - const blob = blobsContainer.selectAll("g").data(groupedData, group => group.name); - // remove node if necessary - blob.exit() - .remove(); - // add node if necessary - const enteringBlob = blob + const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); + const blobUpdate = blobs.selectAll("g").data(groupedData, group => group.name); + + blobUpdate .enter() .append("g") - .classed("paths", true) - .attr("class", d => d.name); - const enteringPahts = enteringBlob - .append("path"); - // update node (?) - enteringPahts.merge(svg.selectAll("g.paths path")) + .attr("class", d => d.name) + .append("path") + .merge(svg.selectAll("g.paths path")) .attr("class", d => `blob-area-${d.name}`) .attr("d", d => radialLineGenerator(d.data)) .attr("stroke", d => colorScale(d.name)) .attr("stroke-width", 1.5) .attr("fill", d => colorScale(d.name)) .style("fill-opacity", configuration.opacity.selected); + + blobUpdate.exit().remove(); } handleLegendOnHover = (event: CustomEvent) => { From 7be6201d68fe04f9bc33357b2ddf237108b423e1 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 20 Apr 2020 15:09:58 +0200 Subject: [PATCH 16/95] Radar: rename classes --- packages/core/src/components/graphs/radar.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 8f1602c5b4..c397cb0da3 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -141,7 +141,7 @@ export class Radar extends Component { /////////////// // y axes - const yAxes = DOMUtils.appendOrSelect(svg, "g.y-axis"); + const yAxes = DOMUtils.appendOrSelect(svg, "g.y-axes"); const yAxesUpdate = yAxes.selectAll("path").data(yTicks); yAxesUpdate .enter() @@ -159,7 +159,7 @@ export class Radar extends Component { // x axes const keysValues = uniqBy(data, "key"); - const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axis"); + const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); const xAxesUpdate = xAxes.selectAll("line").data(keysValues); xAxesUpdate .enter() From 216810804993e500ef41c11d5292661eef904c16 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 20 Apr 2020 18:26:09 +0200 Subject: [PATCH 17/95] Radar: fix class name --- packages/core/src/components/graphs/radar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index c397cb0da3..6898a9ec4e 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -182,7 +182,7 @@ export class Radar extends Component { .append("g") .attr("class", d => d.name) .append("path") - .merge(svg.selectAll("g.paths path")) + .merge(svg.selectAll("g.blobs path")) .attr("class", d => `blob-area-${d.name}`) .attr("d", d => radialLineGenerator(d.data)) .attr("stroke", d => colorScale(d.name)) From e67481e0a33d8543670c34377d74aaed4b61b72e Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 21 Apr 2020 09:44:31 +0200 Subject: [PATCH 18/95] Radar: mini refactor --- packages/core/src/components/graphs/radar.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 6898a9ec4e..636ba8cb99 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -176,20 +176,18 @@ export class Radar extends Component { // blobs const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); const blobUpdate = blobs.selectAll("g").data(groupedData, group => group.name); - blobUpdate .enter() .append("g") - .attr("class", d => d.name) + .attr("class", group => group.name) .append("path") .merge(svg.selectAll("g.blobs path")) - .attr("class", d => `blob-area-${d.name}`) - .attr("d", d => radialLineGenerator(d.data)) - .attr("stroke", d => colorScale(d.name)) + .attr("class", group => `blob-area-${group.name}`) + .attr("d", group => radialLineGenerator(group.data)) + .attr("stroke", group => colorScale(group.name)) .attr("stroke-width", 1.5) - .attr("fill", d => colorScale(d.name)) + .attr("fill", group => colorScale(group.name)) .style("fill-opacity", configuration.opacity.selected); - blobUpdate.exit().remove(); } From 083b5ed6d24dc38f4511f83e1a9f4dc2a4156a07 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 21 Apr 2020 12:17:50 +0200 Subject: [PATCH 19/95] Radar: fix blobs bind --- packages/core/src/components/graphs/radar.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 636ba8cb99..75c5aacd67 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -175,13 +175,13 @@ export class Radar extends Component { // blobs const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); - const blobUpdate = blobs.selectAll("g").data(groupedData, group => group.name); + const blobUpdate = blobs.selectAll("g.blob").data(groupedData, group => group.name); blobUpdate .enter() .append("g") - .attr("class", group => group.name) + .attr("class", "blob") .append("path") - .merge(svg.selectAll("g.blobs path")) + .merge(blobUpdate.selectAll("path")) .attr("class", group => `blob-area-${group.name}`) .attr("d", group => radialLineGenerator(group.data)) .attr("stroke", group => colorScale(group.name)) From fe97673e050bf064d45c80e6fd3c952f763667dd Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 21 Apr 2020 12:18:12 +0200 Subject: [PATCH 20/95] Radar: style changes --- packages/core/src/components/graphs/radar.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 75c5aacd67..f6b75c2a8c 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -120,8 +120,8 @@ export class Radar extends Component { const center = DOMUtils.appendOrSelect(debugContainer, "circle.center") .attr("cx", cx) .attr("cy", cy) - .attr("r", 3) - .attr("fill", "red"); + .attr("r", 2) + .attr("fill", "gold"); // circumferences const circumferences = DOMUtils.appendOrSelect(debugContainer, "g.circumferences"); From 0ad42c65c5575a91cb87a0514fbb65eb982b03fd Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 21 Apr 2020 16:22:28 +0200 Subject: [PATCH 21/95] Radar: fix min radius logic --- packages/core/src/components/graphs/radar.ts | 61 ++++---------------- 1 file changed, 12 insertions(+), 49 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index f6b75c2a8c..003241b273 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -2,13 +2,7 @@ import { Component } from "../component"; import { DOMUtils } from "../../services"; import * as Configuration from "../../configuration"; -import { Tools } from "../../tools"; -import { - CalloutDirections, - Roles, - TooltipTypes, - Events -} from "../../interfaces"; +import { Events } from "../../interfaces"; // D3 Imports import { scaleBand, scaleLinear } from "d3-scale"; @@ -16,7 +10,6 @@ import { max } from "d3-array"; import { lineRadial, curveLinearClosed } from "d3-shape"; const DEBUG = false; -const MIN_DEFAULT_TICK = 8; interface Datum { group?: string; @@ -51,12 +44,6 @@ export class Radar extends Component { const options = this.model.getOptions(); const configuration = Configuration.options.radarChart.radar; - // console.log(" data:", data); - // console.log(" displayData:", displayData); - // console.log(" groupedData:", groupedData); - // console.log(" options:", options); - // console.log(" configuration:", configuration); - ///////////////////////////// // Computations ///////////////////////////// @@ -66,8 +53,9 @@ export class Radar extends Component { const cy = height / 2; const fontSize = 10; + const margin = 2 * fontSize; const size = Math.min(width, height); - const diameter = size - 2 * fontSize; + const diameter = size - margin; const radius = diameter / 2; // given a key, return the corrisponding angle in radiants @@ -76,14 +64,12 @@ export class Radar extends Component { .range([0, 2 * Math.PI]); const ticksNumber = 5; + const minRange = 10; const yScale = scaleLinear() .domain([0, max(displayData.map(d => d.value))]) - .range([0, radius]) - .nice(); - const completeYTicks = yScale.ticks(ticksNumber); // original d3 ticks - const stepTicks = completeYTicks[1] - completeYTicks[0]; - const minRoundedTick = roundXToN(MIN_DEFAULT_TICK, stepTicks); - const yTicks = range(minRoundedTick, max(completeYTicks), stepTicks); // ticks starting from rounded MIN_DEFAULT_TICK and not from 0 + .range([minRange, radius]) + .nice(ticksNumber); + const yTicks = yScale.ticks(ticksNumber); const colorScale = (key: string): string => this.model.getFillColor(key); @@ -125,7 +111,7 @@ export class Radar extends Component { // circumferences const circumferences = DOMUtils.appendOrSelect(debugContainer, "g.circumferences"); - const circumferencesUpdate = circumferences.selectAll("circle").data(completeYTicks); + const circumferencesUpdate = circumferences.selectAll("circle").data(yTicks); circumferencesUpdate .enter() .append("circle") @@ -166,10 +152,10 @@ export class Radar extends Component { .append("line") .merge(xAxesUpdate) .attr("class", key => `x-axis-${key}`) - .attr("x1", key => getCoordinates(key, yScale(minRoundedTick), cx, cy).x) - .attr("y1", key => getCoordinates(key, yScale(minRoundedTick), cx, cy).y) - .attr("x2", key => getCoordinates(key, radius, cx, cy).x) - .attr("y2", key => getCoordinates(key, radius, cx, cy).y) + .attr("x1", key => getCoordinates(key, yScale.range()[0], cx, cy).x) + .attr("y1", key => getCoordinates(key, yScale.range()[0], cx, cy).y) + .attr("x2", key => getCoordinates(key, yScale.range()[1], cx, cy).x) + .attr("y2", key => getCoordinates(key, yScale.range()[1], cx, cy).y) .attr("stroke", "#dcdcdc"); xAxesUpdate.exit().remove(); @@ -227,26 +213,3 @@ function uniqBy(dataset: any, attribute: string): any[] { const uniqValues = [...Array.from(new Set(allTheValuesByAttribute))]; return uniqValues; } - -function radToDeg(rad: number) { - return rad * (180 / Math.PI); -} - -function degToRad(deg: number) { - return deg * (Math.PI / 180); -} - -// Round x to the nearest multiple of n -// Example: -// x = 7, n = 5 -> 5 -// x = 8, n = 5 -> 10 -function roundXToN(x: number, n: number) { - const xRoundedToN = n * Math.round(x / n); - return xRoundedToN === 0 ? n : xRoundedToN; -} - -// Returns an array of numbers between start and end (inclusive), equally spaced by step interval -function range(start: number, end: number, step: number) { - const arrayLenght = (end - start) / step + 1; - return Array.from({ length: arrayLenght }, (_, i) => (i * step) + start); -} From 118d8f08dc9c9450fece239214037ea2f8888c88 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 21 Apr 2020 16:43:40 +0200 Subject: [PATCH 22/95] Radar: bind displayData to x axes not data --- packages/core/src/components/graphs/radar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 003241b273..b7b38bd6b9 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -144,7 +144,7 @@ export class Radar extends Component { yAxesUpdate.exit().remove(); // x axes - const keysValues = uniqBy(data, "key"); + const keysValues = uniqBy(displayData, "key"); const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); const xAxesUpdate = xAxes.selectAll("line").data(keysValues); xAxesUpdate From 127847dd031899e4d4ec8f588e35f9f0253211ec Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 21 Apr 2020 16:49:53 +0200 Subject: [PATCH 23/95] Radar: add transitions --- packages/core/src/components/graphs/radar.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index b7b38bd6b9..cb46c06ecb 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -139,6 +139,7 @@ export class Radar extends Component { const points = xAxesKeys.map(key => ({ key, value: tickValue })); return radialLineGenerator(points); }) + .transition(this.services.transitions.getTransition("y-axis-update-enter", animate)) .attr("fill", "none") .attr("stroke", "#dcdcdc"); yAxesUpdate.exit().remove(); @@ -156,6 +157,7 @@ export class Radar extends Component { .attr("y1", key => getCoordinates(key, yScale.range()[0], cx, cy).y) .attr("x2", key => getCoordinates(key, yScale.range()[1], cx, cy).x) .attr("y2", key => getCoordinates(key, yScale.range()[1], cx, cy).y) + .transition(this.services.transitions.getTransition("x-axis-update-enter", animate)) .attr("stroke", "#dcdcdc"); xAxesUpdate.exit().remove(); @@ -170,6 +172,7 @@ export class Radar extends Component { .merge(blobUpdate.selectAll("path")) .attr("class", group => `blob-area-${group.name}`) .attr("d", group => radialLineGenerator(group.data)) + .transition(this.services.transitions.getTransition("blob-update-enter", animate)) .attr("stroke", group => colorScale(group.name)) .attr("stroke-width", 1.5) .attr("fill", group => colorScale(group.name)) From 83ebbb5380d7ded1da0440f163cf18d3eb889aeb Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 21 Apr 2020 18:57:33 +0200 Subject: [PATCH 24/95] Radar: add key labels (wip) --- packages/core/src/components/graphs/radar.ts | 30 ++++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index cb46c06ecb..262b49287f 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -128,11 +128,11 @@ export class Radar extends Component { // y axes const yAxes = DOMUtils.appendOrSelect(svg, "g.y-axes"); - const yAxesUpdate = yAxes.selectAll("path").data(yTicks); - yAxesUpdate + const yAxisUpdate = yAxes.selectAll("path").data(yTicks, d => d); + yAxisUpdate .enter() .append("path") - .merge(yAxesUpdate) + .merge(yAxisUpdate) .attr("transform", `translate(${cx}, ${cy})`) .attr("d", tickValue => { const xAxesKeys = xScale.domain(); @@ -142,24 +142,36 @@ export class Radar extends Component { .transition(this.services.transitions.getTransition("y-axis-update-enter", animate)) .attr("fill", "none") .attr("stroke", "#dcdcdc"); - yAxesUpdate.exit().remove(); + yAxisUpdate.exit().remove(); // x axes const keysValues = uniqBy(displayData, "key"); const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); - const xAxesUpdate = xAxes.selectAll("line").data(keysValues); - xAxesUpdate + const xAxisUpdate = xAxes.selectAll("g.x-axis").data(keysValues); + const xAxisEnter = xAxisUpdate .enter() + .append("g") + .attr("class", "x-axis"); + // add axes + xAxisEnter .append("line") - .merge(xAxesUpdate) - .attr("class", key => `x-axis-${key}`) + .merge(xAxisUpdate.selectAll("line")) .attr("x1", key => getCoordinates(key, yScale.range()[0], cx, cy).x) .attr("y1", key => getCoordinates(key, yScale.range()[0], cx, cy).y) .attr("x2", key => getCoordinates(key, yScale.range()[1], cx, cy).x) .attr("y2", key => getCoordinates(key, yScale.range()[1], cx, cy).y) .transition(this.services.transitions.getTransition("x-axis-update-enter", animate)) .attr("stroke", "#dcdcdc"); - xAxesUpdate.exit().remove(); + // add labels + xAxisEnter + .append("text") + .merge(xAxisUpdate.selectAll("text")) + .text(d => d) + .attr("stroke", "#dcdcdc") + .attr("x", key => getCoordinates(key, yScale.range()[1], cx, cy).x) + .attr("y", key => getCoordinates(key, yScale.range()[1], cx, cy).y) + .style("text-anchor", "middle"); + xAxisUpdate.exit().remove(); // blobs const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); From bc9d5b80c3413e89b36bad7968d31123f9f4f005 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 21 Apr 2020 18:59:00 +0200 Subject: [PATCH 25/95] Radar: change margin value --- packages/core/src/components/graphs/radar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 262b49287f..471f60cc84 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -52,7 +52,7 @@ export class Radar extends Component { const cx = width / 2; const cy = height / 2; - const fontSize = 10; + const fontSize = 30; const margin = 2 * fontSize; const size = Math.min(width, height); const diameter = size - margin; From 6878a53872c9a6b9e54f6e4519889e2758b424bf Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 22 Apr 2020 10:04:24 +0200 Subject: [PATCH 26/95] Radar: fix labels position --- packages/core/src/components/graphs/radar.ts | 89 +++++++++++++++++++- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 471f60cc84..9de2fc461a 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -37,6 +37,7 @@ export class Radar extends Component { if (!width || !height) { return; } + console.log("\n"); const data: Array = this.model.getData(); const displayData: Array = this.model.getDisplayData(); @@ -44,6 +45,13 @@ export class Radar extends Component { const options = this.model.getOptions(); const configuration = Configuration.options.radarChart.radar; + // console.log("animate:", animate); + // console.log("data:", data); + // console.log("displayData:", displayData); + // console.log("groupedData:", groupedData); + // console.log("options:", options); + // console.log("configuration:", configuration); + ///////////////////////////// // Computations ///////////////////////////// @@ -57,11 +65,13 @@ export class Radar extends Component { const size = Math.min(width, height); const diameter = size - margin; const radius = diameter / 2; + // console.log("radius:", radius); // given a key, return the corrisponding angle in radiants const xScale = scaleBand() .domain(displayData.map(d => d.key)) .range([0, 2 * Math.PI]); + // console.log(`xScale [${xScale.domain()}] -> [${xScale.range()}]`); const ticksNumber = 5; const minRange = 10; @@ -70,6 +80,7 @@ export class Radar extends Component { .range([minRange, radius]) .nice(ticksNumber); const yTicks = yScale.ticks(ticksNumber); + // console.log(`yScale [${yScale.domain()}] -> [${yScale.range()}]`); const colorScale = (key: string): string => this.model.getFillColor(key); @@ -145,6 +156,7 @@ export class Radar extends Component { yAxisUpdate.exit().remove(); // x axes + const labelPadding = 10; const keysValues = uniqBy(displayData, "key"); const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); const xAxisUpdate = xAxes.selectAll("g.x-axis").data(keysValues); @@ -167,10 +179,10 @@ export class Radar extends Component { .append("text") .merge(xAxisUpdate.selectAll("text")) .text(d => d) - .attr("stroke", "#dcdcdc") - .attr("x", key => getCoordinates(key, yScale.range()[1], cx, cy).x) - .attr("y", key => getCoordinates(key, yScale.range()[1], cx, cy).y) - .style("text-anchor", "middle"); + .attr("x", key => getCoordinates(key, yScale.range()[1] + labelPadding, cx, cy).x) + .attr("y", key => getCoordinates(key, yScale.range()[1] + labelPadding, cx, cy).y) + .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) + .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline); xAxisUpdate.exit().remove(); // blobs @@ -228,3 +240,72 @@ function uniqBy(dataset: any, attribute: string): any[] { const uniqValues = [...Array.from(new Set(allTheValuesByAttribute))]; return uniqValues; } + +function radToDeg(rad: number) { + return rad * (180 / Math.PI); +} + +function isInRange(x: number, minMax: number[]): boolean { + return x >= minMax[0] && x <= minMax[1]; +} + +function radialLabelPlacement(angleRadians: number) { + const angle = radToDeg(angleRadians) % 360; // rounded angle + + let textAnchor: "start" | "middle" | "end" = "middle"; // *___ __*__ ___* + let dominantBaseline: "baseline" | "middle" | "hanging" = "middle"; // __* --*-- --. + + let quadrant = 0; + + if (isInRange(angle, [0, 90])) { + quadrant = 0; + } else if (isInRange(angle, [90, 180])) { + quadrant = 1; + } else if (isInRange(angle, [180, 270])) { + quadrant = 2; + } else if (isInRange(angle, [270, 360])) { + quadrant = 3; + } + + if (quadrant === 0) { + textAnchor = "start"; + dominantBaseline = "baseline"; + } else if (quadrant === 1) { + textAnchor = "start"; + dominantBaseline = "hanging"; + } else if (quadrant === 2) { + textAnchor = "end"; + dominantBaseline = "hanging"; + } else if (quadrant === 3) { + textAnchor = "end"; + dominantBaseline = "baseline"; + } + + let edge = null; + + if (isInRange(angle, [0, 10]) || isInRange(angle, [350, 0])) { + edge = 0; + } else if (isInRange(angle, [80, 100])) { + edge = 1; + } else if (isInRange(angle, [170, 190])) { + edge = 2; + } else if (isInRange(angle, [260, 280])) { + edge = 3; + } + + if (edge === 0) { + textAnchor = "middle"; + dominantBaseline = "baseline"; + } else if (edge === 1) { + textAnchor = "start"; + dominantBaseline = "middle"; + } else if (edge === 2) { + textAnchor = "middle"; + dominantBaseline = "hanging"; + } else if (edge === 3) { + textAnchor = "end"; + dominantBaseline = "middle"; + } + + return { textAnchor, dominantBaseline }; +} From e1d0a2c553433fac27fffe3a2b4cc0dc2ddf0916 Mon Sep 17 00:00:00 2001 From: natashadecoste Date: Fri, 17 Apr 2020 14:27:31 -0400 Subject: [PATCH 27/95] fix(core): fix backwards compatibility for pie/donut (#576) * fix(core): fix pie model to ignore dataset name when converting to tabular * apply review changes --- packages/core/src/model-pie.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/core/src/model-pie.ts b/packages/core/src/model-pie.ts index 0c9e67561f..d30650aa5d 100644 --- a/packages/core/src/model-pie.ts +++ b/packages/core/src/model-pie.ts @@ -8,6 +8,23 @@ export class PieChartModel extends ChartModel { super(services); } + getTabularData(data) { + const tabularData = super.getTabularData(data); + + // if the data was changed to tabular format + // update the group to the key so the slices render with the correct tooltips and colors + if (data !== tabularData) { + // If a label was set for the overall dataset, reassign it to key value + tabularData.forEach(d => { + if (d.key && d.key !== d.group) { + d.group = d.key; + } + }); + } + + return tabularData; + } + sanitize(data) { const tabularData = this.getTabularData(data); From e0e994c9618b8bb46d9e4a5e5c4f0838e0cce971 Mon Sep 17 00:00:00 2001 From: Eliad Moosavi Date: Fri, 17 Apr 2020 14:29:29 -0400 Subject: [PATCH 28/95] provide all datapoint info to stacked bar tooltips (#573) --- .../core/src/components/graphs/bar-stacked.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/graphs/bar-stacked.ts b/packages/core/src/components/graphs/bar-stacked.ts index a68b988098..366a5ed38e 100644 --- a/packages/core/src/components/graphs/bar-stacked.ts +++ b/packages/core/src/components/graphs/bar-stacked.ts @@ -147,20 +147,31 @@ export class StackedBar extends Bar { }); }) .on("mousemove", function(datum) { + const displayData = self.model.getDisplayData(); const hoveredElement = select(this); const domainIdentifier = self.services.cartesianScales.getDomainIdentifier(); const rangeIdentifier = self.services.cartesianScales.getRangeIdentifier(); const { groupMapsTo } = self.model.getOptions().data; - // Show tooltip - self.services.events.dispatchEvent(Events.Tooltip.SHOW, { - hoveredElement, - data: { + let matchingDataPoint = displayData.find(d => { + return d[rangeIdentifier] === datum.data[datum.group] && + d[domainIdentifier].toString() === datum.data.sharedStackKey && + d[groupMapsTo] === datum.group; + }); + + if (matchingDataPoint === undefined) { + matchingDataPoint = { [domainIdentifier]: datum.data.sharedStackKey, [rangeIdentifier]: datum.data[datum.group], [groupMapsTo]: datum.group - }, + }; + } + + // Show tooltip + self.services.events.dispatchEvent(Events.Tooltip.SHOW, { + hoveredElement, + data: matchingDataPoint, type: TooltipTypes.DATAPOINT }); }) From 9788a82951e80b94be66ef9c6731aa555b7dd259 Mon Sep 17 00:00:00 2001 From: Eliad Moosavi Date: Fri, 17 Apr 2020 15:14:16 -0400 Subject: [PATCH 29/95] Merge pull request #574 from theiliad/axis-formatters * optimize axis tick formatting * Update packages/core/src/components/axes/axis.ts Co-Authored-By: natashadecoste * optimize axis formatter for time-scale when user has already provided one Co-authored-by: natashadecoste --- packages/core/src/components/axes/axis.ts | 31 +++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/axes/axis.ts b/packages/core/src/components/axes/axis.ts index 2803d9dc3a..0665ae9e14 100644 --- a/packages/core/src/components/axes/axis.ts +++ b/packages/core/src/components/axes/axis.ts @@ -103,6 +103,7 @@ export class Axis extends Component { fakeTick.remove(); const isTimeScaleType = this.scaleType === ScaleTypes.TIME || axisOptions.scaleType === ScaleTypes.TIME; + const scaleType = this.scaleType || axisOptions.scaleType || ScaleTypes.LINEAR; // Initialize axis object const axis = axisFunction(scale).tickSizeOuter(0); @@ -139,20 +140,34 @@ export class Axis extends Component { axis.tickValues(tickValues); } + } - // create the right ticks formatter - let formatter; - if (isTimeScaleType) { - const timeInterval = computeTimeIntervalName(axis.tickValues()); + // create the right ticks formatter + let formatter; + const userProvidedFormatter = Tools.getProperty(axisOptions, "ticks", "formatter"); + if (isTimeScaleType) { + const timeInterval = computeTimeIntervalName(axis.tickValues()); + if (userProvidedFormatter === null) { formatter = (t: number, i: number) => formatTick(t, i, timeInterval, timeScaleOptions); } else { - formatter = Tools.getProperty(axisOptions, "ticks", "formatter"); + formatter = (t: number, i: number) => { + const defaultFormattedValue = formatTick(t, i, timeInterval, timeScaleOptions); + return userProvidedFormatter(t, i, defaultFormattedValue); + }; + } + } else { + if (userProvidedFormatter === null) { + if (scaleType === ScaleTypes.LINEAR) { + formatter = t => t.toLocaleString(); + } + } else { + formatter = userProvidedFormatter; } - - // Set ticks formatter - axis.tickFormat(formatter); } + // Set ticks formatter + axis.tickFormat(formatter); + // Position and transition the axis switch (axisPosition) { case AxisPositions.LEFT: From c384d3a5d442b19715608992da844ffb5c06ad5d Mon Sep 17 00:00:00 2001 From: carbon-bot Date: Fri, 17 Apr 2020 19:24:08 +0000 Subject: [PATCH 30/95] v0.30.12 --- CHANGELOG.md | 11 +++++++++++ lerna.json | 2 +- packages/angular/CHANGELOG.md | 8 ++++++++ packages/angular/package.json | 4 ++-- packages/core/CHANGELOG.md | 11 +++++++++++ packages/core/package.json | 2 +- packages/react/CHANGELOG.md | 8 ++++++++ packages/react/package.json | 4 ++-- packages/vue/CHANGELOG.md | 8 ++++++++ packages/vue/package.json | 4 ++-- 10 files changed, 54 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf368f138..e5586053be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.12](https://github.com/IBM/carbon-charts/compare/v0.30.11...v0.30.12) (2020-04-17) + + +### Bug Fixes + +* **core:** fix backwards compatibility for pie/donut ([#576](https://github.com/IBM/carbon-charts/issues/576)) ([aed1a4d](https://github.com/IBM/carbon-charts/commit/aed1a4dbb2d5ff8f85ac29b69b32c1035a326071)) + + + + + ## [0.30.11](https://github.com/IBM/carbon-charts/compare/v0.30.10...v0.30.11) (2020-04-15) **Note:** Version bump only for package @carbon/charts-monorepo diff --git a/lerna.json b/lerna.json index e86f6f59d5..81a3bc9675 100644 --- a/lerna.json +++ b/lerna.json @@ -12,5 +12,5 @@ ] } }, - "version": "0.30.11" + "version": "0.30.12" } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index 81185ad890..b59dd05ec5 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.12](https://github.com/IBM/carbon-charts/compare/v0.30.11...v0.30.12) (2020-04-17) + +**Note:** Version bump only for package @carbon/charts-angular + + + + + ## [0.30.11](https://github.com/IBM/carbon-charts/compare/v0.30.10...v0.30.11) (2020-04-15) **Note:** Version bump only for package @carbon/charts-angular diff --git a/packages/angular/package.json b/packages/angular/package.json index 3ab5615426..476ceb9374 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-angular", - "version": "0.30.11", + "version": "0.30.12", "description": "Carbon charting components for Angular", "main": "index.js", "scripts": { @@ -39,7 +39,7 @@ "scss" ], "dependencies": { - "@carbon/charts": "^0.30.11" + "@carbon/charts": "^0.30.12" }, "peerDependencies": { "@angular/common": "^6.0.0 || ^7.0.0 || ^8.0.0", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 7c46e1d89c..74e5fdbee3 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.12](https://github.com/IBM/carbon-charts/compare/v0.30.11...v0.30.12) (2020-04-17) + + +### Bug Fixes + +* **core:** fix backwards compatibility for pie/donut ([#576](https://github.com/IBM/carbon-charts/issues/576)) ([aed1a4d](https://github.com/IBM/carbon-charts/commit/aed1a4dbb2d5ff8f85ac29b69b32c1035a326071)) + + + + + ## [0.30.11](https://github.com/IBM/carbon-charts/compare/v0.30.10...v0.30.11) (2020-04-15) **Note:** Version bump only for package @carbon/charts diff --git a/packages/core/package.json b/packages/core/package.json index 2706269253..bff23560a6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts", - "version": "0.30.11", + "version": "0.30.12", "description": "Carbon charting components", "main": "./bundle.js", "module": "./index.js", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 3a57bb7609..3f38af6b4f 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.12](https://github.com/IBM/carbon-charts/compare/v0.30.11...v0.30.12) (2020-04-17) + +**Note:** Version bump only for package @carbon/charts-react + + + + + ## [0.30.11](https://github.com/IBM/carbon-charts/compare/v0.30.10...v0.30.11) (2020-04-15) **Note:** Version bump only for package @carbon/charts-react diff --git a/packages/react/package.json b/packages/react/package.json index d5102a5e64..6911914593 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-react", - "version": "0.30.11", + "version": "0.30.12", "description": "Carbon charting components for React", "main": "./bundle.js", "module": "./index.js", @@ -45,7 +45,7 @@ }, "homepage": "https://github.com/IBM/carbon-charts#readme", "dependencies": { - "@carbon/charts": "^0.30.11" + "@carbon/charts": "^0.30.12" }, "peerDependencies": { "react": "^16.6.3", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index dfc60b267e..2dd0d3abd7 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.12](https://github.com/IBM/carbon-charts/compare/v0.30.11...v0.30.12) (2020-04-17) + +**Note:** Version bump only for package @carbon/charts-vue + + + + + ## [0.30.11](https://github.com/IBM/carbon-charts/compare/v0.30.10...v0.30.11) (2020-04-15) **Note:** Version bump only for package @carbon/charts-vue diff --git a/packages/vue/package.json b/packages/vue/package.json index 6b9d4ed859..71a4182175 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-vue", - "version": "0.30.11", + "version": "0.30.12", "description": "Carbon charting components for Vue", "main": "charts-vue.umd.min.js", "scripts": { @@ -14,7 +14,7 @@ "clean": "rm -rf dist demo/bundle" }, "dependencies": { - "@carbon/charts": "^0.30.11", + "@carbon/charts": "^0.30.12", "vue": "2.5.21" }, "devDependencies": { From 13d48c9eb800df4f5383f22def7b96cd8d3a93e5 Mon Sep 17 00:00:00 2001 From: Eliad Moosavi Date: Mon, 20 Apr 2020 16:35:02 -0400 Subject: [PATCH 31/95] fix line graph update issues --- packages/core/src/components/graphs/line.ts | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 3a59c57ebf..50d0bcb46e 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -37,29 +37,26 @@ export class Line extends Component { }); const groupedData = this.model.getGroupedData(); - // Update the bound data on line groups - const lineGroups = svg.selectAll("g.lines") + // Update the bound data on lines + const lines = svg.selectAll("path.line") .data(groupedData, group => group.name); // Remove elements that need to be exited // We need exit at the top here to make sure that // Data filters are processed before entering new elements // Or updating existing ones - lineGroups.exit() + lines.exit() .attr("opacity", 0) .remove(); - // Add line groups that need to be introduced - const enteringLineGroups = lineGroups.enter() - .append("g") - .classed("lines", true); - - // Enter paths that need to be introduced - const enteringPaths = enteringLineGroups.append("path") + // Add lines that need to be introduced + const enteringLines = lines.enter() + .append("path") + .classed("line", true) .attr("opacity", 0); // Apply styles and datum - enteringPaths.merge(svg.selectAll("g.lines path")) + enteringLines.merge(lines) .attr("stroke", (group, i) => { return this.model.getStrokeColor(group.name) }) @@ -74,7 +71,6 @@ export class Line extends Component { // Transition .transition(this.services.transitions.getTransition("line-update-enter", animate)) .attr("opacity", 1) - .attr("class", "line") .attr("d", group => { const { data } = group; return lineGenerator(data); From 66af81ec4dec144b548cf319d6572a7f683380bb Mon Sep 17 00:00:00 2001 From: carbon-bot Date: Mon, 20 Apr 2020 21:08:48 +0000 Subject: [PATCH 32/95] v0.30.13 --- CHANGELOG.md | 8 ++++++++ lerna.json | 2 +- packages/angular/CHANGELOG.md | 8 ++++++++ packages/angular/package.json | 4 ++-- packages/core/CHANGELOG.md | 8 ++++++++ packages/core/package.json | 2 +- packages/react/CHANGELOG.md | 8 ++++++++ packages/react/package.json | 4 ++-- packages/vue/CHANGELOG.md | 8 ++++++++ packages/vue/package.json | 4 ++-- 10 files changed, 48 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5586053be..fc57af255b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.13](https://github.com/IBM/carbon-charts/compare/v0.30.12...v0.30.13) (2020-04-20) + +**Note:** Version bump only for package @carbon/charts-monorepo + + + + + ## [0.30.12](https://github.com/IBM/carbon-charts/compare/v0.30.11...v0.30.12) (2020-04-17) diff --git a/lerna.json b/lerna.json index 81a3bc9675..65c8b56c5c 100644 --- a/lerna.json +++ b/lerna.json @@ -12,5 +12,5 @@ ] } }, - "version": "0.30.12" + "version": "0.30.13" } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index b59dd05ec5..99b538c921 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.13](https://github.com/IBM/carbon-charts/compare/v0.30.12...v0.30.13) (2020-04-20) + +**Note:** Version bump only for package @carbon/charts-angular + + + + + ## [0.30.12](https://github.com/IBM/carbon-charts/compare/v0.30.11...v0.30.12) (2020-04-17) **Note:** Version bump only for package @carbon/charts-angular diff --git a/packages/angular/package.json b/packages/angular/package.json index 476ceb9374..0bfa03cef3 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-angular", - "version": "0.30.12", + "version": "0.30.13", "description": "Carbon charting components for Angular", "main": "index.js", "scripts": { @@ -39,7 +39,7 @@ "scss" ], "dependencies": { - "@carbon/charts": "^0.30.12" + "@carbon/charts": "^0.30.13" }, "peerDependencies": { "@angular/common": "^6.0.0 || ^7.0.0 || ^8.0.0", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 74e5fdbee3..9b7e79015e 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.13](https://github.com/IBM/carbon-charts/compare/v0.30.12...v0.30.13) (2020-04-20) + +**Note:** Version bump only for package @carbon/charts + + + + + ## [0.30.12](https://github.com/IBM/carbon-charts/compare/v0.30.11...v0.30.12) (2020-04-17) diff --git a/packages/core/package.json b/packages/core/package.json index bff23560a6..2df6f137be 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts", - "version": "0.30.12", + "version": "0.30.13", "description": "Carbon charting components", "main": "./bundle.js", "module": "./index.js", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 3f38af6b4f..db51f01482 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.13](https://github.com/IBM/carbon-charts/compare/v0.30.12...v0.30.13) (2020-04-20) + +**Note:** Version bump only for package @carbon/charts-react + + + + + ## [0.30.12](https://github.com/IBM/carbon-charts/compare/v0.30.11...v0.30.12) (2020-04-17) **Note:** Version bump only for package @carbon/charts-react diff --git a/packages/react/package.json b/packages/react/package.json index 6911914593..98cb52a316 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-react", - "version": "0.30.12", + "version": "0.30.13", "description": "Carbon charting components for React", "main": "./bundle.js", "module": "./index.js", @@ -45,7 +45,7 @@ }, "homepage": "https://github.com/IBM/carbon-charts#readme", "dependencies": { - "@carbon/charts": "^0.30.12" + "@carbon/charts": "^0.30.13" }, "peerDependencies": { "react": "^16.6.3", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 2dd0d3abd7..e8c6658948 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.13](https://github.com/IBM/carbon-charts/compare/v0.30.12...v0.30.13) (2020-04-20) + +**Note:** Version bump only for package @carbon/charts-vue + + + + + ## [0.30.12](https://github.com/IBM/carbon-charts/compare/v0.30.11...v0.30.12) (2020-04-17) **Note:** Version bump only for package @carbon/charts-vue diff --git a/packages/vue/package.json b/packages/vue/package.json index 71a4182175..526c923cd9 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-vue", - "version": "0.30.12", + "version": "0.30.13", "description": "Carbon charting components for Vue", "main": "charts-vue.umd.min.js", "scripts": { @@ -14,7 +14,7 @@ "clean": "rm -rf dist demo/bundle" }, "dependencies": { - "@carbon/charts": "^0.30.12", + "@carbon/charts": "^0.30.13", "vue": "2.5.21" }, "devDependencies": { From a9740d1d4b36554bed26c5d4f5b9b111c44c02b8 Mon Sep 17 00:00:00 2001 From: Serena Girardi Date: Mon, 20 Apr 2020 14:51:15 +0200 Subject: [PATCH 33/95] fix(core): Support for Horizontal Line and Scatter Chart --- packages/core/demo/data/index.ts | 10 ++++++ packages/core/demo/data/line.ts | 33 +++++++++++++++++++ packages/core/src/components/graphs/line.ts | 14 +++++--- .../core/src/components/graphs/scatter.ts | 16 ++++++--- .../core/src/services/scales-cartesian.ts | 4 +-- packages/core/src/tools.ts | 4 +++ 6 files changed, 70 insertions(+), 11 deletions(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index a50344350d..64f7e156fa 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -204,6 +204,16 @@ let allDemoGroups = [ options: lineDemos.lineTimeSeriesRotatedTicksOptions, data: lineDemos.lineTimeSeriesDataRotatedTicks, chartType: chartTypes.LineChart + }, + { + options: lineDemos.lineTimeSeriesHorizontalOptions, + data: lineDemos.lineTimeSeriesData, + chartType: chartTypes.LineChart + }, + { + options: lineDemos.lineHorizontalOptions, + data: lineDemos.lineData, + chartType: chartTypes.LineChart } ] }, diff --git a/packages/core/demo/data/line.ts b/packages/core/demo/data/line.ts index 4eac27597e..0a375c15a1 100644 --- a/packages/core/demo/data/line.ts +++ b/packages/core/demo/data/line.ts @@ -98,3 +98,36 @@ export const lineTimeSeriesRotatedTicksOptions = { } } }; + +export const lineHorizontalOptions = { + title: "Line Horizontal (discrete)", + axes: { + left: { + title: "2019 Annual Sales Figures", + mapsTo: "key", + scaleType: "labels" + }, + bottom: { + mapsTo: "value", + title: "Conversion rate", + scaleType: "linear" + } + } +}; + +export const lineTimeSeriesHorizontalOptions = { + title: "Line Horizontal (time series)", + axes: { + left: { + title: "2019 Annual Sales Figures", + mapsTo: "date", + scaleType: "time" + }, + bottom: { + mapsTo: "value", + title: "Conversion rate", + scaleType: "linear" + } + }, + curve: "curveMonotoneY" +}; diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 50d0bcb46e..4982ce65fc 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -2,6 +2,7 @@ import { Component } from "../component"; import * as Configuration from "../../configuration"; import { Roles, Events } from "../../interfaces"; +import { Tools } from "../../tools"; // D3 Imports import { select } from "d3-selection"; @@ -20,14 +21,19 @@ export class Line extends Component { render(animate = true) { const svg = this.getContainerSVG(); + const { cartesianScales, curves } = this.services; + + const getDomainValue = (d, i) => cartesianScales.getDomainValue(d, i); + const getRangeValue = (d, i) => cartesianScales.getRangeValue(d, i); + const [getXValue, getYValue] = Tools.flipBasedOnOrientation(getDomainValue, getRangeValue, cartesianScales.getOrientation()); // D3 line generator function const lineGenerator = line() - .x((d, i) => this.services.cartesianScales.getDomainValue(d, i)) - .y((d, i) => this.services.cartesianScales.getRangeValue(d, i)) - .curve(this.services.curves.getD3Curve()) + .x(getXValue) + .y(getYValue) + .curve(curves.getD3Curve()) .defined((datum: any, i) => { - const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); + const rangeIdentifier = cartesianScales.getRangeIdentifier(); const value = datum[rangeIdentifier]; if (value === null || value === undefined) { return false; diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index be7beefea4..ac5e954948 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -1,6 +1,7 @@ // Internal Imports import { Component } from "../component"; import { TooltipTypes, Roles, Events } from "../../interfaces"; +import { Tools } from "../../tools"; // D3 Imports import { select, Selection, event as d3Event } from "d3-selection"; @@ -60,18 +61,23 @@ export class Scatter extends Component { // Chart options mixed with the internal configurations const options = this.model.getOptions(); const { filled } = options.points; + const { cartesianScales, transitions } = this.services; const { groupMapsTo } = options.data; - const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); - const rangeIdentifier = this.services.cartesianScales.getRangeIdentifier(); + const domainIdentifier = cartesianScales.getDomainIdentifier(); + const rangeIdentifier = cartesianScales.getRangeIdentifier(); + + const getDomainValue = (d, i) => cartesianScales.getDomainValue(d, i); + const getRangeValue = (d, i) => cartesianScales.getRangeValue(d, i); + const [getXValue, getYValue] = Tools.flipBasedOnOrientation(getDomainValue, getRangeValue, cartesianScales.getOrientation()); selection.raise() .classed("dot", true) .classed("filled", d => this.model.getIsFilled(d[groupMapsTo], d[domainIdentifier], d, filled)) .classed("unfilled", d => !this.model.getIsFilled(d[groupMapsTo], d[domainIdentifier], d, filled)) - .attr("cx", (d, i) => this.services.cartesianScales.getDomainValue(d, i)) - .transition(this.services.transitions.getTransition("scatter-update-enter", animate)) - .attr("cy", (d, i) => this.services.cartesianScales.getRangeValue(d, i)) + .attr("cx", getXValue) + .transition(transitions.getTransition("scatter-update-enter", animate)) + .attr("cy", getYValue) .attr("r", options.points.radius) .attr("fill", d => { if (this.model.getIsFilled(d[groupMapsTo], d[domainIdentifier], d, filled)) { diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 652cffa286..085ebe842a 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -90,7 +90,7 @@ export class CartesianScales extends Service { const axisOptions = Tools.getProperty(options, "axes"); // If right axis has been specified as `main` - if (Tools.getProperty(axisOptions, "axes", AxisPositions.RIGHT, "main") === true) { + if (Tools.getProperty(axisOptions, AxisPositions.RIGHT, "main") === true) { return AxisPositions.RIGHT; } @@ -102,7 +102,7 @@ export class CartesianScales extends Service { const axisOptions = Tools.getProperty(options, "axes"); // If top axis has been specified as `main` - if (Tools.getProperty(axisOptions, "axes", AxisPositions.TOP, "main") === true) { + if (Tools.getProperty(axisOptions, AxisPositions.TOP, "main") === true) { return AxisPositions.TOP; } diff --git a/packages/core/src/tools.ts b/packages/core/src/tools.ts index 4b6a7b5f0b..9b2a1b20e5 100644 --- a/packages/core/src/tools.ts +++ b/packages/core/src/tools.ts @@ -278,4 +278,8 @@ export namespace Tools { return `M${x0},${y0}L${x0},${y1}L${x1},${y1}L${x1},${y0}L${x0},${y0}`; }; + + export function flipBasedOnOrientation(domain: D, range: R, orientation?: CartesianOrientations): [D, R] | [R, D] { + return orientation === CartesianOrientations.VERTICAL ? [domain, range] : [range, domain]; + } } From e8462bebbf7bcda42d77d9c4c35b53a07ff8abf0 Mon Sep 17 00:00:00 2001 From: Serena Girardi Date: Tue, 21 Apr 2020 10:13:02 +0200 Subject: [PATCH 34/95] Change method name --- packages/core/src/components/graphs/line.ts | 6 +++++- packages/core/src/components/graphs/scatter.ts | 6 +++++- packages/core/src/tools.ts | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/graphs/line.ts b/packages/core/src/components/graphs/line.ts index 4982ce65fc..6877e45a9c 100644 --- a/packages/core/src/components/graphs/line.ts +++ b/packages/core/src/components/graphs/line.ts @@ -25,7 +25,11 @@ export class Line extends Component { const getDomainValue = (d, i) => cartesianScales.getDomainValue(d, i); const getRangeValue = (d, i) => cartesianScales.getRangeValue(d, i); - const [getXValue, getYValue] = Tools.flipBasedOnOrientation(getDomainValue, getRangeValue, cartesianScales.getOrientation()); + const [getXValue, getYValue] = Tools.flipDomainAndRangeBasedOnOrientation( + getDomainValue, + getRangeValue, + cartesianScales.getOrientation() + ); // D3 line generator function const lineGenerator = line() diff --git a/packages/core/src/components/graphs/scatter.ts b/packages/core/src/components/graphs/scatter.ts index ac5e954948..7828942b8e 100644 --- a/packages/core/src/components/graphs/scatter.ts +++ b/packages/core/src/components/graphs/scatter.ts @@ -69,7 +69,11 @@ export class Scatter extends Component { const getDomainValue = (d, i) => cartesianScales.getDomainValue(d, i); const getRangeValue = (d, i) => cartesianScales.getRangeValue(d, i); - const [getXValue, getYValue] = Tools.flipBasedOnOrientation(getDomainValue, getRangeValue, cartesianScales.getOrientation()); + const [getXValue, getYValue] = Tools.flipDomainAndRangeBasedOnOrientation( + getDomainValue, + getRangeValue, + cartesianScales.getOrientation() + ); selection.raise() .classed("dot", true) diff --git a/packages/core/src/tools.ts b/packages/core/src/tools.ts index 9b2a1b20e5..95f2f334ab 100644 --- a/packages/core/src/tools.ts +++ b/packages/core/src/tools.ts @@ -279,7 +279,7 @@ export namespace Tools { return `M${x0},${y0}L${x0},${y1}L${x1},${y1}L${x1},${y0}L${x0},${y0}`; }; - export function flipBasedOnOrientation(domain: D, range: R, orientation?: CartesianOrientations): [D, R] | [R, D] { + export function flipDomainAndRangeBasedOnOrientation(domain: D, range: R, orientation?: CartesianOrientations): [D, R] | [R, D] { return orientation === CartesianOrientations.VERTICAL ? [domain, range] : [range, domain]; } } From 58d7683b931659d880a10bac97ba5f2e9ed72228 Mon Sep 17 00:00:00 2001 From: carbon-bot Date: Tue, 21 Apr 2020 23:07:05 +0000 Subject: [PATCH 35/95] v0.30.14 --- CHANGELOG.md | 11 +++++++++++ lerna.json | 2 +- packages/angular/CHANGELOG.md | 8 ++++++++ packages/angular/package.json | 4 ++-- packages/core/CHANGELOG.md | 11 +++++++++++ packages/core/package.json | 2 +- packages/react/CHANGELOG.md | 8 ++++++++ packages/react/package.json | 4 ++-- packages/vue/CHANGELOG.md | 8 ++++++++ packages/vue/package.json | 4 ++-- 10 files changed, 54 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc57af255b..4d10fdea5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.14](https://github.com/IBM/carbon-charts/compare/v0.30.13...v0.30.14) (2020-04-21) + + +### Bug Fixes + +* **core:** Support for Horizontal Line and Scatter Chart ([28b571f](https://github.com/IBM/carbon-charts/commit/28b571fa4565de5b81a5a178776eb701a88bfe91)) + + + + + ## [0.30.13](https://github.com/IBM/carbon-charts/compare/v0.30.12...v0.30.13) (2020-04-20) **Note:** Version bump only for package @carbon/charts-monorepo diff --git a/lerna.json b/lerna.json index 65c8b56c5c..969e6e2556 100644 --- a/lerna.json +++ b/lerna.json @@ -12,5 +12,5 @@ ] } }, - "version": "0.30.13" + "version": "0.30.14" } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index 99b538c921..ee2b73112a 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.14](https://github.com/IBM/carbon-charts/compare/v0.30.13...v0.30.14) (2020-04-21) + +**Note:** Version bump only for package @carbon/charts-angular + + + + + ## [0.30.13](https://github.com/IBM/carbon-charts/compare/v0.30.12...v0.30.13) (2020-04-20) **Note:** Version bump only for package @carbon/charts-angular diff --git a/packages/angular/package.json b/packages/angular/package.json index 0bfa03cef3..3db701f94b 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-angular", - "version": "0.30.13", + "version": "0.30.14", "description": "Carbon charting components for Angular", "main": "index.js", "scripts": { @@ -39,7 +39,7 @@ "scss" ], "dependencies": { - "@carbon/charts": "^0.30.13" + "@carbon/charts": "^0.30.14" }, "peerDependencies": { "@angular/common": "^6.0.0 || ^7.0.0 || ^8.0.0", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 9b7e79015e..228bb284d8 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.14](https://github.com/IBM/carbon-charts/compare/v0.30.13...v0.30.14) (2020-04-21) + + +### Bug Fixes + +* **core:** Support for Horizontal Line and Scatter Chart ([28b571f](https://github.com/IBM/carbon-charts/commit/28b571fa4565de5b81a5a178776eb701a88bfe91)) + + + + + ## [0.30.13](https://github.com/IBM/carbon-charts/compare/v0.30.12...v0.30.13) (2020-04-20) **Note:** Version bump only for package @carbon/charts diff --git a/packages/core/package.json b/packages/core/package.json index 2df6f137be..267b34e7a3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts", - "version": "0.30.13", + "version": "0.30.14", "description": "Carbon charting components", "main": "./bundle.js", "module": "./index.js", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index db51f01482..978678a598 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.14](https://github.com/IBM/carbon-charts/compare/v0.30.13...v0.30.14) (2020-04-21) + +**Note:** Version bump only for package @carbon/charts-react + + + + + ## [0.30.13](https://github.com/IBM/carbon-charts/compare/v0.30.12...v0.30.13) (2020-04-20) **Note:** Version bump only for package @carbon/charts-react diff --git a/packages/react/package.json b/packages/react/package.json index 98cb52a316..7ee919848f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-react", - "version": "0.30.13", + "version": "0.30.14", "description": "Carbon charting components for React", "main": "./bundle.js", "module": "./index.js", @@ -45,7 +45,7 @@ }, "homepage": "https://github.com/IBM/carbon-charts#readme", "dependencies": { - "@carbon/charts": "^0.30.13" + "@carbon/charts": "^0.30.14" }, "peerDependencies": { "react": "^16.6.3", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index e8c6658948..e7c9669e5e 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.14](https://github.com/IBM/carbon-charts/compare/v0.30.13...v0.30.14) (2020-04-21) + +**Note:** Version bump only for package @carbon/charts-vue + + + + + ## [0.30.13](https://github.com/IBM/carbon-charts/compare/v0.30.12...v0.30.13) (2020-04-20) **Note:** Version bump only for package @carbon/charts-vue diff --git a/packages/vue/package.json b/packages/vue/package.json index 526c923cd9..c928d3f7b5 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-vue", - "version": "0.30.13", + "version": "0.30.14", "description": "Carbon charting components for Vue", "main": "charts-vue.umd.min.js", "scripts": { @@ -14,7 +14,7 @@ "clean": "rm -rf dist demo/bundle" }, "dependencies": { - "@carbon/charts": "^0.30.13", + "@carbon/charts": "^0.30.14", "vue": "2.5.21" }, "devDependencies": { From 904bc83aac8c528185fcaffaa255574f6af91711 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 22 Apr 2020 17:51:50 +0200 Subject: [PATCH 36/95] Fix linting problem about methods order --- .../core/src/components/graphs/bar-grouped.ts | 126 ++++---- packages/core/src/model.ts | 274 +++++++++--------- .../core/src/services/scales-cartesian.ts | 102 +++---- 3 files changed, 251 insertions(+), 251 deletions(-) diff --git a/packages/core/src/components/graphs/bar-grouped.ts b/packages/core/src/components/graphs/bar-grouped.ts index 0b564474d7..aea707eb4a 100644 --- a/packages/core/src/components/graphs/bar-grouped.ts +++ b/packages/core/src/components/graphs/bar-grouped.ts @@ -31,69 +31,6 @@ export class GroupedBar extends Bar { eventsFragment.addEventListener(Events.Legend.ITEM_MOUSEOUT, this.handleLegendMouseOut); } - protected getDataCorrespondingToLabel(label: string) { - const displayData = this.model.getDisplayData(); - const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); - - return displayData.filter(datum => datum[domainIdentifier] === label); - } - - protected getGroupWidth() { - const numOfActiveDataGroups = this.model.getActiveDataGroupNames().length; - const totalGroupPadding = this.getTotalGroupPadding(); - - return this.getBarWidth() * numOfActiveDataGroups + totalGroupPadding; - } - - - protected getTotalGroupPadding() { - const numOfActiveDataGroups = this.model.getActiveDataGroupNames().length; - - if (numOfActiveDataGroups === 1) { - return 0; - } - - const domainScale = this.services.cartesianScales.getDomainScale(); - const padding = Math.min( - 5, - 5 * (domainScale.step() / 70) - ); - - return padding * (numOfActiveDataGroups - 1); - } - - // Gets the correct width for bars based on options & configurations - protected getBarWidth() { - const options = this.model.getOptions(); - const providedWidth = Tools.getProperty(options, "bars", "width"); - const providedMaxWidth = Tools.getProperty(options, "bars", "maxWidth"); - - // If there's a provided width, compare with maxWidth and - // Determine which to return - if (providedWidth !== null) { - if (providedMaxWidth === null) { - return providedWidth; - } else if (providedWidth <= providedMaxWidth) { - return providedWidth; - } - } - - const numOfActiveDataGroups = this.model.getActiveDataGroupNames().length; - const totalGroupPadding = this.getTotalGroupPadding(); - - const domainScale = this.services.cartesianScales.getDomainScale(); - return Math.min( - providedMaxWidth, - (domainScale.step() - totalGroupPadding) / numOfActiveDataGroups - ); - } - - protected setGroupScale() { - this.groupScale = scaleBand() - .domain(this.model.getActiveDataGroupNames()) - .rangeRound([0, this.getGroupWidth()]); - } - render(animate: boolean) { // Chart options mixed with the internal configurations const displayData = this.model.getDisplayData(); @@ -272,4 +209,67 @@ export class GroupedBar extends Bar { eventsFragment.removeEventListener(Events.Legend.ITEM_HOVER, this.handleLegendOnHover); eventsFragment.removeEventListener(Events.Legend.ITEM_MOUSEOUT, this.handleLegendMouseOut); } + + protected getDataCorrespondingToLabel(label: string) { + const displayData = this.model.getDisplayData(); + const domainIdentifier = this.services.cartesianScales.getDomainIdentifier(); + + return displayData.filter(datum => datum[domainIdentifier] === label); + } + + protected getGroupWidth() { + const numOfActiveDataGroups = this.model.getActiveDataGroupNames().length; + const totalGroupPadding = this.getTotalGroupPadding(); + + return this.getBarWidth() * numOfActiveDataGroups + totalGroupPadding; + } + + + protected getTotalGroupPadding() { + const numOfActiveDataGroups = this.model.getActiveDataGroupNames().length; + + if (numOfActiveDataGroups === 1) { + return 0; + } + + const domainScale = this.services.cartesianScales.getDomainScale(); + const padding = Math.min( + 5, + 5 * (domainScale.step() / 70) + ); + + return padding * (numOfActiveDataGroups - 1); + } + + // Gets the correct width for bars based on options & configurations + protected getBarWidth() { + const options = this.model.getOptions(); + const providedWidth = Tools.getProperty(options, "bars", "width"); + const providedMaxWidth = Tools.getProperty(options, "bars", "maxWidth"); + + // If there's a provided width, compare with maxWidth and + // Determine which to return + if (providedWidth !== null) { + if (providedMaxWidth === null) { + return providedWidth; + } else if (providedWidth <= providedMaxWidth) { + return providedWidth; + } + } + + const numOfActiveDataGroups = this.model.getActiveDataGroupNames().length; + const totalGroupPadding = this.getTotalGroupPadding(); + + const domainScale = this.services.cartesianScales.getDomainScale(); + return Math.min( + providedMaxWidth, + (domainScale.step() - totalGroupPadding) / numOfActiveDataGroups + ); + } + + protected setGroupScale() { + this.groupScale = scaleBand() + .domain(this.model.getActiveDataGroupNames()) + .rangeRound([0, this.getGroupWidth()]); + } } diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 3133912c09..ca8de70965 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -39,65 +39,6 @@ export class ChartModel { this.services = services; } - /** - * Converts data provided in the older format to tabular - * - */ - protected transformToTabularData(data) { - console.warn("We've updated the charting data format to be tabular by default. The current format you're using is deprecated and will be removed in v1.0, read more here https://carbon-design-system.github.io/carbon-charts/?path=/story/tutorials--tabular-data-format") - const tabularData = []; - const { datasets, labels } = data; - - // Loop through all datasets - datasets.forEach(dataset => { - // Update each data point to the new format - dataset.data.forEach((datum, i) => { - let group; - - const datasetLabel = Tools.getProperty(dataset, "label"); - if (datasetLabel === null) { - const correspondingLabel = Tools.getProperty(labels, i); - if (correspondingLabel) { - group = correspondingLabel; - } else { - group = "Ungrouped"; - } - } else { - group = datasetLabel; - } - - const updatedDatum = { - group, - key: labels[i] - }; - - if (isNaN(datum)) { - updatedDatum["value"] = datum.value; - updatedDatum["date"] = datum.date; - } else { - updatedDatum["value"] = datum; - } - - tabularData.push(updatedDatum); - }); - }); - - return tabularData; - } - - protected getTabularData(data) { - // if data is not an array - if (!Array.isArray(data)) { - return this.transformToTabularData(data); - } - - return data; - } - - protected sanitize(data) { - return this.getTabularData(data); - } - getDisplayData() { if (!this.get("data")) { return null; @@ -137,43 +78,6 @@ export class ChartModel { return sanitizedData; } - /* - * Data groups - */ - protected updateAllDataGroups() { - // allDataGroups is used to generate a color scale that applies - // to all the groups. Now when the data updates, you might remove a group, - // and then bring it back in a newer data update, therefore - // the order of the groups in allDataGroups matters so that you'd never - // have an incorrect color assigned to a group. - - // Also, a new group should only be added to allDataGroups if - // it doesn't currently exist - - if (!this.allDataGroups) { - this.allDataGroups = this.getDataGroupNames(); - } else { - // Loop through current data groups - this.getDataGroupNames().forEach(dataGroupName => { - // If group name hasn't been stored yet, store it - if (this.allDataGroups.indexOf(dataGroupName) === -1) { - this.allDataGroups.push(dataGroupName); - } - }); - } - } - - protected generateDataGroups(data) { - const { groupMapsTo } = this.getOptions().data; - const { ACTIVE } = Configuration.legend.items.status; - - const uniqueDataGroups = map(data, datum => datum[groupMapsTo]).keys(); - return uniqueDataGroups.map(groupName => ({ - name: groupName, - status: ACTIVE - })); - } - getDataGroups() { return this.get("dataGroups"); } @@ -353,6 +257,142 @@ export class ChartModel { }); } + /** + * Should the data point be filled? + * @param group + * @param key + * @param value + * @param defaultFilled the default for this chart + */ + getIsFilled(group: any, key?: any, data?: any, defaultFilled?: boolean) { + const options = this.getOptions(); + if (options.getIsFilled) { + return options.getIsFilled(group, key, data, defaultFilled); + } else { + return defaultFilled; + } + } + + getFillColor(group: any, key?: any, data?: any) { + const options = this.getOptions(); + const defaultFillColor = this.getFillScale()(group); + if (options.getFillColor) { + return options.getFillColor(group, key, data, defaultFillColor); + } else { + return defaultFillColor; + } + } + + getStrokeColor(group: any, key?: any, data?: any) { + const options = this.getOptions(); + const defaultStrokeColor = this.colorScale(group); + if (options.getStrokeColor) { + return options.getStrokeColor(group, key, data, defaultStrokeColor); + } else { + return defaultStrokeColor; + } + } + + getFillScale() { + return this.colorScale; + } + + /** + * Converts data provided in the older format to tabular + * + */ + protected transformToTabularData(data) { + console.warn("We've updated the charting data format to be tabular by default. The current format you're using is deprecated and will be removed in v1.0, read more here https://carbon-design-system.github.io/carbon-charts/?path=/story/tutorials--tabular-data-format") + const tabularData = []; + const { datasets, labels } = data; + + // Loop through all datasets + datasets.forEach(dataset => { + // Update each data point to the new format + dataset.data.forEach((datum, i) => { + let group; + + const datasetLabel = Tools.getProperty(dataset, "label"); + if (datasetLabel === null) { + const correspondingLabel = Tools.getProperty(labels, i); + if (correspondingLabel) { + group = correspondingLabel; + } else { + group = "Ungrouped"; + } + } else { + group = datasetLabel; + } + + const updatedDatum = { + group, + key: labels[i] + }; + + if (isNaN(datum)) { + updatedDatum["value"] = datum.value; + updatedDatum["date"] = datum.date; + } else { + updatedDatum["value"] = datum; + } + + tabularData.push(updatedDatum); + }); + }); + + return tabularData; + } + + protected getTabularData(data) { + // if data is not an array + if (!Array.isArray(data)) { + return this.transformToTabularData(data); + } + + return data; + } + + protected sanitize(data) { + return this.getTabularData(data); + } + + /* + * Data groups + */ + protected updateAllDataGroups() { + // allDataGroups is used to generate a color scale that applies + // to all the groups. Now when the data updates, you might remove a group, + // and then bring it back in a newer data update, therefore + // the order of the groups in allDataGroups matters so that you'd never + // have an incorrect color assigned to a group. + + // Also, a new group should only be added to allDataGroups if + // it doesn't currently exist + + if (!this.allDataGroups) { + this.allDataGroups = this.getDataGroupNames(); + } else { + // Loop through current data groups + this.getDataGroupNames().forEach(dataGroupName => { + // If group name hasn't been stored yet, store it + if (this.allDataGroups.indexOf(dataGroupName) === -1) { + this.allDataGroups.push(dataGroupName); + } + }); + } + } + + protected generateDataGroups(data) { + const { groupMapsTo } = this.getOptions().data; + const { ACTIVE } = Configuration.legend.items.status; + + const uniqueDataGroups = map(data, datum => datum[groupMapsTo]).keys(); + return uniqueDataGroups.map(groupName => ({ + name: groupName, + status: ACTIVE + })); + } + /* * Fill scales */ @@ -392,46 +432,6 @@ export class ChartModel { }); this.colorScale = scaleOrdinal().range(colorRange) - .domain(this.allDataGroups); - } - - /** - * Should the data point be filled? - * @param group - * @param key - * @param value - * @param defaultFilled the default for this chart - */ - getIsFilled(group: any, key?: any, data?: any, defaultFilled?: boolean) { - const options = this.getOptions(); - if (options.getIsFilled) { - return options.getIsFilled(group, key, data, defaultFilled); - } else { - return defaultFilled; - } - } - - getFillColor(group: any, key?: any, data?: any) { - const options = this.getOptions(); - const defaultFillColor = this.getFillScale()(group); - if (options.getFillColor) { - return options.getFillColor(group, key, data, defaultFillColor); - } else { - return defaultFillColor; - } - } - - getStrokeColor(group: any, key?: any, data?: any) { - const options = this.getOptions(); - const defaultStrokeColor = this.colorScale(group); - if (options.getStrokeColor) { - return options.getStrokeColor(group, key, data, defaultStrokeColor); - } else { - return defaultStrokeColor; - } - } - - getFillScale() { - return this.colorScale; + .domain(this.allDataGroups); } } diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 085ebe842a..2b3cb6b21f 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -85,57 +85,6 @@ export class CartesianScales extends Service { }); } - protected findMainVerticalAxisPosition() { - const options = this.model.getOptions(); - const axisOptions = Tools.getProperty(options, "axes"); - - // If right axis has been specified as `main` - if (Tools.getProperty(axisOptions, AxisPositions.RIGHT, "main") === true) { - return AxisPositions.RIGHT; - } - - return AxisPositions.LEFT; - } - - protected findMainHorizontalAxisPosition() { - const options = this.model.getOptions(); - const axisOptions = Tools.getProperty(options, "axes"); - - // If top axis has been specified as `main` - if (Tools.getProperty(axisOptions, AxisPositions.TOP, "main") === true) { - return AxisPositions.TOP; - } - - return AxisPositions.BOTTOM; - } - - protected findDomainAndRangeAxesPositions(mainVerticalAxisPosition: AxisPositions, mainHorizontalAxisPosition: AxisPositions) { - const options = this.model.getOptions(); - - const mainVerticalAxisOptions = Tools.getProperty(options, "axes", mainVerticalAxisPosition); - const mainHorizontalAxisOptions = Tools.getProperty(options, "axes", mainHorizontalAxisPosition); - - const mainVerticalScaleType = mainVerticalAxisOptions.scaleType || ScaleTypes.LINEAR; - const mainHorizontalScaleType = mainHorizontalAxisOptions.scaleType || ScaleTypes.LINEAR; - - const result = { - domainAxisPosition: null, - rangeAxisPosition: null - }; - if (mainHorizontalScaleType === ScaleTypes.LABELS || mainHorizontalScaleType === ScaleTypes.TIME) { - result.domainAxisPosition = mainHorizontalAxisPosition; - result.rangeAxisPosition = mainVerticalAxisPosition; - } else if (mainVerticalScaleType === ScaleTypes.LABELS || mainVerticalScaleType === ScaleTypes.TIME) { - result.domainAxisPosition = mainVerticalAxisPosition; - result.rangeAxisPosition = mainHorizontalAxisPosition; - } else { - result.domainAxisPosition = mainHorizontalAxisPosition; - result.rangeAxisPosition = mainVerticalAxisPosition; - } - - return result; - } - findDomainAndRangeAxes() { // find main axes between (left & right) && (bottom & top) const mainVerticalAxisPosition = this.findMainVerticalAxisPosition(); @@ -264,6 +213,57 @@ export class CartesianScales extends Service { }); } + protected findMainVerticalAxisPosition() { + const options = this.model.getOptions(); + const axisOptions = Tools.getProperty(options, "axes"); + + // If right axis has been specified as `main` + if (Tools.getProperty(axisOptions, AxisPositions.RIGHT, "main") === true) { + return AxisPositions.RIGHT; + } + + return AxisPositions.LEFT; + } + + protected findMainHorizontalAxisPosition() { + const options = this.model.getOptions(); + const axisOptions = Tools.getProperty(options, "axes"); + + // If top axis has been specified as `main` + if (Tools.getProperty(axisOptions, AxisPositions.TOP, "main") === true) { + return AxisPositions.TOP; + } + + return AxisPositions.BOTTOM; + } + + protected findDomainAndRangeAxesPositions(mainVerticalAxisPosition: AxisPositions, mainHorizontalAxisPosition: AxisPositions) { + const options = this.model.getOptions(); + + const mainVerticalAxisOptions = Tools.getProperty(options, "axes", mainVerticalAxisPosition); + const mainHorizontalAxisOptions = Tools.getProperty(options, "axes", mainHorizontalAxisPosition); + + const mainVerticalScaleType = mainVerticalAxisOptions.scaleType || ScaleTypes.LINEAR; + const mainHorizontalScaleType = mainHorizontalAxisOptions.scaleType || ScaleTypes.LINEAR; + + const result = { + domainAxisPosition: null, + rangeAxisPosition: null + }; + if (mainHorizontalScaleType === ScaleTypes.LABELS || mainHorizontalScaleType === ScaleTypes.TIME) { + result.domainAxisPosition = mainHorizontalAxisPosition; + result.rangeAxisPosition = mainVerticalAxisPosition; + } else if (mainVerticalScaleType === ScaleTypes.LABELS || mainVerticalScaleType === ScaleTypes.TIME) { + result.domainAxisPosition = mainVerticalAxisPosition; + result.rangeAxisPosition = mainHorizontalAxisPosition; + } else { + result.domainAxisPosition = mainHorizontalAxisPosition; + result.rangeAxisPosition = mainVerticalAxisPosition; + } + + return result; + } + protected getScaleDomain(axisPosition: AxisPositions) { const options = this.model.getOptions(); const axisOptions = Tools.getProperty(options, "axes", axisPosition); From e50224ffa5c53bfcb14d8fda0497030a70cc70c1 Mon Sep 17 00:00:00 2001 From: carbon-bot Date: Wed, 22 Apr 2020 16:57:05 +0000 Subject: [PATCH 37/95] v0.30.15 --- CHANGELOG.md | 8 ++++++++ lerna.json | 2 +- packages/angular/CHANGELOG.md | 8 ++++++++ packages/angular/package.json | 4 ++-- packages/core/CHANGELOG.md | 8 ++++++++ packages/core/package.json | 2 +- packages/react/CHANGELOG.md | 8 ++++++++ packages/react/package.json | 4 ++-- packages/vue/CHANGELOG.md | 8 ++++++++ packages/vue/package.json | 4 ++-- 10 files changed, 48 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d10fdea5c..1be0b56c83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.15](https://github.com/IBM/carbon-charts/compare/v0.30.14...v0.30.15) (2020-04-22) + +**Note:** Version bump only for package @carbon/charts-monorepo + + + + + ## [0.30.14](https://github.com/IBM/carbon-charts/compare/v0.30.13...v0.30.14) (2020-04-21) diff --git a/lerna.json b/lerna.json index 969e6e2556..781eeb8904 100644 --- a/lerna.json +++ b/lerna.json @@ -12,5 +12,5 @@ ] } }, - "version": "0.30.14" + "version": "0.30.15" } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index ee2b73112a..d92cb66329 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.15](https://github.com/IBM/carbon-charts/compare/v0.30.14...v0.30.15) (2020-04-22) + +**Note:** Version bump only for package @carbon/charts-angular + + + + + ## [0.30.14](https://github.com/IBM/carbon-charts/compare/v0.30.13...v0.30.14) (2020-04-21) **Note:** Version bump only for package @carbon/charts-angular diff --git a/packages/angular/package.json b/packages/angular/package.json index 3db701f94b..162defa5f6 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-angular", - "version": "0.30.14", + "version": "0.30.15", "description": "Carbon charting components for Angular", "main": "index.js", "scripts": { @@ -39,7 +39,7 @@ "scss" ], "dependencies": { - "@carbon/charts": "^0.30.14" + "@carbon/charts": "^0.30.15" }, "peerDependencies": { "@angular/common": "^6.0.0 || ^7.0.0 || ^8.0.0", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 228bb284d8..5fc4274ddc 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.15](https://github.com/IBM/carbon-charts/compare/v0.30.14...v0.30.15) (2020-04-22) + +**Note:** Version bump only for package @carbon/charts + + + + + ## [0.30.14](https://github.com/IBM/carbon-charts/compare/v0.30.13...v0.30.14) (2020-04-21) diff --git a/packages/core/package.json b/packages/core/package.json index 267b34e7a3..66e67ff092 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts", - "version": "0.30.14", + "version": "0.30.15", "description": "Carbon charting components", "main": "./bundle.js", "module": "./index.js", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 978678a598..c192f0c125 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.15](https://github.com/IBM/carbon-charts/compare/v0.30.14...v0.30.15) (2020-04-22) + +**Note:** Version bump only for package @carbon/charts-react + + + + + ## [0.30.14](https://github.com/IBM/carbon-charts/compare/v0.30.13...v0.30.14) (2020-04-21) **Note:** Version bump only for package @carbon/charts-react diff --git a/packages/react/package.json b/packages/react/package.json index 7ee919848f..5b2fb14810 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-react", - "version": "0.30.14", + "version": "0.30.15", "description": "Carbon charting components for React", "main": "./bundle.js", "module": "./index.js", @@ -45,7 +45,7 @@ }, "homepage": "https://github.com/IBM/carbon-charts#readme", "dependencies": { - "@carbon/charts": "^0.30.14" + "@carbon/charts": "^0.30.15" }, "peerDependencies": { "react": "^16.6.3", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index e7c9669e5e..302aedf39e 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.15](https://github.com/IBM/carbon-charts/compare/v0.30.14...v0.30.15) (2020-04-22) + +**Note:** Version bump only for package @carbon/charts-vue + + + + + ## [0.30.14](https://github.com/IBM/carbon-charts/compare/v0.30.13...v0.30.14) (2020-04-21) **Note:** Version bump only for package @carbon/charts-vue diff --git a/packages/vue/package.json b/packages/vue/package.json index c928d3f7b5..1013dda615 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-vue", - "version": "0.30.14", + "version": "0.30.15", "description": "Carbon charting components for Vue", "main": "charts-vue.umd.min.js", "scripts": { @@ -14,7 +14,7 @@ "clean": "rm -rf dist demo/bundle" }, "dependencies": { - "@carbon/charts": "^0.30.14", + "@carbon/charts": "^0.30.15", "vue": "2.5.21" }, "devDependencies": { From b845a7fcf04aa41f756d8749b63367315a3cb3da Mon Sep 17 00:00:00 2001 From: natashadecoste Date: Thu, 23 Apr 2020 13:34:04 -0400 Subject: [PATCH 38/95] Code comments for tools.ts (#416) * update documentation for tools.ts * docs(tools.ts): api documentation Co-authored-by: Eliad Moosavi --- packages/core/src/tools.ts | 55 ++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/packages/core/src/tools.ts b/packages/core/src/tools.ts index 95f2f334ab..e59ecff974 100644 --- a/packages/core/src/tools.ts +++ b/packages/core/src/tools.ts @@ -50,7 +50,7 @@ export namespace Tools { const providedAxisOptions = providedOptions.axes[axisName]; if (providedAxisOptions["primary"] || providedAxisOptions["secondary"]) { - console.warn("`primary` & `secondary` are no longer needed for axis configurations. Read more here https://carbon-design-system.github.io/carbon-charts/?path=/story/tutorials--tabular-data-format") + console.warn("`primary` & `secondary` are no longer needed for axis configurations. Read more here https://carbon-design-system.github.io/carbon-charts/?path=/story/tutorials--tabular-data-format"); } const identifier = providedAxisOptions["mapsTo"]; @@ -95,9 +95,10 @@ export namespace Tools { } /** - * Returns an elements's x and y translations from attribute transform + * Gets elements's x and y translations from transform attribute or returns null + * * @param {HTMLElement} element - * @returns an object containing the x and y translations or null + * @returns an object containing the translated x and y values or null */ export function getTranslationValues(elementRef: HTMLElement) { // regex to ONLY get values for translate (instead of all rotate, translate, skew, etc) @@ -121,7 +122,7 @@ export namespace Tools { *************************************/ /** - * Gets x and y coordinates from a HTML transform attribute + * Gets x and y coordinates from HTML transform attribute * * @export * @param {any} string the transform attribute string ie. transform(x,y) @@ -138,12 +139,29 @@ export namespace Tools { }; } + /** + * Returns string value for height/width using pixels if there isn't a specified unit of measure + * + * @param value string or number value to be checked for unit of measure + */ + export function formatWidthHeightValues(value) { + const stringValue = value.toString(); + + // If the value provided contains any letters + // Return it the same way + if (stringValue.match(/[a-z]/i)) { + return stringValue; + } + + return stringValue + "px"; + } + /** * Capitalizes first letter of a string * * @export - * @param {any} string the string whose first letter you'd like to capitalize - * @returns The input string with its first letter capitalized + * @param {any} string the input string to perform first letter capitalization with + * @returns The transformed string after first letter is capitalized */ export function capitalizeFirstLetter(string) { return string[0].toUpperCase() + string.slice(1); @@ -151,11 +169,10 @@ export namespace Tools { /** * Get the percentage of a datapoint compared to the entire dataset. - * Returns 1 significant digit. * @export * @param {any} item * @param {any} fullData - * @returns The percentage in the form of a number + * @returns The percentage in the form of a number (1 significant digit if necessary) */ export function convertValueToPercentage(item, fullData) { const percentage = item / fullData.reduce((accum, val) => accum + val.value, 0) * 100; @@ -166,13 +183,15 @@ export namespace Tools { /************************************** * Object/array related checks * *************************************/ + /** - * Get the difference between two arrays' items + * Compares two arrays to return the difference between two arrays' items. * * @export - * @param {any[]} oldArray - * @param {any[]} newArray - * @returns The items missing in newArray from oldArray, and items added to newArray compared to oldArray + * @param {any[]} oldArray the array to check for missing items + * @param {any[]} newArray the array to check for newly added items + * @returns An object containing items missing (existing in oldArray but not newArray) + * and items added (existing in newArray but not in oldArray). Object is of the form { missing: [], added: [] } */ export function arrayDifferences(oldArray: any[], newArray: any[]) { const difference = { @@ -196,7 +215,7 @@ export namespace Tools { } /** - * Lists out the duplicated keys in an array of data + * Gets the duplicated keys from an array of data * * @export * @param {*} data - array of data @@ -220,11 +239,12 @@ export namespace Tools { // ================================================================================ // D3 Extensions // ================================================================================ + /** * In D3, moves an element to the front of the canvas * * @export - * @param {any} element + * @param {any} element input element to moved in front * @returns The function to be used by D3 to push element to the top of the canvas */ export function moveToFront(element) { @@ -237,6 +257,13 @@ export namespace Tools { // Style Helpers // ================================================================================ + /** + * Gets a speicified property from within an object. + * + * @param object the object containing the property to retrieve + * @param propPath nested properties used to extract the final property from within the object + * (i.e "style", "color" would retrieve the color property from within an object that has "color" nested within "style") + */ export const getProperty = (object, ...propPath) => { let position = object; if (position) { From dc0ffae8b9850f81a1eaf4dd19acc545b9dd436f Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 23 Apr 2020 22:16:53 +0200 Subject: [PATCH 39/95] Merge pull request #588 from accurat/invalid-time-value-bug * Fix bug `RangeError Invalid time value` * Extends domain refactor * Time series: add example with addSpaceOnEdge = 0 * Rename functions * Change method order --- packages/core/demo/data/index.ts | 5 + packages/core/demo/data/time-series-axis.ts | 37 +++++ .../core/src/services/scales-cartesian.ts | 134 +++++++++--------- 3 files changed, 108 insertions(+), 68 deletions(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 64f7e156fa..3300651634 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -330,6 +330,11 @@ let allDemoGroups = [ data: timeSeriesAxisDemos.lineTimeSeriesDataSingleDatum, options: timeSeriesAxisDemos.lineTimeSeriesSingleDatumOptions, chartType: chartTypes.LineChart + }, + { + data: timeSeriesAxisDemos.lineTimeSeriesNoExtendedDomainData, + options: timeSeriesAxisDemos.lineTimeSeriesNoExtendedDomainOptions, + chartType: chartTypes.LineChart } ] }, diff --git a/packages/core/demo/data/time-series-axis.ts b/packages/core/demo/data/time-series-axis.ts index 644728988d..876bc8a98f 100644 --- a/packages/core/demo/data/time-series-axis.ts +++ b/packages/core/demo/data/time-series-axis.ts @@ -297,6 +297,7 @@ export const lineTimeSeriesYearlyOptions = { } }; +// single datum export const lineTimeSeriesDataSingleDatum = { labels: ["Qty"], datasets: [ @@ -318,3 +319,39 @@ export const lineTimeSeriesSingleDatumOptions = { } } }; + +// addSpaceOnEdges = 0 +export const lineTimeSeriesNoExtendedDomainData = { + labels: ["Qty"], + datasets: [ + { + label: "Dataset 1", + data: [ + { date: new Date(2019, 11, 30), value: 10 }, + { date: new Date(2019, 11, 31), value: 10 }, + { date: new Date(2020, 0, 1), value: 10 }, + { date: new Date(2020, 0, 2), value: 10 }, + { date: new Date(2020, 0, 3), value: 10 }, + { date: new Date(2020, 0, 4), value: 10 }, + { date: new Date(2020, 0, 5), value: 10 } + ] + } + ] +}; + +export const lineTimeSeriesNoExtendedDomainOptions = { + title: "Line (time series) - addSpaceOnEdges = 0", + axes: { + left: {}, + bottom: { + scaleType: "time" + } + }, + timeScale: { + addSpaceOnEdges: 0 + }, + points: { + radius: 1 + }, + filled: false +}; diff --git a/packages/core/src/services/scales-cartesian.ts b/packages/core/src/services/scales-cartesian.ts index 2b3cb6b21f..669d13f4f9 100644 --- a/packages/core/src/services/scales-cartesian.ts +++ b/packages/core/src/services/scales-cartesian.ts @@ -36,18 +36,6 @@ import { addSeconds } from "date-fns"; -function addPaddingInDomain([lower, upper]: number[], paddingRatio: number) { - const domainLength = upper - lower; - const padding = domainLength * paddingRatio; - - // If padding crosses 0, keep 0 as new upper bound - const newUpper = upper <= 0 && upper + padding > 0 ? 0 : upper + padding; - // If padding crosses 0, keep 0 as new lower bound - const newLower = lower >= 0 && lower - padding < 0 ? 0 : lower - padding; - - return [newLower, newUpper]; -} - export class CartesianScales extends Service { protected scaleTypes = { top: null, @@ -213,6 +201,17 @@ export class CartesianScales extends Service { }); } + extendsDomain(axisPosition: AxisPositions, domain: any) { + const options = this.model.getOptions(); + const axisOptions = Tools.getProperty(options, "axes", axisPosition); + if (axisOptions.scaleType === ScaleTypes.TIME) { + const spaceToAddToEdges = Tools.getProperty(options, "timeScale", "addSpaceOnEdges"); + return addSpacingToTimeDomain(domain, spaceToAddToEdges); + } else { + return addSpacingToContinuousDomain(domain, Configuration.axis.paddingRatio); + } + } + protected findMainVerticalAxisPosition() { const options = this.model.getOptions(); const axisOptions = Tools.getProperty(options, "axes"); @@ -279,80 +278,28 @@ export class CartesianScales extends Service { } // If scale is a LABELS scale, return some labels as the domain - if (axisOptions && axisOptions.scaleType === ScaleTypes.LABELS) { + if (axisOptions && scaleType === ScaleTypes.LABELS) { // Get unique values return map(displayData, d => d[mapsTo]).keys(); } - // Get the extent of the domain let domain; let allDataValues; // If the scale is stacked if (axisOptions.stacked) { const dataValuesGroupedByKeys = this.model.getDataValuesGroupedByKeys(); - allDataValues = dataValuesGroupedByKeys.map(dataValues => { - return sum( - values(dataValues) as any - ); - }); + allDataValues = dataValuesGroupedByKeys.map(dataValues => sum(values(dataValues) as any)); } else { allDataValues = displayData.map(datum => datum[mapsTo]); } - if (axisOptions.scaleType !== ScaleTypes.TIME && includeZero) { + if (scaleType !== ScaleTypes.TIME && includeZero) { allDataValues.push(0); } domain = extent(allDataValues); - - if (axisOptions.scaleType === ScaleTypes.TIME) { - const spaceToAddToEdges = Tools.getProperty(options, "timeScale", "addSpaceOnEdges"); - - if (spaceToAddToEdges) { - const startDate = new Date(domain[0]); - const endDate = new Date(domain[1]); - - if (differenceInYears(endDate, startDate) > 1) { - return [subYears(startDate, spaceToAddToEdges), addYears(endDate, spaceToAddToEdges)]; - } - - if (differenceInMonths(endDate, startDate) > 1) { - return [subMonths(startDate, spaceToAddToEdges), addMonths(endDate, spaceToAddToEdges)]; - } - - if (differenceInDays(endDate, startDate) > 1) { - return [subDays(startDate, spaceToAddToEdges), addDays(endDate, spaceToAddToEdges)]; - } - - if (differenceInHours(endDate, startDate) > 1) { - return [subHours(startDate, spaceToAddToEdges), addHours(endDate, spaceToAddToEdges)]; - } - - if (differenceInMinutes(endDate, startDate) > 30) { - return [subMinutes(startDate, spaceToAddToEdges * 30), addMinutes(endDate, spaceToAddToEdges * 30)]; - } - - if (differenceInMinutes(endDate, startDate) > 1) { - return [subMinutes(startDate, spaceToAddToEdges), addMinutes(endDate, spaceToAddToEdges)]; - } - - if (differenceInSeconds(endDate, startDate) > 15) { - return [subSeconds(startDate, spaceToAddToEdges * 15), addSeconds(endDate, spaceToAddToEdges * 15)]; - } - - if (differenceInSeconds(endDate, startDate) > 1) { - return [subSeconds(startDate, spaceToAddToEdges), addSeconds(endDate, spaceToAddToEdges)]; - } - - return [startDate, endDate]; - } - } - - domain = addPaddingInDomain(domain, Configuration.axis.paddingRatio); - if (scaleType === ScaleTypes.TIME) { - domain = domain.map(d => new Date(d)); - } + domain = this.extendsDomain(axisPosition, domain); return domain; } @@ -383,3 +330,54 @@ export class CartesianScales extends Service { return scale; } } + +function addSpacingToTimeDomain(domain: any, spaceToAddToEdges: number) { + const startDate = new Date(domain[0]); + const endDate = new Date(domain[1]); + + if (differenceInYears(endDate, startDate) > 1) { + return [subYears(startDate, spaceToAddToEdges), addYears(endDate, spaceToAddToEdges)]; + } + + if (differenceInMonths(endDate, startDate) > 1) { + return [subMonths(startDate, spaceToAddToEdges), addMonths(endDate, spaceToAddToEdges)]; + } + + if (differenceInDays(endDate, startDate) > 1) { + return [subDays(startDate, spaceToAddToEdges), addDays(endDate, spaceToAddToEdges)]; + } + + if (differenceInHours(endDate, startDate) > 1) { + return [subHours(startDate, spaceToAddToEdges), addHours(endDate, spaceToAddToEdges)]; + } + + if (differenceInMinutes(endDate, startDate) > 30) { + return [subMinutes(startDate, spaceToAddToEdges * 30), addMinutes(endDate, spaceToAddToEdges * 30)]; + } + + if (differenceInMinutes(endDate, startDate) > 1) { + return [subMinutes(startDate, spaceToAddToEdges), addMinutes(endDate, spaceToAddToEdges)]; + } + + if (differenceInSeconds(endDate, startDate) > 15) { + return [subSeconds(startDate, spaceToAddToEdges * 15), addSeconds(endDate, spaceToAddToEdges * 15)]; + } + + if (differenceInSeconds(endDate, startDate) > 1) { + return [subSeconds(startDate, spaceToAddToEdges), addSeconds(endDate, spaceToAddToEdges)]; + } + + return [startDate, endDate]; +} + +function addSpacingToContinuousDomain([lower, upper]: number[], paddingRatio: number) { + const domainLength = upper - lower; + const padding = domainLength * paddingRatio; + + // If padding crosses 0, keep 0 as new upper bound + const newUpper = upper <= 0 && upper + padding > 0 ? 0 : upper + padding; + // If padding crosses 0, keep 0 as new lower bound + const newLower = lower >= 0 && lower - padding < 0 ? 0 : lower - padding; + + return [newLower, newUpper]; +} From 5c2a1c6de1d0925160aa28729ca88ebdb76ea3ea Mon Sep 17 00:00:00 2001 From: carbon-bot Date: Thu, 23 Apr 2020 20:27:18 +0000 Subject: [PATCH 40/95] v0.30.16 --- CHANGELOG.md | 8 ++++++++ lerna.json | 2 +- packages/angular/CHANGELOG.md | 8 ++++++++ packages/angular/package.json | 4 ++-- packages/core/CHANGELOG.md | 8 ++++++++ packages/core/package.json | 2 +- packages/react/CHANGELOG.md | 8 ++++++++ packages/react/package.json | 4 ++-- packages/vue/CHANGELOG.md | 8 ++++++++ packages/vue/package.json | 4 ++-- 10 files changed, 48 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1be0b56c83..c6fa9f5a7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.16](https://github.com/IBM/carbon-charts/compare/v0.30.15...v0.30.16) (2020-04-23) + +**Note:** Version bump only for package @carbon/charts-monorepo + + + + + ## [0.30.15](https://github.com/IBM/carbon-charts/compare/v0.30.14...v0.30.15) (2020-04-22) **Note:** Version bump only for package @carbon/charts-monorepo diff --git a/lerna.json b/lerna.json index 781eeb8904..f4b756ecef 100644 --- a/lerna.json +++ b/lerna.json @@ -12,5 +12,5 @@ ] } }, - "version": "0.30.15" + "version": "0.30.16" } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index d92cb66329..6edd473d44 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.16](https://github.com/IBM/carbon-charts/compare/v0.30.15...v0.30.16) (2020-04-23) + +**Note:** Version bump only for package @carbon/charts-angular + + + + + ## [0.30.15](https://github.com/IBM/carbon-charts/compare/v0.30.14...v0.30.15) (2020-04-22) **Note:** Version bump only for package @carbon/charts-angular diff --git a/packages/angular/package.json b/packages/angular/package.json index 162defa5f6..6721442806 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-angular", - "version": "0.30.15", + "version": "0.30.16", "description": "Carbon charting components for Angular", "main": "index.js", "scripts": { @@ -39,7 +39,7 @@ "scss" ], "dependencies": { - "@carbon/charts": "^0.30.15" + "@carbon/charts": "^0.30.16" }, "peerDependencies": { "@angular/common": "^6.0.0 || ^7.0.0 || ^8.0.0", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 5fc4274ddc..18103a11d7 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.16](https://github.com/IBM/carbon-charts/compare/v0.30.15...v0.30.16) (2020-04-23) + +**Note:** Version bump only for package @carbon/charts + + + + + ## [0.30.15](https://github.com/IBM/carbon-charts/compare/v0.30.14...v0.30.15) (2020-04-22) **Note:** Version bump only for package @carbon/charts diff --git a/packages/core/package.json b/packages/core/package.json index 66e67ff092..5b570800a5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts", - "version": "0.30.15", + "version": "0.30.16", "description": "Carbon charting components", "main": "./bundle.js", "module": "./index.js", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index c192f0c125..01624c41ef 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.16](https://github.com/IBM/carbon-charts/compare/v0.30.15...v0.30.16) (2020-04-23) + +**Note:** Version bump only for package @carbon/charts-react + + + + + ## [0.30.15](https://github.com/IBM/carbon-charts/compare/v0.30.14...v0.30.15) (2020-04-22) **Note:** Version bump only for package @carbon/charts-react diff --git a/packages/react/package.json b/packages/react/package.json index 5b2fb14810..c7287c3df8 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-react", - "version": "0.30.15", + "version": "0.30.16", "description": "Carbon charting components for React", "main": "./bundle.js", "module": "./index.js", @@ -45,7 +45,7 @@ }, "homepage": "https://github.com/IBM/carbon-charts#readme", "dependencies": { - "@carbon/charts": "^0.30.15" + "@carbon/charts": "^0.30.16" }, "peerDependencies": { "react": "^16.6.3", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 302aedf39e..258cff6ba2 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.16](https://github.com/IBM/carbon-charts/compare/v0.30.15...v0.30.16) (2020-04-23) + +**Note:** Version bump only for package @carbon/charts-vue + + + + + ## [0.30.15](https://github.com/IBM/carbon-charts/compare/v0.30.14...v0.30.15) (2020-04-22) **Note:** Version bump only for package @carbon/charts-vue diff --git a/packages/vue/package.json b/packages/vue/package.json index 1013dda615..7708b64792 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-vue", - "version": "0.30.15", + "version": "0.30.16", "description": "Carbon charting components for Vue", "main": "charts-vue.umd.min.js", "scripts": { @@ -14,7 +14,7 @@ "clean": "rm -rf dist demo/bundle" }, "dependencies": { - "@carbon/charts": "^0.30.15", + "@carbon/charts": "^0.30.16", "vue": "2.5.21" }, "devDependencies": { From 1c3dae35900b94eaed2ebba9bda0ae43703ec3ed Mon Sep 17 00:00:00 2001 From: Eliad Moosavi Date: Thu, 23 Apr 2020 15:57:29 -0400 Subject: [PATCH 41/95] remove bbox dependency on pie labels --- packages/core/src/components/graphs/pie.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/graphs/pie.ts b/packages/core/src/components/graphs/pie.ts index f4d5fe601d..a12d60c218 100644 --- a/packages/core/src/components/graphs/pie.ts +++ b/packages/core/src/components/graphs/pie.ts @@ -139,13 +139,14 @@ export class Pie extends Component { }) // Calculate dimensions in order to transform .datum(function(d) { - const textLength = this.getComputedTextLength(); - d.textOffsetX = textLength / 2; - d.textOffsetY = Math.ceil(select(this).node().getBBox().height / 2); - const marginedRadius = radius + 7; const theta = ((d.endAngle - d.startAngle) / 2) + d.startAngle; + const deg = theta / Math.PI * 180; + + const textLength = this.getComputedTextLength(); + d.textOffsetX = textLength / 2; + d.textOffsetY = (deg > 90 && deg < 270) ? 10 : 0; d.xPosition = (d.textOffsetX + marginedRadius) * Math.sin(theta); d.yPosition = (d.textOffsetY + marginedRadius) * -Math.cos(theta); From 783822dfa280992f93070e90366e796067741e90 Mon Sep 17 00:00:00 2001 From: carbon-bot Date: Thu, 23 Apr 2020 21:02:19 +0000 Subject: [PATCH 42/95] v0.30.17 --- CHANGELOG.md | 8 ++++++++ lerna.json | 2 +- packages/angular/CHANGELOG.md | 8 ++++++++ packages/angular/package.json | 4 ++-- packages/core/CHANGELOG.md | 8 ++++++++ packages/core/package.json | 2 +- packages/react/CHANGELOG.md | 8 ++++++++ packages/react/package.json | 4 ++-- packages/vue/CHANGELOG.md | 8 ++++++++ packages/vue/package.json | 4 ++-- 10 files changed, 48 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6fa9f5a7c..1e8ca08156 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.17](https://github.com/IBM/carbon-charts/compare/v0.30.16...v0.30.17) (2020-04-23) + +**Note:** Version bump only for package @carbon/charts-monorepo + + + + + ## [0.30.16](https://github.com/IBM/carbon-charts/compare/v0.30.15...v0.30.16) (2020-04-23) **Note:** Version bump only for package @carbon/charts-monorepo diff --git a/lerna.json b/lerna.json index f4b756ecef..a186fb57cd 100644 --- a/lerna.json +++ b/lerna.json @@ -12,5 +12,5 @@ ] } }, - "version": "0.30.16" + "version": "0.30.17" } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index 6edd473d44..855a09f85b 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.17](https://github.com/IBM/carbon-charts/compare/v0.30.16...v0.30.17) (2020-04-23) + +**Note:** Version bump only for package @carbon/charts-angular + + + + + ## [0.30.16](https://github.com/IBM/carbon-charts/compare/v0.30.15...v0.30.16) (2020-04-23) **Note:** Version bump only for package @carbon/charts-angular diff --git a/packages/angular/package.json b/packages/angular/package.json index 6721442806..7b36bbeee2 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-angular", - "version": "0.30.16", + "version": "0.30.17", "description": "Carbon charting components for Angular", "main": "index.js", "scripts": { @@ -39,7 +39,7 @@ "scss" ], "dependencies": { - "@carbon/charts": "^0.30.16" + "@carbon/charts": "^0.30.17" }, "peerDependencies": { "@angular/common": "^6.0.0 || ^7.0.0 || ^8.0.0", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 18103a11d7..3a86f69f8a 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.17](https://github.com/IBM/carbon-charts/compare/v0.30.16...v0.30.17) (2020-04-23) + +**Note:** Version bump only for package @carbon/charts + + + + + ## [0.30.16](https://github.com/IBM/carbon-charts/compare/v0.30.15...v0.30.16) (2020-04-23) **Note:** Version bump only for package @carbon/charts diff --git a/packages/core/package.json b/packages/core/package.json index 5b570800a5..4ea7901ce8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts", - "version": "0.30.16", + "version": "0.30.17", "description": "Carbon charting components", "main": "./bundle.js", "module": "./index.js", diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 01624c41ef..16bea8193c 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.17](https://github.com/IBM/carbon-charts/compare/v0.30.16...v0.30.17) (2020-04-23) + +**Note:** Version bump only for package @carbon/charts-react + + + + + ## [0.30.16](https://github.com/IBM/carbon-charts/compare/v0.30.15...v0.30.16) (2020-04-23) **Note:** Version bump only for package @carbon/charts-react diff --git a/packages/react/package.json b/packages/react/package.json index c7287c3df8..cf6bc2ab3f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-react", - "version": "0.30.16", + "version": "0.30.17", "description": "Carbon charting components for React", "main": "./bundle.js", "module": "./index.js", @@ -45,7 +45,7 @@ }, "homepage": "https://github.com/IBM/carbon-charts#readme", "dependencies": { - "@carbon/charts": "^0.30.16" + "@carbon/charts": "^0.30.17" }, "peerDependencies": { "react": "^16.6.3", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 258cff6ba2..22f817e879 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.30.17](https://github.com/IBM/carbon-charts/compare/v0.30.16...v0.30.17) (2020-04-23) + +**Note:** Version bump only for package @carbon/charts-vue + + + + + ## [0.30.16](https://github.com/IBM/carbon-charts/compare/v0.30.15...v0.30.16) (2020-04-23) **Note:** Version bump only for package @carbon/charts-vue diff --git a/packages/vue/package.json b/packages/vue/package.json index 7708b64792..0e59e19d8f 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@carbon/charts-vue", - "version": "0.30.16", + "version": "0.30.17", "description": "Carbon charting components for Vue", "main": "charts-vue.umd.min.js", "scripts": { @@ -14,7 +14,7 @@ "clean": "rm -rf dist demo/bundle" }, "dependencies": { - "@carbon/charts": "^0.30.16", + "@carbon/charts": "^0.30.17", "vue": "2.5.21" }, "devDependencies": { From 99c1e0a4026826352379aa3e93b04b196d5c42f8 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 23 Apr 2020 12:04:20 +0200 Subject: [PATCH 43/95] Radar: fix y axes transitions --- packages/core/src/components/graphs/radar.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 9de2fc461a..48cc7d26a9 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -139,21 +139,23 @@ export class Radar extends Component { // y axes const yAxes = DOMUtils.appendOrSelect(svg, "g.y-axes"); - const yAxisUpdate = yAxes.selectAll("path").data(yTicks, d => d); + const yAxisUpdate = yAxes.selectAll("path").data(yTicks, (yTickValue, i) => i); yAxisUpdate .enter() .append("path") .merge(yAxisUpdate) .attr("transform", `translate(${cx}, ${cy})`) + .attr("opacity", 0) + .transition(this.services.transitions.getTransition("y-axis-update-enter", animate)) .attr("d", tickValue => { const xAxesKeys = xScale.domain(); const points = xAxesKeys.map(key => ({ key, value: tickValue })); return radialLineGenerator(points); }) - .transition(this.services.transitions.getTransition("y-axis-update-enter", animate)) .attr("fill", "none") + .attr("opacity", 1) .attr("stroke", "#dcdcdc"); - yAxisUpdate.exit().remove(); + yAxisUpdate.exit().attr("opacity", 0).remove(); // x axes const labelPadding = 10; From 77db7e14af9a9e151da75c063a67e735e114bbd0 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Fri, 24 Apr 2020 08:42:46 +0200 Subject: [PATCH 44/95] Radar: use the new .join d3 method --- packages/core/src/components/graphs/radar.ts | 61 +++++++++++--------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 48cc7d26a9..33a09591cc 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -91,7 +91,7 @@ export class Radar extends Component { // given the key (= value corrisponding to a an x axis), a radius r and translation values // return the coordinates of the corrisponding point stayng on the x axis with radius r - const getCoordinates = (key: string, r: number, tx = 0, ty = 0) => { + const polarCoords = (key: string, r: number, tx = 0, ty = 0) => { const angle = xScale(key) - Math.PI / 2; // translate by tx and ty const x = r * Math.cos(angle) + tx; @@ -139,7 +139,7 @@ export class Radar extends Component { // y axes const yAxes = DOMUtils.appendOrSelect(svg, "g.y-axes"); - const yAxisUpdate = yAxes.selectAll("path").data(yTicks, (yTickValue, i) => i); + const yAxisUpdate = yAxes.selectAll("path").data(yTicks); yAxisUpdate .enter() .append("path") @@ -155,37 +155,44 @@ export class Radar extends Component { .attr("fill", "none") .attr("opacity", 1) .attr("stroke", "#dcdcdc"); - yAxisUpdate.exit().attr("opacity", 0).remove(); + yAxisUpdate + .exit() + .attr("opacity", 0) + .remove(); // x axes const labelPadding = 10; - const keysValues = uniqBy(displayData, "key"); + const keys = uniqBy(displayData, "key"); const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); - const xAxisUpdate = xAxes.selectAll("g.x-axis").data(keysValues); - const xAxisEnter = xAxisUpdate - .enter() - .append("g") - .attr("class", "x-axis"); - // add axes - xAxisEnter - .append("line") - .merge(xAxisUpdate.selectAll("line")) - .attr("x1", key => getCoordinates(key, yScale.range()[0], cx, cy).x) - .attr("y1", key => getCoordinates(key, yScale.range()[0], cx, cy).y) - .attr("x2", key => getCoordinates(key, yScale.range()[1], cx, cy).x) - .attr("y2", key => getCoordinates(key, yScale.range()[1], cx, cy).y) - .transition(this.services.transitions.getTransition("x-axis-update-enter", animate)) - .attr("stroke", "#dcdcdc"); - // add labels - xAxisEnter - .append("text") - .merge(xAxisUpdate.selectAll("text")) + const xAxisUpdate = xAxes.selectAll("g.x-axis").data(keys); + xAxisUpdate.join( + enter => enter.append("g") + .attr("class", "x-axis") + .call(selection => selection + .append("line") + ) + .call(selection => selection + .append("text") + ), + update => update, + exit => exit.remove() + ) + .call(selection => selection + .select("line") + .attr("stroke", "#dcdcdc") + .attr("x1", key => polarCoords(key, yScale.range()[0], cx, cy).x) + .attr("y1", key => polarCoords(key, yScale.range()[0], cx, cy).y) + .attr("x2", key => polarCoords(key, yScale.range()[1], cx, cy).x) + .attr("y2", key => polarCoords(key, yScale.range()[1], cx, cy).y) + ) + .call(selection => selection + .select("text") .text(d => d) - .attr("x", key => getCoordinates(key, yScale.range()[1] + labelPadding, cx, cy).x) - .attr("y", key => getCoordinates(key, yScale.range()[1] + labelPadding, cx, cy).y) + .attr("x", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).x) + .attr("y", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).y) .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) - .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline); - xAxisUpdate.exit().remove(); + .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline) + ); // blobs const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); From bf0cdc1ced0cab1a1ce2dd77b5d5c2a33e53bb6c Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 27 Apr 2020 08:25:56 +0200 Subject: [PATCH 45/95] Use join to manage enter, update, exit logic --- packages/core/src/components/graphs/radar.ts | 73 ++++++++++---------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 33a09591cc..ae9bc6c361 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -140,29 +140,26 @@ export class Radar extends Component { // y axes const yAxes = DOMUtils.appendOrSelect(svg, "g.y-axes"); const yAxisUpdate = yAxes.selectAll("path").data(yTicks); - yAxisUpdate - .enter() - .append("path") - .merge(yAxisUpdate) - .attr("transform", `translate(${cx}, ${cy})`) - .attr("opacity", 0) - .transition(this.services.transitions.getTransition("y-axis-update-enter", animate)) - .attr("d", tickValue => { - const xAxesKeys = xScale.domain(); - const points = xAxesKeys.map(key => ({ key, value: tickValue })); - return radialLineGenerator(points); - }) - .attr("fill", "none") - .attr("opacity", 1) - .attr("stroke", "#dcdcdc"); - yAxisUpdate - .exit() - .attr("opacity", 0) - .remove(); + yAxisUpdate.join( + enter => enter.append("path"), + update => update, + exit => exit.remove() + ) + .attr("transform", `translate(${cx}, ${cy})`) + .attr("opacity", 0) + .transition(this.services.transitions.getTransition("y-axis-update-enter", animate)) + .attr("d", tickValue => { + const xAxesKeys = xScale.domain(); + const points = xAxesKeys.map(key => ({ key, value: tickValue })); + return radialLineGenerator(points); + }) + .attr("fill", "none") + .attr("opacity", 1) + .attr("stroke", "#dcdcdc"); // x axes const labelPadding = 10; - const keys = uniqBy(displayData, "key"); + const keys = Array.from(new Set(displayData.map(d => d.key))); const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); const xAxisUpdate = xAxes.selectAll("g.x-axis").data(keys); xAxisUpdate.join( @@ -170,6 +167,12 @@ export class Radar extends Component { .attr("class", "x-axis") .call(selection => selection .append("line") + .attr("stroke", "#dcdcdc") + .attr("opacity", 0) + .call(e => e + .transition(this.services.transitions.getTransition("x-axis-update-enter", animate)) + .attr("opacity", 1) + ) ) .call(selection => selection .append("text") @@ -179,7 +182,6 @@ export class Radar extends Component { ) .call(selection => selection .select("line") - .attr("stroke", "#dcdcdc") .attr("x1", key => polarCoords(key, yScale.range()[0], cx, cy).x) .attr("y1", key => polarCoords(key, yScale.range()[0], cx, cy).y) .attr("x2", key => polarCoords(key, yScale.range()[1], cx, cy).x) @@ -197,20 +199,25 @@ export class Radar extends Component { // blobs const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); const blobUpdate = blobs.selectAll("g.blob").data(groupedData, group => group.name); - blobUpdate - .enter() - .append("g") - .attr("class", "blob") - .append("path") - .merge(blobUpdate.selectAll("path")) - .attr("class", group => `blob-area-${group.name}`) + blobUpdate.join( + enter => enter.append("g") + .attr("class", "blob") + .call(selection => selection + .append("path") + .attr("class", group => `blob-area-${group.name}`) + ), + update => update, + exit => exit.remove() + ) + .call(selection => selection + .select("path") .attr("d", group => radialLineGenerator(group.data)) .transition(this.services.transitions.getTransition("blob-update-enter", animate)) .attr("stroke", group => colorScale(group.name)) .attr("stroke-width", 1.5) .attr("fill", group => colorScale(group.name)) - .style("fill-opacity", configuration.opacity.selected); - blobUpdate.exit().remove(); + .style("fill-opacity", configuration.opacity.selected) + ); } handleLegendOnHover = (event: CustomEvent) => { @@ -244,12 +251,6 @@ export class Radar extends Component { } } -function uniqBy(dataset: any, attribute: string): any[] { - const allTheValuesByAttribute = dataset.map(d => d[attribute]); - const uniqValues = [...Array.from(new Set(allTheValuesByAttribute))]; - return uniqValues; -} - function radToDeg(rad: number) { return rad * (180 / Math.PI); } From 62ef53788cfc5609a7424ab5abaaf2f9a30d47ec Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 27 Apr 2020 17:27:10 +0200 Subject: [PATCH 46/95] Radar: fill missing data --- packages/core/demo/data/radar.ts | 3 +- packages/core/src/components/graphs/radar.ts | 35 ++++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/core/demo/data/radar.ts b/packages/core/demo/data/radar.ts index 1e2b58bb74..0a185d414c 100644 --- a/packages/core/demo/data/radar.ts +++ b/packages/core/demo/data/radar.ts @@ -12,8 +12,7 @@ export const radarData = [ { group: "Oil", key: "New York", value: 18 }, { group: "Water", key: "New York", value: 8 }, { group: "Sugar", key: "Sydney", value: 12 }, - { group: "Oil", key: "Sydney", value: 16 }, - { group: "Water", key: "Sydney", value: 23 } + { group: "Oil", key: "Sydney", value: 16 } ]; export const radarOptions = { diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index ae9bc6c361..1653d31d67 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -3,6 +3,8 @@ import { Component } from "../component"; import { DOMUtils } from "../../services"; import * as Configuration from "../../configuration"; import { Events } from "../../interfaces"; +import { Tools } from "../../tools"; +import { flatMapDeep } from "lodash-es"; // D3 Imports import { scaleBand, scaleLinear } from "d3-scale"; @@ -39,9 +41,14 @@ export class Radar extends Component { } console.log("\n"); - const data: Array = this.model.getData(); const displayData: Array = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); + + const uniqKeys: string[] = Array.from(new Set(displayData.map(d => d.key))); + const uniqGroups: string[] = Array.from(new Set(displayData.map(d => d.group))); + const displayDataNormalized: Array = normalizeFlatData(displayData, uniqKeys, uniqGroups); + const groupedDataNormalized = normalizeGroupedData(groupedData, uniqKeys); + const options = this.model.getOptions(); const configuration = Configuration.options.radarChart.radar; @@ -69,14 +76,14 @@ export class Radar extends Component { // given a key, return the corrisponding angle in radiants const xScale = scaleBand() - .domain(displayData.map(d => d.key)) + .domain(displayDataNormalized.map(d => d.key)) .range([0, 2 * Math.PI]); // console.log(`xScale [${xScale.domain()}] -> [${xScale.range()}]`); const ticksNumber = 5; const minRange = 10; const yScale = scaleLinear() - .domain([0, max(displayData.map(d => d.value))]) + .domain([0, max(displayDataNormalized.map(d => d.value))]) .range([minRange, radius]) .nice(ticksNumber); const yTicks = yScale.ticks(ticksNumber); @@ -159,9 +166,8 @@ export class Radar extends Component { // x axes const labelPadding = 10; - const keys = Array.from(new Set(displayData.map(d => d.key))); const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); - const xAxisUpdate = xAxes.selectAll("g.x-axis").data(keys); + const xAxisUpdate = xAxes.selectAll("g.x-axis").data(uniqKeys); xAxisUpdate.join( enter => enter.append("g") .attr("class", "x-axis") @@ -198,7 +204,7 @@ export class Radar extends Component { // blobs const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); - const blobUpdate = blobs.selectAll("g.blob").data(groupedData, group => group.name); + const blobUpdate = blobs.selectAll("g.blob").data(groupedDataNormalized, group => group.name); blobUpdate.join( enter => enter.append("g") .attr("class", "blob") @@ -319,3 +325,20 @@ function radialLabelPlacement(angleRadians: number) { return { textAnchor, dominantBaseline }; } + +/** + * If there are missing data on key, create corrisponding data with value = 0. + */ +function normalizeFlatData(dataset: Array, keys: string[], groups: string[]) { + const completeBlankData = flatMapDeep(keys.map(key => { + return groups.map(group => ({key, group, value: 0})); + })); + return Tools.merge(completeBlankData, dataset); +} + +function normalizeGroupedData(dataset: any, keys: string[]) { + return dataset.map(({name, data}) => { + const completeBlankData = keys.map(k => ({ group: name, key: k, value: 0 })); + return { name, data: Tools.merge(completeBlankData, data) }; + }); +} From df4bf57ad966ac9abd657439f5bf785f951b9acd Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 27 Apr 2020 19:44:23 +0200 Subject: [PATCH 47/95] Radar: fix animations but need a refactor --- packages/core/src/components/graphs/radar.ts | 70 ++++++++++++++------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 1653d31d67..b7f524f6b6 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -39,7 +39,7 @@ export class Radar extends Component { if (!width || !height) { return; } - console.log("\n"); + // console.log("\n"); const displayData: Array = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); @@ -174,32 +174,58 @@ export class Radar extends Component { .call(selection => selection .append("line") .attr("stroke", "#dcdcdc") - .attr("opacity", 0) - .call(e => e - .transition(this.services.transitions.getTransition("x-axis-update-enter", animate)) - .attr("opacity", 1) - ) + .attr("x1", key => polarCoords(key, 0, cx, cy).x) + .attr("y1", key => polarCoords(key, 0, cx, cy).y) + .attr("x2", key => polarCoords(key, 1, cx, cy).x) + .attr("y2", key => polarCoords(key, 1, cx, cy).y) + .transition().duration(500) + // .transition(this.services.transitions.getTransition("x-axis-line-enter", animate)) + .attr("x1", key => polarCoords(key, yScale.range()[0], cx, cy).x) + .attr("y1", key => polarCoords(key, yScale.range()[0], cx, cy).y) + .attr("x2", key => polarCoords(key, yScale.range()[1], cx, cy).x) + .attr("y2", key => polarCoords(key, yScale.range()[1], cx, cy).y) ) .call(selection => selection .append("text") + .text(d => d) + .attr("x", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).x) + .attr("y", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).y) + .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) + .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline) + .attr("opacity", 0) + .transition().duration(500) + // .transition(this.services.transitions.getTransition("x-axis-text-enter", animate)) + .attr("opacity", 1) ), - update => update, + + update => update + .call(selection => selection + .select("line") + .attr("x1", key => polarCoords(key, 0, cx, cy).x) + .attr("y1", key => polarCoords(key, 0, cx, cy).y) + .attr("x2", key => polarCoords(key, 1, cx, cy).x) + .attr("y2", key => polarCoords(key, 1, cx, cy).y) + .transition().duration(500) + // .transition(this.services.transitions.getTransition("x-axis-line-update", animate)) + .attr("x1", key => polarCoords(key, yScale.range()[0], cx, cy).x) + .attr("y1", key => polarCoords(key, yScale.range()[0], cx, cy).y) + .attr("x2", key => polarCoords(key, yScale.range()[1], cx, cy).x) + .attr("y2", key => polarCoords(key, yScale.range()[1], cx, cy).y) + ) + .call(selection => selection + .select("text") + .text(d => d) + .attr("x", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).x) + .attr("y", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).y) + .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) + .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline) + .attr("opacity", 0) + .transition().duration(500) + // .transition(this.services.transitions.getTransition("x-axis-text-update", animate)) + .attr("opacity", 1) + ), + exit => exit.remove() - ) - .call(selection => selection - .select("line") - .attr("x1", key => polarCoords(key, yScale.range()[0], cx, cy).x) - .attr("y1", key => polarCoords(key, yScale.range()[0], cx, cy).y) - .attr("x2", key => polarCoords(key, yScale.range()[1], cx, cy).x) - .attr("y2", key => polarCoords(key, yScale.range()[1], cx, cy).y) - ) - .call(selection => selection - .select("text") - .text(d => d) - .attr("x", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).x) - .attr("y", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).y) - .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) - .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline) ); // blobs From 99c25b02814c70450435bcb092c6c9eb39015db0 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 27 Apr 2020 21:31:42 +0200 Subject: [PATCH 48/95] Radar: add events --- packages/core/src/components/graphs/radar.ts | 51 ++++++++++++++++++++ packages/core/src/interfaces/events.ts | 10 ++++ 2 files changed, 61 insertions(+) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index b7f524f6b6..70c26d65c8 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -7,6 +7,7 @@ import { Tools } from "../../tools"; import { flatMapDeep } from "lodash-es"; // D3 Imports +import { select } from "d3-selection"; import { scaleBand, scaleLinear } from "d3-scale"; import { max } from "d3-array"; import { lineRadial, curveLinearClosed } from "d3-shape"; @@ -250,6 +251,9 @@ export class Radar extends Component { .attr("fill", group => colorScale(group.name)) .style("fill-opacity", configuration.opacity.selected) ); + + // Add event listeners + this.addEventListeners(); } handleLegendOnHover = (event: CustomEvent) => { @@ -281,6 +285,53 @@ export class Radar extends Component { eventsFragment.removeEventListener(Events.Legend.ITEM_HOVER, this.handleLegendOnHover); eventsFragment.removeEventListener(Events.Legend.ITEM_MOUSEOUT, this.handleLegendMouseOut); } + + addEventListeners() { + const self = this; + this.parent.selectAll(".blobs .blob > path") + .on("mouseover", function (datum) { + // Dispatch mouse event + self.services.events.dispatchEvent(Events.Radar.BLOB_MOUSEOVER, { + element: select(this), + datum + }); + }) + .on("mousemove", function (datum) { + const hoveredElement = select(this); + + // Changhe style + hoveredElement.classed("hovered", true) + .transition(self.services.transitions.getTransition("blob_path_mouseover")) + .style("fill-opacity", 0.8); + + // Dispatch mouse event + self.services.events.dispatchEvent(Events.Radar.BLOB_MOUSEMOVE, { + element: hoveredElement, + datum + }); + }) + .on("click", function(datum) { + // Dispatch mouse event + self.services.events.dispatchEvent(Events.Radar.BLOB_CLICK, { + element: select(this), + datum + }); + }) + .on("mouseout", function(datum) { + const hoveredElement = select(this); + + // Change style + hoveredElement.classed("hovered", false) + .transition(self.services.transitions.getTransition("blob_path_mouseout")) + .style("fill-opacity", 0.5); + + // Dispatch mouse event + self.services.events.dispatchEvent(Events.Radar.BLOB_MOUSEMOVE, { + element: hoveredElement, + datum + }); + }); + } } function radToDeg(rad: number) { diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 93ab6b034a..89e5432cdc 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -63,6 +63,16 @@ export enum Line { POINT_MOUSEOUT = "scatter-mouseout" } +/** + * enum of all radar graph events + */ +export enum Radar { + BLOB_MOUSEOVER = "radar-blob-mouseover", + BLOB_MOUSEMOVE = "radar-blob-mousemove", + BLOB_CLICK = "radar-blolb-click", + BLOB_MOUSEOUT = "radar-blob-mouseout" +} + /** * enum of all tooltip events */ From d86ebbdf639164ac630e08005edf90f5044d6ee5 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 28 Apr 2020 15:38:09 +0200 Subject: [PATCH 49/95] Radar: add tooltip --- packages/core/src/charts/radar.ts | 4 +- .../components/essentials/tooltip-radar.ts | 33 ++++++++ packages/core/src/components/graphs/radar.ts | 84 +++++++++++++++++-- packages/core/src/components/index.ts | 1 + packages/core/src/configuration.ts | 5 ++ packages/core/src/interfaces/events.ts | 6 +- 6 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/components/essentials/tooltip-radar.ts diff --git a/packages/core/src/charts/radar.ts b/packages/core/src/charts/radar.ts index 77ac8aad0e..56499552cd 100644 --- a/packages/core/src/charts/radar.ts +++ b/packages/core/src/charts/radar.ts @@ -11,7 +11,8 @@ import { Tools } from "../tools"; import { // the imports below are needed because of typescript bug (error TS4029) Legend, - LayoutComponent + LayoutComponent, + TooltipRadar } from "../components/index"; import { Radar } from "../components/graphs/radar"; @@ -46,6 +47,7 @@ export class RadarChart extends Chart { // get the base chart components and export with tooltip const components: any[] = this.getChartComponents(graphFrameComponents); + components.push(new TooltipRadar(this.model, this.services)); return components; } } diff --git a/packages/core/src/components/essentials/tooltip-radar.ts b/packages/core/src/components/essentials/tooltip-radar.ts new file mode 100644 index 0000000000..7d3968ef12 --- /dev/null +++ b/packages/core/src/components/essentials/tooltip-radar.ts @@ -0,0 +1,33 @@ +import { Tooltip } from "./tooltip"; +import { Tools } from "../../tools"; + +export class TooltipRadar extends Tooltip { + getMultilineTooltipHTML(data: any) { + // sort them so they are in the same order as the graph + data.sort((a, b) => b.value - a.value); + + return "
    " + + data.map(datum => { + const { groupMapsTo } = this.model.getOptions().data; + + const rangeIdentifier = "value"; + + const userProvidedValueFormatter = Tools.getProperty(this.model.getOptions(), "tooltip", "valueFormatter"); + const formattedValue = userProvidedValueFormatter + ? userProvidedValueFormatter(datum[rangeIdentifier]) + : datum[rangeIdentifier].toLocaleString("en"); + + // For the tooltip color, we always want the normal stroke color, not dynamically determined by data value. + const indicatorColor = this.model.getStrokeColor(datum[groupMapsTo]); + + return ` +
  • +
    + +

    ${datum[groupMapsTo]}

    +

    ${formattedValue}

    +
    +
  • `; + }).join("") + "
"; + } +} diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 70c26d65c8..f906e7b62a 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -22,6 +22,10 @@ interface Datum { export class Radar extends Component { type = "radar"; + uniqKeys: string[]; + uniqGroups: string[]; + displayDataNormalized: Array; + groupedDataNormalized: any; init() { const { events } = this.services; @@ -32,6 +36,8 @@ export class Radar extends Component { } render(animate = true) { + const self = this; + ///////////////////////////// // Containers ///////////////////////////// @@ -45,10 +51,10 @@ export class Radar extends Component { const displayData: Array = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); - const uniqKeys: string[] = Array.from(new Set(displayData.map(d => d.key))); - const uniqGroups: string[] = Array.from(new Set(displayData.map(d => d.group))); - const displayDataNormalized: Array = normalizeFlatData(displayData, uniqKeys, uniqGroups); - const groupedDataNormalized = normalizeGroupedData(groupedData, uniqKeys); + this.uniqKeys = Array.from(new Set(displayData.map(d => d.key))); + this.uniqGroups = Array.from(new Set(displayData.map(d => d.group))); + this.displayDataNormalized = normalizeFlatData(displayData, this.uniqKeys, this.uniqGroups); + this.groupedDataNormalized = normalizeGroupedData(groupedData, this.uniqKeys); const options = this.model.getOptions(); const configuration = Configuration.options.radarChart.radar; @@ -77,14 +83,14 @@ export class Radar extends Component { // given a key, return the corrisponding angle in radiants const xScale = scaleBand() - .domain(displayDataNormalized.map(d => d.key)) + .domain(this.displayDataNormalized.map(d => d.key)) .range([0, 2 * Math.PI]); // console.log(`xScale [${xScale.domain()}] -> [${xScale.range()}]`); const ticksNumber = 5; const minRange = 10; const yScale = scaleLinear() - .domain([0, max(displayDataNormalized.map(d => d.value))]) + .domain([0, max(this.displayDataNormalized.map(d => d.value))]) .range([minRange, radius]) .nice(ticksNumber); const yTicks = yScale.ticks(ticksNumber); @@ -168,7 +174,7 @@ export class Radar extends Component { // x axes const labelPadding = 10; const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); - const xAxisUpdate = xAxes.selectAll("g.x-axis").data(uniqKeys); + const xAxisUpdate = xAxes.selectAll("g.x-axis").data(this.uniqKeys); xAxisUpdate.join( enter => enter.append("g") .attr("class", "x-axis") @@ -231,7 +237,7 @@ export class Radar extends Component { // blobs const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); - const blobUpdate = blobs.selectAll("g.blob").data(groupedDataNormalized, group => group.name); + const blobUpdate = blobs.selectAll("g.blob").data(this.groupedDataNormalized, group => group.name); blobUpdate.join( enter => enter.append("g") .attr("class", "blob") @@ -288,6 +294,8 @@ export class Radar extends Component { addEventListeners() { const self = this; + + // events on blobs this.parent.selectAll(".blobs .blob > path") .on("mouseover", function (datum) { // Dispatch mouse event @@ -326,10 +334,68 @@ export class Radar extends Component { .style("fill-opacity", 0.5); // Dispatch mouse event - self.services.events.dispatchEvent(Events.Radar.BLOB_MOUSEMOVE, { + self.services.events.dispatchEvent(Events.Radar.BLOB_MOUSEOUT, { + element: hoveredElement, + datum + }); + }); + + // events on x axes + this.parent.selectAll(".x-axes .x-axis > line") + .on("mouseover", function (datum) { + // Dispatch mouse event + self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEOVER, { + element: select(this), + datum + }); + }) + .on("mousemove", function (datum) { + const hoveredElement = select(this); + + // Changhe style + hoveredElement.classed("hovered", true) + .transition(self.services.transitions.getTransition("x_axis_line_mouseover")) + .attr("stroke", "purple"); + + // Dispatch mouse event + self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEMOVE, { element: hoveredElement, datum }); + + // get the items that should be highlighted + const itemsToHighlight = self.displayDataNormalized.filter(d => d.key === datum); + + // Show tooltip + self.services.events.dispatchEvent(Events.Tooltip.SHOW, { + hoveredElement, + multidata: itemsToHighlight, + type: TooltipTypes.GRIDLINE + }); + }) + .on("click", function(datum) { + // Dispatch mouse event + self.services.events.dispatchEvent(Events.Radar.X_AXIS_CLICK, { + element: select(this), + datum + }); + }) + .on("mouseout", function(datum) { + const hoveredElement = select(this); + + // Change style + hoveredElement.classed("hovered", false) + .transition(self.services.transitions.getTransition("x_axis_line_mouseout")) + .attr("stroke", "white"); + + // Dispatch mouse event + self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEOUT, { + element: hoveredElement, + datum + }); + + // Hide tooltip + self.services.events.dispatchEvent(Events.Tooltip.HIDE, { hoveredElement }); }); } } diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index c323f462c5..c7ec39c338 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -7,6 +7,7 @@ export * from "./essentials/tooltip"; export * from "./essentials/tooltip-bar"; export * from "./essentials/tooltip-pie"; export * from "./essentials/tooltip-scatter"; +export * from "./essentials/tooltip-radar"; // GRAPHS export * from "./graphs/bar-simple"; diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index b2db9f71e0..03165cce03 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -284,6 +284,11 @@ const radarChart: RadarChartOptions = Tools.merge({}, chart, { unselected: 0.2, selected: 0.5 } + }, + tooltip: { + gridline: { + enabled: true + } } } as RadarChartOptions); diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 89e5432cdc..af91c58fa0 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -70,7 +70,11 @@ export enum Radar { BLOB_MOUSEOVER = "radar-blob-mouseover", BLOB_MOUSEMOVE = "radar-blob-mousemove", BLOB_CLICK = "radar-blolb-click", - BLOB_MOUSEOUT = "radar-blob-mouseout" + BLOB_MOUSEOUT = "radar-blob-mouseout", + X_AXIS_MOUSEOVER = "radar-x-axis-mouseover", + X_AXIS_MOUSEMOVE = "radar-x-axis-mousemove", + X_AXIS_CLICK = "radar-x-axisb-click", + X_AXIS_MOUSEOUT = "radar-x-axis-mouseout" } /** From 2cfbf971a4aa7a2c1d09c88a2f01bde9c1b4e7a7 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 28 Apr 2020 16:58:30 +0200 Subject: [PATCH 50/95] Radar: miss an import --- packages/core/src/components/graphs/radar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index f906e7b62a..a6d98c32fe 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -2,7 +2,7 @@ import { Component } from "../component"; import { DOMUtils } from "../../services"; import * as Configuration from "../../configuration"; -import { Events } from "../../interfaces"; +import { Events, TooltipTypes } from "../../interfaces"; import { Tools } from "../../tools"; import { flatMapDeep } from "lodash-es"; From 3c0a100c22c6ec3b8a37855e9555848be8de08f6 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 29 Apr 2020 07:42:39 +0200 Subject: [PATCH 51/95] Radar: use groupMapsTo --- packages/core/src/components/graphs/radar.ts | 52 +++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index a6d98c32fe..1d21a5788d 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -22,6 +22,7 @@ interface Datum { export class Radar extends Component { type = "radar"; + groupMapsTo: string; uniqKeys: string[]; uniqGroups: string[]; displayDataNormalized: Array; @@ -50,21 +51,23 @@ export class Radar extends Component { const displayData: Array = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); + const options = this.model.getOptions(); + const configuration = Configuration.options.radarChart.radar; + this.groupMapsTo = options.data.groupMapsTo; this.uniqKeys = Array.from(new Set(displayData.map(d => d.key))); - this.uniqGroups = Array.from(new Set(displayData.map(d => d.group))); - this.displayDataNormalized = normalizeFlatData(displayData, this.uniqKeys, this.uniqGroups); - this.groupedDataNormalized = normalizeGroupedData(groupedData, this.uniqKeys); + this.uniqGroups = Array.from(new Set(displayData.map(d => d[this.groupMapsTo]))); + this.displayDataNormalized = this.normalizeFlatData(displayData); + this.groupedDataNormalized = this.normalizeGroupedData(groupedData); - const options = this.model.getOptions(); - const configuration = Configuration.options.radarChart.radar; // console.log("animate:", animate); // console.log("data:", data); // console.log("displayData:", displayData); // console.log("groupedData:", groupedData); - // console.log("options:", options); - // console.log("configuration:", configuration); + console.log("options:", options); + console.log("configuration:", configuration); + console.log("groupMapsTo:", this.groupMapsTo); ///////////////////////////// // Computations @@ -262,6 +265,24 @@ export class Radar extends Component { this.addEventListeners(); } + // Given a flat array of objects, if there are missing data on key, + // creates corrisponding data with value = 0 + normalizeFlatData = (dataset: Array) => { + const completeBlankData = flatMapDeep(this.uniqKeys.map(key => { + return this.uniqGroups.map(group => ({ key, [this.groupMapsTo]: group, value: 0 })); + })); + return Tools.merge(completeBlankData, dataset); + } + + // Given a a grouped array of objects, if there are missing data on key, + // creates corrisponding data with value = 0 + normalizeGroupedData = (dataset: any) => { + return dataset.map(({ name, data }) => { + const completeBlankData = this.uniqKeys.map(k => ({ [this.groupMapsTo]: name, key: k, value: 0 })); + return { name, data: Tools.merge(completeBlankData, data) }; + }); + } + handleLegendOnHover = (event: CustomEvent) => { const { hoveredElement } = event.detail; const { opacity } = Configuration.options.radarChart.radar; @@ -468,20 +489,3 @@ function radialLabelPlacement(angleRadians: number) { return { textAnchor, dominantBaseline }; } - -/** - * If there are missing data on key, create corrisponding data with value = 0. - */ -function normalizeFlatData(dataset: Array, keys: string[], groups: string[]) { - const completeBlankData = flatMapDeep(keys.map(key => { - return groups.map(group => ({key, group, value: 0})); - })); - return Tools.merge(completeBlankData, dataset); -} - -function normalizeGroupedData(dataset: any, keys: string[]) { - return dataset.map(({name, data}) => { - const completeBlankData = keys.map(k => ({ group: name, key: k, value: 0 })); - return { name, data: Tools.merge(completeBlankData, data) }; - }); -} From aa394f8b71a181c8dc827dc1120d3e31599a9bbd Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 29 Apr 2020 08:02:17 +0200 Subject: [PATCH 52/95] Radar: number of spokes doesn't change --- packages/core/src/components/graphs/radar.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 1d21a5788d..4b7e57aabe 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -49,13 +49,14 @@ export class Radar extends Component { } // console.log("\n"); + const data: Array = this.model.getData(); const displayData: Array = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); const options = this.model.getOptions(); const configuration = Configuration.options.radarChart.radar; this.groupMapsTo = options.data.groupMapsTo; - this.uniqKeys = Array.from(new Set(displayData.map(d => d.key))); + this.uniqKeys = Array.from(new Set(data.map(d => d.key))); this.uniqGroups = Array.from(new Set(displayData.map(d => d[this.groupMapsTo]))); this.displayDataNormalized = this.normalizeFlatData(displayData); this.groupedDataNormalized = this.normalizeGroupedData(groupedData); @@ -65,9 +66,9 @@ export class Radar extends Component { // console.log("data:", data); // console.log("displayData:", displayData); // console.log("groupedData:", groupedData); - console.log("options:", options); - console.log("configuration:", configuration); - console.log("groupMapsTo:", this.groupMapsTo); + // console.log("options:", options); + // console.log("configuration:", configuration); + // console.log("groupMapsTo:", this.groupMapsTo); ///////////////////////////// // Computations From b4ab00e31c914d691ff374692d43a936cb8268fc Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 29 Apr 2020 09:30:59 +0200 Subject: [PATCH 53/95] Radar: use groupMapsTo for data mapping --- packages/core/src/components/graphs/radar.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 4b7e57aabe..d9ff5b0ccb 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -270,7 +270,7 @@ export class Radar extends Component { // creates corrisponding data with value = 0 normalizeFlatData = (dataset: Array) => { const completeBlankData = flatMapDeep(this.uniqKeys.map(key => { - return this.uniqGroups.map(group => ({ key, [this.groupMapsTo]: group, value: 0 })); + return this.uniqGroups.map(group => ({ key, [this.groupMapsTo]: group, value: null })); })); return Tools.merge(completeBlankData, dataset); } @@ -279,7 +279,7 @@ export class Radar extends Component { // creates corrisponding data with value = 0 normalizeGroupedData = (dataset: any) => { return dataset.map(({ name, data }) => { - const completeBlankData = this.uniqKeys.map(k => ({ [this.groupMapsTo]: name, key: k, value: 0 })); + const completeBlankData = this.uniqKeys.map(k => ({ [this.groupMapsTo]: name, key: k, value: null })); return { name, data: Tools.merge(completeBlankData, data) }; }); } From c22baa4e7de5efadfd7887848fbc28857c176ba4 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 29 Apr 2020 09:31:37 +0200 Subject: [PATCH 54/95] Radar: show NA when data are missing --- packages/core/src/components/essentials/tooltip-radar.ts | 2 +- packages/core/src/configuration.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/essentials/tooltip-radar.ts b/packages/core/src/components/essentials/tooltip-radar.ts index 7d3968ef12..0777be2374 100644 --- a/packages/core/src/components/essentials/tooltip-radar.ts +++ b/packages/core/src/components/essentials/tooltip-radar.ts @@ -15,7 +15,7 @@ export class TooltipRadar extends Tooltip { const userProvidedValueFormatter = Tools.getProperty(this.model.getOptions(), "tooltip", "valueFormatter"); const formattedValue = userProvidedValueFormatter ? userProvidedValueFormatter(datum[rangeIdentifier]) - : datum[rangeIdentifier].toLocaleString("en"); + : datum[rangeIdentifier]; // For the tooltip color, we always want the normal stroke color, not dynamically determined by data value. const indicatorColor = this.model.getStrokeColor(datum[groupMapsTo]); diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 03165cce03..bb6f986252 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -288,7 +288,8 @@ const radarChart: RadarChartOptions = Tools.merge({}, chart, { tooltip: { gridline: { enabled: true - } + }, + valueFormatter: value => value ? value : "NA" } } as RadarChartOptions); From 8564cecadfa277e99126d4c4456f5c94fc367629 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 29 Apr 2020 11:13:37 +0200 Subject: [PATCH 55/95] Radar: add y labels --- packages/core/src/components/graphs/radar.ts | 27 +++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index d9ff5b0ccb..2aa55e5126 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -9,7 +9,7 @@ import { flatMapDeep } from "lodash-es"; // D3 Imports import { select } from "d3-selection"; import { scaleBand, scaleLinear } from "d3-scale"; -import { max } from "d3-array"; +import { max, extent } from "d3-array"; import { lineRadial, curveLinearClosed } from "d3-shape"; const DEBUG = false; @@ -175,8 +175,23 @@ export class Radar extends Component { .attr("opacity", 1) .attr("stroke", "#dcdcdc"); + // y labels + const yLabelPadding = 5; + const yLabels = DOMUtils.appendOrSelect(svg, "g.y-labels"); + const yLabelUpdate = yLabels.selectAll("text").data(extent(yTicks)); + yLabelUpdate.join( + enter => enter.append("text"), + update => update, + exit => exit.remove() + ) + .text(tick => tick) + .attr("x", tick => polarCoords("London", yScale(tick), cx + yLabelPadding, cy).x) + .attr("y", tick => polarCoords("London", yScale(tick), cy, cy).y) + .style("text-anchor", "start") + .style("dominant-baseline", "middle"); + // x axes - const labelPadding = 10; + const xLabelPadding = 10; const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); const xAxisUpdate = xAxes.selectAll("g.x-axis").data(this.uniqKeys); xAxisUpdate.join( @@ -199,8 +214,8 @@ export class Radar extends Component { .call(selection => selection .append("text") .text(d => d) - .attr("x", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).x) - .attr("y", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).y) + .attr("x", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).x) + .attr("y", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).y) .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline) .attr("opacity", 0) @@ -226,8 +241,8 @@ export class Radar extends Component { .call(selection => selection .select("text") .text(d => d) - .attr("x", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).x) - .attr("y", key => polarCoords(key, yScale.range()[1] + labelPadding, cx, cy).y) + .attr("x", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).x) + .attr("y", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).y) .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline) .attr("opacity", 0) From 875044f88c1c3851b7cba6ca4e8d6fba38cff331 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 29 Apr 2020 11:17:07 +0200 Subject: [PATCH 56/95] Radar: remove events on blobs --- packages/core/src/components/graphs/radar.ts | 45 -------------------- packages/core/src/interfaces/events.ts | 4 -- 2 files changed, 49 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 2aa55e5126..5d18b3440f 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -332,51 +332,6 @@ export class Radar extends Component { addEventListeners() { const self = this; - // events on blobs - this.parent.selectAll(".blobs .blob > path") - .on("mouseover", function (datum) { - // Dispatch mouse event - self.services.events.dispatchEvent(Events.Radar.BLOB_MOUSEOVER, { - element: select(this), - datum - }); - }) - .on("mousemove", function (datum) { - const hoveredElement = select(this); - - // Changhe style - hoveredElement.classed("hovered", true) - .transition(self.services.transitions.getTransition("blob_path_mouseover")) - .style("fill-opacity", 0.8); - - // Dispatch mouse event - self.services.events.dispatchEvent(Events.Radar.BLOB_MOUSEMOVE, { - element: hoveredElement, - datum - }); - }) - .on("click", function(datum) { - // Dispatch mouse event - self.services.events.dispatchEvent(Events.Radar.BLOB_CLICK, { - element: select(this), - datum - }); - }) - .on("mouseout", function(datum) { - const hoveredElement = select(this); - - // Change style - hoveredElement.classed("hovered", false) - .transition(self.services.transitions.getTransition("blob_path_mouseout")) - .style("fill-opacity", 0.5); - - // Dispatch mouse event - self.services.events.dispatchEvent(Events.Radar.BLOB_MOUSEOUT, { - element: hoveredElement, - datum - }); - }); - // events on x axes this.parent.selectAll(".x-axes .x-axis > line") .on("mouseover", function (datum) { diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index af91c58fa0..7fd4b92ad0 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -67,10 +67,6 @@ export enum Line { * enum of all radar graph events */ export enum Radar { - BLOB_MOUSEOVER = "radar-blob-mouseover", - BLOB_MOUSEMOVE = "radar-blob-mousemove", - BLOB_CLICK = "radar-blolb-click", - BLOB_MOUSEOUT = "radar-blob-mouseout", X_AXIS_MOUSEOVER = "radar-x-axis-mouseover", X_AXIS_MOUSEMOVE = "radar-x-axis-mousemove", X_AXIS_CLICK = "radar-x-axisb-click", From 4ec64415feefcc4b58fee9e7c7810b7ddd6cf98c Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 29 Apr 2020 17:26:22 +0200 Subject: [PATCH 57/95] Radar: left align --- packages/core/src/components/graphs/radar.ts | 30 +++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 5d18b3440f..a69f40da40 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -61,7 +61,6 @@ export class Radar extends Component { this.displayDataNormalized = this.normalizeFlatData(displayData); this.groupedDataNormalized = this.normalizeGroupedData(groupedData); - // console.log("animate:", animate); // console.log("data:", data); // console.log("displayData:", displayData); @@ -74,15 +73,13 @@ export class Radar extends Component { // Computations ///////////////////////////// - // center - const cx = width / 2; - const cy = height / 2; - const fontSize = 30; const margin = 2 * fontSize; const size = Math.min(width, height); const diameter = size - margin; const radius = diameter / 2; + const xLabelPadding = 10; + const yLabelPadding = 5; // console.log("radius:", radius); // given a key, return the corrisponding angle in radiants @@ -117,6 +114,26 @@ export class Radar extends Component { return { x, y }; }; + const distanceBetweenPointOnCircAndVerticalDiameter = (a: number, r: number) => { + const angle = a - Math.PI / 2; + return r * Math.sin(angle - Math.PI / 2); + }; + + const leftPadding = max(this.uniqKeys.map(key => { + const fakeTick = DOMUtils.appendOrSelect(svg, `g.fake-tick`); + const fakeTickText = DOMUtils.appendOrSelect(fakeTick, `text`).text(key); + const tickWidth = DOMUtils.getSVGElementSize(fakeTickText.node(), { useBBox: true }).width; + fakeTick.remove(); + const angle = xScale(key); + const distPointDiam = distanceBetweenPointOnCircAndVerticalDiameter(angle, radius); + const span = tickWidth + distPointDiam; + return span; + })); + + // center + const cx = leftPadding + xLabelPadding; + const cy = height / 2; + ///////////////////////////// // Draw ///////////////////////////// @@ -176,7 +193,6 @@ export class Radar extends Component { .attr("stroke", "#dcdcdc"); // y labels - const yLabelPadding = 5; const yLabels = DOMUtils.appendOrSelect(svg, "g.y-labels"); const yLabelUpdate = yLabels.selectAll("text").data(extent(yTicks)); yLabelUpdate.join( @@ -191,7 +207,6 @@ export class Radar extends Component { .style("dominant-baseline", "middle"); // x axes - const xLabelPadding = 10; const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); const xAxisUpdate = xAxes.selectAll("g.x-axis").data(this.uniqKeys); xAxisUpdate.join( @@ -213,6 +228,7 @@ export class Radar extends Component { ) .call(selection => selection .append("text") + // .text(d => `${d} (${radToDeg(xScale(d))}°, ${Math.round(distanceBetweenPointOnCircAndVerticalDiameter(xScale(d), radius))})`) .text(d => d) .attr("x", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).x) .attr("y", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).y) From c86ef0520ac5bcfa92ccacd795335d08431a6b7f Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 30 Apr 2020 18:26:09 +0200 Subject: [PATCH 58/95] Radar: create rectangles over x axis --- packages/core/src/components/graphs/radar.ts | 57 ++++++++++++++------ 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index a69f40da40..0694a58fa9 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -19,6 +19,10 @@ interface Datum { key: string; value: number; } +interface Point { + x: number; + y: number; +} export class Radar extends Component { type = "radar"; @@ -217,8 +221,8 @@ export class Radar extends Component { .attr("stroke", "#dcdcdc") .attr("x1", key => polarCoords(key, 0, cx, cy).x) .attr("y1", key => polarCoords(key, 0, cx, cy).y) - .attr("x2", key => polarCoords(key, 1, cx, cy).x) - .attr("y2", key => polarCoords(key, 1, cx, cy).y) + .attr("x2", key => polarCoords(key, 0, cx, cy).x) + .attr("y2", key => polarCoords(key, 0, cx, cy).y) .transition().duration(500) // .transition(this.services.transitions.getTransition("x-axis-line-enter", animate)) .attr("x1", key => polarCoords(key, yScale.range()[0], cx, cy).x) @@ -229,7 +233,7 @@ export class Radar extends Component { .call(selection => selection .append("text") // .text(d => `${d} (${radToDeg(xScale(d))}°, ${Math.round(distanceBetweenPointOnCircAndVerticalDiameter(xScale(d), radius))})`) - .text(d => d) + .text(key => key) .attr("x", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).x) .attr("y", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).y) .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) @@ -256,7 +260,7 @@ export class Radar extends Component { ) .call(selection => selection .select("text") - .text(d => d) + .text(key => key) .attr("x", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).x) .attr("y", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).y) .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) @@ -269,7 +273,7 @@ export class Radar extends Component { exit => exit.remove() ); - + // blobs const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); const blobUpdate = blobs.selectAll("g.blob").data(this.groupedDataNormalized, group => group.name); @@ -293,6 +297,24 @@ export class Radar extends Component { .style("fill-opacity", configuration.opacity.selected) ); + // rectangles + const xAxesRect = DOMUtils.appendOrSelect(svg, "g.x-axes-rect"); + const xAxisRectUpdate = xAxesRect.selectAll("g.x-axis-rect").data(this.uniqKeys); + xAxisRectUpdate.join( + enter => enter + .append("rect") + .attr("class", "axis-rect") + .attr("x", cx) + .attr("y", cy - 50/2) + .attr("width", radius) + .attr("height", 50) + .attr("fill", "red") + .style("fill-opacity", 0) + .attr("transform", key => `rotate(${radToDeg(xScale(key)) - 90} ${cx} ${cy})`), + update => update, + exit => exit.remove() + ); + // Add event listeners this.addEventListeners(); } @@ -348,8 +370,9 @@ export class Radar extends Component { addEventListeners() { const self = this; - // events on x axes - this.parent.selectAll(".x-axes .x-axis > line") + // events on x axes rects + // this.parent.selectAll(".x-axes .x-axis > line") + this.parent.selectAll(".x-axes-rect > rect") .on("mouseover", function (datum) { // Dispatch mouse event self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEOVER, { @@ -360,10 +383,10 @@ export class Radar extends Component { .on("mousemove", function (datum) { const hoveredElement = select(this); - // Changhe style - hoveredElement.classed("hovered", true) - .transition(self.services.transitions.getTransition("x_axis_line_mouseover")) - .attr("stroke", "purple"); + // // Changhe style + // hoveredElement.classed("hovered", true) + // .transition(self.services.transitions.getTransition("x_axis_line_mouseover")) + // .attr("stroke", "purple"); // Dispatch mouse event self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEMOVE, { @@ -391,10 +414,10 @@ export class Radar extends Component { .on("mouseout", function(datum) { const hoveredElement = select(this); - // Change style - hoveredElement.classed("hovered", false) - .transition(self.services.transitions.getTransition("x_axis_line_mouseout")) - .attr("stroke", "white"); + // // Change style + // hoveredElement.classed("hovered", false) + // .transition(self.services.transitions.getTransition("x_axis_line_mouseout")) + // .attr("stroke", "white"); // Dispatch mouse event self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEOUT, { @@ -412,6 +435,10 @@ function radToDeg(rad: number) { return rad * (180 / Math.PI); } +function degToRad(deg) { + return deg * (Math.PI / 180); +} + function isInRange(x: number, minMax: number[]): boolean { return x >= minMax[0] && x <= minMax[1]; } From 0640d6900893a89b949c189fcc2c423b32314021 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 30 Apr 2020 19:32:20 +0200 Subject: [PATCH 59/95] Radar: make x axis dashed on mouse over --- packages/core/src/components/graphs/radar.ts | 28 +++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 0694a58fa9..f41e1eb1be 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -218,7 +218,9 @@ export class Radar extends Component { .attr("class", "x-axis") .call(selection => selection .append("line") + .attr("class", key => `line-${removeSpaces(key)}`) .attr("stroke", "#dcdcdc") + .attr("stroke-dasharray", "0") .attr("x1", key => polarCoords(key, 0, cx, cy).x) .attr("y1", key => polarCoords(key, 0, cx, cy).y) .attr("x2", key => polarCoords(key, 0, cx, cy).x) @@ -273,7 +275,7 @@ export class Radar extends Component { exit => exit.remove() ); - + // blobs const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); const blobUpdate = blobs.selectAll("g.blob").data(this.groupedDataNormalized, group => group.name); @@ -309,7 +311,7 @@ export class Radar extends Component { .attr("width", radius) .attr("height", 50) .attr("fill", "red") - .style("fill-opacity", 0) + .style("fill-opacity", DEBUG ? 0.1 : 0) .attr("transform", key => `rotate(${radToDeg(xScale(key)) - 90} ${cx} ${cy})`), update => update, exit => exit.remove() @@ -382,11 +384,12 @@ export class Radar extends Component { }) .on("mousemove", function (datum) { const hoveredElement = select(this); + const axisLine = self.parent.select(`.x-axes .x-axis .line-${removeSpaces(datum)}`); - // // Changhe style - // hoveredElement.classed("hovered", true) - // .transition(self.services.transitions.getTransition("x_axis_line_mouseover")) - // .attr("stroke", "purple"); + // Change style + axisLine.classed("hovered", true) + .transition(self.services.transitions.getTransition("x_axis_line_mouseover")) + .attr("stroke-dasharray", "4 4"); // Dispatch mouse event self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEMOVE, { @@ -413,11 +416,12 @@ export class Radar extends Component { }) .on("mouseout", function(datum) { const hoveredElement = select(this); + const axisLine = self.parent.select(`.x-axes .x-axis .line-${removeSpaces(datum)}`); - // // Change style - // hoveredElement.classed("hovered", false) - // .transition(self.services.transitions.getTransition("x_axis_line_mouseout")) - // .attr("stroke", "white"); + // Change style + axisLine.classed("hovered", false) + .transition(self.services.transitions.getTransition("x_axis_line_mouseout")) + .attr("stroke-dasharray", "0"); // Dispatch mouse event self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEOUT, { @@ -503,3 +507,7 @@ function radialLabelPlacement(angleRadians: number) { return { textAnchor, dominantBaseline }; } + +function removeSpaces(str: string): string { + return str.replace(/\s/g, ""); +} From a8e4572cc07339372d6c9a741f50706312cf568d Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Fri, 1 May 2020 10:57:18 +0200 Subject: [PATCH 60/95] Radar: add dots --- packages/core/src/components/graphs/radar.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index f41e1eb1be..104f4aa6d8 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -317,6 +317,21 @@ export class Radar extends Component { exit => exit.remove() ); + // circles + const dots = DOMUtils.appendOrSelect(svg, "g.dots"); + const dotsUpdate = dots.selectAll("g.dot").data(this.displayDataNormalized); + dotsUpdate.join( + enter => enter + .append("circle") + .attr("class", "dot") + .attr("cx", d => polarCoords(d.key, yScale(d.value), cx, cy).x) + .attr("cy", d => polarCoords(d.key, yScale(d.value), cx, cy).y) + .attr("r", 5) + .attr("fill", d => colorScale(d[this.groupMapsTo])), + update => update, + exit => exit.remove() + ); + // Add event listeners this.addEventListeners(); } From 24fed6fc2f2ebc1cb3a32267a471b1a6a97937a4 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Fri, 1 May 2020 11:11:52 +0200 Subject: [PATCH 61/95] Radar: move utility functions in another file --- packages/core/src/components/graphs/radar.ts | 74 +------------------- packages/core/src/services/angle-utils.ts | 72 +++++++++++++++++++ 2 files changed, 73 insertions(+), 73 deletions(-) create mode 100644 packages/core/src/services/angle-utils.ts diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 104f4aa6d8..976a8f6e50 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -5,6 +5,7 @@ import * as Configuration from "../../configuration"; import { Events, TooltipTypes } from "../../interfaces"; import { Tools } from "../../tools"; import { flatMapDeep } from "lodash-es"; +import { radialLabelPlacement, radToDeg, degToRad } from "../../services/angle-utils"; // D3 Imports import { select } from "d3-selection"; @@ -450,79 +451,6 @@ export class Radar extends Component { } } -function radToDeg(rad: number) { - return rad * (180 / Math.PI); -} - -function degToRad(deg) { - return deg * (Math.PI / 180); -} - -function isInRange(x: number, minMax: number[]): boolean { - return x >= minMax[0] && x <= minMax[1]; -} - -function radialLabelPlacement(angleRadians: number) { - const angle = radToDeg(angleRadians) % 360; // rounded angle - - let textAnchor: "start" | "middle" | "end" = "middle"; // *___ __*__ ___* - let dominantBaseline: "baseline" | "middle" | "hanging" = "middle"; // __* --*-- --. - - let quadrant = 0; - - if (isInRange(angle, [0, 90])) { - quadrant = 0; - } else if (isInRange(angle, [90, 180])) { - quadrant = 1; - } else if (isInRange(angle, [180, 270])) { - quadrant = 2; - } else if (isInRange(angle, [270, 360])) { - quadrant = 3; - } - - if (quadrant === 0) { - textAnchor = "start"; - dominantBaseline = "baseline"; - } else if (quadrant === 1) { - textAnchor = "start"; - dominantBaseline = "hanging"; - } else if (quadrant === 2) { - textAnchor = "end"; - dominantBaseline = "hanging"; - } else if (quadrant === 3) { - textAnchor = "end"; - dominantBaseline = "baseline"; - } - - let edge = null; - - if (isInRange(angle, [0, 10]) || isInRange(angle, [350, 0])) { - edge = 0; - } else if (isInRange(angle, [80, 100])) { - edge = 1; - } else if (isInRange(angle, [170, 190])) { - edge = 2; - } else if (isInRange(angle, [260, 280])) { - edge = 3; - } - - if (edge === 0) { - textAnchor = "middle"; - dominantBaseline = "baseline"; - } else if (edge === 1) { - textAnchor = "start"; - dominantBaseline = "middle"; - } else if (edge === 2) { - textAnchor = "middle"; - dominantBaseline = "hanging"; - } else if (edge === 3) { - textAnchor = "end"; - dominantBaseline = "middle"; - } - - return { textAnchor, dominantBaseline }; -} - function removeSpaces(str: string): string { return str.replace(/\s/g, ""); } diff --git a/packages/core/src/services/angle-utils.ts b/packages/core/src/services/angle-utils.ts new file mode 100644 index 0000000000..e62ca719e9 --- /dev/null +++ b/packages/core/src/services/angle-utils.ts @@ -0,0 +1,72 @@ +export function radialLabelPlacement(angleRadians: number) { + const angle = radToDeg(angleRadians) % 360; // rounded angle + + let textAnchor: "start" | "middle" | "end" = "middle"; // *___ __*__ ___* + let dominantBaseline: "baseline" | "middle" | "hanging" = "middle"; // __* --*-- --. + + let quadrant = 0; + + if (isInRange(angle, [0, 90])) { + quadrant = 0; + } else if (isInRange(angle, [90, 180])) { + quadrant = 1; + } else if (isInRange(angle, [180, 270])) { + quadrant = 2; + } else if (isInRange(angle, [270, 360])) { + quadrant = 3; + } + + if (quadrant === 0) { + textAnchor = "start"; + dominantBaseline = "baseline"; + } else if (quadrant === 1) { + textAnchor = "start"; + dominantBaseline = "hanging"; + } else if (quadrant === 2) { + textAnchor = "end"; + dominantBaseline = "hanging"; + } else if (quadrant === 3) { + textAnchor = "end"; + dominantBaseline = "baseline"; + } + + let edge = null; + + if (isInRange(angle, [0, 10]) || isInRange(angle, [350, 0])) { + edge = 0; + } else if (isInRange(angle, [80, 100])) { + edge = 1; + } else if (isInRange(angle, [170, 190])) { + edge = 2; + } else if (isInRange(angle, [260, 280])) { + edge = 3; + } + + if (edge === 0) { + textAnchor = "middle"; + dominantBaseline = "baseline"; + } else if (edge === 1) { + textAnchor = "start"; + dominantBaseline = "middle"; + } else if (edge === 2) { + textAnchor = "middle"; + dominantBaseline = "hanging"; + } else if (edge === 3) { + textAnchor = "end"; + dominantBaseline = "middle"; + } + + return { textAnchor, dominantBaseline }; +} + +function isInRange(x: number, minMax: number[]): boolean { + return x >= minMax[0] && x <= minMax[1]; +} + +export function radToDeg(rad: number): number { + return rad * (180 / Math.PI); +} + +export function degToRad(deg: number): number { + return deg * (Math.PI / 180); +} From 542021d6f45ce8faff12e9fa94f0e1946604a240 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Sat, 2 May 2020 14:23:54 +0200 Subject: [PATCH 62/95] Radar: fix radial label placement for negative angles --- packages/core/src/services/angle-utils.ts | 25 ++++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/core/src/services/angle-utils.ts b/packages/core/src/services/angle-utils.ts index e62ca719e9..6c7174ad79 100644 --- a/packages/core/src/services/angle-utils.ts +++ b/packages/core/src/services/angle-utils.ts @@ -1,5 +1,10 @@ +// compute modulo values correctly also with a negative number +function mod(n: number, m: number): number { + return ((n % m) + m) % m; +} + export function radialLabelPlacement(angleRadians: number) { - const angle = radToDeg(angleRadians) % 360; // rounded angle + const angle = mod(radToDeg(angleRadians), 360); let textAnchor: "start" | "middle" | "end" = "middle"; // *___ __*__ ___* let dominantBaseline: "baseline" | "middle" | "hanging" = "middle"; // __* --*-- --. @@ -18,15 +23,15 @@ export function radialLabelPlacement(angleRadians: number) { if (quadrant === 0) { textAnchor = "start"; - dominantBaseline = "baseline"; + dominantBaseline = "hanging"; } else if (quadrant === 1) { - textAnchor = "start"; + textAnchor = "end"; dominantBaseline = "hanging"; } else if (quadrant === 2) { textAnchor = "end"; - dominantBaseline = "hanging"; + dominantBaseline = "baseline"; } else if (quadrant === 3) { - textAnchor = "end"; + textAnchor = "start"; dominantBaseline = "baseline"; } @@ -43,17 +48,17 @@ export function radialLabelPlacement(angleRadians: number) { } if (edge === 0) { - textAnchor = "middle"; - dominantBaseline = "baseline"; - } else if (edge === 1) { textAnchor = "start"; dominantBaseline = "middle"; - } else if (edge === 2) { + } else if (edge === 1) { textAnchor = "middle"; dominantBaseline = "hanging"; - } else if (edge === 3) { + } else if (edge === 2) { textAnchor = "end"; dominantBaseline = "middle"; + } else if (edge === 3) { + textAnchor = "middle"; + dominantBaseline = "baseline"; } return { textAnchor, dominantBaseline }; From 96ff06c5e1efe68684225d3e8c5d2eb6ef2b2d3e Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Sat, 2 May 2020 16:34:50 +0200 Subject: [PATCH 63/95] Radar: refactor --- packages/core/src/components/graphs/radar.ts | 406 ++++++++----------- packages/core/src/configuration.ts | 6 + packages/core/src/services/angle-utils.ts | 27 +- packages/core/src/styles/graphs/_radar.scss | 37 ++ packages/core/src/styles/graphs/index.scss | 1 + 5 files changed, 246 insertions(+), 231 deletions(-) create mode 100644 packages/core/src/styles/graphs/_radar.scss diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 976a8f6e50..061a9d2c62 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -2,15 +2,23 @@ import { Component } from "../component"; import { DOMUtils } from "../../services"; import * as Configuration from "../../configuration"; -import { Events, TooltipTypes } from "../../interfaces"; +import { Events, TooltipTypes, Roles } from "../../interfaces"; import { Tools } from "../../tools"; -import { flatMapDeep } from "lodash-es"; -import { radialLabelPlacement, radToDeg, degToRad } from "../../services/angle-utils"; +import { flatMapDeep, kebabCase } from "lodash-es"; +import { + Point, + Angle, + radialLabelPlacement, + radToDeg, + degToRad, + polarToCartesianCoords, + distanceBetweenPointOnCircAndVerticalDiameter +} from "../../services/angle-utils"; // D3 Imports import { select } from "d3-selection"; import { scaleBand, scaleLinear } from "d3-scale"; -import { max, extent } from "d3-array"; +import { min, max, extent } from "d3-array"; import { lineRadial, curveLinearClosed } from "d3-shape"; const DEBUG = false; @@ -20,10 +28,6 @@ interface Datum { key: string; value: number; } -interface Point { - x: number; - y: number; -} export class Radar extends Component { type = "radar"; @@ -47,12 +51,24 @@ export class Radar extends Component { ///////////////////////////// // Containers ///////////////////////////// - const svg = this.parent; - const { width, height } = DOMUtils.getSVGElementSize(svg, { useAttrs: true }); + const svg = this.getContainerSVG(); + const { width, height } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); if (!width || !height) { return; } // console.log("\n"); + // console.log({ width, height }); + + const fontSize = 30; // TODO: mmm, probably there is a better way to do that + const margin = 2 * fontSize; + const size = Math.min(width, height); + const diameter = size - margin; + const radius = diameter / 2; + const xLabelPadding = 10; + const yLabelPadding = 5; + const yTicksNumber = 4; // TODO: probably there are a default constant for that value + const minRange = 10; + const xAxisRectHeight = 50; const data: Array = this.model.getData(); const displayData: Array = this.model.getDisplayData(); @@ -78,127 +94,102 @@ export class Radar extends Component { // Computations ///////////////////////////// - const fontSize = 30; - const margin = 2 * fontSize; - const size = Math.min(width, height); - const diameter = size - margin; - const radius = diameter / 2; - const xLabelPadding = 10; - const yLabelPadding = 5; - // console.log("radius:", radius); - // given a key, return the corrisponding angle in radiants - const xScale = scaleBand() + // rotated by -PI/2 because we want angle 0° at -y (12 o’clock) + const xScale = scaleBand() .domain(this.displayDataNormalized.map(d => d.key)) - .range([0, 2 * Math.PI]); + .range([0, 2 * Math.PI].map(a => a - Math.PI / 2) as [Angle, Angle]); // console.log(`xScale [${xScale.domain()}] -> [${xScale.range()}]`); - const ticksNumber = 5; - const minRange = 10; const yScale = scaleLinear() .domain([0, max(this.displayDataNormalized.map(d => d.value))]) .range([minRange, radius]) - .nice(ticksNumber); - const yTicks = yScale.ticks(ticksNumber); + .nice(yTicksNumber); + const yTicks = yScale.ticks(yTicksNumber); // console.log(`yScale [${yScale.domain()}] -> [${yScale.range()}]`); - const colorScale = (key: string): string => this.model.getFillColor(key); + const colorScale = (group: string): string => this.model.getFillColor(group); + // constructs a new radial line generator + // the angle accessor returns the angle in radians with 0° at -y (12 o’clock) + // so map back the angle const radialLineGenerator = lineRadial() - .angle(d => xScale(d.key)) - .radius(d => yScale(d.value)) - .curve(curveLinearClosed); - - // given the key (= value corrisponding to a an x axis), a radius r and translation values - // return the coordinates of the corrisponding point stayng on the x axis with radius r - const polarCoords = (key: string, r: number, tx = 0, ty = 0) => { - const angle = xScale(key) - Math.PI / 2; - // translate by tx and ty - const x = r * Math.cos(angle) + tx; - const y = r * Math.sin(angle) + ty; - return { x, y }; - }; + .angle(d => xScale(d.key) + Math.PI / 2) + .radius(d => yScale(d.value)) + .curve(curveLinearClosed); + + // compute the space that each x label needs + const horizSpaceNeededByEachXLabel = this.uniqKeys.map(key => { + // append temporarily the label to get the exact space that it occupies + const tmpTick = DOMUtils.appendOrSelect(svg, `g.tmp-tick`); + const tmpTickText = DOMUtils.appendOrSelect(tmpTick, `text`).text(key); + const tickWidth = DOMUtils.getSVGElementSize(tmpTickText.node(), { useBBox: true }).width; + tmpTick.remove(); + // compute the distance between the point that the label rapresents and the vertical diameter + const distPointDiam = distanceBetweenPointOnCircAndVerticalDiameter(xScale(key), radius); + // the space each label occupies is the sum of these two values + return tickWidth + distPointDiam; + }); + const leftPadding = max(horizSpaceNeededByEachXLabel); - const distanceBetweenPointOnCircAndVerticalDiameter = (a: number, r: number) => { - const angle = a - Math.PI / 2; - return r * Math.sin(angle - Math.PI / 2); + // center coordinates + const c: Point = { + x: leftPadding + xLabelPadding, + y: height / 2 }; - const leftPadding = max(this.uniqKeys.map(key => { - const fakeTick = DOMUtils.appendOrSelect(svg, `g.fake-tick`); - const fakeTickText = DOMUtils.appendOrSelect(fakeTick, `text`).text(key); - const tickWidth = DOMUtils.getSVGElementSize(fakeTickText.node(), { useBBox: true }).width; - fakeTick.remove(); - const angle = xScale(key); - const distPointDiam = distanceBetweenPointOnCircAndVerticalDiameter(angle, radius); - const span = tickWidth + distPointDiam; - return span; - })); - - // center - const cx = leftPadding + xLabelPadding; - const cy = height / 2; - ///////////////////////////// // Draw ///////////////////////////// - /////////////// - const debugContainer = DOMUtils.appendOrSelect(svg, "g.debug"); - - // backdrop - const backdropRect = DOMUtils.appendOrSelect(debugContainer, "rect.radar-backdrop") - .attr("width", "100%") - .attr("height", "100%") - .attr("stroke", "red") - .attr("fill", "none"); - - // center - const center = DOMUtils.appendOrSelect(debugContainer, "circle.center") - .attr("cx", cx) - .attr("cy", cy) - .attr("r", 2) - .attr("fill", "gold"); - - // circumferences - const circumferences = DOMUtils.appendOrSelect(debugContainer, "g.circumferences"); - const circumferencesUpdate = circumferences.selectAll("circle").data(yTicks); - circumferencesUpdate - .enter() - .append("circle") - .merge(circumferencesUpdate) - .attr("cx", cx) - .attr("cy", cy) - .attr("r", d => yScale(d)) - .attr("fill", "none") - .attr("stroke", "red"); - circumferencesUpdate.exit().remove(); - - svg.select("g.debug").attr("opacity", DEBUG ? 0.5 : 0); + if (DEBUG) { + const debugContainer = DOMUtils.appendOrSelect(svg, "g.debug"); + const backdropRect = DOMUtils.appendOrSelect(debugContainer, "rect.backdrop") + .attr("width", "100%") + .attr("height", "100%") + .attr("stroke", "gold") + .attr("opacity", 0.2) + .attr("fill", "none"); + const center = DOMUtils.appendOrSelect(debugContainer, "circle.center") + .attr("cx", c.x) + .attr("cy", c.y) + .attr("r", 2) + .attr("opacity", 0.2) + .attr("fill", "gold"); + const circumferences = DOMUtils.appendOrSelect(debugContainer, "g.circumferences"); + const circumferencesUpdate = circumferences.selectAll("circle").data(yTicks); + circumferencesUpdate + .enter() + .append("circle") + .merge(circumferencesUpdate) + .attr("cx", c.x) + .attr("cy", c.y) + .attr("r", tick => yScale(tick)) + .attr("fill", "none") + .attr("opacity", 0.2) + .attr("stroke", "gold"); + circumferencesUpdate.exit().remove(); + } /////////////// // y axes - const yAxes = DOMUtils.appendOrSelect(svg, "g.y-axes"); - const yAxisUpdate = yAxes.selectAll("path").data(yTicks); + const yAxes = DOMUtils.appendOrSelect(svg, "g.y-axes").attr("role", Roles.GROUP); + const yAxisUpdate = yAxes.selectAll("path").data(yTicks, tick => tick); yAxisUpdate.join( - enter => enter.append("path"), + enter => enter.append("path").attr("role", Roles.GRAPHICS_SYMBOL), update => update, exit => exit.remove() ) - .attr("transform", `translate(${cx}, ${cy})`) - .attr("opacity", 0) - .transition(this.services.transitions.getTransition("y-axis-update-enter", animate)) - .attr("d", tickValue => { - const xAxesKeys = xScale.domain(); - const points = xAxesKeys.map(key => ({ key, value: tickValue })); - return radialLineGenerator(points); + .attr("transform", `translate(${c.x}, ${c.y})`) + .attr("d", tick => { + // for each tick, create array of data corrisponding to the points composing the shape + const yShapeData = this.uniqKeys.map(key => ({ key, value: tick })); + return radialLineGenerator(yShapeData); }) - .attr("fill", "none") - .attr("opacity", 1) - .attr("stroke", "#dcdcdc"); + .attr("fill", "none"); - // y labels - const yLabels = DOMUtils.appendOrSelect(svg, "g.y-labels"); + // y labels (show only the min and the max labels) + const yLabels = DOMUtils.appendOrSelect(svg, "g.y-labels").attr("role", Roles.GROUP); const yLabelUpdate = yLabels.selectAll("text").data(extent(yTicks)); yLabelUpdate.join( enter => enter.append("text"), @@ -206,132 +197,85 @@ export class Radar extends Component { exit => exit.remove() ) .text(tick => tick) - .attr("x", tick => polarCoords("London", yScale(tick), cx + yLabelPadding, cy).x) - .attr("y", tick => polarCoords("London", yScale(tick), cy, cy).y) + .attr("x", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).x + yLabelPadding) + .attr("y", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).y) .style("text-anchor", "start") .style("dominant-baseline", "middle"); // x axes - const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes"); - const xAxisUpdate = xAxes.selectAll("g.x-axis").data(this.uniqKeys); + const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes").attr("role", Roles.GROUP); + const xAxisUpdate = xAxes.selectAll("line").data(this.uniqKeys, key => key); xAxisUpdate.join( - enter => enter.append("g") - .attr("class", "x-axis") - .call(selection => selection - .append("line") - .attr("class", key => `line-${removeSpaces(key)}`) - .attr("stroke", "#dcdcdc") - .attr("stroke-dasharray", "0") - .attr("x1", key => polarCoords(key, 0, cx, cy).x) - .attr("y1", key => polarCoords(key, 0, cx, cy).y) - .attr("x2", key => polarCoords(key, 0, cx, cy).x) - .attr("y2", key => polarCoords(key, 0, cx, cy).y) - .transition().duration(500) - // .transition(this.services.transitions.getTransition("x-axis-line-enter", animate)) - .attr("x1", key => polarCoords(key, yScale.range()[0], cx, cy).x) - .attr("y1", key => polarCoords(key, yScale.range()[0], cx, cy).y) - .attr("x2", key => polarCoords(key, yScale.range()[1], cx, cy).x) - .attr("y2", key => polarCoords(key, yScale.range()[1], cx, cy).y) - ) - .call(selection => selection - .append("text") - // .text(d => `${d} (${radToDeg(xScale(d))}°, ${Math.round(distanceBetweenPointOnCircAndVerticalDiameter(xScale(d), radius))})`) - .text(key => key) - .attr("x", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).x) - .attr("y", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).y) - .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) - .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline) - .attr("opacity", 0) - .transition().duration(500) - // .transition(this.services.transitions.getTransition("x-axis-text-enter", animate)) - .attr("opacity", 1) - ), - - update => update - .call(selection => selection - .select("line") - .attr("x1", key => polarCoords(key, 0, cx, cy).x) - .attr("y1", key => polarCoords(key, 0, cx, cy).y) - .attr("x2", key => polarCoords(key, 1, cx, cy).x) - .attr("y2", key => polarCoords(key, 1, cx, cy).y) - .transition().duration(500) - // .transition(this.services.transitions.getTransition("x-axis-line-update", animate)) - .attr("x1", key => polarCoords(key, yScale.range()[0], cx, cy).x) - .attr("y1", key => polarCoords(key, yScale.range()[0], cx, cy).y) - .attr("x2", key => polarCoords(key, yScale.range()[1], cx, cy).x) - .attr("y2", key => polarCoords(key, yScale.range()[1], cx, cy).y) - ) - .call(selection => selection - .select("text") - .text(key => key) - .attr("x", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).x) - .attr("y", key => polarCoords(key, yScale.range()[1] + xLabelPadding, cx, cy).y) - .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) - .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline) - .attr("opacity", 0) - .transition().duration(500) - // .transition(this.services.transitions.getTransition("x-axis-text-update", animate)) - .attr("opacity", 1) - ), - + enter => enter.append("line").attr("role", Roles.GRAPHICS_SYMBOL), + update => update, + exit => exit.remove() + ) + .attr("class", key => `x-axis-${kebabCase(key)}`) // replace spaces with - + .attr("stroke-dasharray", "0") + .attr("x1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).x) + .attr("y1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).y) + .attr("x2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).x) + .attr("y2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).y); + + // x labels + const xLabels = DOMUtils.appendOrSelect(svg, "g.x-labels").attr("role", Roles.GROUP); + const xLabelUpdate = xLabels.selectAll("text").data(this.uniqKeys); + xLabelUpdate.join( + enter => enter.append("text"), + update => update, exit => exit.remove() - ); + ) + .text(key => DEBUG ? `${key} ${radToDeg(xScale(key))}° <-- ${radToDeg(xScale(key) + Math.PI / 2)}°` : key) + .attr("x", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).x) + .attr("y", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).y) + .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) + .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline); // blobs - const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("transform", `translate(${cx}, ${cy})`); - const blobUpdate = blobs.selectAll("g.blob").data(this.groupedDataNormalized, group => group.name); + const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("role", Roles.GROUP); + const blobUpdate = blobs.selectAll("path").data(this.groupedDataNormalized, group => group.name); blobUpdate.join( - enter => enter.append("g") - .attr("class", "blob") - .call(selection => selection - .append("path") - .attr("class", group => `blob-area-${group.name}`) - ), + enter => enter.append("path").attr("role", Roles.GRAPHICS_SYMBOL), update => update, exit => exit.remove() ) - .call(selection => selection - .select("path") - .attr("d", group => radialLineGenerator(group.data)) - .transition(this.services.transitions.getTransition("blob-update-enter", animate)) - .attr("stroke", group => colorScale(group.name)) - .attr("stroke-width", 1.5) - .attr("fill", group => colorScale(group.name)) - .style("fill-opacity", configuration.opacity.selected) - ); - - // rectangles - const xAxesRect = DOMUtils.appendOrSelect(svg, "g.x-axes-rect"); - const xAxisRectUpdate = xAxesRect.selectAll("g.x-axis-rect").data(this.uniqKeys); - xAxisRectUpdate.join( - enter => enter - .append("rect") - .attr("class", "axis-rect") - .attr("x", cx) - .attr("y", cy - 50/2) - .attr("width", radius) - .attr("height", 50) - .attr("fill", "red") - .style("fill-opacity", DEBUG ? 0.1 : 0) - .attr("transform", key => `rotate(${radToDeg(xScale(key)) - 90} ${cx} ${cy})`), + .attr("class", "blob") + .attr("transform", `translate(${c.x}, ${c.y})`) + .attr("d", group => radialLineGenerator(group.data)) + .attr("fill", group => colorScale(group.name)) + .style("fill-opacity", configuration.opacity.selected) + .attr("stroke", group => colorScale(group.name)); + + // data dots + const dots = DOMUtils.appendOrSelect(svg, "g.dots").attr("role", Roles.GROUP); + const dotsUpdate = dots.selectAll("circle").data(this.displayDataNormalized); + dotsUpdate.join( + enter => enter.append("circle").attr("role", Roles.GRAPHICS_SYMBOL), update => update, exit => exit.remove() - ); + ) + .attr("class", d => kebabCase(d.key)) + .attr("cx", d => polarToCartesianCoords(xScale(d.key), yScale(d.value), c).x) + .attr("cy", d => polarToCartesianCoords(xScale(d.key), yScale(d.value), c).y) + .attr("r", 0) + .attr("opacity", 0) + .attr("fill", d => colorScale(d[this.groupMapsTo])); - // circles - const dots = DOMUtils.appendOrSelect(svg, "g.dots"); - const dotsUpdate = dots.selectAll("g.dot").data(this.displayDataNormalized); - dotsUpdate.join( - enter => enter - .append("circle") - .attr("class", "dot") - .attr("cx", d => polarCoords(d.key, yScale(d.value), cx, cy).x) - .attr("cy", d => polarCoords(d.key, yScale(d.value), cx, cy).y) - .attr("r", 5) - .attr("fill", d => colorScale(d[this.groupMapsTo])), + // rectangles + const xAxesRect = DOMUtils.appendOrSelect(svg, "g.x-axes-rect").attr("role", Roles.GROUP); + const xAxisRectUpdate = xAxesRect.selectAll("rect").data(this.uniqKeys); + xAxisRectUpdate.join( + enter => enter.append("rect").attr("role", Roles.GRAPHICS_SYMBOL), update => update, exit => exit.remove() - ); + ) + .attr("x", c.x) + .attr("y", c.y - xAxisRectHeight / 2) + .attr("width", yScale.range()[1]) + .attr("height", xAxisRectHeight) + .attr("fill", "red") + .style("fill-opacity", DEBUG ? 0.1 : 0) + .attr("transform", key => `rotate(${radToDeg(xScale(key))}, ${c.x}, ${c.y})`); // Add event listeners this.addEventListeners(); @@ -359,7 +303,7 @@ export class Radar extends Component { const { hoveredElement } = event.detail; const { opacity } = Configuration.options.radarChart.radar; this.parent.selectAll("g.blobs path") - .transition(this.services.transitions.getTransition("legend-hover-path")) + .transition(this.services.transitions.getTransition("legend-hover-blob")) .style("fill-opacity", group => { if (group.name !== hoveredElement.datum().name) { return opacity.unselected; @@ -369,14 +313,16 @@ export class Radar extends Component { } handleLegendMouseOut = (event: CustomEvent) => { + const { opacity } = Configuration.options.radarChart.radar; this.parent.selectAll("g.blobs path") - .transition(this.services.transitions.getTransition("legend-mouseout-path")) - .style("fill-opacity", 0.5); + .transition(this.services.transitions.getTransition("legend-mouseout-blob")) + .style("fill-opacity", opacity.selected); } destroy() { // Remove event listeners - this.parent.selectAll("path") + this.parent.selectAll(".x-axes-rect > rect") + .on("mouseover", null) .on("mousemove", null) .on("mouseout", null); // Remove legend listeners @@ -389,23 +335,26 @@ export class Radar extends Component { const self = this; // events on x axes rects - // this.parent.selectAll(".x-axes .x-axis > line") this.parent.selectAll(".x-axes-rect > rect") - .on("mouseover", function (datum) { + .on("mouseover", function(datum) { // Dispatch mouse event self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEOVER, { element: select(this), datum }); }) - .on("mousemove", function (datum) { + .on("mousemove", function(datum) { const hoveredElement = select(this); - const axisLine = self.parent.select(`.x-axes .x-axis .line-${removeSpaces(datum)}`); + const axisLine = self.parent.select(`.x-axes .x-axis-${kebabCase(datum)}`); + const dots = self.parent.selectAll(`.dots circle.${kebabCase(datum)}`); // Change style axisLine.classed("hovered", true) - .transition(self.services.transitions.getTransition("x_axis_line_mouseover")) .attr("stroke-dasharray", "4 4"); + dots.classed("hovered", true) + .transition(self.services.transitions.getTransition("radar_dots_mouseover")) + .attr("opacity", 1) + .attr("r", 5); // Dispatch mouse event self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEMOVE, { @@ -432,12 +381,16 @@ export class Radar extends Component { }) .on("mouseout", function(datum) { const hoveredElement = select(this); - const axisLine = self.parent.select(`.x-axes .x-axis .line-${removeSpaces(datum)}`); + const axisLine = self.parent.select(`.x-axes .x-axis-${kebabCase(datum)}`); + const dots = self.parent.selectAll(`.dots circle.${kebabCase(datum)}`); // Change style axisLine.classed("hovered", false) - .transition(self.services.transitions.getTransition("x_axis_line_mouseout")) .attr("stroke-dasharray", "0"); + dots.classed("hovered", false) + .transition(self.services.transitions.getTransition("radar_dots_mouseout")) + .attr("opacity", 0) + .attr("r", 0); // Dispatch mouse event self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEOUT, { @@ -446,11 +399,8 @@ export class Radar extends Component { }); // Hide tooltip + self.services.events.dispatchEvent("hide-tooltip", { hoveredElement }); self.services.events.dispatchEvent(Events.Tooltip.HIDE, { hoveredElement }); }); } } - -function removeSpaces(str: string): string { - return str.replace(/\s/g, ""); -} diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index bb6f986252..bda69398fb 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -335,6 +335,12 @@ export const transitions = { }, graph_element_mouseout_fill_update: { duration: 100 + }, + radar_dots_mouseover: { + duration: 80 + }, + radar_dots_mouseout: { + duration: 80 } }; diff --git a/packages/core/src/services/angle-utils.ts b/packages/core/src/services/angle-utils.ts index 6c7174ad79..621ddbe430 100644 --- a/packages/core/src/services/angle-utils.ts +++ b/packages/core/src/services/angle-utils.ts @@ -1,9 +1,16 @@ +export interface Point { + x: number; + y: number; +} + +export type Angle = number; + // compute modulo values correctly also with a negative number function mod(n: number, m: number): number { return ((n % m) + m) % m; } -export function radialLabelPlacement(angleRadians: number) { +export function radialLabelPlacement(angleRadians: Angle) { const angle = mod(radToDeg(angleRadians), 360); let textAnchor: "start" | "middle" | "end" = "middle"; // *___ __*__ ___* @@ -68,10 +75,24 @@ function isInRange(x: number, minMax: number[]): boolean { return x >= minMax[0] && x <= minMax[1]; } -export function radToDeg(rad: number): number { +export function radToDeg(rad: Angle): Angle { return rad * (180 / Math.PI); } -export function degToRad(deg: number): number { +export function degToRad(deg: Angle): Angle { return deg * (Math.PI / 180); } + +export function polarToCartesianCoords(a: Angle, r: number, t: Point = { x: 0, y: 0 }) { + const x = r * Math.cos(a) + t.x; + const y = r * Math.sin(a) + t.y; + return { x, y }; +} + +// Return the distance between a point (described with polar coordinates) +// on a circumference and the vertical diameter. +// If the point is on the left if the diameter, its distance is positive, +// if it is on the right of the diameter, its distance is negative. +export function distanceBetweenPointOnCircAndVerticalDiameter(a: Angle, r: number) { + return r * Math.sin(a - Math.PI / 2); +} diff --git a/packages/core/src/styles/graphs/_radar.scss b/packages/core/src/styles/graphs/_radar.scss new file mode 100644 index 0000000000..21fed6f63d --- /dev/null +++ b/packages/core/src/styles/graphs/_radar.scss @@ -0,0 +1,37 @@ + +.#{$prefix}--#{$charts-prefix}--radar { + .blobs path { + @if $ui-background == map-get($carbon--theme--g90, 'ui-background') { + mix-blend-mode: normal; + // mix-blend-mode: screen; + } @else if $ui-background == map-get($carbon--theme--g100, 'ui-background') { + mix-blend-mode: normal; + // mix-blend-mode: screen; + } @else { + mix-blend-mode: normal; + // mix-blend-mode: multiply; + } + stroke-width: 1.5px; + } + + .y-axes path, + .x-axes line { + stroke-width: 1px; + stroke: $ui-03; + } + + .x-axes line.hovered { + @if $ui-background == map-get($carbon--theme--g90, 'ui-background') { + stroke: lime; // $focus + } @else if $ui-background == map-get($carbon--theme--g100, 'ui-background') { + stroke: cyan; + } @else { + stroke: deeppink; + } + } + + .y-labels text { + mix-blend-mode: normal; + } +} + diff --git a/packages/core/src/styles/graphs/index.scss b/packages/core/src/styles/graphs/index.scss index 8edd327212..625897fe3c 100644 --- a/packages/core/src/styles/graphs/index.scss +++ b/packages/core/src/styles/graphs/index.scss @@ -1,3 +1,4 @@ @import "./bubble"; @import "./line"; @import "./scatter"; +@import "./radar"; From 82cc37a37dbd57fcf3735103cf7007b003ddb72d Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 08:47:29 +0200 Subject: [PATCH 64/95] Radar: radialLabelPlacement refactor --- packages/core/src/services/angle-utils.ts | 79 +++++++---------------- 1 file changed, 22 insertions(+), 57 deletions(-) diff --git a/packages/core/src/services/angle-utils.ts b/packages/core/src/services/angle-utils.ts index 621ddbe430..bbbbf5ba2b 100644 --- a/packages/core/src/services/angle-utils.ts +++ b/packages/core/src/services/angle-utils.ts @@ -5,74 +5,39 @@ export interface Point { export type Angle = number; -// compute modulo values correctly also with a negative number -function mod(n: number, m: number): number { - return ((n % m) + m) % m; +interface LabelAlignment { + textAnchor: "start" | "middle" | "end"; // *___ __*__ ___* + dominantBaseline: "baseline" | "middle" | "hanging"; // __* --*-- --. } -export function radialLabelPlacement(angleRadians: Angle) { +export function radialLabelPlacement(angleRadians: Angle): LabelAlignment { const angle = mod(radToDeg(angleRadians), 360); - let textAnchor: "start" | "middle" | "end" = "middle"; // *___ __*__ ___* - let dominantBaseline: "baseline" | "middle" | "hanging" = "middle"; // __* --*-- --. - - let quadrant = 0; - - if (isInRange(angle, [0, 90])) { - quadrant = 0; - } else if (isInRange(angle, [90, 180])) { - quadrant = 1; - } else if (isInRange(angle, [180, 270])) { - quadrant = 2; - } else if (isInRange(angle, [270, 360])) { - quadrant = 3; - } - - if (quadrant === 0) { - textAnchor = "start"; - dominantBaseline = "hanging"; - } else if (quadrant === 1) { - textAnchor = "end"; - dominantBaseline = "hanging"; - } else if (quadrant === 2) { - textAnchor = "end"; - dominantBaseline = "baseline"; - } else if (quadrant === 3) { - textAnchor = "start"; - dominantBaseline = "baseline"; - } - - let edge = null; - if (isInRange(angle, [0, 10]) || isInRange(angle, [350, 0])) { - edge = 0; + return { textAnchor: "start", dominantBaseline: "middle" }; + } else if (isInRange(angle, [10, 80])) { + return { textAnchor: "start", dominantBaseline: "hanging" }; } else if (isInRange(angle, [80, 100])) { - edge = 1; + return { textAnchor: "middle", dominantBaseline: "hanging" }; + } else if (isInRange(angle, [100, 170])) { + return { textAnchor: "end", dominantBaseline: "hanging" }; } else if (isInRange(angle, [170, 190])) { - edge = 2; + return { textAnchor: "end", dominantBaseline: "middle" }; + } else if (isInRange(angle, [190, 260])) { + return { textAnchor: "end", dominantBaseline: "baseline" }; } else if (isInRange(angle, [260, 280])) { - edge = 3; - } - - if (edge === 0) { - textAnchor = "start"; - dominantBaseline = "middle"; - } else if (edge === 1) { - textAnchor = "middle"; - dominantBaseline = "hanging"; - } else if (edge === 2) { - textAnchor = "end"; - dominantBaseline = "middle"; - } else if (edge === 3) { - textAnchor = "middle"; - dominantBaseline = "baseline"; + return { textAnchor: "middle", dominantBaseline: "baseline" }; + } else { // 280 - 350 + return { textAnchor: "start", dominantBaseline: "baseline" }; } +} - return { textAnchor, dominantBaseline }; +function mod(n: number, m: number) { + return ((n % m) + m) % m; } -function isInRange(x: number, minMax: number[]): boolean { - return x >= minMax[0] && x <= minMax[1]; +function isInRange(x: number, [min, max]: [number, number]) { + return x >= min && x <= max; } export function radToDeg(rad: Angle): Angle { @@ -83,7 +48,7 @@ export function degToRad(deg: Angle): Angle { return deg * (Math.PI / 180); } -export function polarToCartesianCoords(a: Angle, r: number, t: Point = { x: 0, y: 0 }) { +export function polarToCartesianCoords(a: Angle, r: number, t: Point = { x: 0, y: 0 }): Point { const x = r * Math.cos(a) + t.x; const y = r * Math.sin(a) + t.y; return { x, y }; From f4232bce9dddf3a226236d14bab91b31b9bb4f4d Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 09:47:25 +0200 Subject: [PATCH 65/95] Radar: better logic to center chart --- packages/core/src/components/graphs/radar.ts | 87 +++++++++----------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 061a9d2c62..f84fe7e58d 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -28,14 +28,19 @@ interface Datum { key: string; value: number; } +interface GroupedDatum { + name: string; + data: Array; +} export class Radar extends Component { type = "radar"; + svg: SVGElement; groupMapsTo: string; uniqKeys: string[]; uniqGroups: string[]; displayDataNormalized: Array; - groupedDataNormalized: any; + groupedDataNormalized: Array; init() { const { events } = this.services; @@ -48,28 +53,13 @@ export class Radar extends Component { render(animate = true) { const self = this; - ///////////////////////////// - // Containers - ///////////////////////////// - const svg = this.getContainerSVG(); + this.svg = this.getContainerSVG(); const { width, height } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); if (!width || !height) { return; } - // console.log("\n"); // console.log({ width, height }); - const fontSize = 30; // TODO: mmm, probably there is a better way to do that - const margin = 2 * fontSize; - const size = Math.min(width, height); - const diameter = size - margin; - const radius = diameter / 2; - const xLabelPadding = 10; - const yLabelPadding = 5; - const yTicksNumber = 4; // TODO: probably there are a default constant for that value - const minRange = 10; - const xAxisRectHeight = 50; - const data: Array = this.model.getData(); const displayData: Array = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); @@ -82,17 +72,16 @@ export class Radar extends Component { this.displayDataNormalized = this.normalizeFlatData(displayData); this.groupedDataNormalized = this.normalizeGroupedData(groupedData); - // console.log("animate:", animate); - // console.log("data:", data); - // console.log("displayData:", displayData); - // console.log("groupedData:", groupedData); - // console.log("options:", options); - // console.log("configuration:", configuration); - // console.log("groupMapsTo:", this.groupMapsTo); - - ///////////////////////////// - // Computations - ///////////////////////////// + const xLabelPadding = 10; + const yLabelPadding = 5; + const labelHeight = this.labelDimensions(this.uniqKeys[0]).height; + const margin = 2 * (labelHeight + yLabelPadding); + const size = Math.min(width, height); + const diameter = size - margin; + const radius = diameter / 2; + const yTicksNumber = 4; + const minRange = 10; + const xAxisRectHeight = 50; // given a key, return the corrisponding angle in radiants // rotated by -PI/2 because we want angle 0° at -y (12 o’clock) @@ -114,17 +103,13 @@ export class Radar extends Component { // the angle accessor returns the angle in radians with 0° at -y (12 o’clock) // so map back the angle const radialLineGenerator = lineRadial() - .angle(d => xScale(d.key) + Math.PI / 2) - .radius(d => yScale(d.value)) - .curve(curveLinearClosed); + .angle(d => xScale(d.key) + Math.PI / 2) + .radius(d => yScale(d.value)) + .curve(curveLinearClosed); // compute the space that each x label needs const horizSpaceNeededByEachXLabel = this.uniqKeys.map(key => { - // append temporarily the label to get the exact space that it occupies - const tmpTick = DOMUtils.appendOrSelect(svg, `g.tmp-tick`); - const tmpTickText = DOMUtils.appendOrSelect(tmpTick, `text`).text(key); - const tickWidth = DOMUtils.getSVGElementSize(tmpTickText.node(), { useBBox: true }).width; - tmpTick.remove(); + const tickWidth = this.labelDimensions(key).width; // compute the distance between the point that the label rapresents and the vertical diameter const distPointDiam = distanceBetweenPointOnCircAndVerticalDiameter(xScale(key), radius); // the space each label occupies is the sum of these two values @@ -139,11 +124,11 @@ export class Radar extends Component { }; ///////////////////////////// - // Draw + // Drawing section ///////////////////////////// if (DEBUG) { - const debugContainer = DOMUtils.appendOrSelect(svg, "g.debug"); + const debugContainer = DOMUtils.appendOrSelect(this.svg, "g.debug"); const backdropRect = DOMUtils.appendOrSelect(debugContainer, "rect.backdrop") .attr("width", "100%") .attr("height", "100%") @@ -170,10 +155,9 @@ export class Radar extends Component { .attr("stroke", "gold"); circumferencesUpdate.exit().remove(); } - /////////////// // y axes - const yAxes = DOMUtils.appendOrSelect(svg, "g.y-axes").attr("role", Roles.GROUP); + const yAxes = DOMUtils.appendOrSelect(this.svg, "g.y-axes").attr("role", Roles.GROUP); const yAxisUpdate = yAxes.selectAll("path").data(yTicks, tick => tick); yAxisUpdate.join( enter => enter.append("path").attr("role", Roles.GRAPHICS_SYMBOL), @@ -189,7 +173,7 @@ export class Radar extends Component { .attr("fill", "none"); // y labels (show only the min and the max labels) - const yLabels = DOMUtils.appendOrSelect(svg, "g.y-labels").attr("role", Roles.GROUP); + const yLabels = DOMUtils.appendOrSelect(this.svg, "g.y-labels").attr("role", Roles.GROUP); const yLabelUpdate = yLabels.selectAll("text").data(extent(yTicks)); yLabelUpdate.join( enter => enter.append("text"), @@ -203,7 +187,7 @@ export class Radar extends Component { .style("dominant-baseline", "middle"); // x axes - const xAxes = DOMUtils.appendOrSelect(svg, "g.x-axes").attr("role", Roles.GROUP); + const xAxes = DOMUtils.appendOrSelect(this.svg, "g.x-axes").attr("role", Roles.GROUP); const xAxisUpdate = xAxes.selectAll("line").data(this.uniqKeys, key => key); xAxisUpdate.join( enter => enter.append("line").attr("role", Roles.GRAPHICS_SYMBOL), @@ -218,7 +202,7 @@ export class Radar extends Component { .attr("y2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).y); // x labels - const xLabels = DOMUtils.appendOrSelect(svg, "g.x-labels").attr("role", Roles.GROUP); + const xLabels = DOMUtils.appendOrSelect(this.svg, "g.x-labels").attr("role", Roles.GROUP); const xLabelUpdate = xLabels.selectAll("text").data(this.uniqKeys); xLabelUpdate.join( enter => enter.append("text"), @@ -232,7 +216,7 @@ export class Radar extends Component { .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline); // blobs - const blobs = DOMUtils.appendOrSelect(svg, "g.blobs").attr("role", Roles.GROUP); + const blobs = DOMUtils.appendOrSelect(this.svg, "g.blobs").attr("role", Roles.GROUP); const blobUpdate = blobs.selectAll("path").data(this.groupedDataNormalized, group => group.name); blobUpdate.join( enter => enter.append("path").attr("role", Roles.GRAPHICS_SYMBOL), @@ -247,7 +231,7 @@ export class Radar extends Component { .attr("stroke", group => colorScale(group.name)); // data dots - const dots = DOMUtils.appendOrSelect(svg, "g.dots").attr("role", Roles.GROUP); + const dots = DOMUtils.appendOrSelect(this.svg, "g.dots").attr("role", Roles.GROUP); const dotsUpdate = dots.selectAll("circle").data(this.displayDataNormalized); dotsUpdate.join( enter => enter.append("circle").attr("role", Roles.GRAPHICS_SYMBOL), @@ -262,7 +246,7 @@ export class Radar extends Component { .attr("fill", d => colorScale(d[this.groupMapsTo])); // rectangles - const xAxesRect = DOMUtils.appendOrSelect(svg, "g.x-axes-rect").attr("role", Roles.GROUP); + const xAxesRect = DOMUtils.appendOrSelect(this.svg, "g.x-axes-rect").attr("role", Roles.GROUP); const xAxisRectUpdate = xAxesRect.selectAll("rect").data(this.uniqKeys); xAxisRectUpdate.join( enter => enter.append("rect").attr("role", Roles.GRAPHICS_SYMBOL), @@ -281,6 +265,15 @@ export class Radar extends Component { this.addEventListeners(); } + // append temporarily the label to get the exact space that it occupies + labelDimensions = (label: string) => { + const tmpTick = DOMUtils.appendOrSelect(this.svg, `g.tmp-tick`); + const tmpTickText = DOMUtils.appendOrSelect(tmpTick, `text`).text(label); + const { width, height } = DOMUtils.getSVGElementSize(tmpTickText.node(), { useBBox: true }); + tmpTick.remove(); + return { width, height }; + } + // Given a flat array of objects, if there are missing data on key, // creates corrisponding data with value = 0 normalizeFlatData = (dataset: Array) => { @@ -292,7 +285,7 @@ export class Radar extends Component { // Given a a grouped array of objects, if there are missing data on key, // creates corrisponding data with value = 0 - normalizeGroupedData = (dataset: any) => { + normalizeGroupedData = (dataset: Array) => { return dataset.map(({ name, data }) => { const completeBlankData = this.uniqKeys.map(k => ({ [this.groupMapsTo]: name, key: k, value: null })); return { name, data: Tools.merge(completeBlankData, data) }; From 989858e8004e5e9eb870de402b1fdd99f1c3c9d3 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 13:58:36 +0200 Subject: [PATCH 66/95] Radar: fix x axis style when overed --- packages/core/src/styles/graphs/_radar.scss | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/packages/core/src/styles/graphs/_radar.scss b/packages/core/src/styles/graphs/_radar.scss index 21fed6f63d..9d831137ed 100644 --- a/packages/core/src/styles/graphs/_radar.scss +++ b/packages/core/src/styles/graphs/_radar.scss @@ -1,16 +1,6 @@ .#{$prefix}--#{$charts-prefix}--radar { .blobs path { - @if $ui-background == map-get($carbon--theme--g90, 'ui-background') { - mix-blend-mode: normal; - // mix-blend-mode: screen; - } @else if $ui-background == map-get($carbon--theme--g100, 'ui-background') { - mix-blend-mode: normal; - // mix-blend-mode: screen; - } @else { - mix-blend-mode: normal; - // mix-blend-mode: multiply; - } stroke-width: 1.5px; } @@ -22,16 +12,12 @@ .x-axes line.hovered { @if $ui-background == map-get($carbon--theme--g90, 'ui-background') { - stroke: lime; // $focus + stroke: $carbon--white-0; } @else if $ui-background == map-get($carbon--theme--g100, 'ui-background') { - stroke: cyan; + stroke: $carbon--white-0; } @else { - stroke: deeppink; + stroke: $carbon--black-100; } } - - .y-labels text { - mix-blend-mode: normal; - } } From 4b167832aba451ddaf45073420159cd0119ab14e Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 13:58:50 +0200 Subject: [PATCH 67/95] Radar: change blobs opacity --- packages/core/src/configuration.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index bda69398fb..f221f1a8e9 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -281,8 +281,8 @@ const donutChart: DonutChartOptions = Tools.merge({}, pieChart, { const radarChart: RadarChartOptions = Tools.merge({}, chart, { radar: { opacity: { - unselected: 0.2, - selected: 0.5 + unselected: 0.1, + selected: 0.3 } }, tooltip: { From bf8b8eb0f53debb0c7e5d568d9e244a4f5acc090 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 14:21:37 +0200 Subject: [PATCH 68/95] Radar: create y axes transition --- packages/core/src/components/graphs/radar.ts | 66 ++++++++++++++++---- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index f84fe7e58d..3ee94d7273 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -17,12 +17,15 @@ import { // D3 Imports import { select } from "d3-selection"; -import { scaleBand, scaleLinear } from "d3-scale"; +import { scaleBand, scaleLinear, ScaleLinear } from "d3-scale"; import { min, max, extent } from "d3-array"; import { lineRadial, curveLinearClosed } from "d3-shape"; const DEBUG = false; +// used to make transitions +let oldYScale: ScaleLinear; + interface Datum { group?: string; key: string; @@ -107,6 +110,13 @@ export class Radar extends Component { .radius(d => yScale(d.value)) .curve(curveLinearClosed); + // this line generator is necessary in order to make a transition of a value from the + // position it occupies using the old scale to the position it occupies using the new scale + const oldRadialLineGenerator = lineRadial() + .angle(radialLineGenerator.angle()) + .radius(d => oldYScale ? oldYScale(d.value) : minRange) + .curve(radialLineGenerator.curve()); + // compute the space that each x label needs const horizSpaceNeededByEachXLabel = this.uniqKeys.map(key => { const tickWidth = this.labelDimensions(key).width; @@ -154,23 +164,55 @@ export class Radar extends Component { .attr("opacity", 0.2) .attr("stroke", "gold"); circumferencesUpdate.exit().remove(); + const vertDiam = DOMUtils.appendOrSelect(debugContainer, "line.vertDiam") + .attr("x1", c.x) + .attr("y1", c.y - radius - 10) + .attr("x2", c.x) + .attr("y2", c.y + radius + 10) + .attr("opacity", 0.2) + .attr("stroke", "gold"); + const horizDiam = DOMUtils.appendOrSelect(debugContainer, "line.horizDiam") + .attr("x1", c.x - radius - 10) + .attr("y1", c.y) + .attr("x2", c.x + radius + 10) + .attr("y2", c.y) + .attr("opacity", 0.2) + .attr("stroke", "gold"); } // y axes const yAxes = DOMUtils.appendOrSelect(this.svg, "g.y-axes").attr("role", Roles.GROUP); const yAxisUpdate = yAxes.selectAll("path").data(yTicks, tick => tick); + // for each tick, create array of data corrisponding to the points composing the shape + const shapeData = (tick: number) => this.uniqKeys.map(key => ({ key, value: tick })) as Datum[]; yAxisUpdate.join( - enter => enter.append("path").attr("role", Roles.GRAPHICS_SYMBOL), - update => update, - exit => exit.remove() - ) - .attr("transform", `translate(${c.x}, ${c.y})`) - .attr("d", tick => { - // for each tick, create array of data corrisponding to the points composing the shape - const yShapeData = this.uniqKeys.map(key => ({ key, value: tick })); - return radialLineGenerator(yShapeData); - }) - .attr("fill", "none"); + enter => enter + .append("path") + .attr("role", Roles.GRAPHICS_SYMBOL) + .attr("opacity", 0) + .attr("transform", `translate(${c.x}, ${c.y})`) + .attr("fill", "none") + .attr("d", tick => oldRadialLineGenerator(shapeData(tick))) + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 1) + .attr("d", tick => radialLineGenerator(shapeData(tick))) + ), + update => update + .call(selection => selection + .transition().duration(2000) + .attr("transform", `translate(${c.x}, ${c.y})`) + .attr("d", tick => radialLineGenerator(shapeData(tick))) + ), + exit => exit + .call(selection => selection + .transition().duration(2000) + .attr("d", tick => radialLineGenerator(shapeData(tick))) + .attr("opacity", 0) + .remove() + ) + ); + oldYScale = yScale; // save the current scale as the old one // y labels (show only the min and the max labels) const yLabels = DOMUtils.appendOrSelect(this.svg, "g.y-labels").attr("role", Roles.GROUP); From fa2f0640e86cffc096e42104e2d6511738852b10 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 14:31:47 +0200 Subject: [PATCH 69/95] Radar: create x axes transition --- packages/core/src/components/graphs/radar.ts | 34 ++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 3ee94d7273..3e34de81c5 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -232,16 +232,32 @@ export class Radar extends Component { const xAxes = DOMUtils.appendOrSelect(this.svg, "g.x-axes").attr("role", Roles.GROUP); const xAxisUpdate = xAxes.selectAll("line").data(this.uniqKeys, key => key); xAxisUpdate.join( - enter => enter.append("line").attr("role", Roles.GRAPHICS_SYMBOL), + enter => enter + .append("line") + .attr("role", Roles.GRAPHICS_SYMBOL) + .attr("opacity", 0) + .attr("class", key => `x-axis-${kebabCase(key)}`) // replace spaces with - + .attr("stroke-dasharray", "0") + .attr("x1", key => polarToCartesianCoords(xScale(key), 0, c).x) + .attr("y1", key => polarToCartesianCoords(xScale(key), 0, c).y) + .attr("x2", key => polarToCartesianCoords(xScale(key), 0, c).x) + .attr("y2", key => polarToCartesianCoords(xScale(key), 0, c).y) + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 1) + .attr("x1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).x) + .attr("y1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).y) + .attr("x2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).x) + .attr("y2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).y) + ), update => update, - exit => exit.remove() - ) - .attr("class", key => `x-axis-${kebabCase(key)}`) // replace spaces with - - .attr("stroke-dasharray", "0") - .attr("x1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).x) - .attr("y1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).y) - .attr("x2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).x) - .attr("y2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).y); + exit => exit + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 0) + .remove() + ) + ); // x labels const xLabels = DOMUtils.appendOrSelect(this.svg, "g.x-labels").attr("role", Roles.GROUP); From 5d063aa214654e410a54c560bb221a12dd343c72 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 14:42:22 +0200 Subject: [PATCH 70/95] Radar: create blobs transition --- packages/core/src/components/graphs/radar.ts | 41 ++++++++++++++------ 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 3e34de81c5..39c350a53d 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -212,7 +212,6 @@ export class Radar extends Component { .remove() ) ); - oldYScale = yScale; // save the current scale as the old one // y labels (show only the min and the max labels) const yLabels = DOMUtils.appendOrSelect(this.svg, "g.y-labels").attr("role", Roles.GROUP); @@ -277,16 +276,36 @@ export class Radar extends Component { const blobs = DOMUtils.appendOrSelect(this.svg, "g.blobs").attr("role", Roles.GROUP); const blobUpdate = blobs.selectAll("path").data(this.groupedDataNormalized, group => group.name); blobUpdate.join( - enter => enter.append("path").attr("role", Roles.GRAPHICS_SYMBOL), - update => update, - exit => exit.remove() - ) - .attr("class", "blob") - .attr("transform", `translate(${c.x}, ${c.y})`) - .attr("d", group => radialLineGenerator(group.data)) - .attr("fill", group => colorScale(group.name)) - .style("fill-opacity", configuration.opacity.selected) - .attr("stroke", group => colorScale(group.name)); + enter => enter + .append("path") + .attr("class", "blob") + .attr("role", Roles.GRAPHICS_SYMBOL) + .attr("opacity", 0) + .attr("transform", `translate(${c.x}, ${c.y})`) + .attr("fill", group => colorScale(group.name)) + .style("fill-opacity", configuration.opacity.selected) + .attr("stroke", group => colorScale(group.name)) + .attr("d", group => oldRadialLineGenerator(group.data)) + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 1) + .attr("d", group => radialLineGenerator(group.data)) + ), + update => update + .call(selection => selection + .transition().duration(2000) + .attr("transform", `translate(${c.x}, ${c.y})`) + .attr("d", group => radialLineGenerator(group.data)) + ), + exit => exit + .call(selection => selection + .transition().duration(2000) + .attr("d", group => radialLineGenerator(group.data)) + .attr("opacity", 0) + .remove() + ) + ); + oldYScale = yScale; // save the current scale as the old one // data dots const dots = DOMUtils.appendOrSelect(this.svg, "g.dots").attr("role", Roles.GROUP); From 687db10b387046287a165816e83341e6da738bc4 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 15:18:48 +0200 Subject: [PATCH 71/95] Radar: x and y labels transition --- packages/core/src/components/graphs/radar.ts | 61 ++++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 39c350a53d..2ce484500b 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -217,15 +217,31 @@ export class Radar extends Component { const yLabels = DOMUtils.appendOrSelect(this.svg, "g.y-labels").attr("role", Roles.GROUP); const yLabelUpdate = yLabels.selectAll("text").data(extent(yTicks)); yLabelUpdate.join( - enter => enter.append("text"), - update => update, - exit => exit.remove() - ) - .text(tick => tick) - .attr("x", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).x + yLabelPadding) - .attr("y", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).y) - .style("text-anchor", "start") - .style("dominant-baseline", "middle"); + enter => enter + .append("text") + .attr("opacity", 0) + .text(tick => tick) + .attr("x", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).x + yLabelPadding) + .attr("y", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).y) + .style("text-anchor", "start") + .style("dominant-baseline", "middle") + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 1) + ), + update => update + .call(selection => selection + .transition().duration(2000) + .attr("x", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).x + yLabelPadding) + .attr("y", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).y) + ), + exit => exit + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 0) + .remove() + ) + ); // x axes const xAxes = DOMUtils.appendOrSelect(this.svg, "g.x-axes").attr("role", Roles.GROUP); @@ -262,15 +278,26 @@ export class Radar extends Component { const xLabels = DOMUtils.appendOrSelect(this.svg, "g.x-labels").attr("role", Roles.GROUP); const xLabelUpdate = xLabels.selectAll("text").data(this.uniqKeys); xLabelUpdate.join( - enter => enter.append("text"), + enter => enter + .append("text") + .text(key => DEBUG ? `${key} ${radToDeg(xScale(key))}° <-- ${radToDeg(xScale(key) + Math.PI / 2)}°` : key) + .attr("opacity", 0) + .attr("x", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).x) + .attr("y", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).y) + .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) + .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline) + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 1) + ), update => update, - exit => exit.remove() - ) - .text(key => DEBUG ? `${key} ${radToDeg(xScale(key))}° <-- ${radToDeg(xScale(key) + Math.PI / 2)}°` : key) - .attr("x", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).x) - .attr("y", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).y) - .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) - .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline); + exit => exit + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 0) + .remove() + ) + ); // blobs const blobs = DOMUtils.appendOrSelect(this.svg, "g.blobs").attr("role", Roles.GROUP); From 3f7cc750af4c53f079664a4e61ae5de5624f9ef0 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 15:53:14 +0200 Subject: [PATCH 72/95] Radar: fix transitions --- packages/core/src/components/graphs/radar.ts | 32 +++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 2ce484500b..e345c3160f 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -199,8 +199,9 @@ export class Radar extends Component { .attr("d", tick => radialLineGenerator(shapeData(tick))) ), update => update - .call(selection => selection - .transition().duration(2000) + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 1) .attr("transform", `translate(${c.x}, ${c.y})`) .attr("d", tick => radialLineGenerator(shapeData(tick))) ), @@ -221,8 +222,8 @@ export class Radar extends Component { .append("text") .attr("opacity", 0) .text(tick => tick) - .attr("x", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).x + yLabelPadding) - .attr("y", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).y) + .attr("x", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).x + yLabelPadding) + .attr("y", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).y) .style("text-anchor", "start") .style("dominant-baseline", "middle") .call(selection => selection @@ -232,6 +233,7 @@ export class Radar extends Component { update => update .call(selection => selection .transition().duration(2000) + .attr("opacity", 1) .attr("x", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).x + yLabelPadding) .attr("y", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).y) ), @@ -265,7 +267,15 @@ export class Radar extends Component { .attr("x2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).x) .attr("y2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).y) ), - update => update, + update => update + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 1) + .attr("x1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).x) + .attr("y1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).y) + .attr("x2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).x) + .attr("y2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).y) + ), exit => exit .call(selection => selection .transition().duration(2000) @@ -290,7 +300,13 @@ export class Radar extends Component { .transition().duration(2000) .attr("opacity", 1) ), - update => update, + update => update + .call(selection => selection + .transition().duration(2000) + .attr("opacity", 1) + .attr("x", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).x) + .attr("y", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).y) + ), exit => exit .call(selection => selection .transition().duration(2000) @@ -321,6 +337,7 @@ export class Radar extends Component { update => update .call(selection => selection .transition().duration(2000) + .attr("opacity", 1) .attr("transform", `translate(${c.x}, ${c.y})`) .attr("d", group => radialLineGenerator(group.data)) ), @@ -332,7 +349,6 @@ export class Radar extends Component { .remove() ) ); - oldYScale = yScale; // save the current scale as the old one // data dots const dots = DOMUtils.appendOrSelect(this.svg, "g.dots").attr("role", Roles.GROUP); @@ -367,6 +383,8 @@ export class Radar extends Component { // Add event listeners this.addEventListeners(); + + oldYScale = yScale; // save the current scale as the old one } // append temporarily the label to get the exact space that it occupies From e34147f988d8fece9deddda77eae16ece810915c Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 15:53:35 +0200 Subject: [PATCH 73/95] Radar: more left space for y labels --- packages/core/src/components/graphs/radar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index e345c3160f..4e4326586e 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -76,7 +76,7 @@ export class Radar extends Component { this.groupedDataNormalized = this.normalizeGroupedData(groupedData); const xLabelPadding = 10; - const yLabelPadding = 5; + const yLabelPadding = 8; const labelHeight = this.labelDimensions(this.uniqKeys[0]).height; const margin = 2 * (labelHeight + yLabelPadding); const size = Math.min(width, height); From 343a5dee22b597eeb1466b5b7903280af0f0cf8c Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 17:01:48 +0200 Subject: [PATCH 74/95] Radar: remove unused imports --- packages/core/src/components/graphs/radar.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 4e4326586e..700a1087f8 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -10,7 +10,6 @@ import { Angle, radialLabelPlacement, radToDeg, - degToRad, polarToCartesianCoords, distanceBetweenPointOnCircAndVerticalDiameter } from "../../services/angle-utils"; @@ -18,7 +17,7 @@ import { // D3 Imports import { select } from "d3-selection"; import { scaleBand, scaleLinear, ScaleLinear } from "d3-scale"; -import { min, max, extent } from "d3-array"; +import { max, extent } from "d3-array"; import { lineRadial, curveLinearClosed } from "d3-shape"; const DEBUG = false; From 5fb07f85679c3abea76b60f9ae8cfc4f77bd8ac5 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 17:02:18 +0200 Subject: [PATCH 75/95] Radar: use the right transitions --- packages/core/src/components/graphs/radar.ts | 43 ++++++++++---------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 700a1087f8..e79a82e1bb 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -193,20 +193,20 @@ export class Radar extends Component { .attr("fill", "none") .attr("d", tick => oldRadialLineGenerator(shapeData(tick))) .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_y_axes_enter", animate)) .attr("opacity", 1) .attr("d", tick => radialLineGenerator(shapeData(tick))) ), update => update - .call(selection => selection - .transition().duration(2000) + .call(selection => selection + .transition(this.services.transitions.getTransition("radar_y_axes_update", animate)) .attr("opacity", 1) .attr("transform", `translate(${c.x}, ${c.y})`) .attr("d", tick => radialLineGenerator(shapeData(tick))) ), exit => exit .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_y_axes_exit", animate)) .attr("d", tick => radialLineGenerator(shapeData(tick))) .attr("opacity", 0) .remove() @@ -226,19 +226,20 @@ export class Radar extends Component { .style("text-anchor", "start") .style("dominant-baseline", "middle") .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_y_labels_enter", animate)) + // .transition().duration(2000) .attr("opacity", 1) ), update => update .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_y_labels_update", animate)) .attr("opacity", 1) .attr("x", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).x + yLabelPadding) .attr("y", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).y) ), exit => exit .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_y_labels_exit", animate)) .attr("opacity", 0) .remove() ) @@ -259,7 +260,7 @@ export class Radar extends Component { .attr("x2", key => polarToCartesianCoords(xScale(key), 0, c).x) .attr("y2", key => polarToCartesianCoords(xScale(key), 0, c).y) .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_x_axes_enter", animate)) .attr("opacity", 1) .attr("x1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).x) .attr("y1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).y) @@ -268,16 +269,16 @@ export class Radar extends Component { ), update => update .call(selection => selection - .transition().duration(2000) - .attr("opacity", 1) - .attr("x1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).x) - .attr("y1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).y) - .attr("x2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).x) - .attr("y2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).y) + .transition(this.services.transitions.getTransition("radar_x_axes_update", animate)) + .attr("opacity", 1) + .attr("x1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).x) + .attr("y1", key => polarToCartesianCoords(xScale(key), yScale.range()[0], c).y) + .attr("x2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).x) + .attr("y2", key => polarToCartesianCoords(xScale(key), yScale.range()[1], c).y) ), exit => exit .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_x_axes_exit", animate)) .attr("opacity", 0) .remove() ) @@ -296,19 +297,19 @@ export class Radar extends Component { .style("text-anchor", key => radialLabelPlacement(xScale(key)).textAnchor) .style("dominant-baseline", key => radialLabelPlacement(xScale(key)).dominantBaseline) .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_x_labels_enter", animate)) .attr("opacity", 1) ), update => update .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_x_labels_update", animate)) .attr("opacity", 1) .attr("x", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).x) .attr("y", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).y) ), exit => exit .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_x_labels_exit", animate)) .attr("opacity", 0) .remove() ) @@ -329,20 +330,20 @@ export class Radar extends Component { .attr("stroke", group => colorScale(group.name)) .attr("d", group => oldRadialLineGenerator(group.data)) .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_blobs_enter", animate)) .attr("opacity", 1) .attr("d", group => radialLineGenerator(group.data)) ), update => update .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_blobs_update", animate)) .attr("opacity", 1) .attr("transform", `translate(${c.x}, ${c.y})`) .attr("d", group => radialLineGenerator(group.data)) ), exit => exit .call(selection => selection - .transition().duration(2000) + .transition(this.services.transitions.getTransition("radar_blobs_exit", animate)) .attr("d", group => radialLineGenerator(group.data)) .attr("opacity", 0) .remove() From f548da614622a3c2765cc277b22b0bbcb0e6a394 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 17:03:19 +0200 Subject: [PATCH 76/95] Radar: remove logs --- packages/core/src/components/graphs/radar.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index e79a82e1bb..33d848bc69 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -60,7 +60,6 @@ export class Radar extends Component { if (!width || !height) { return; } - // console.log({ width, height }); const data: Array = this.model.getData(); const displayData: Array = this.model.getDisplayData(); @@ -90,14 +89,12 @@ export class Radar extends Component { const xScale = scaleBand() .domain(this.displayDataNormalized.map(d => d.key)) .range([0, 2 * Math.PI].map(a => a - Math.PI / 2) as [Angle, Angle]); - // console.log(`xScale [${xScale.domain()}] -> [${xScale.range()}]`); const yScale = scaleLinear() .domain([0, max(this.displayDataNormalized.map(d => d.value))]) .range([minRange, radius]) .nice(yTicksNumber); const yTicks = yScale.ticks(yTicksNumber); - // console.log(`yScale [${yScale.domain()}] -> [${yScale.range()}]`); const colorScale = (group: string): string => this.model.getFillColor(group); From 245e3a63317c4203bc99eb46a4435bc0fb33025d Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 17:05:59 +0200 Subject: [PATCH 77/95] Radar: remove debug stuff --- packages/core/src/components/graphs/radar.ts | 49 +------------------- 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 33d848bc69..836ec00d68 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -20,8 +20,6 @@ import { scaleBand, scaleLinear, ScaleLinear } from "d3-scale"; import { max, extent } from "d3-array"; import { lineRadial, curveLinearClosed } from "d3-shape"; -const DEBUG = false; - // used to make transitions let oldYScale: ScaleLinear; @@ -133,49 +131,6 @@ export class Radar extends Component { // Drawing section ///////////////////////////// - if (DEBUG) { - const debugContainer = DOMUtils.appendOrSelect(this.svg, "g.debug"); - const backdropRect = DOMUtils.appendOrSelect(debugContainer, "rect.backdrop") - .attr("width", "100%") - .attr("height", "100%") - .attr("stroke", "gold") - .attr("opacity", 0.2) - .attr("fill", "none"); - const center = DOMUtils.appendOrSelect(debugContainer, "circle.center") - .attr("cx", c.x) - .attr("cy", c.y) - .attr("r", 2) - .attr("opacity", 0.2) - .attr("fill", "gold"); - const circumferences = DOMUtils.appendOrSelect(debugContainer, "g.circumferences"); - const circumferencesUpdate = circumferences.selectAll("circle").data(yTicks); - circumferencesUpdate - .enter() - .append("circle") - .merge(circumferencesUpdate) - .attr("cx", c.x) - .attr("cy", c.y) - .attr("r", tick => yScale(tick)) - .attr("fill", "none") - .attr("opacity", 0.2) - .attr("stroke", "gold"); - circumferencesUpdate.exit().remove(); - const vertDiam = DOMUtils.appendOrSelect(debugContainer, "line.vertDiam") - .attr("x1", c.x) - .attr("y1", c.y - radius - 10) - .attr("x2", c.x) - .attr("y2", c.y + radius + 10) - .attr("opacity", 0.2) - .attr("stroke", "gold"); - const horizDiam = DOMUtils.appendOrSelect(debugContainer, "line.horizDiam") - .attr("x1", c.x - radius - 10) - .attr("y1", c.y) - .attr("x2", c.x + radius + 10) - .attr("y2", c.y) - .attr("opacity", 0.2) - .attr("stroke", "gold"); - } - // y axes const yAxes = DOMUtils.appendOrSelect(this.svg, "g.y-axes").attr("role", Roles.GROUP); const yAxisUpdate = yAxes.selectAll("path").data(yTicks, tick => tick); @@ -287,7 +242,7 @@ export class Radar extends Component { xLabelUpdate.join( enter => enter .append("text") - .text(key => DEBUG ? `${key} ${radToDeg(xScale(key))}° <-- ${radToDeg(xScale(key) + Math.PI / 2)}°` : key) + .text(key => key) .attr("opacity", 0) .attr("x", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).x) .attr("y", key => polarToCartesianCoords(xScale(key), yScale.range()[1] + xLabelPadding, c).y) @@ -375,7 +330,7 @@ export class Radar extends Component { .attr("width", yScale.range()[1]) .attr("height", xAxisRectHeight) .attr("fill", "red") - .style("fill-opacity", DEBUG ? 0.1 : 0) + .style("fill-opacity", 0) .attr("transform", key => `rotate(${radToDeg(xScale(key))}, ${c.x}, ${c.y})`); // Add event listeners From 33e016e0c297f455717ba7da1a35bf8122f44762 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Mon, 4 May 2020 20:48:43 +0200 Subject: [PATCH 78/95] Radar: update y labels --- packages/core/src/components/graphs/radar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 836ec00d68..d2ea8f8d9b 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -179,12 +179,12 @@ export class Radar extends Component { .style("dominant-baseline", "middle") .call(selection => selection .transition(this.services.transitions.getTransition("radar_y_labels_enter", animate)) - // .transition().duration(2000) .attr("opacity", 1) ), update => update .call(selection => selection .transition(this.services.transitions.getTransition("radar_y_labels_update", animate)) + .text(tick => tick) .attr("opacity", 1) .attr("x", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).x + yLabelPadding) .attr("y", tick => polarToCartesianCoords(- Math.PI / 2, yScale(tick), c).y) From 13ca9db36090aebb282e8a33c694a700afe6b859 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 5 May 2020 09:07:53 +0200 Subject: [PATCH 79/95] Radar: fix Firefox bug --- packages/core/src/components/graphs/radar.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index d2ea8f8d9b..f014475bf9 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -51,13 +51,8 @@ export class Radar extends Component { } render(animate = true) { - const self = this; - this.svg = this.getContainerSVG(); const { width, height } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); - if (!width || !height) { - return; - } const data: Array = this.model.getData(); const displayData: Array = this.model.getDisplayData(); @@ -82,6 +77,10 @@ export class Radar extends Component { const minRange = 10; const xAxisRectHeight = 50; + if (radius <= 0) { + return; + } + // given a key, return the corrisponding angle in radiants // rotated by -PI/2 because we want angle 0° at -y (12 o’clock) const xScale = scaleBand() From 7f93d982ab07c00db03ef2400fb1c7fb2eb5d7a1 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 5 May 2020 09:30:26 +0200 Subject: [PATCH 80/95] Radar: move constants to configuration --- packages/core/src/components/graphs/radar.ts | 9 ++------- packages/core/src/configuration.ts | 7 ++++++- packages/core/src/interfaces/charts.ts | 5 +++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index f014475bf9..3ccc6f934d 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -59,6 +59,7 @@ export class Radar extends Component { const groupedData = this.model.getGroupedData(); const options = this.model.getOptions(); const configuration = Configuration.options.radarChart.radar; + const { xLabelPadding, yLabelPadding, yTicksNumber, minRange, xAxisRectHeight, opacity } = configuration; this.groupMapsTo = options.data.groupMapsTo; this.uniqKeys = Array.from(new Set(data.map(d => d.key))); @@ -66,16 +67,10 @@ export class Radar extends Component { this.displayDataNormalized = this.normalizeFlatData(displayData); this.groupedDataNormalized = this.normalizeGroupedData(groupedData); - const xLabelPadding = 10; - const yLabelPadding = 8; - const labelHeight = this.labelDimensions(this.uniqKeys[0]).height; const margin = 2 * (labelHeight + yLabelPadding); const size = Math.min(width, height); const diameter = size - margin; const radius = diameter / 2; - const yTicksNumber = 4; - const minRange = 10; - const xAxisRectHeight = 50; if (radius <= 0) { return; @@ -277,7 +272,7 @@ export class Radar extends Component { .attr("opacity", 0) .attr("transform", `translate(${c.x}, ${c.y})`) .attr("fill", group => colorScale(group.name)) - .style("fill-opacity", configuration.opacity.selected) + .style("fill-opacity", opacity.selected) .attr("stroke", group => colorScale(group.name)) .attr("d", group => oldRadialLineGenerator(group.data)) .call(selection => selection diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 4a81614e58..0b531e2134 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -283,7 +283,12 @@ const radarChart: RadarChartOptions = Tools.merge({}, chart, { opacity: { unselected: 0.1, selected: 0.3 - } + }, + xLabelPadding: 10, + yLabelPadding: 8, + yTicksNumber: 4, + minRange: 10, + xAxisRectHeight: 50 }, tooltip: { gridline: { diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index ba492cca87..69a91cef4f 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -199,5 +199,10 @@ export interface RadarChartOptions extends BaseChartOptions { unselected: number, selected: number } + xLabelPadding: number, + yLabelPadding: number, + yTicksNumber: number, + minRange: number, + xAxisRectHeight: number }; } From b89dae0aed5247e5b297fd193673c23644014113 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 5 May 2020 09:32:20 +0200 Subject: [PATCH 81/95] Radar: rename variables --- packages/core/src/components/graphs/radar.ts | 33 ++++++++++---------- packages/core/src/configuration.ts | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 3ccc6f934d..2d0cf94a98 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -37,8 +37,8 @@ export class Radar extends Component { type = "radar"; svg: SVGElement; groupMapsTo: string; - uniqKeys: string[]; - uniqGroups: string[]; + uniqueKeys: string[]; + uniqueGroups: string[]; displayDataNormalized: Array; groupedDataNormalized: Array; @@ -62,11 +62,12 @@ export class Radar extends Component { const { xLabelPadding, yLabelPadding, yTicksNumber, minRange, xAxisRectHeight, opacity } = configuration; this.groupMapsTo = options.data.groupMapsTo; - this.uniqKeys = Array.from(new Set(data.map(d => d.key))); - this.uniqGroups = Array.from(new Set(displayData.map(d => d[this.groupMapsTo]))); + this.uniqueKeys = Array.from(new Set(data.map(d => d.key))); + this.uniqueGroups = Array.from(new Set(displayData.map(d => d[this.groupMapsTo]))); this.displayDataNormalized = this.normalizeFlatData(displayData); this.groupedDataNormalized = this.normalizeGroupedData(groupedData); + const labelHeight = this.getLabelDimensions(this.uniqueKeys[0]).height; const margin = 2 * (labelHeight + yLabelPadding); const size = Math.min(width, height); const diameter = size - margin; @@ -106,12 +107,12 @@ export class Radar extends Component { .curve(radialLineGenerator.curve()); // compute the space that each x label needs - const horizSpaceNeededByEachXLabel = this.uniqKeys.map(key => { - const tickWidth = this.labelDimensions(key).width; + const horizSpaceNeededByEachXLabel = this.uniqueKeys.map(key => { + const tickWidth = this.getLabelDimensions(key).width; // compute the distance between the point that the label rapresents and the vertical diameter - const distPointDiam = distanceBetweenPointOnCircAndVerticalDiameter(xScale(key), radius); + const distanceFromDiameter = distanceBetweenPointOnCircAndVerticalDiameter(xScale(key), radius); // the space each label occupies is the sum of these two values - return tickWidth + distPointDiam; + return tickWidth + distanceFromDiameter; }); const leftPadding = max(horizSpaceNeededByEachXLabel); @@ -129,7 +130,7 @@ export class Radar extends Component { const yAxes = DOMUtils.appendOrSelect(this.svg, "g.y-axes").attr("role", Roles.GROUP); const yAxisUpdate = yAxes.selectAll("path").data(yTicks, tick => tick); // for each tick, create array of data corrisponding to the points composing the shape - const shapeData = (tick: number) => this.uniqKeys.map(key => ({ key, value: tick })) as Datum[]; + const shapeData = (tick: number) => this.uniqueKeys.map(key => ({ key, value: tick })) as Datum[]; yAxisUpdate.join( enter => enter .append("path") @@ -193,7 +194,7 @@ export class Radar extends Component { // x axes const xAxes = DOMUtils.appendOrSelect(this.svg, "g.x-axes").attr("role", Roles.GROUP); - const xAxisUpdate = xAxes.selectAll("line").data(this.uniqKeys, key => key); + const xAxisUpdate = xAxes.selectAll("line").data(this.uniqueKeys, key => key); xAxisUpdate.join( enter => enter .append("line") @@ -232,7 +233,7 @@ export class Radar extends Component { // x labels const xLabels = DOMUtils.appendOrSelect(this.svg, "g.x-labels").attr("role", Roles.GROUP); - const xLabelUpdate = xLabels.selectAll("text").data(this.uniqKeys); + const xLabelUpdate = xLabels.selectAll("text").data(this.uniqueKeys); xLabelUpdate.join( enter => enter .append("text") @@ -313,7 +314,7 @@ export class Radar extends Component { // rectangles const xAxesRect = DOMUtils.appendOrSelect(this.svg, "g.x-axes-rect").attr("role", Roles.GROUP); - const xAxisRectUpdate = xAxesRect.selectAll("rect").data(this.uniqKeys); + const xAxisRectUpdate = xAxesRect.selectAll("rect").data(this.uniqueKeys); xAxisRectUpdate.join( enter => enter.append("rect").attr("role", Roles.GRAPHICS_SYMBOL), update => update, @@ -334,7 +335,7 @@ export class Radar extends Component { } // append temporarily the label to get the exact space that it occupies - labelDimensions = (label: string) => { + getLabelDimensions = (label: string) => { const tmpTick = DOMUtils.appendOrSelect(this.svg, `g.tmp-tick`); const tmpTickText = DOMUtils.appendOrSelect(tmpTick, `text`).text(label); const { width, height } = DOMUtils.getSVGElementSize(tmpTickText.node(), { useBBox: true }); @@ -345,8 +346,8 @@ export class Radar extends Component { // Given a flat array of objects, if there are missing data on key, // creates corrisponding data with value = 0 normalizeFlatData = (dataset: Array) => { - const completeBlankData = flatMapDeep(this.uniqKeys.map(key => { - return this.uniqGroups.map(group => ({ key, [this.groupMapsTo]: group, value: null })); + const completeBlankData = flatMapDeep(this.uniqueKeys.map(key => { + return this.uniqueGroups.map(group => ({ key, [this.groupMapsTo]: group, value: null })); })); return Tools.merge(completeBlankData, dataset); } @@ -355,7 +356,7 @@ export class Radar extends Component { // creates corrisponding data with value = 0 normalizeGroupedData = (dataset: Array) => { return dataset.map(({ name, data }) => { - const completeBlankData = this.uniqKeys.map(k => ({ [this.groupMapsTo]: name, key: k, value: null })); + const completeBlankData = this.uniqueKeys.map(k => ({ [this.groupMapsTo]: name, key: k, value: null })); return { name, data: Tools.merge(completeBlankData, data) }; }); } diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 0b531e2134..d9ad2d6add 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -294,7 +294,7 @@ const radarChart: RadarChartOptions = Tools.merge({}, chart, { gridline: { enabled: true }, - valueFormatter: value => value ? value : "NA" + valueFormatter: value => value ? value : "N/A" } } as RadarChartOptions); From f3bfc05caddd2a498cbe689b07c207d0794a99e6 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 5 May 2020 09:33:07 +0200 Subject: [PATCH 82/95] Radar: fix typo --- packages/core/src/components/graphs/radar.ts | 10 +++++----- packages/core/src/interfaces/events.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 2d0cf94a98..0880d18e64 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -77,7 +77,7 @@ export class Radar extends Component { return; } - // given a key, return the corrisponding angle in radiants + // given a key, return the corresponding angle in radiants // rotated by -PI/2 because we want angle 0° at -y (12 o’clock) const xScale = scaleBand() .domain(this.displayDataNormalized.map(d => d.key)) @@ -123,13 +123,13 @@ export class Radar extends Component { }; ///////////////////////////// - // Drawing section + // Drawing the radar ///////////////////////////// // y axes const yAxes = DOMUtils.appendOrSelect(this.svg, "g.y-axes").attr("role", Roles.GROUP); const yAxisUpdate = yAxes.selectAll("path").data(yTicks, tick => tick); - // for each tick, create array of data corrisponding to the points composing the shape + // for each tick, create array of data corresponding to the points composing the shape const shapeData = (tick: number) => this.uniqueKeys.map(key => ({ key, value: tick })) as Datum[]; yAxisUpdate.join( enter => enter @@ -344,7 +344,7 @@ export class Radar extends Component { } // Given a flat array of objects, if there are missing data on key, - // creates corrisponding data with value = 0 + // creates corresponding data with value = 0 normalizeFlatData = (dataset: Array) => { const completeBlankData = flatMapDeep(this.uniqueKeys.map(key => { return this.uniqueGroups.map(group => ({ key, [this.groupMapsTo]: group, value: null })); @@ -353,7 +353,7 @@ export class Radar extends Component { } // Given a a grouped array of objects, if there are missing data on key, - // creates corrisponding data with value = 0 + // creates corresponding data with value = 0 normalizeGroupedData = (dataset: Array) => { return dataset.map(({ name, data }) => { const completeBlankData = this.uniqueKeys.map(k => ({ [this.groupMapsTo]: name, key: k, value: null })); diff --git a/packages/core/src/interfaces/events.ts b/packages/core/src/interfaces/events.ts index 7fd4b92ad0..72c458ada4 100644 --- a/packages/core/src/interfaces/events.ts +++ b/packages/core/src/interfaces/events.ts @@ -69,7 +69,7 @@ export enum Line { export enum Radar { X_AXIS_MOUSEOVER = "radar-x-axis-mouseover", X_AXIS_MOUSEMOVE = "radar-x-axis-mousemove", - X_AXIS_CLICK = "radar-x-axisb-click", + X_AXIS_CLICK = "radar-x-axis-click", X_AXIS_MOUSEOUT = "radar-x-axis-mouseout" } From e29769e25920d894493ff4221111ce146c68678c Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 5 May 2020 10:06:48 +0200 Subject: [PATCH 83/95] Radar: update comments --- packages/core/src/components/graphs/radar.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 0880d18e64..f5f18198b7 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -63,7 +63,7 @@ export class Radar extends Component { this.groupMapsTo = options.data.groupMapsTo; this.uniqueKeys = Array.from(new Set(data.map(d => d.key))); - this.uniqueGroups = Array.from(new Set(displayData.map(d => d[this.groupMapsTo]))); + this.uniqueGroups = Array.from(new Set(data.map(d => d[this.groupMapsTo]))); this.displayDataNormalized = this.normalizeFlatData(displayData); this.groupedDataNormalized = this.normalizeGroupedData(groupedData); @@ -344,7 +344,7 @@ export class Radar extends Component { } // Given a flat array of objects, if there are missing data on key, - // creates corresponding data with value = 0 + // creates corresponding data with value = null normalizeFlatData = (dataset: Array) => { const completeBlankData = flatMapDeep(this.uniqueKeys.map(key => { return this.uniqueGroups.map(group => ({ key, [this.groupMapsTo]: group, value: null })); @@ -353,7 +353,7 @@ export class Radar extends Component { } // Given a a grouped array of objects, if there are missing data on key, - // creates corresponding data with value = 0 + // creates corresponding data with value = null normalizeGroupedData = (dataset: Array) => { return dataset.map(({ name, data }) => { const completeBlankData = this.uniqueKeys.map(k => ({ [this.groupMapsTo]: name, key: k, value: null })); From b95a96ec02b48a59499d92cbd3c8a31d66c656dc Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Tue, 5 May 2020 14:28:01 +0200 Subject: [PATCH 84/95] Radar: move lodash methods inside Tools --- packages/core/src/components/graphs/radar.ts | 15 +++++++-------- packages/core/src/tools.ts | 4 ++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index f5f18198b7..181ef14d37 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -4,7 +4,6 @@ import { DOMUtils } from "../../services"; import * as Configuration from "../../configuration"; import { Events, TooltipTypes, Roles } from "../../interfaces"; import { Tools } from "../../tools"; -import { flatMapDeep, kebabCase } from "lodash-es"; import { Point, Angle, @@ -200,7 +199,7 @@ export class Radar extends Component { .append("line") .attr("role", Roles.GRAPHICS_SYMBOL) .attr("opacity", 0) - .attr("class", key => `x-axis-${kebabCase(key)}`) // replace spaces with - + .attr("class", key => `x-axis-${Tools.kebabCase(key)}`) // replace spaces with - .attr("stroke-dasharray", "0") .attr("x1", key => polarToCartesianCoords(xScale(key), 0, c).x) .attr("y1", key => polarToCartesianCoords(xScale(key), 0, c).y) @@ -305,7 +304,7 @@ export class Radar extends Component { update => update, exit => exit.remove() ) - .attr("class", d => kebabCase(d.key)) + .attr("class", d => Tools.kebabCase(d.key)) .attr("cx", d => polarToCartesianCoords(xScale(d.key), yScale(d.value), c).x) .attr("cy", d => polarToCartesianCoords(xScale(d.key), yScale(d.value), c).y) .attr("r", 0) @@ -346,7 +345,7 @@ export class Radar extends Component { // Given a flat array of objects, if there are missing data on key, // creates corresponding data with value = null normalizeFlatData = (dataset: Array) => { - const completeBlankData = flatMapDeep(this.uniqueKeys.map(key => { + const completeBlankData = Tools.flatMapDeep(this.uniqueKeys.map(key => { return this.uniqueGroups.map(group => ({ key, [this.groupMapsTo]: group, value: null })); })); return Tools.merge(completeBlankData, dataset); @@ -407,8 +406,8 @@ export class Radar extends Component { }) .on("mousemove", function(datum) { const hoveredElement = select(this); - const axisLine = self.parent.select(`.x-axes .x-axis-${kebabCase(datum)}`); - const dots = self.parent.selectAll(`.dots circle.${kebabCase(datum)}`); + const axisLine = self.parent.select(`.x-axes .x-axis-${Tools.kebabCase(datum)}`); + const dots = self.parent.selectAll(`.dots circle.${Tools.kebabCase(datum)}`); // Change style axisLine.classed("hovered", true) @@ -443,8 +442,8 @@ export class Radar extends Component { }) .on("mouseout", function(datum) { const hoveredElement = select(this); - const axisLine = self.parent.select(`.x-axes .x-axis-${kebabCase(datum)}`); - const dots = self.parent.selectAll(`.dots circle.${kebabCase(datum)}`); + const axisLine = self.parent.select(`.x-axes .x-axis-${Tools.kebabCase(datum)}`); + const dots = self.parent.selectAll(`.dots circle.${Tools.kebabCase(datum)}`); // Change style axisLine.classed("hovered", false) diff --git a/packages/core/src/tools.ts b/packages/core/src/tools.ts index 2d16b6dd5d..1cbcb2ee61 100644 --- a/packages/core/src/tools.ts +++ b/packages/core/src/tools.ts @@ -12,6 +12,8 @@ import { uniq as lodashUnique, clamp as lodashClamp, isEqual as lodashIsEqual, + flatMapDeep as lodashFlatMapDeep, + kebabCase as lodashKebabCase, // the imports below are needed because of typescript bug (error TS4029) Cancelable, DebounceSettings @@ -26,6 +28,8 @@ export namespace Tools { export const removeArrayDuplicates = lodashUnique; export const clamp = lodashClamp; export const isEqual = lodashIsEqual; + export const flatMapDeep = lodashFlatMapDeep; + export const kebabCase = lodashKebabCase; /** * Returns default chart options merged with provided options, From 6abb99dfec3f4fc77a80ad52f601df258d375c68 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 6 May 2020 10:56:18 +0200 Subject: [PATCH 85/95] Radar: add examples and make radar more generic --- packages/core/demo/data/index.ts | 10 +++ packages/core/demo/data/radar.ts | 87 +++++++++++++++++++- packages/core/src/components/graphs/radar.ts | 67 ++++++++------- packages/core/src/configuration.ts | 4 + packages/core/src/interfaces/charts.ts | 4 + 5 files changed, 136 insertions(+), 36 deletions(-) diff --git a/packages/core/demo/data/index.ts b/packages/core/demo/data/index.ts index 969b51a21b..e48e737787 100644 --- a/packages/core/demo/data/index.ts +++ b/packages/core/demo/data/index.ts @@ -351,6 +351,16 @@ let allDemoGroups = [ data: radarDemos.radarData, options: radarDemos.radarOptions, chartType: chartTypes.RadarChart + }, + { + data: radarDemos.radarWithMissingDataData, + options: radarDemos.radarWithMissingDataOptions, + chartType: chartTypes.RadarChart + }, + { + data: radarDemos.radarDenseData, + options: radarDemos.radarDenseOptions, + chartType: chartTypes.RadarChart } ] } diff --git a/packages/core/demo/data/radar.ts b/packages/core/demo/data/radar.ts index 0a185d414c..b87fbb3790 100644 --- a/packages/core/demo/data/radar.ts +++ b/packages/core/demo/data/radar.ts @@ -1,4 +1,32 @@ + +// simple radar export const radarData = [ + { product: "Product 1", feature: "Price", score: 60 }, + { product: "Product 1", feature: "Usability", score: 92 }, + { product: "Product 1", feature: "Availability", score: 5 }, + { product: "Product 1", feature: "Performance", score: 85 }, + { product: "Product 1", feature: "Quality", score: 60 }, + { product: "Product 2", feature: "Price", score: 70 }, + { product: "Product 2", feature: "Usability", score: 63 }, + { product: "Product 2", feature: "Availability", score: 78 }, + { product: "Product 2", feature: "Performance", score: 50 }, + { product: "Product 2", feature: "Quality", score: 30 } +]; +export const radarOptions = { + title: "Radar", + radar: { + axes: { + angle: "feature", + value: "score" + } + }, + data: { + groupMapsTo: "product" + } +}; + +// radar with missing data +export const radarWithMissingDataData = [ { group: "Sugar", key: "London", value: 25 }, { group: "Oil", key: "London", value: 6 }, { group: "Water", key: "London", value: 12 }, @@ -14,7 +42,62 @@ export const radarData = [ { group: "Sugar", key: "Sydney", value: 12 }, { group: "Oil", key: "Sydney", value: 16 } ]; +export const radarWithMissingDataOptions = { + title: "Radar - Missing data" +}; -export const radarOptions = { - title: "Radar" +// radar dense +export const radarDenseData = [ + { month: "January", activity: "Eating", hoursAvg: 2 }, + { month: "January", activity: "Drinking", hoursAvg: 6 }, + { month: "January", activity: "Sleeping", hoursAvg: 6 }, + { month: "January", activity: "Working", hoursAvg: 8 }, + { month: "January", activity: "Walking", hoursAvg: 1 }, + { month: "January", activity: "Running", hoursAvg: 0.5 }, + { month: "January", activity: "Cycling", hoursAvg: 1 }, + { month: "January", activity: "Swimming", hoursAvg: 0 }, + { month: "February", activity: "Eating", hoursAvg: 1.5 }, + { month: "February", activity: "Drinking", hoursAvg: 9 }, + { month: "February", activity: "Sleeping", hoursAvg: 7 }, + { month: "February", activity: "Working", hoursAvg: 9 }, + { month: "February", activity: "Walking", hoursAvg: 2 }, + { month: "February", activity: "Running", hoursAvg: 2 }, + { month: "February", activity: "Cycling", hoursAvg: 0 }, + { month: "February", activity: "Swimming", hoursAvg: 1.5 }, + { month: "March", activity: "Eating", hoursAvg: 3 }, + { month: "March", activity: "Drinking", hoursAvg: 5 }, + { month: "March", activity: "Sleeping", hoursAvg: 5 }, + { month: "March", activity: "Working", hoursAvg: 6 }, + { month: "March", activity: "Walking", hoursAvg: 3 }, + { month: "March", activity: "Running", hoursAvg: 9 }, + { month: "March", activity: "Cycling", hoursAvg: 1 }, + { month: "March", activity: "Swimming", hoursAvg: 7 }, + { month: "April", activity: "Eating", hoursAvg: 5 }, + { month: "April", activity: "Drinking", hoursAvg: 1 }, + { month: "April", activity: "Sleeping", hoursAvg: 4 }, + { month: "April", activity: "Working", hoursAvg: 2 }, + { month: "April", activity: "Walking", hoursAvg: 5 }, + { month: "April", activity: "Running", hoursAvg: 4 }, + { month: "April", activity: "Cycling", hoursAvg: 6 }, + { month: "April", activity: "Swimming", hoursAvg: 3 }, + { month: "May", activity: "Eating", hoursAvg: 7 }, + { month: "May", activity: "Drinking", hoursAvg: 0 }, + { month: "May", activity: "Sleeping", hoursAvg: 5 }, + { month: "May", activity: "Working", hoursAvg: 4 }, + { month: "May", activity: "Walking", hoursAvg: 8 }, + { month: "May", activity: "Running", hoursAvg: 2 }, + { month: "May", activity: "Cycling", hoursAvg: 3 }, + { month: "May", activity: "Swimming", hoursAvg: 1 } +]; +export const radarDenseOptions = { + title: "Radar - Dense", + radar: { + axes: { + angle: "activity", + value: "hoursAvg" + } + }, + data: { + groupMapsTo: "month" + } }; diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 181ef14d37..fe5a16be8d 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -22,24 +22,14 @@ import { lineRadial, curveLinearClosed } from "d3-shape"; // used to make transitions let oldYScale: ScaleLinear; -interface Datum { - group?: string; - key: string; - value: number; -} -interface GroupedDatum { - name: string; - data: Array; -} - export class Radar extends Component { type = "radar"; svg: SVGElement; groupMapsTo: string; uniqueKeys: string[]; uniqueGroups: string[]; - displayDataNormalized: Array; - groupedDataNormalized: Array; + displayDataNormalized: any; + groupedDataNormalized: any; init() { const { events } = this.services; @@ -53,16 +43,17 @@ export class Radar extends Component { this.svg = this.getContainerSVG(); const { width, height } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true }); - const data: Array = this.model.getData(); - const displayData: Array = this.model.getDisplayData(); + const data = this.model.getData(); + const displayData = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); const options = this.model.getOptions(); const configuration = Configuration.options.radarChart.radar; + const groupMapsTo = options.data.groupMapsTo; + const { angle, value } = options.radar.axes; const { xLabelPadding, yLabelPadding, yTicksNumber, minRange, xAxisRectHeight, opacity } = configuration; - this.groupMapsTo = options.data.groupMapsTo; - this.uniqueKeys = Array.from(new Set(data.map(d => d.key))); - this.uniqueGroups = Array.from(new Set(data.map(d => d[this.groupMapsTo]))); + this.uniqueKeys = Array.from(new Set(data.map(d => d[angle]))); + this.uniqueGroups = Array.from(new Set(data.map(d => d[groupMapsTo]))); this.displayDataNormalized = this.normalizeFlatData(displayData); this.groupedDataNormalized = this.normalizeGroupedData(groupedData); @@ -79,11 +70,11 @@ export class Radar extends Component { // given a key, return the corresponding angle in radiants // rotated by -PI/2 because we want angle 0° at -y (12 o’clock) const xScale = scaleBand() - .domain(this.displayDataNormalized.map(d => d.key)) + .domain(this.displayDataNormalized.map(d => d[angle])) .range([0, 2 * Math.PI].map(a => a - Math.PI / 2) as [Angle, Angle]); const yScale = scaleLinear() - .domain([0, max(this.displayDataNormalized.map(d => d.value))]) + .domain([0, max(this.displayDataNormalized.map(d => d[value]) as number[])]) .range([minRange, radius]) .nice(yTicksNumber); const yTicks = yScale.ticks(yTicksNumber); @@ -93,16 +84,16 @@ export class Radar extends Component { // constructs a new radial line generator // the angle accessor returns the angle in radians with 0° at -y (12 o’clock) // so map back the angle - const radialLineGenerator = lineRadial() - .angle(d => xScale(d.key) + Math.PI / 2) - .radius(d => yScale(d.value)) + const radialLineGenerator = lineRadial() + .angle(d => xScale(d[angle]) + Math.PI / 2) + .radius(d => yScale(d[value])) .curve(curveLinearClosed); // this line generator is necessary in order to make a transition of a value from the // position it occupies using the old scale to the position it occupies using the new scale - const oldRadialLineGenerator = lineRadial() + const oldRadialLineGenerator = lineRadial() .angle(radialLineGenerator.angle()) - .radius(d => oldYScale ? oldYScale(d.value) : minRange) + .radius(d => oldYScale ? oldYScale(d[value]) : minRange) .curve(radialLineGenerator.curve()); // compute the space that each x label needs @@ -129,7 +120,7 @@ export class Radar extends Component { const yAxes = DOMUtils.appendOrSelect(this.svg, "g.y-axes").attr("role", Roles.GROUP); const yAxisUpdate = yAxes.selectAll("path").data(yTicks, tick => tick); // for each tick, create array of data corresponding to the points composing the shape - const shapeData = (tick: number) => this.uniqueKeys.map(key => ({ key, value: tick })) as Datum[]; + const shapeData = (tick: number) => this.uniqueKeys.map(key => ({ [angle]: key, [value]: tick })); yAxisUpdate.join( enter => enter .append("path") @@ -304,12 +295,12 @@ export class Radar extends Component { update => update, exit => exit.remove() ) - .attr("class", d => Tools.kebabCase(d.key)) - .attr("cx", d => polarToCartesianCoords(xScale(d.key), yScale(d.value), c).x) - .attr("cy", d => polarToCartesianCoords(xScale(d.key), yScale(d.value), c).y) + .attr("class", d => Tools.kebabCase(d[angle])) + .attr("cx", d => polarToCartesianCoords(xScale(d[angle]), yScale(d[value]), c).x) + .attr("cy", d => polarToCartesianCoords(xScale(d[angle]), yScale(d[value]), c).y) .attr("r", 0) .attr("opacity", 0) - .attr("fill", d => colorScale(d[this.groupMapsTo])); + .attr("fill", d => colorScale(d[groupMapsTo])); // rectangles const xAxesRect = DOMUtils.appendOrSelect(this.svg, "g.x-axes-rect").attr("role", Roles.GROUP); @@ -344,18 +335,24 @@ export class Radar extends Component { // Given a flat array of objects, if there are missing data on key, // creates corresponding data with value = null - normalizeFlatData = (dataset: Array) => { + normalizeFlatData = (dataset: any) => { + const options = this.model.getOptions(); + const { angle, value } = options.radar.axes; + const groupMapsTo = options.data.groupMapsTo; const completeBlankData = Tools.flatMapDeep(this.uniqueKeys.map(key => { - return this.uniqueGroups.map(group => ({ key, [this.groupMapsTo]: group, value: null })); + return this.uniqueGroups.map(group => ({ [angle]: key, [groupMapsTo]: group, [value]: null })); })); return Tools.merge(completeBlankData, dataset); } // Given a a grouped array of objects, if there are missing data on key, // creates corresponding data with value = null - normalizeGroupedData = (dataset: Array) => { + normalizeGroupedData = (dataset: any) => { + const options = this.model.getOptions(); + const { angle, value } = options.radar.axes; + const groupMapsTo = options.data.groupMapsTo; return dataset.map(({ name, data }) => { - const completeBlankData = this.uniqueKeys.map(k => ({ [this.groupMapsTo]: name, key: k, value: null })); + const completeBlankData = this.uniqueKeys.map(k => ({ [groupMapsTo]: name, [angle]: k, [value]: null })); return { name, data: Tools.merge(completeBlankData, data) }; }); } @@ -394,6 +391,8 @@ export class Radar extends Component { addEventListeners() { const self = this; + const configuration = Configuration.options.radarChart.radar.axes; + const { angle, value } = configuration; // events on x axes rects this.parent.selectAll(".x-axes-rect > rect") @@ -424,7 +423,7 @@ export class Radar extends Component { }); // get the items that should be highlighted - const itemsToHighlight = self.displayDataNormalized.filter(d => d.key === datum); + const itemsToHighlight = self.displayDataNormalized.filter(d => d[angle] === datum); // Show tooltip self.services.events.dispatchEvent(Events.Tooltip.SHOW, { diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index d9ad2d6add..7329cceaf3 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -280,6 +280,10 @@ const donutChart: DonutChartOptions = Tools.merge({}, pieChart, { */ const radarChart: RadarChartOptions = Tools.merge({}, chart, { radar: { + axes: { + angle: "key", + value: "value" + }, opacity: { unselected: 0.1, selected: 0.3 diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 69a91cef4f..8593a9ddf8 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -198,6 +198,10 @@ export interface RadarChartOptions extends BaseChartOptions { opacity: { unselected: number, selected: number + }, + axes: { + angle: string, + value: string } xLabelPadding: number, yLabelPadding: number, From 7c2e47d228cf649473c6a6ffa451a5e12558cc54 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 6 May 2020 11:13:28 +0200 Subject: [PATCH 86/95] Radar: fix tooltip value --- .../core/src/components/essentials/tooltip-radar.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/essentials/tooltip-radar.ts b/packages/core/src/components/essentials/tooltip-radar.ts index 0777be2374..351eff6428 100644 --- a/packages/core/src/components/essentials/tooltip-radar.ts +++ b/packages/core/src/components/essentials/tooltip-radar.ts @@ -8,14 +8,14 @@ export class TooltipRadar extends Tooltip { return "
    " + data.map(datum => { - const { groupMapsTo } = this.model.getOptions().data; - - const rangeIdentifier = "value"; + const options = this.model.getOptions(); + const { groupMapsTo } = options.data; + const { angle, value } = options.radar.axes; const userProvidedValueFormatter = Tools.getProperty(this.model.getOptions(), "tooltip", "valueFormatter"); const formattedValue = userProvidedValueFormatter - ? userProvidedValueFormatter(datum[rangeIdentifier]) - : datum[rangeIdentifier]; + ? userProvidedValueFormatter(datum[value]) + : datum[value]; // For the tooltip color, we always want the normal stroke color, not dynamically determined by data value. const indicatorColor = this.model.getStrokeColor(datum[groupMapsTo]); From 7066dc2c8765cd4b2d2d2dc1e4fd5e615b7c8995 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 6 May 2020 11:14:38 +0200 Subject: [PATCH 87/95] Radar: fix tooltip data order --- .../core/src/components/essentials/tooltip-radar.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/components/essentials/tooltip-radar.ts b/packages/core/src/components/essentials/tooltip-radar.ts index 351eff6428..4906fe371c 100644 --- a/packages/core/src/components/essentials/tooltip-radar.ts +++ b/packages/core/src/components/essentials/tooltip-radar.ts @@ -3,15 +3,15 @@ import { Tools } from "../../tools"; export class TooltipRadar extends Tooltip { getMultilineTooltipHTML(data: any) { + const options = this.model.getOptions(); + const { groupMapsTo } = options.data; + const { angle, value } = options.radar.axes; + // sort them so they are in the same order as the graph - data.sort((a, b) => b.value - a.value); + data.sort((a, b) => b[value] - a[value]); return "
      " + data.map(datum => { - const options = this.model.getOptions(); - const { groupMapsTo } = options.data; - const { angle, value } = options.radar.axes; - const userProvidedValueFormatter = Tools.getProperty(this.model.getOptions(), "tooltip", "valueFormatter"); const formattedValue = userProvidedValueFormatter ? userProvidedValueFormatter(datum[value]) From 158be110ef481c16fcb2101f9b9d8ec034bd1172 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 6 May 2020 11:27:07 +0200 Subject: [PATCH 88/95] Radar: fix radar default formatter --- packages/core/src/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index 7329cceaf3..a5a50ab315 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -298,7 +298,7 @@ const radarChart: RadarChartOptions = Tools.merge({}, chart, { gridline: { enabled: true }, - valueFormatter: value => value ? value : "N/A" + valueFormatter: value => value !== null && value !== undefined ? value : "N/A" } } as RadarChartOptions); From f5e64dfd50498c0fa140f54dbc4fd3c208763a9f Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 6 May 2020 11:29:08 +0200 Subject: [PATCH 89/95] Radar: fix showing dots --- packages/core/src/components/graphs/radar.ts | 5 +---- packages/core/src/configuration.ts | 6 ------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index fe5a16be8d..7eb780faf1 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -391,8 +391,7 @@ export class Radar extends Component { addEventListeners() { const self = this; - const configuration = Configuration.options.radarChart.radar.axes; - const { angle, value } = configuration; + const { angle, value } = this.model.getOptions().radar.axes; // events on x axes rects this.parent.selectAll(".x-axes-rect > rect") @@ -412,7 +411,6 @@ export class Radar extends Component { axisLine.classed("hovered", true) .attr("stroke-dasharray", "4 4"); dots.classed("hovered", true) - .transition(self.services.transitions.getTransition("radar_dots_mouseover")) .attr("opacity", 1) .attr("r", 5); @@ -448,7 +446,6 @@ export class Radar extends Component { axisLine.classed("hovered", false) .attr("stroke-dasharray", "0"); dots.classed("hovered", false) - .transition(self.services.transitions.getTransition("radar_dots_mouseout")) .attr("opacity", 0) .attr("r", 0); diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index a5a50ab315..c72f04d43d 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -344,12 +344,6 @@ export const transitions = { }, graph_element_mouseout_fill_update: { duration: 100 - }, - radar_dots_mouseover: { - duration: 80 - }, - radar_dots_mouseout: { - duration: 80 } }; From ee6bf99aba2f1a0b13ac61a621635bfaddbbf158 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Wed, 6 May 2020 11:29:50 +0200 Subject: [PATCH 90/95] Radar: use variable instead of recompute value --- packages/core/src/components/essentials/tooltip-radar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/essentials/tooltip-radar.ts b/packages/core/src/components/essentials/tooltip-radar.ts index 4906fe371c..6faf280f90 100644 --- a/packages/core/src/components/essentials/tooltip-radar.ts +++ b/packages/core/src/components/essentials/tooltip-radar.ts @@ -12,7 +12,7 @@ export class TooltipRadar extends Tooltip { return "
        " + data.map(datum => { - const userProvidedValueFormatter = Tools.getProperty(this.model.getOptions(), "tooltip", "valueFormatter"); + const userProvidedValueFormatter = Tools.getProperty(options, "tooltip", "valueFormatter"); const formattedValue = userProvidedValueFormatter ? userProvidedValueFormatter(datum[value]) : datum[value]; From 2dd2b3fe810dee2d3831bb38ca469cf1a5c1b376 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 7 May 2020 08:19:19 +0200 Subject: [PATCH 91/95] Radar: create enums from textAnchor and dominantBaseline values --- packages/core/src/interfaces/enums.ts | 18 ++++++++++++++++++ packages/core/src/services/angle-utils.ts | 22 ++++++++++++---------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/core/src/interfaces/enums.ts b/packages/core/src/interfaces/enums.ts index 7416ca237d..c6f83571cb 100644 --- a/packages/core/src/interfaces/enums.ts +++ b/packages/core/src/interfaces/enums.ts @@ -104,3 +104,21 @@ export enum CalloutDirections { LEFT = "left", RIGHT = "right" } + +/** + * enum of all possible attributes used to aling text horizontally + */ +export enum TextAnchor { + START = "start", + MIDDLE = "middle", + END = "end" +} + +/** + * enum of all possible attributes used to aling text vertically + */ +export enum DominantBaseline { + BASELINE = "baseline", + MIDDLE = "middle", + HANGING = "hanging" +} diff --git a/packages/core/src/services/angle-utils.ts b/packages/core/src/services/angle-utils.ts index bbbbf5ba2b..c76aec56e2 100644 --- a/packages/core/src/services/angle-utils.ts +++ b/packages/core/src/services/angle-utils.ts @@ -1,3 +1,5 @@ +import { TextAnchor, DominantBaseline } from "../interfaces/enums"; + export interface Point { x: number; y: number; @@ -6,29 +8,29 @@ export interface Point { export type Angle = number; interface LabelAlignment { - textAnchor: "start" | "middle" | "end"; // *___ __*__ ___* - dominantBaseline: "baseline" | "middle" | "hanging"; // __* --*-- --. + textAnchor: TextAnchor; + dominantBaseline: DominantBaseline; } export function radialLabelPlacement(angleRadians: Angle): LabelAlignment { const angle = mod(radToDeg(angleRadians), 360); if (isInRange(angle, [0, 10]) || isInRange(angle, [350, 0])) { - return { textAnchor: "start", dominantBaseline: "middle" }; + return { textAnchor: TextAnchor.START, dominantBaseline: DominantBaseline.MIDDLE }; } else if (isInRange(angle, [10, 80])) { - return { textAnchor: "start", dominantBaseline: "hanging" }; + return { textAnchor: TextAnchor.START, dominantBaseline: DominantBaseline.HANGING }; } else if (isInRange(angle, [80, 100])) { - return { textAnchor: "middle", dominantBaseline: "hanging" }; + return { textAnchor: TextAnchor.MIDDLE, dominantBaseline: DominantBaseline.HANGING }; } else if (isInRange(angle, [100, 170])) { - return { textAnchor: "end", dominantBaseline: "hanging" }; + return { textAnchor: TextAnchor.END, dominantBaseline: DominantBaseline.HANGING }; } else if (isInRange(angle, [170, 190])) { - return { textAnchor: "end", dominantBaseline: "middle" }; + return { textAnchor: TextAnchor.END, dominantBaseline: DominantBaseline.MIDDLE }; } else if (isInRange(angle, [190, 260])) { - return { textAnchor: "end", dominantBaseline: "baseline" }; + return { textAnchor: TextAnchor.END, dominantBaseline: DominantBaseline.BASELINE }; } else if (isInRange(angle, [260, 280])) { - return { textAnchor: "middle", dominantBaseline: "baseline" }; + return { textAnchor: TextAnchor.MIDDLE, dominantBaseline: DominantBaseline.BASELINE }; } else { // 280 - 350 - return { textAnchor: "start", dominantBaseline: "baseline" }; + return { textAnchor: TextAnchor.START, dominantBaseline: DominantBaseline.BASELINE }; } } From 3f349c35ecd8acdc981380f09cc4b5636052dec8 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 7 May 2020 08:25:54 +0200 Subject: [PATCH 92/95] Radar: put dots radius to radar configuration --- packages/core/src/components/graphs/radar.ts | 4 ++-- packages/core/src/configuration.ts | 3 ++- packages/core/src/interfaces/charts.ts | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index 7eb780faf1..c1c1adefe7 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -391,7 +391,7 @@ export class Radar extends Component { addEventListeners() { const self = this; - const { angle, value } = this.model.getOptions().radar.axes; + const { axes: { angle }, dotsRadius } = Tools.getProperty(this.model.getOptions(), "radar"); // events on x axes rects this.parent.selectAll(".x-axes-rect > rect") @@ -412,7 +412,7 @@ export class Radar extends Component { .attr("stroke-dasharray", "4 4"); dots.classed("hovered", true) .attr("opacity", 1) - .attr("r", 5); + .attr("r", dotsRadius); // Dispatch mouse event self.services.events.dispatchEvent(Events.Radar.X_AXIS_MOUSEMOVE, { diff --git a/packages/core/src/configuration.ts b/packages/core/src/configuration.ts index c72f04d43d..66608c71b8 100644 --- a/packages/core/src/configuration.ts +++ b/packages/core/src/configuration.ts @@ -292,7 +292,8 @@ const radarChart: RadarChartOptions = Tools.merge({}, chart, { yLabelPadding: 8, yTicksNumber: 4, minRange: 10, - xAxisRectHeight: 50 + xAxisRectHeight: 50, + dotsRadius: 5 }, tooltip: { gridline: { diff --git a/packages/core/src/interfaces/charts.ts b/packages/core/src/interfaces/charts.ts index 8593a9ddf8..3d3b77dc3a 100644 --- a/packages/core/src/interfaces/charts.ts +++ b/packages/core/src/interfaces/charts.ts @@ -207,6 +207,7 @@ export interface RadarChartOptions extends BaseChartOptions { yLabelPadding: number, yTicksNumber: number, minRange: number, - xAxisRectHeight: number + xAxisRectHeight: number, + dotsRadius: number }; } From d2db359a07bb6618245d6e1a3be9da7e14461fe8 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 7 May 2020 08:35:11 +0200 Subject: [PATCH 93/95] Radar: use tabs instead of spaces --- packages/core/src/styles/graphs/_radar.scss | 25 ++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/core/src/styles/graphs/_radar.scss b/packages/core/src/styles/graphs/_radar.scss index 9d831137ed..d461571124 100644 --- a/packages/core/src/styles/graphs/_radar.scss +++ b/packages/core/src/styles/graphs/_radar.scss @@ -1,23 +1,22 @@ .#{$prefix}--#{$charts-prefix}--radar { - .blobs path { - stroke-width: 1.5px; + .blobs path { + stroke-width: 1.5px; } - - .y-axes path, - .x-axes line { - stroke-width: 1px; - stroke: $ui-03; - } - .x-axes line.hovered { - @if $ui-background == map-get($carbon--theme--g90, 'ui-background') { + .y-axes path, + .x-axes line { + stroke-width: 1px; + stroke: $ui-03; + } + + .x-axes line.hovered { + @if $ui-background == map-get($carbon--theme--g90, "ui-background") { stroke: $carbon--white-0; - } @else if $ui-background == map-get($carbon--theme--g100, 'ui-background') { + } @else if $ui-background == map-get($carbon--theme--g100, "ui-background") { stroke: $carbon--white-0; } @else { stroke: $carbon--black-100; } - } + } } - From 3a94dbf711e0b905facd4566bafaf10c4d9e0d87 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 7 May 2020 08:35:44 +0200 Subject: [PATCH 94/95] Radar: use helper function for accesing properties --- packages/core/src/components/graphs/radar.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index c1c1adefe7..ebf7fce7a2 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -47,9 +47,9 @@ export class Radar extends Component { const displayData = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); const options = this.model.getOptions(); - const configuration = Configuration.options.radarChart.radar; - const groupMapsTo = options.data.groupMapsTo; - const { angle, value } = options.radar.axes; + const configuration = Tools.getProperty(Configuration, "options", "radarChart", "radar"); + const { angle, value } = Tools.getProperty(options, "radar", "axes"); + const groupMapsTo = Tools.getProperty(options, "data", "groupMapsTo"); const { xLabelPadding, yLabelPadding, yTicksNumber, minRange, xAxisRectHeight, opacity } = configuration; this.uniqueKeys = Array.from(new Set(data.map(d => d[angle]))); @@ -337,8 +337,8 @@ export class Radar extends Component { // creates corresponding data with value = null normalizeFlatData = (dataset: any) => { const options = this.model.getOptions(); - const { angle, value } = options.radar.axes; - const groupMapsTo = options.data.groupMapsTo; + const { angle, value } = Tools.getProperty(options, "radar", "axes"); + const groupMapsTo = Tools.getProperty(options, "data", "groupMapsTo"); const completeBlankData = Tools.flatMapDeep(this.uniqueKeys.map(key => { return this.uniqueGroups.map(group => ({ [angle]: key, [groupMapsTo]: group, [value]: null })); })); @@ -349,8 +349,8 @@ export class Radar extends Component { // creates corresponding data with value = null normalizeGroupedData = (dataset: any) => { const options = this.model.getOptions(); - const { angle, value } = options.radar.axes; - const groupMapsTo = options.data.groupMapsTo; + const { angle, value } = Tools.getProperty(options, "radar", "axes"); + const groupMapsTo = Tools.getProperty(options, "data", "groupMapsTo"); return dataset.map(({ name, data }) => { const completeBlankData = this.uniqueKeys.map(k => ({ [groupMapsTo]: name, [angle]: k, [value]: null })); return { name, data: Tools.merge(completeBlankData, data) }; @@ -359,7 +359,7 @@ export class Radar extends Component { handleLegendOnHover = (event: CustomEvent) => { const { hoveredElement } = event.detail; - const { opacity } = Configuration.options.radarChart.radar; + const { opacity } = Tools.getProperty(Configuration, "options", "radarChart", "radar"); this.parent.selectAll("g.blobs path") .transition(this.services.transitions.getTransition("legend-hover-blob")) .style("fill-opacity", group => { @@ -371,7 +371,7 @@ export class Radar extends Component { } handleLegendMouseOut = (event: CustomEvent) => { - const { opacity } = Configuration.options.radarChart.radar; + const { opacity } = Tools.getProperty(Configuration, "options", "radarChart", "radar"); this.parent.selectAll("g.blobs path") .transition(this.services.transitions.getTransition("legend-mouseout-blob")) .style("fill-opacity", opacity.selected); From 7cdc17c2d7cb8bcd1f2e8de1d9918ac209fcf110 Mon Sep 17 00:00:00 2001 From: Ilaria Venturini Date: Thu, 7 May 2020 15:05:58 +0200 Subject: [PATCH 95/95] Radar: get property from options --- packages/core/src/components/graphs/radar.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/graphs/radar.ts b/packages/core/src/components/graphs/radar.ts index ebf7fce7a2..6764f591f6 100644 --- a/packages/core/src/components/graphs/radar.ts +++ b/packages/core/src/components/graphs/radar.ts @@ -1,7 +1,6 @@ // Internal Imports import { Component } from "../component"; import { DOMUtils } from "../../services"; -import * as Configuration from "../../configuration"; import { Events, TooltipTypes, Roles } from "../../interfaces"; import { Tools } from "../../tools"; import { @@ -47,10 +46,9 @@ export class Radar extends Component { const displayData = this.model.getDisplayData(); const groupedData = this.model.getGroupedData(); const options = this.model.getOptions(); - const configuration = Tools.getProperty(Configuration, "options", "radarChart", "radar"); const { angle, value } = Tools.getProperty(options, "radar", "axes"); const groupMapsTo = Tools.getProperty(options, "data", "groupMapsTo"); - const { xLabelPadding, yLabelPadding, yTicksNumber, minRange, xAxisRectHeight, opacity } = configuration; + const { xLabelPadding, yLabelPadding, yTicksNumber, minRange, xAxisRectHeight, opacity } = Tools.getProperty(options, "radar"); this.uniqueKeys = Array.from(new Set(data.map(d => d[angle]))); this.uniqueGroups = Array.from(new Set(data.map(d => d[groupMapsTo]))); @@ -359,22 +357,22 @@ export class Radar extends Component { handleLegendOnHover = (event: CustomEvent) => { const { hoveredElement } = event.detail; - const { opacity } = Tools.getProperty(Configuration, "options", "radarChart", "radar"); + const opacity = Tools.getProperty(this.model.getOptions(), "radar", "opacity"); this.parent.selectAll("g.blobs path") .transition(this.services.transitions.getTransition("legend-hover-blob")) .style("fill-opacity", group => { if (group.name !== hoveredElement.datum().name) { - return opacity.unselected; + return Tools.getProperty(opacity, "unselected"); } - return opacity.selected; + return Tools.getProperty(opacity, "selected"); }); } handleLegendMouseOut = (event: CustomEvent) => { - const { opacity } = Tools.getProperty(Configuration, "options", "radarChart", "radar"); + const opacity = Tools.getProperty(this.model.getOptions(), "radar", "opacity"); this.parent.selectAll("g.blobs path") .transition(this.services.transitions.getTransition("legend-mouseout-blob")) - .style("fill-opacity", opacity.selected); + .style("fill-opacity", Tools.getProperty(opacity, "selected")); } destroy() {