Skip to content

Commit

Permalink
Merge pull request carbon-design-system#639 from sophiiae/selectedGroups
Browse files Browse the repository at this point in the history
* Add selectedGroups option

* Bind selectedGroups with legend and dataGroups

* improvement: move selectedGroup under data option

re carbon-design-system#610

* refactor: update selectedGroup position

* improvement: grab options from getOptions

* improvement: fix naming and update comments

* improvement: simplify the code

* improvement: remove updateSelectedGroups

* fix: remove updateSelectedGroups from legend

* test: add unit test for setting up selectedGroups

* test: modify unit test

* test: renaming

* test: revise unit test

* test: update test description

* style: revise the comment for selectedGroup

* test: update test

* test: simplify the test code

* style: format the code

* test: add more tests

* test: remove tests for events

* test: remove async function

* fix: fix check mark position issue and add demos

* fix: fix multiple check marks bug

* fix: fix chart shrinks between legend click for selected groups

* fix: fix legend height change with first legend click

* fix: fix demo broken

* refactor: renaming

* refactor: get legend height

* remove semicolon

Co-authored-by: Eliad Moosavi <theiliad@users.noreply.github.com>

* fix: fix data domain

* refactor: rename the demo

* fix: fix angular and react height issue

Co-authored-by: Fei <fei-fei.zheng@ibm.com>
Co-authored-by: Eliad Moosavi <theiliad@users.noreply.github.com>
  • Loading branch information
3 people authored and linhenry0417 committed Jul 23, 2020
1 parent cd1fb9e commit e10c1f2
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 15 deletions.
19 changes: 19 additions & 0 deletions packages/core/demo/data/bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ export const groupedBarOptions = {
}
};

export const groupedBarSelectedGroupsData = groupedBarData;

// Grouped bar with selected groups option
export const groupedBarSelectedGroupsOptions = {
title: "Grouped bar (selected groups)",
data: {
selectedGroups: ["Dataset 1", "Dataset 3"]
},
axes: {
left: {
mapsTo: "value",
},
bottom: {
scaleType: "labels",
mapsTo: "key",
},
},
};

// Horizontal Grouped
export const groupedHorizontalBarData = groupedBarData;

