diff --git a/media/js/src/GraphEditor.jsx b/media/js/src/GraphEditor.jsx
index 836b4d6c1..7f7920b9c 100644
--- a/media/js/src/GraphEditor.jsx
+++ b/media/js/src/GraphEditor.jsx
@@ -18,6 +18,7 @@ import RevenueElasticityEditor from './editors/RevenueElasticityEditor.jsx';
import OptimalChoiceCostMinimizingEditor from './editors/OptimalChoiceCostMinimizingEditor.jsx';
import TaxationLinearDemandEditor from './editors/TaxationLinearDemandEditor.jsx';
import TaxRevenueEditor from './editors/TaxRevenueEditor.jsx';
+import NegativeProductionExternalityProducerEditor from './editors/NegativeProductionExternalityProducerEditor.jsx';
import JXGBoard from './JXGBoard.jsx';
import GraphPane from './GraphPane.jsx';
@@ -330,6 +331,13 @@ export default class GraphEditor extends React.Component {
{...commonEditorProps}
{...this.props}
/>;
+ } else if (this.props.gType === 26) {
+ rightSide =
+ ;
}
const hasIntersection = ![
diff --git a/media/js/src/GraphViewer.jsx b/media/js/src/GraphViewer.jsx
index c6c33ca50..7e164ed74 100644
--- a/media/js/src/GraphViewer.jsx
+++ b/media/js/src/GraphViewer.jsx
@@ -16,6 +16,8 @@ import OptimalChoiceConsumptionEditor from './editors/OptimalChoiceConsumption.j
import CostFunctionsEditor from './editors/CostFunctionsEditor.jsx';
import OptimalChoiceCostMinimizingEditor from './editors/OptimalChoiceCostMinimizingEditor.jsx';
import TaxationLinearDemandEditor from './editors/TaxationLinearDemandEditor.jsx';
+import NegativeProductionExternalityProducerEditor from
+'./editors/NegativeProductionExternalityProducerEditor.jsx';
import ExportGraphButton from './buttons/ExportGraphButton.jsx';
import ResetGraphButton from './buttons/ResetGraphButton.jsx';
@@ -330,7 +332,14 @@ export default class GraphViewer extends React.Component {
{...commonViewerProps}
{...this.props}
/>;
- }
+ } else if (this.props.gType === 26) {
+ rightSide =
+ ;
+ }
return (
diff --git a/media/js/src/JXGBoard.jsx b/media/js/src/JXGBoard.jsx
index 8772debf3..f1044a9e7 100644
--- a/media/js/src/JXGBoard.jsx
+++ b/media/js/src/JXGBoard.jsx
@@ -570,6 +570,18 @@ export default class JXGBoard extends React.Component {
xTicks = this.visibleTicks;
yTicks = xTicks;
break;
+ case 26:
+ xTicks = this.visibleTicks;
+ yTicks = xTicks;
+ break;
+ case 28:
+ xTicks = this.visibleTicks;
+ yTicks = xTicks;
+ break;
+ case 27:
+ xTicks = this.visibleTicks;
+ yTicks = xTicks;
+ break;
default:
xAxisLabel = options.gXAxisLabel ? options.gXAxisLabel : 'x';
yAxisLabel = options.gYAxisLabel ? options.gYAxisLabel : 'y';
diff --git a/media/js/src/editors/NegativeProductionExternalityProducerEditor.jsx b/media/js/src/editors/NegativeProductionExternalityProducerEditor.jsx
new file mode 100644
index 000000000..48e646667
--- /dev/null
+++ b/media/js/src/editors/NegativeProductionExternalityProducerEditor.jsx
@@ -0,0 +1,137 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import RangeEditor from '../form-components/RangeEditor.jsx';
+import { handleFormUpdate } from '../utils.js';
+
+export default class NegativeProductionExternalityProducerEditor extends React.Component {
+ render() {
+ const me = this;
+
+ const modesLeft = [
+ 'Negative Producer Externality',
+ 'Unregulated',
+ 'Welfare',
+ ];
+ const modesRight = [
+ 'Pigouvian Tax',
+ 'Pigouvian Tax (Welfare)',
+ ];
+
+ const radioButtons1 = modesLeft.map((optionTitle, idx) =>
+
+
+
+
+ );
+
+ const radioButtons2 = modesRight.map(function(optionTitle, idx) {
+ const newIdx = idx + modesLeft.length;
+ return (
+
+
+
+
+ );
+ });
+
+ return (
+ <>
+
+
+ {radioButtons1}
+
+
+ {radioButtons2}
+
+
+
+
+ {this.props.displaySliders && (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ >
+ );
+ }
+}
+
+NegativeProductionExternalityProducerEditor.propTypes = {
+ gType: PropTypes.number.isRequired,
+
+ gA1: PropTypes.number.isRequired,
+ gA2: PropTypes.number.isRequired,
+ gA3: PropTypes.number.isRequired,
+ gA4: PropTypes.number.isRequired,
+ gA5: PropTypes.number.isRequired,
+ gA6: PropTypes.number,
+
+ gFunctionChoice: PropTypes.number.isRequired,
+ gToggle: PropTypes.bool.isRequired,
+
+ displaySliders: PropTypes.bool.isRequired
+};
diff --git a/media/js/src/graphUtils.js b/media/js/src/graphUtils.js
index 3f9c06915..9c5cdf8d0 100644
--- a/media/js/src/graphUtils.js
+++ b/media/js/src/graphUtils.js
@@ -15,6 +15,9 @@ import {
import {
defaults as cobbDouglasDefaults
} from './graphs/CobbDouglasGraph.js';
+import {
+ defaults as externalitiesDefaults
+} from './graphs/NegativeProductionExternalityProducerGraph.js';
/**
* Set defaults to the given state update object based on toggle and
@@ -130,6 +133,8 @@ export const getDefaultGraphState = function(graphType, state) {
});
} else if (graphType === 25) {
Object.assign(state, linearDemandSupplySurplusDefaults[0]);
+ } else if (graphType >= 26 && graphType <= 28) {
+ Object.assign(state, externalitiesDefaults[0]);
}
return state;
diff --git a/media/js/src/graphs/NegativeProductionExternalityProducerGraph.js b/media/js/src/graphs/NegativeProductionExternalityProducerGraph.js
index 75c206e8d..6b4425b5f 100644
--- a/media/js/src/graphs/NegativeProductionExternalityProducerGraph.js
+++ b/media/js/src/graphs/NegativeProductionExternalityProducerGraph.js
@@ -1,6 +1,105 @@
-import {Graph} from './Graph.js';
+import {Graph, positiveRange} from './Graph.js';
+
+export const defaults = [
+ {
+ gXAxisMax: 1000,
+ gYAxisMax: 2500,
+ gA1: 500,
+ gA2: 2,
+ gA3: 50,
+ gA4: 2,
+ }
+];
+
+// Marginal Benefit
+const mb = function(a) {
+ return a;
+};
+
+const mc = function(c, d, q) {
+ return c + d * q;
+};
+
+// External Marginal Cost
+const emc = function(f, g, q) {
+ return f + g * q;
+};
+
+const pemc = function(f, g, p) {
+ return (p - f) / g;
+};
+
+
+// Social Marginal Cost
+const smc = function(c, d, f, g, q) {
+ return mc(c, d, q) + emc(f, g, q);
+};
+
+const psmc = function(c, d, f, g, p) {
+ return (p - c - f) / (d + g);
+};
export class NegativeProductionExternalityProducerGraph extends Graph {
+ static getGraphPane(gFunctionChoice) {
+ return [
+ {
+ label: 'Unregulated Output q*',
+ color: 'red',
+ value: 500
+ },
+ {
+ label: 'Socially Desirable Output q
soc',
+ color: 'orange',
+ value: 247.5
+ },
+ {
+ label: 'Market Price P*',
+ color: 'red',
+ value: 1500
+ },
+ ];
+ }
+ make() {
+ const me = this;
+
+ const mbLine = function() {
+ return mb(me.options.gA1);
+ };
+
+ this.l1 = this.board.create(
+ 'functiongraph',
+ [positiveRange(mbLine), 0, this.options.gXAxisMax], {
+ name: 'MB',
+ withLabel: true,
+ label: {
+ strokeColor: this.l1Color
+ },
+ strokeWidth: 2,
+ strokeColor: this.l1Color,
+ fixed: true,
+ highlight: false
+ }
+ );
+
+ const mcLine = function(c, d, x) {
+ return mc(me.options.gA2, me.options.gA3, x);
+ };
+
+ this.l2 = this.board.create(
+ 'functiongraph',
+ [positiveRange(mcLine), 0, this.options.gXAxisMax], {
+ name: 'MC',
+ withLabel: true,
+ label: {
+ strokeColor: this.l2Color
+ },
+ strokeWidth: 2,
+ strokeColor: this.l2Color,
+ fixed: true,
+ highlight: false
+ }
+ );
+ }
}
export const mkNegativeProductionExternalityProducer = function(board, options) {