Expand Down
10 changes: 10 additions & 0 deletions packages/core/demo/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ let allDemoGroups = [
data: barDemos.groupedBarData,
chartType: chartTypes.GroupedBarChart
},
{
options: barDemos.groupedBarSelectedGroupsOptions,
data: barDemos.groupedBarSelectedGroupsData,
chartType: chartTypes.GroupedBarChart
},
{
options: barDemos.groupedBarEmptyStateOptions,
data: barDemos.groupedBarEmptyStateData,
Expand Down Expand Up @@ -383,6 +388,11 @@ let allDemoGroups = [
data: lineDemos.lineData,
chartType: chartTypes.LineChart
},
{
options: lineDemos.lineSelectedGroupsOptions,
data: lineDemos.lineSelectedGroupsData,
chartType: chartTypes.LineChart,
},
{
options: lineDemos.lineTimeSeriesRotatedTicksOptions,
data: lineDemos.lineTimeSeriesDataRotatedTicks,
Expand Down
46 changes: 44 additions & 2 deletions packages/core/demo/data/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const lineLongLabelOptions = {
};

export const lineCustomDomainOptions = {
title: "Line (discrete with custom domain)",
title: "Line (discrete with customized domain)",
axes: {
bottom: {
title: "2019 Annual Sales Figures",
Expand All @@ -118,6 +118,48 @@ export const lineCustomDomainOptions = {
}
};

export const lineSelectedGroupsData = [
{ group: "Dataset 1", key: "Qty", value: 34200 },
{ group: "Dataset 1", key: "More", value: 23500 },
{ group: "Dataset 1", key: "Sold", value: 53100 },
{ group: "Dataset 1", key: "Restocking", value: 42300 },
{ group: "Dataset 1", key: "Misc", value: 12300 },
{ group: "Dataset 2", key: "Qty", value: 34200 },
{ group: "Dataset 2", key: "More", value: 56000 },
{ group: "Dataset 2", key: "Sold", value: 42300 },
{ group: "Dataset 2", key: "Restocking", value: 21400 },
{ group: "Dataset 2", key: "Misc", value: 0 },
{ group: "Dataset 3", key: "Qty", value: 41200 },
{ group: "Dataset 3", key: "More", value: 18400 },
{ group: "Dataset 3", key: "Sold", value: 34210 },
{ group: "Dataset 3", key: "Restocking", value: 1400 },
{ group: "Dataset 3", key: "Misc", value: 42100 },
{ group: "Dataset 4", key: "Qty", value: 22000 },
{ group: "Dataset 4", key: "More", value: 1200 },
{ group: "Dataset 4", key: "Sold", value: 9000 },
{ group: "Dataset 4", key: "Restocking", value: 24000, audienceSize: 10 },
{ group: "Dataset 4", key: "Misc", value: 3000, audienceSize: 10 },
];

export const lineSelectedGroupsOptions = {
title: "Line (selected groups)",
data: {
selectedGroups: ["Dataset 1", "Dataset 3"]
},
axes: {
bottom: {
title: "2019 Annual Sales Figures",
mapsTo: "key",
scaleType: "labels",
},
left: {
mapsTo: "value",
title: "Conversion rate",
scaleType: "linear",
},
},
};

export const lineTimeSeriesData = [
{ group: "Dataset 1", date: new Date(2019, 0, 1), value: 50000 },
{ group: "Dataset 1", date: new Date(2019, 0, 5), value: 65000 },
Expand Down Expand Up @@ -159,7 +201,7 @@ export const lineTimeSeriesOptions = {
};

export const lineTimeSeriesCustomDomainOptions = {
title: "Line (time series with custom domain)",
title: "Line (time series with customized domain)",
axes: {
bottom: {
title: "2019 Annual Sales Figures",
Expand Down
24 changes: 18 additions & 6 deletions packages/core/src/components/essentials/legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ export class Legend extends Component {
const addedLegendItems = legendItems
.enter()
.append("g")
.classed("legend-item", true);
.classed("legend-item", true)
.classed("active", function (d, i) {
return d.status === options.legend.items.status.ACTIVE;
});

// Configs
const checkboxRadius = options.legend.checkbox.radius;
Expand Down Expand Up @@ -216,14 +219,24 @@ export class Legend extends Component {
legendItem
.select("text")
.attr("x", startingPoint + spaceNeededForCheckbox)
.attr("y", yOffset + yPosition + 2);
.attr("y", yOffset + yPosition + 3);

lastYPosition = yPosition;

// Test if legendItems are placed in the correct direction
const testHorizontal = (!legendOrientation ||
legendOrientation === LegendOrientations.HORIZONTAL) &&
legendItem.select("rect.checkbox").attr("y") === '0';

const testVertical = legendOrientation === LegendOrientations.VERTICAL &&
legendItem.select("rect.checkbox").attr("x") === '0';

const hasCorrectLegendDirection = testHorizontal || testVertical;

// Render checkbox check icon
if (
hasDeactivatedItems &&
legendItem.select("g.check").empty()
if (hasDeactivatedItems &&
legendItem.select("g.check").empty() &&
hasCorrectLegendDirection
) {
legendItem.append("g").classed("check", true).html(`
<svg focusable="false" preserveAspectRatio="xMidYMid meet"
Expand Down Expand Up @@ -281,7 +294,6 @@ export class Legend extends Component {

// Configs
const checkboxRadius = options.legend.checkbox.radius;

const hoveredItem = select(this);
hoveredItem
.append("rect")
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/components/layout/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,18 @@ export class LayoutComponent extends Component {
select(this),
{ useBBox: true }
);

if (d.data.id === "legend") {
const svgSize = DOMUtils.getSVGElementSize(
select(this),
{ useAttrs: true }
);

if (svgSize.height < 40) {
matchingSVGDimensions.height = svgSize.height;
}
}

if (growth === LayoutGrowth.PREFERRED) {
const matchingSVGWidth = horizontal
? matchingSVGDimensions.width
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ const chart: BaseChartOptions = {
},
data: {
groupMapsTo: "group",
loading: false
loading: false,
selectedGroups: []
},
color: {
scale: null
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/interfaces/charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ export interface BaseChartOptions {
* used to simulate data loading
*/
loading?: Boolean;
/**
* options related to pre-selected data groups
* Remains empty if every legend item is active or dataset doesn't have the data groups.
*/
selectedGroups?: string[];
};
/**
* options related to color scales
Expand Down
60 changes: 55 additions & 5 deletions packages/core/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,11 @@ export class ChartModel {
this.services = services;
}

getDisplayData() {
getAllDataFromDomain() {
if (!this.get("data")) {
return null;
}

const { ACTIVE } = Configuration.legend.items.status;
const dataGroups = this.getDataGroups();

// Remove datasets that have been disabled
Expand Down Expand Up @@ -77,6 +76,24 @@ export class ChartModel {
}

return displayData.filter((datum) => {
return dataGroups.find(
(group) => group.name === datum[groupMapsTo]
);
});
}

getDisplayData() {
if (!this.get("data")) {
return null;
}

const { ACTIVE } = Configuration.legend.items.status;
const dataGroups = this.getDataGroups();
const { groupMapsTo } = this.getOptions().data;

const allDataFromDomain = this.getAllDataFromDomain();

return allDataFromDomain.filter((datum) => {
const group = dataGroups.find(
(group) => group.name === datum[groupMapsTo]
);
Expand Down Expand Up @@ -339,6 +356,22 @@ export class ChartModel {
});
}

// Updates selected groups
const updatedActiveItems = dataGroups.filter(group => group.status === ACTIVE);
const options = this.getOptions();

const hasUpdatedDeactivatedItems = dataGroups.some(
group => group.status === DISABLED
);

// If there are deactivated items, map the item name into selected groups
if (hasUpdatedDeactivatedItems) {
options.data.selectedGroups = updatedActiveItems.map(activeItem => activeItem.name);
} else {
// If every item is active, clear array
options.data.selectedGroups = [];
};

// dispatch legend filtering event with the status of all the dataLabels
this.services.events.dispatchEvent(Events.Legend.ITEMS_UPDATE, {
dataGroups
Expand Down Expand Up @@ -479,15 +512,32 @@ export class ChartModel {

protected generateDataGroups(data) {
const { groupMapsTo } = this.getOptions().data;
const { ACTIVE } = Configuration.legend.items.status;
const { ACTIVE, DISABLED } = Configuration.legend.items.status;
const options = this.getOptions();

const uniqueDataGroups = map(
data,
(datum) => datum[groupMapsTo]
).keys();
return uniqueDataGroups.map((groupName) => ({

// check if selectedGroups can be applied to chart with current data groups
if (options.data.selectedGroups.length) {
const hasAllSelectedGroups = options.data.selectedGroups
.every(groupName => uniqueDataGroups.includes(groupName));
if (!hasAllSelectedGroups) {
options.data.selectedGroups = [];
};
}

// Get group status based on items in selected groups
const getStatus = (groupName) =>
!options.data.selectedGroups.length || options.data.selectedGroups.includes(groupName)
? ACTIVE
: DISABLED;

return uniqueDataGroups.map(groupName => ({
name: groupName,
status: ACTIVE
status: getStatus(groupName)
}));
}

Expand Down
56 changes: 56 additions & 0 deletions packages/core/src/selectedGroups.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { TestEnvironment } from "./tests/index";

// import the settings for the css prefixes
import settings from "carbon-components/es/globals/js/settings";

import { options } from "./configuration";
import { Events } from "./interfaces";

import { select } from "d3-selection";

describe("selectedGroups option", () => {
beforeEach(function () {
const testEnvironment = new TestEnvironment();
testEnvironment.render();

this.chart = testEnvironment.getChartReference();
this.testEnvironment = testEnvironment;
});

describe("selected legend labels", () => {
it("should match the selected groups provided in options", function (done) {
const sampleSelectedGroups = ["Dataset 1", "Dataset 3"];

const chartEventsService = this.chart.services.events;

const renderCb = () => {
// Remove render event listener
chartEventsService.removeEventListener(
Events.Chart.RENDER_FINISHED,
renderCb
);

const selectedLegendLabels = select(
`g.${settings.prefix}--${options.chart.style.prefix}--legend`
)
.selectAll("g.legend-item.active > text")
.nodes()
.map((item) => item["innerHTML"]);

expect(selectedLegendLabels).toEqual(sampleSelectedGroups);

done();
};

// Add event listener for when chart render is finished
chartEventsService.addEventListener(
Events.Chart.RENDER_FINISHED,
renderCb
);
});
});

afterEach(function () {
this.testEnvironment.destroy();
});
});
5 changes: 4 additions & 1 deletion packages/core/src/tests/test-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { groupedBarData, groupedBarOptions } from "../../demo/data";

export const data = groupedBarData as ChartData;
export const options = Object.assign(groupedBarOptions, {
title: "My chart"
title: "My chart",
data: {
selectedGroups: ["Dataset 1", "Dataset 3"]
}
}) as any;

export class TestEnvironment {
Expand Down

0 comments on commit e10c1f2

Please sign in to comment.